View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.shardingsphere.driver.jdbc.core.statement;
19  
20  import lombok.AccessLevel;
21  import lombok.Getter;
22  import org.apache.shardingsphere.database.connector.core.metadata.database.metadata.option.keygen.DialectGeneratedKeyOption;
23  import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
24  import org.apache.shardingsphere.database.connector.core.type.DatabaseTypeRegistry;
25  import org.apache.shardingsphere.database.exception.core.SQLExceptionTransformEngine;
26  import org.apache.shardingsphere.driver.executor.callback.add.StatementAddCallback;
27  import org.apache.shardingsphere.driver.executor.engine.batch.preparedstatement.DriverExecuteBatchExecutor;
28  import org.apache.shardingsphere.driver.executor.engine.facade.DriverExecutorFacade;
29  import org.apache.shardingsphere.driver.jdbc.adapter.AbstractPreparedStatementAdapter;
30  import org.apache.shardingsphere.driver.jdbc.core.connection.ShardingSphereConnection;
31  import org.apache.shardingsphere.driver.jdbc.core.resultset.GeneratedKeysResultSet;
32  import org.apache.shardingsphere.driver.jdbc.core.resultset.ShardingSphereResultSet;
33  import org.apache.shardingsphere.driver.jdbc.core.statement.metadata.ShardingSphereParameterMetaData;
34  import org.apache.shardingsphere.infra.annotation.HighFrequencyInvocation;
35  import org.apache.shardingsphere.infra.binder.context.aware.ParameterAware;
36  import org.apache.shardingsphere.infra.binder.context.segment.insert.keygen.GeneratedKeyContext;
37  import org.apache.shardingsphere.infra.binder.context.statement.SQLStatementContext;
38  import org.apache.shardingsphere.infra.binder.context.statement.type.dml.InsertStatementContext;
39  import org.apache.shardingsphere.infra.binder.engine.SQLBindEngine;
40  import org.apache.shardingsphere.infra.exception.ShardingSpherePreconditions;
41  import org.apache.shardingsphere.infra.exception.kernel.syntax.EmptySQLException;
42  import org.apache.shardingsphere.infra.executor.sql.prepare.driver.jdbc.JDBCDriverType;
43  import org.apache.shardingsphere.infra.executor.sql.prepare.driver.jdbc.StatementOption;
44  import org.apache.shardingsphere.infra.hint.HintManager;
45  import org.apache.shardingsphere.infra.hint.HintValueContext;
46  import org.apache.shardingsphere.infra.hint.SQLHintUtils;
47  import org.apache.shardingsphere.infra.metadata.ShardingSphereMetaData;
48  import org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase;
49  import org.apache.shardingsphere.infra.rule.attribute.datanode.DataNodeRuleAttribute;
50  import org.apache.shardingsphere.infra.rule.attribute.resoure.StorageConnectorReusableRuleAttribute;
51  import org.apache.shardingsphere.infra.session.query.QueryContext;
52  import org.apache.shardingsphere.parser.rule.SQLParserRule;
53  import org.apache.shardingsphere.sql.parser.statement.core.statement.SQLStatement;
54  
55  import java.sql.ParameterMetaData;
56  import java.sql.PreparedStatement;
57  import java.sql.ResultSet;
58  import java.sql.SQLException;
59  import java.sql.Statement;
60  import java.util.ArrayList;
61  import java.util.Collection;
62  import java.util.Collections;
63  import java.util.LinkedList;
64  import java.util.List;
65  import java.util.Map;
66  import java.util.Optional;
67  
68  /**
69   * ShardingSphere prepared statement.
70   */
71  @HighFrequencyInvocation
72  public final class ShardingSpherePreparedStatement extends AbstractPreparedStatementAdapter {
73      
74      @Getter
75      private final ShardingSphereConnection connection;
76      
77      private final ShardingSphereMetaData metaData;
78      
79      private final String sql;
80      
81      private final HintValueContext hintValueContext;
82      
83      private final SQLStatementContext sqlStatementContext;
84      
85      private final ShardingSphereDatabase usedDatabase;
86      
87      private final StatementOption statementOption;
88      
89      @Getter(AccessLevel.PROTECTED)
90      private final StatementManager statementManager;
91      
92      @Getter
93      private final ParameterMetaData parameterMetaData;
94      
95      private final DriverExecutorFacade driverExecutorFacade;
96      
97      private final DriverExecuteBatchExecutor executeBatchExecutor;
98      
99      private final List<PreparedStatement> statements = new ArrayList<>();
100     
101     private final List<List<Object>> parameterSets = new ArrayList<>();
102     
103     private final Collection<Comparable<?>> generatedValues = new LinkedList<>();
104     
105     private final boolean statementsCacheable;
106     
107     private Map<String, Integer> columnLabelAndIndexMap;
108     
109     private ResultSet currentResultSet;
110     
111     private ResultSet currentBatchGeneratedKeysResultSet;
112     
113     private QueryContext queryContext;
114     
115     public ShardingSpherePreparedStatement(final ShardingSphereConnection connection, final String sql) throws SQLException {
116         this(connection, sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT, false, null);
117     }
118     
119     public ShardingSpherePreparedStatement(final ShardingSphereConnection connection, final String sql, final int resultSetType, final int resultSetConcurrency) throws SQLException {
120         this(connection, sql, resultSetType, resultSetConcurrency, ResultSet.HOLD_CURSORS_OVER_COMMIT, false, null);
121     }
122     
123     public ShardingSpherePreparedStatement(final ShardingSphereConnection connection, final String sql, final int autoGeneratedKeys) throws SQLException {
124         this(connection, sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT, RETURN_GENERATED_KEYS == autoGeneratedKeys, null);
125     }
126     
127     public ShardingSpherePreparedStatement(final ShardingSphereConnection connection, final String sql, final String[] columns) throws SQLException {
128         this(connection, sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT, true, columns);
129     }
130     
131     public ShardingSpherePreparedStatement(final ShardingSphereConnection connection, final String sql, final int resultSetType, final int resultSetConcurrency,
132                                            final int resultSetHoldability) throws SQLException {
133         this(connection, sql, resultSetType, resultSetConcurrency, resultSetHoldability, false, null);
134     }
135     
136     private ShardingSpherePreparedStatement(final ShardingSphereConnection connection, final String originSQL, final int resultSetType, final int resultSetConcurrency,
137                                             final int resultSetHoldability, final boolean returnGeneratedKeys, final String[] columns) throws SQLException {
138         ShardingSpherePreconditions.checkNotEmpty(originSQL, () -> new EmptySQLException().toSQLException());
139         this.connection = connection;
140         metaData = connection.getContextManager().getMetaDataContexts().getMetaData();
141         sql = SQLHintUtils.removeHint(originSQL);
142         hintValueContext = SQLHintUtils.extractHint(originSQL);
143         DatabaseType databaseType = metaData.getDatabase(connection.getCurrentDatabaseName()).getProtocolType();
144         SQLStatement sqlStatement = metaData.getGlobalRuleMetaData().getSingleRule(SQLParserRule.class).getSQLParserEngine(databaseType).parse(sql, true);
145         sqlStatementContext = new SQLBindEngine(metaData, connection.getCurrentDatabaseName(), hintValueContext).bind(sqlStatement);
146         String usedDatabaseName = sqlStatementContext.getTablesContext().getDatabaseName().orElse(connection.getCurrentDatabaseName());
147         connection.getDatabaseConnectionManager().getConnectionContext().setCurrentDatabaseName(connection.getCurrentDatabaseName());
148         usedDatabase = metaData.getDatabase(usedDatabaseName);
149         statementOption = returnGeneratedKeys ? new StatementOption(true, columns) : new StatementOption(resultSetType, resultSetConcurrency, resultSetHoldability);
150         statementManager = new StatementManager();
151         connection.getStatementManagers().add(statementManager);
152         parameterMetaData = new ShardingSphereParameterMetaData(sqlStatement);
153         driverExecutorFacade = new DriverExecutorFacade(connection, statementOption, statementManager, JDBCDriverType.PREPARED_STATEMENT);
154         executeBatchExecutor = new DriverExecuteBatchExecutor(connection, metaData, statementOption, statementManager, usedDatabase);
155         statementsCacheable = isStatementsCacheable();
156     }
157     
158     private boolean isStatementsCacheable() {
159         return usedDatabase.getRuleMetaData().getAttributes(StorageConnectorReusableRuleAttribute.class).size() == usedDatabase.getRuleMetaData().getRules().size()
160                 && !HintManager.isInstantiated();
161     }
162     
163     @Override
164     public ResultSet executeQuery() throws SQLException {
165         try {
166             if (statementsCacheable && !statements.isEmpty()) {
167                 resetParameters();
168                 return statements.iterator().next().executeQuery();
169             }
170             clearPrevious();
171             QueryContext queryContext = createQueryContext();
172             this.queryContext = queryContext;
173             handleAutoCommitBeforeExecution(queryContext.getSqlStatementContext().getSqlStatement(), connection);
174             findGeneratedKey().ifPresent(optional -> generatedValues.addAll(optional.getGeneratedValues()));
175             currentResultSet =
176                     driverExecutorFacade.executeQuery(usedDatabase, metaData, queryContext, this, columnLabelAndIndexMap, (StatementAddCallback<PreparedStatement>) this::addStatements, this::replay);
177             if (currentResultSet instanceof ShardingSphereResultSet) {
178                 columnLabelAndIndexMap = ((ShardingSphereResultSet) currentResultSet).getColumnLabelAndIndexMap();
179             }
180             return currentResultSet;
181             // CHECKSTYLE:OFF
182         } catch (final RuntimeException | SQLException ex) {
183             // CHECKSTYLE:ON
184             handleExceptionInTransaction(connection, metaData);
185             throw SQLExceptionTransformEngine.toSQLException(ex, usedDatabase.getProtocolType());
186         } finally {
187             executeBatchExecutor.clear();
188             clearParameters();
189         }
190     }
191     
192     private void addStatements(final Collection<PreparedStatement> statements, final Collection<List<Object>> parameterSets) {
193         this.statements.addAll(statements);
194         this.parameterSets.addAll(parameterSets);
195     }
196     
197     private void resetParameters() throws SQLException {
198         replaySetParameter(statements, Collections.singletonList(getParameters()));
199     }
200     
201     @Override
202     public int executeUpdate() throws SQLException {
203         try {
204             if (statementsCacheable && !statements.isEmpty()) {
205                 resetParameters();
206                 return statements.iterator().next().executeUpdate();
207             }
208             clearPrevious();
209             QueryContext queryContext = createQueryContext();
210             this.queryContext = queryContext;
211             handleAutoCommitBeforeExecution(queryContext.getSqlStatementContext().getSqlStatement(), connection);
212             int result = driverExecutorFacade.executeUpdate(usedDatabase, metaData, queryContext,
213                     (sql, statement) -> ((PreparedStatement) statement).executeUpdate(), (StatementAddCallback<PreparedStatement>) this::addStatements, this::replay);
214             findGeneratedKey().ifPresent(optional -> generatedValues.addAll(optional.getGeneratedValues()));
215             return result;
216             // CHECKSTYLE:OFF
217         } catch (final RuntimeException | SQLException ex) {
218             // CHECKSTYLE:ON
219             handleExceptionInTransaction(connection, metaData);
220             throw SQLExceptionTransformEngine.toSQLException(ex, usedDatabase.getProtocolType());
221         } finally {
222             clearBatch();
223         }
224     }
225     
226     @Override
227     public boolean execute() throws SQLException {
228         try {
229             if (statementsCacheable && !statements.isEmpty()) {
230                 resetParameters();
231                 return statements.iterator().next().execute();
232             }
233             clearPrevious();
234             QueryContext queryContext = createQueryContext();
235             this.queryContext = queryContext;
236             handleAutoCommitBeforeExecution(queryContext.getSqlStatementContext().getSqlStatement(), connection);
237             boolean result = driverExecutorFacade.execute(usedDatabase, metaData, queryContext, (sql, statement) -> ((PreparedStatement) statement).execute(),
238                     (StatementAddCallback<PreparedStatement>) this::addStatements, this::replay);
239             findGeneratedKey().ifPresent(optional -> generatedValues.addAll(optional.getGeneratedValues()));
240             return result;
241             // CHECKSTYLE:OFF
242         } catch (final RuntimeException | SQLException ex) {
243             // CHECKSTYLE:ON
244             handleExceptionInTransaction(connection, metaData);
245             throw SQLExceptionTransformEngine.toSQLException(ex, usedDatabase.getProtocolType());
246         } finally {
247             clearBatch();
248         }
249     }
250     
251     @Override
252     public ResultSet getResultSet() throws SQLException {
253         if (null != currentResultSet) {
254             return currentResultSet;
255         }
256         driverExecutorFacade.getResultSet(usedDatabase, queryContext, this, statements).ifPresent(optional -> currentResultSet = optional);
257         if (null == columnLabelAndIndexMap && currentResultSet instanceof ShardingSphereResultSet) {
258             columnLabelAndIndexMap = ((ShardingSphereResultSet) currentResultSet).getColumnLabelAndIndexMap();
259         }
260         return currentResultSet;
261     }
262     
263     private QueryContext createQueryContext() {
264         List<Object> params = new ArrayList<>(getParameters());
265         if (sqlStatementContext instanceof ParameterAware) {
266             ((ParameterAware) sqlStatementContext).bindParameters(params);
267         }
268         return new QueryContext(sqlStatementContext, sql, params, hintValueContext, connection.getDatabaseConnectionManager().getConnectionContext(), metaData, true);
269     }
270     
271     private void replay() throws SQLException {
272         replaySetParameter(statements, parameterSets);
273         for (Statement each : statements) {
274             getMethodInvocationRecorder().replay(each);
275         }
276     }
277     
278     private void replaySetParameter(final List<PreparedStatement> statements, final List<List<Object>> parameterSets) throws SQLException {
279         for (int i = 0; i < statements.size(); i++) {
280             replaySetParameter(statements.get(i), parameterSets.get(i));
281         }
282     }
283     
284     private void clearPrevious() {
285         currentResultSet = null;
286         statements.clear();
287         parameterSets.clear();
288         generatedValues.clear();
289     }
290     
291     private Optional<GeneratedKeyContext> findGeneratedKey() {
292         return sqlStatementContext instanceof InsertStatementContext ? ((InsertStatementContext) sqlStatementContext).getGeneratedKeyContext() : Optional.empty();
293     }
294     
295     @Override
296     public ResultSet getGeneratedKeys() throws SQLException {
297         if (null != currentBatchGeneratedKeysResultSet) {
298             return currentBatchGeneratedKeysResultSet;
299         }
300         Optional<GeneratedKeyContext> generatedKey = findGeneratedKey();
301         if (generatedKey.isPresent() && statementOption.isReturnGeneratedKeys() && !generatedValues.isEmpty()) {
302             return new GeneratedKeysResultSet(getGeneratedKeysColumnName(generatedKey.get().getColumnName()), generatedValues.iterator(), this);
303         }
304         for (PreparedStatement each : statements) {
305             ResultSet resultSet = each.getGeneratedKeys();
306             while (resultSet.next()) {
307                 generatedValues.add((Comparable<?>) resultSet.getObject(1));
308             }
309         }
310         String columnName = generatedKey.map(GeneratedKeyContext::getColumnName).orElse(null);
311         return new GeneratedKeysResultSet(getGeneratedKeysColumnName(columnName), generatedValues.iterator(), this);
312     }
313     
314     private String getGeneratedKeysColumnName(final String columnName) {
315         Optional<DialectGeneratedKeyOption> generatedKeyOption = new DatabaseTypeRegistry(usedDatabase.getProtocolType()).getDialectDatabaseMetaData().getGeneratedKeyOption();
316         return generatedKeyOption.isPresent() ? generatedKeyOption.get().getColumnName() : columnName;
317     }
318     
319     @Override
320     public void addBatch() {
321         currentResultSet = null;
322         QueryContext queryContext = createQueryContext();
323         this.queryContext = queryContext;
324         executeBatchExecutor.addBatch(queryContext, usedDatabase);
325         findGeneratedKey().ifPresent(optional -> generatedValues.addAll(optional.getGeneratedValues()));
326         clearParameters();
327     }
328     
329     @Override
330     public int[] executeBatch() throws SQLException {
331         try {
332             return executeBatchExecutor.executeBatch(usedDatabase, sqlStatementContext, generatedValues, statementOption,
333                     (StatementAddCallback<PreparedStatement>) (statements, parameterSets) -> this.statements.addAll(statements),
334                     this::replaySetParameter,
335                     () -> {
336                         currentBatchGeneratedKeysResultSet = getGeneratedKeys();
337                         statements.clear();
338                     });
339             // CHECKSTYLE:OFF
340         } catch (final RuntimeException ex) {
341             // CHECKSTYLE:ON
342             handleExceptionInTransaction(connection, metaData);
343             throw SQLExceptionTransformEngine.toSQLException(ex, usedDatabase.getProtocolType());
344         } finally {
345             clearBatch();
346         }
347     }
348     
349     @Override
350     public void clearBatch() {
351         currentResultSet = null;
352         closeCurrentBatchGeneratedKeysResultSet();
353         executeBatchExecutor.clear();
354         clearParameters();
355     }
356     
357     private void closeCurrentBatchGeneratedKeysResultSet() {
358         if (null != currentBatchGeneratedKeysResultSet) {
359             try {
360                 currentBatchGeneratedKeysResultSet.close();
361             } catch (final SQLException ignored) {
362             } finally {
363                 currentBatchGeneratedKeysResultSet = null;
364             }
365         }
366     }
367     
368     @SuppressWarnings("MagicConstant")
369     @Override
370     public int getResultSetType() {
371         return statementOption.getResultSetType();
372     }
373     
374     @SuppressWarnings("MagicConstant")
375     @Override
376     public int getResultSetConcurrency() {
377         return statementOption.getResultSetConcurrency();
378     }
379     
380     @Override
381     public int getResultSetHoldability() {
382         return statementOption.getResultSetHoldability();
383     }
384     
385     @Override
386     public boolean isAccumulate() {
387         for (DataNodeRuleAttribute each : usedDatabase.getRuleMetaData().getAttributes(DataNodeRuleAttribute.class)) {
388             if (each.isNeedAccumulate(sqlStatementContext.getTablesContext().getTableNames())) {
389                 return true;
390             }
391         }
392         return false;
393     }
394     
395     @Override
396     public Collection<PreparedStatement> getRoutedStatements() {
397         return statements;
398     }
399     
400     @Override
401     protected void closeExecutor() throws SQLException {
402         driverExecutorFacade.close();
403     }
404 }