2 * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 2 of the License.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 package com.mysql.clusterj.jdbc;
20 import com.mysql.clusterj.ClusterJFatalInternalException;
21 import com.mysql.clusterj.ClusterJUserException;
22 import com.mysql.clusterj.core.query.QueryDomainTypeImpl;
23 import com.mysql.clusterj.core.query.QueryExecutionContextImpl;
24 import com.mysql.clusterj.core.spi.DomainTypeHandler;
25 import com.mysql.clusterj.core.spi.QueryExecutionContext;
26 import com.mysql.clusterj.core.spi.SessionSPI;
27 import com.mysql.clusterj.core.spi.ValueHandler;
28 import com.mysql.clusterj.core.store.ResultData;
29 import com.mysql.clusterj.core.util.I18NHelper;
30 import com.mysql.clusterj.core.util.Logger;
31 import com.mysql.clusterj.core.util.LoggerFactoryService;
32 import com.mysql.jdbc.ParameterBindings;
33 import com.mysql.jdbc.ResultSetInternalMethods;
35 import java.sql.SQLException;
36 import java.util.Arrays;
37 import java.util.HashMap;
38 import java.util.List;
41 /** This class contains behavior to execute various SQL commands. There is one subclass for each
42 * command to be executed.
44 public class SQLExecutor {
46 /** My message translator */
47 static final I18NHelper local = I18NHelper.getInstance(SQLExecutor.class);
50 static final Logger logger = LoggerFactoryService.getFactory().getInstance(SQLExecutor.class);
52 /** The domain type handler for this SQL statement */
53 DomainTypeHandler<?> domainTypeHandler = null;
55 /** The column names in the SQL statement */
56 protected List<String> columnNames = null;
58 /** The number of fields in the domain object (also the number of mapped columns) */
59 protected int numberOfFields;
61 /** The number of parameters in the where clause */
62 protected int numberOfParameters;
64 /** The map of field numbers to parameter numbers */
65 protected int[] fieldNumberToColumnNumberMap = null;
67 /** The map of column numbers to field numbers */
68 protected int[] columnNumberToFieldNumberMap = null;
70 /** The map of column names to parameter numbers */
71 protected Map<String, Integer> columnNameToFieldNumberMap = new HashMap<String, Integer>();
73 /** The query domain type for qualified SELECT and DELETE operations */
74 protected QueryDomainTypeImpl<?> queryDomainType;
76 public SQLExecutor(DomainTypeHandlerImpl<?> domainTypeHandler, List<String> columnNames, int numberOfParameters) {
77 this(domainTypeHandler, columnNames);
78 this.numberOfParameters = numberOfParameters;
81 public SQLExecutor(DomainTypeHandlerImpl<?> domainTypeHandler, List<String> columnNames) {
82 this.domainTypeHandler = domainTypeHandler;
83 this.columnNames = columnNames;
84 initializeFieldNumberMap();
87 public SQLExecutor(DomainTypeHandlerImpl<?> domainTypeHandler) {
88 this.domainTypeHandler = domainTypeHandler;
91 public SQLExecutor(DomainTypeHandlerImpl<?> domainTypeHandler, List<String> columnNames,
92 QueryDomainTypeImpl<?> queryDomainType) {
93 this(domainTypeHandler, columnNames);
94 this.queryDomainType = queryDomainType;
95 initializeFieldNumberMap();
98 public SQLExecutor(DomainTypeHandlerImpl<?> domainTypeHandler, QueryDomainTypeImpl<?> queryDomainType,
99 int numberOfParameters) {
100 this.domainTypeHandler = domainTypeHandler;
101 this.queryDomainType = queryDomainType;
102 this.numberOfParameters = numberOfParameters;
105 /** This is the public interface exposed to other parts of the component. Calling this
106 * method executes the SQL statement via the clusterj api, or returns null indicating that
107 * the JDBC driver should execute the SQL statement.
109 public interface Executor {
111 /** Execute the SQL command
112 * @param session the clusterj session which must not be null
113 * @param parameterBindings the parameter bindings from the prepared statement
114 * @return the result of executing the statement, or null
115 * @throws SQLException
117 ResultSetInternalMethods execute(InterceptorImpl interceptor,
118 ParameterBindings parameterBindings) throws SQLException;
121 /** This class implements the Executor contract but returns null, indicating that
122 * the JDBC driver should implement the call itself.
124 public static class Noop implements Executor {
126 public ResultSetInternalMethods execute(InterceptorImpl interceptor,
127 ParameterBindings parameterBindings) throws SQLException {
132 /** This class implements the Executor contract for Select operations.
134 public static class Select extends SQLExecutor implements Executor {
136 public Select(DomainTypeHandlerImpl<?> domainTypeHandler, List<String> columnNames, QueryDomainTypeImpl<?> queryDomainType) {
137 super(domainTypeHandler, columnNames, queryDomainType);
138 if (queryDomainType == null) {
139 throw new ClusterJFatalInternalException("queryDomainType must not be null for Select.");
143 public ResultSetInternalMethods execute(InterceptorImpl interceptor,
144 ParameterBindings parameterBindings) throws SQLException {
145 // create value handler to copy data from parameters to ndb
146 // count the parameters
147 int count = countParameters(parameterBindings);
148 SessionSPI session = interceptor.getSession();
149 Map<String, Object> parameters = createParameterMap(queryDomainType, parameterBindings, 0, count);
150 QueryExecutionContext context = new QueryExecutionContextImpl(session, parameters);
151 session.startAutoTransaction();
153 ResultData resultData = queryDomainType.getResultData(context);
154 // session.endAutoTransaction();
155 return new ResultSetInternalMethodsImpl(resultData, columnNumberToFieldNumberMap,
156 columnNameToFieldNumberMap, session);
157 } catch (Exception e) {
159 session.failAutoTransaction();
165 /** This class implements the Executor contract for Delete operations.
167 public static class Delete extends SQLExecutor implements Executor {
169 public Delete (DomainTypeHandlerImpl<?> domainTypeHandler, QueryDomainTypeImpl<?> queryDomainType,
170 int numberOfParameters) {
171 super(domainTypeHandler, queryDomainType, numberOfParameters);
174 public Delete (DomainTypeHandlerImpl<?> domainTypeHandler) {
175 super(domainTypeHandler);
178 public ResultSetInternalMethods execute(InterceptorImpl interceptor,
179 ParameterBindings parameterBindings) throws SQLException {
180 SessionSPI session = interceptor.getSession();
181 if (queryDomainType == null) {
182 int rowsDeleted = session.deletePersistentAll(domainTypeHandler);
183 if (logger.isDebugEnabled()) logger.debug("deleteAll deleted: " + rowsDeleted);
184 return new ResultSetInternalMethodsUpdateCount(rowsDeleted);
186 int numberOfBoundParameters = countParameters(parameterBindings);
187 int numberOfStatements = numberOfBoundParameters / numberOfParameters;
188 long[] deleteResults = new long[numberOfStatements];
189 if (logger.isDebugEnabled()) logger.debug(
190 " numberOfParameters: " + numberOfParameters
191 + " numberOfBoundParameters: " + numberOfBoundParameters
192 + " numberOfStatements: " + numberOfStatements
194 QueryExecutionContextJDBCImpl context =
195 new QueryExecutionContextJDBCImpl(session, parameterBindings, numberOfParameters);
196 for (int i = 0; i < numberOfStatements; ++i) {
197 // this will execute each statement in the batch using different parameters
198 int statementRowsDeleted = queryDomainType.deletePersistentAll(context);
199 if (logger.isDebugEnabled()) logger.debug("statement " + i
200 + " deleted " + statementRowsDeleted);
201 deleteResults[i] = statementRowsDeleted;
202 context.nextStatement();
204 return new ResultSetInternalMethodsUpdateCount(deleteResults);
209 /** This class implements the Executor contract for Insert operations.
211 public static class Insert extends SQLExecutor implements Executor {
213 public Insert(DomainTypeHandlerImpl<?> domainTypeHandler, List<String> columnNames) {
214 super(domainTypeHandler, columnNames, columnNames.size());
217 public ResultSetInternalMethods execute(InterceptorImpl interceptor,
218 ParameterBindings parameterBindings) throws SQLException {
219 SessionSPI session = interceptor.getSession();
220 int numberOfBoundParameters = countParameters(parameterBindings);
221 int numberOfStatements = numberOfBoundParameters / numberOfParameters;
222 if (logger.isDebugEnabled()) logger.debug("SQLExecutor.Insert.execute"
223 + " numberOfParameters: " + numberOfParameters
224 + " numberOfBoundParameters: " + numberOfBoundParameters
225 + " numberOfFields: " + numberOfFields
226 + " numberOfStatements: " + numberOfStatements
228 // interceptor.beforeClusterjStart();
229 // session asks for values by field number which are converted to parameter number
230 for (int offset = 0; offset < numberOfBoundParameters; offset += numberOfParameters) {
231 ValueHandler valueHandler = getValueHandler(parameterBindings, fieldNumberToColumnNumberMap, offset);
232 session.insert(domainTypeHandler, valueHandler);
235 // interceptor.afterClusterjStart();
236 return new ResultSetInternalMethodsUpdateCount(numberOfStatements);
240 /** Create the parameter map assigning each bound parameter a number.
241 * The result is a map in which the key is a String whose key is a cardinal number
242 * starting with 1 (for JDBC which uses 1-origin for numbering)
243 * and whose value is the parameter's value.
244 * @param queryDomainType the query domain type
245 * @param parameterBindings the parameter bindings
246 * @param offset the number of parameters to skip
247 * @param count the number of parameters to use
249 * @throws SQLException
251 protected Map<String, Object> createParameterMap(QueryDomainTypeImpl<?> queryDomainType,
252 ParameterBindings parameterBindings, int offset, int count) throws SQLException {
253 Map<String, Object> result = new HashMap<String, Object>();
254 int first = offset + 1;
255 int last = offset + count + 1;
256 for (int i = first; i < last; ++i) {
257 Object placeholder = parameterBindings.getObject(i);
258 if (logger.isDetailEnabled())
259 logger.detail("Put placeholder " + i + " value: " + placeholder + " of type " + placeholder.getClass());
260 result.put(String.valueOf(i), placeholder);
265 /** Initialize the mappings between the Java representation of the row (domain type handler)
266 * and the JDBC/database representation of the row. The JDBC driver asks for columns by column
267 * index or column name, 1-origin. The domain type handler returns data by field number, 0-origin.
268 * The domain type handler has representations for all columns in the database, whereas the JDBC
269 * driver has a specific set of columns referenced by the SQL statement.
270 * For insert, the column number to field number mapping will map parameters to field numbers,
271 * e.g. INSERT INTO EMPLOYEE (id, name, age) VALUES (?, ?, ?)
272 * For select, the column number to field number mapping will map result set columns to field numbers,
273 * e.g. SELECT id, name, age FROM EMPLOYEE
275 private void initializeFieldNumberMap() {
276 // the index into the int[] is the 0-origin field number (columns in order of definition in the schema)
277 // the value is the index into the parameter bindings (columns in order of the sql insert statement)
278 String[] fieldNames = domainTypeHandler.getFieldNames();
279 numberOfFields = fieldNames.length;
280 fieldNumberToColumnNumberMap = new int[numberOfFields];
281 columnNumberToFieldNumberMap = new int[1 + columnNames.size()];
282 for (int i= 0; i < numberOfFields; ++i) {
283 columnNameToFieldNumberMap.put(fieldNames[i], i);
284 int index = columnNames.indexOf(fieldNames[i]);
286 // index origin 1 for JDBC interfaces
287 fieldNumberToColumnNumberMap[i] = index + 1;
288 columnNumberToFieldNumberMap[index + 1] = i;
290 // field is not in column list
291 fieldNumberToColumnNumberMap[i] = -1;
294 // make sure all columns are fields and if not, throw an exception
295 for (String columnName: columnNames) {
296 if (columnNameToFieldNumberMap.get(columnName) == null) {
297 throw new ClusterJUserException(
298 local.message("ERR_Column_Name_Not_In_Table", columnName,
299 Arrays.toString(fieldNames),
300 domainTypeHandler.getTableName()));
303 if (logger.isDetailEnabled()) {
304 StringBuilder buffer = new StringBuilder();
305 for (int i = 0; i < fieldNumberToColumnNumberMap.length; ++i) {
306 int columnNumber = fieldNumberToColumnNumberMap[i];
307 buffer.append("field ");
309 buffer.append(" mapped to ");
310 buffer.append(columnNumber);
312 buffer.append(columnNumber == -1?"nothing":(columnNames.get(columnNumber - 1)));
315 logger.detail(buffer.toString());
319 /** Create a value handler (part of the clusterj spi) to retrieve values from jdbc parameter bindings.
320 * @param parameterBindings the jdbc parameter bindings from prepared statements
321 * @param fieldNumberToParameterNumberMap map from field number to parameter number
322 * @param offset into the parameter bindings for this instance (used for batch execution)
325 protected ValueHandler getValueHandler(ParameterBindings parameterBindings,
326 int[] fieldNumberToParameterNumberMap, int offset) {
327 return new ValueHandlerImpl(parameterBindings, fieldNumberToParameterNumberMap, offset);
330 /** If detailed logging is enabled write the parameter bindings to the log.
331 * @param parameterBindings the jdbc parameter bindings
333 protected static void logParameterBindings(ParameterBindings parameterBindings) {
334 if (logger.isDetailEnabled()) {
338 String value = parameterBindings.getObject(++i).toString();
339 // parameters are 1-origin per jdbc specification
340 logger.detail("parameterBinding: parameter " + i + " has value: " + value);
341 } catch (Exception e) {
342 // we don't know how many parameters are bound...
349 /** Count the number of bound parameters. If this is a batch execution, then the
350 * number of bound parameters is the number of statements in the batch times the
351 * number of parameters per statement.
352 * If detailed logging is enabled write the parameter bindings to the log.
353 * @param parameterBindings the jdbc parameter bindings
355 protected static int countParameters(ParameterBindings parameterBindings) {
360 // parameters are 1-origin per jdbc specification
361 Object objectValue = parameterBindings.getObject(i);
362 if (logger.isDetailEnabled()) {
363 logger.detail("parameterBinding: parameter " + i
364 + " has value: " + objectValue
365 + " of type " + objectValue.getClass());
367 } catch (Exception e) {
368 // we don't know how many parameters are bound...