1 /* 2 * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License, version 2.0, 6 * as published by the Free Software Foundation. 7 * 8 * This program is also distributed with certain software (including 9 * but not limited to OpenSSL) that is licensed under separate terms, 10 * as designated in a particular file or component or in included license 11 * documentation. The authors of MySQL hereby grant you an additional 12 * permission to link the program and your derivative works with the 13 * separately licensed software that they have included with MySQL. 14 * 15 * This program is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 * GNU General Public License, version 2.0, for more details. 19 * 20 * You should have received a copy of the GNU General Public License 21 * along with this program; if not, write to the Free Software 22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 23 */ 24 25 package com.mysql.clusterj.jdbc; 26 27 import com.mysql.clusterj.ClusterJFatalInternalException; 28 import com.mysql.clusterj.ClusterJUserException; 29 import com.mysql.clusterj.core.query.QueryDomainTypeImpl; 30 import com.mysql.clusterj.core.query.QueryExecutionContextImpl; 31 import com.mysql.clusterj.core.spi.DomainTypeHandler; 32 import com.mysql.clusterj.core.spi.QueryExecutionContext; 33 import com.mysql.clusterj.core.spi.SessionSPI; 34 import com.mysql.clusterj.core.spi.ValueHandler; 35 import com.mysql.clusterj.core.store.ResultData; 36 import com.mysql.clusterj.core.util.I18NHelper; 37 import com.mysql.clusterj.core.util.Logger; 38 import com.mysql.clusterj.core.util.LoggerFactoryService; 39 import com.mysql.jdbc.ParameterBindings; 40 import com.mysql.jdbc.ResultSetInternalMethods; 41 42 import java.sql.SQLException; 43 import java.util.Arrays; 44 import java.util.HashMap; 45 import java.util.List; 46 import java.util.Map; 47 48 /** This class contains behavior to execute various SQL commands. There is one subclass for each 49 * command to be executed. 50 */ 51 public class SQLExecutor { 52 53 /** My message translator */ 54 static final I18NHelper local = I18NHelper.getInstance(SQLExecutor.class); 55 56 /** My logger */ 57 static final Logger logger = LoggerFactoryService.getFactory().getInstance(SQLExecutor.class); 58 59 /** The domain type handler for this SQL statement */ 60 DomainTypeHandler<?> domainTypeHandler = null; 61 62 /** The column names in the SQL statement */ 63 protected List<String> columnNames = null; 64 65 /** The number of fields in the domain object (also the number of mapped columns) */ 66 protected int numberOfFields; 67 68 /** The number of parameters in the where clause */ 69 protected int numberOfParameters; 70 71 /** The map of field numbers to parameter numbers */ 72 protected int[] fieldNumberToColumnNumberMap = null; 73 74 /** The map of column numbers to field numbers */ 75 protected int[] columnNumberToFieldNumberMap = null; 76 77 /** The map of column names to parameter numbers */ 78 protected Map<String, Integer> columnNameToFieldNumberMap = new HashMap<String, Integer>(); 79 80 /** The query domain type for qualified SELECT and DELETE operations */ 81 protected QueryDomainTypeImpl<?> queryDomainType; 82 SQLExecutor(DomainTypeHandlerImpl<?> domainTypeHandler, List<String> columnNames, int numberOfParameters)83 public SQLExecutor(DomainTypeHandlerImpl<?> domainTypeHandler, List<String> columnNames, int numberOfParameters) { 84 this(domainTypeHandler, columnNames); 85 this.numberOfParameters = numberOfParameters; 86 } 87 SQLExecutor(DomainTypeHandlerImpl<?> domainTypeHandler, List<String> columnNames)88 public SQLExecutor(DomainTypeHandlerImpl<?> domainTypeHandler, List<String> columnNames) { 89 this.domainTypeHandler = domainTypeHandler; 90 this.columnNames = columnNames; 91 initializeFieldNumberMap(); 92 } 93 SQLExecutor(DomainTypeHandlerImpl<?> domainTypeHandler)94 public SQLExecutor(DomainTypeHandlerImpl<?> domainTypeHandler) { 95 this.domainTypeHandler = domainTypeHandler; 96 } 97 SQLExecutor(DomainTypeHandlerImpl<?> domainTypeHandler, List<String> columnNames, QueryDomainTypeImpl<?> queryDomainType)98 public SQLExecutor(DomainTypeHandlerImpl<?> domainTypeHandler, List<String> columnNames, 99 QueryDomainTypeImpl<?> queryDomainType) { 100 this(domainTypeHandler, columnNames); 101 this.queryDomainType = queryDomainType; 102 initializeFieldNumberMap(); 103 } 104 SQLExecutor(DomainTypeHandlerImpl<?> domainTypeHandler, QueryDomainTypeImpl<?> queryDomainType, int numberOfParameters)105 public SQLExecutor(DomainTypeHandlerImpl<?> domainTypeHandler, QueryDomainTypeImpl<?> queryDomainType, 106 int numberOfParameters) { 107 this.domainTypeHandler = domainTypeHandler; 108 this.queryDomainType = queryDomainType; 109 this.numberOfParameters = numberOfParameters; 110 } 111 112 /** This is the public interface exposed to other parts of the component. Calling this 113 * method executes the SQL statement via the clusterj api, or returns null indicating that 114 * the JDBC driver should execute the SQL statement. 115 */ 116 public interface Executor { 117 118 /** Execute the SQL command 119 * @param session the clusterj session which must not be null 120 * @param parameterBindings the parameter bindings from the prepared statement 121 * @return the result of executing the statement, or null 122 * @throws SQLException 123 */ execute(InterceptorImpl interceptor, ParameterBindings parameterBindings)124 ResultSetInternalMethods execute(InterceptorImpl interceptor, 125 ParameterBindings parameterBindings) throws SQLException; 126 } 127 128 /** This class implements the Executor contract but returns null, indicating that 129 * the JDBC driver should implement the call itself. 130 */ 131 public static class Noop implements Executor { 132 execute(InterceptorImpl interceptor, ParameterBindings parameterBindings)133 public ResultSetInternalMethods execute(InterceptorImpl interceptor, 134 ParameterBindings parameterBindings) throws SQLException { 135 return null; 136 } 137 } 138 139 /** This class implements the Executor contract for Select operations. 140 */ 141 public static class Select extends SQLExecutor implements Executor { 142 Select(DomainTypeHandlerImpl<?> domainTypeHandler, List<String> columnNames, QueryDomainTypeImpl<?> queryDomainType)143 public Select(DomainTypeHandlerImpl<?> domainTypeHandler, List<String> columnNames, QueryDomainTypeImpl<?> queryDomainType) { 144 super(domainTypeHandler, columnNames, queryDomainType); 145 if (queryDomainType == null) { 146 throw new ClusterJFatalInternalException("queryDomainType must not be null for Select."); 147 } 148 } 149 execute(InterceptorImpl interceptor, ParameterBindings parameterBindings)150 public ResultSetInternalMethods execute(InterceptorImpl interceptor, 151 ParameterBindings parameterBindings) throws SQLException { 152 // create value handler to copy data from parameters to ndb 153 // count the parameters 154 int count = countParameters(parameterBindings); 155 SessionSPI session = interceptor.getSession(); 156 Map<String, Object> parameters = createParameterMap(queryDomainType, parameterBindings, 0, count); 157 QueryExecutionContext context = new QueryExecutionContextImpl(session, parameters); 158 session.startAutoTransaction(); 159 try { 160 ResultData resultData = queryDomainType.getResultData(context); 161 // session.endAutoTransaction(); 162 return new ResultSetInternalMethodsImpl(resultData, columnNumberToFieldNumberMap, 163 columnNameToFieldNumberMap, session); 164 } catch (Exception e) { 165 e.printStackTrace(); 166 session.failAutoTransaction(); 167 return null; 168 } 169 } 170 } 171 172 /** This class implements the Executor contract for Delete operations. 173 */ 174 public static class Delete extends SQLExecutor implements Executor { 175 Delete(DomainTypeHandlerImpl<?> domainTypeHandler, QueryDomainTypeImpl<?> queryDomainType, int numberOfParameters)176 public Delete (DomainTypeHandlerImpl<?> domainTypeHandler, QueryDomainTypeImpl<?> queryDomainType, 177 int numberOfParameters) { 178 super(domainTypeHandler, queryDomainType, numberOfParameters); 179 } 180 Delete(DomainTypeHandlerImpl<?> domainTypeHandler)181 public Delete (DomainTypeHandlerImpl<?> domainTypeHandler) { 182 super(domainTypeHandler); 183 } 184 execute(InterceptorImpl interceptor, ParameterBindings parameterBindings)185 public ResultSetInternalMethods execute(InterceptorImpl interceptor, 186 ParameterBindings parameterBindings) throws SQLException { 187 SessionSPI session = interceptor.getSession(); 188 if (queryDomainType == null) { 189 int rowsDeleted = session.deletePersistentAll(domainTypeHandler); 190 if (logger.isDebugEnabled()) logger.debug("deleteAll deleted: " + rowsDeleted); 191 return new ResultSetInternalMethodsUpdateCount(rowsDeleted); 192 } else { 193 int numberOfBoundParameters = countParameters(parameterBindings); 194 int numberOfStatements = numberOfBoundParameters / numberOfParameters; 195 long[] deleteResults = new long[numberOfStatements]; 196 if (logger.isDebugEnabled()) logger.debug( 197 " numberOfParameters: " + numberOfParameters 198 + " numberOfBoundParameters: " + numberOfBoundParameters 199 + " numberOfStatements: " + numberOfStatements 200 ); 201 QueryExecutionContextJDBCImpl context = 202 new QueryExecutionContextJDBCImpl(session, parameterBindings, numberOfParameters); 203 for (int i = 0; i < numberOfStatements; ++i) { 204 // this will execute each statement in the batch using different parameters 205 int statementRowsDeleted = queryDomainType.deletePersistentAll(context); 206 if (logger.isDebugEnabled()) logger.debug("statement " + i 207 + " deleted " + statementRowsDeleted); 208 deleteResults[i] = statementRowsDeleted; 209 context.nextStatement(); 210 } 211 return new ResultSetInternalMethodsUpdateCount(deleteResults); 212 } 213 } 214 } 215 216 /** This class implements the Executor contract for Insert operations. 217 */ 218 public static class Insert extends SQLExecutor implements Executor { 219 Insert(DomainTypeHandlerImpl<?> domainTypeHandler, List<String> columnNames)220 public Insert(DomainTypeHandlerImpl<?> domainTypeHandler, List<String> columnNames) { 221 super(domainTypeHandler, columnNames, columnNames.size()); 222 } 223 execute(InterceptorImpl interceptor, ParameterBindings parameterBindings)224 public ResultSetInternalMethods execute(InterceptorImpl interceptor, 225 ParameterBindings parameterBindings) throws SQLException { 226 SessionSPI session = interceptor.getSession(); 227 int numberOfBoundParameters = countParameters(parameterBindings); 228 int numberOfStatements = numberOfBoundParameters / numberOfParameters; 229 if (logger.isDebugEnabled()) logger.debug("SQLExecutor.Insert.execute" 230 + " numberOfParameters: " + numberOfParameters 231 + " numberOfBoundParameters: " + numberOfBoundParameters 232 + " numberOfFields: " + numberOfFields 233 + " numberOfStatements: " + numberOfStatements 234 ); 235 // interceptor.beforeClusterjStart(); 236 // session asks for values by field number which are converted to parameter number 237 for (int offset = 0; offset < numberOfBoundParameters; offset += numberOfParameters) { 238 ValueHandler valueHandler = getValueHandler(parameterBindings, fieldNumberToColumnNumberMap, offset); 239 session.insert(domainTypeHandler, valueHandler); 240 } 241 session.flush(); 242 // interceptor.afterClusterjStart(); 243 return new ResultSetInternalMethodsUpdateCount(numberOfStatements); 244 } 245 } 246 247 /** Create the parameter map assigning each bound parameter a number. 248 * The result is a map in which the key is a String whose key is a cardinal number 249 * starting with 1 (for JDBC which uses 1-origin for numbering) 250 * and whose value is the parameter's value. 251 * @param queryDomainType the query domain type 252 * @param parameterBindings the parameter bindings 253 * @param offset the number of parameters to skip 254 * @param count the number of parameters to use 255 * @return 256 * @throws SQLException 257 */ createParameterMap(QueryDomainTypeImpl<?> queryDomainType, ParameterBindings parameterBindings, int offset, int count)258 protected Map<String, Object> createParameterMap(QueryDomainTypeImpl<?> queryDomainType, 259 ParameterBindings parameterBindings, int offset, int count) throws SQLException { 260 Map<String, Object> result = new HashMap<String, Object>(); 261 int first = offset + 1; 262 int last = offset + count + 1; 263 for (int i = first; i < last; ++i) { 264 Object placeholder = parameterBindings.getObject(i); 265 if (logger.isDetailEnabled()) 266 logger.detail("Put placeholder " + i + " value: " + placeholder + " of type " + placeholder.getClass()); 267 result.put(String.valueOf(i), placeholder); 268 } 269 return result; 270 } 271 272 /** Initialize the mappings between the Java representation of the row (domain type handler) 273 * and the JDBC/database representation of the row. The JDBC driver asks for columns by column 274 * index or column name, 1-origin. The domain type handler returns data by field number, 0-origin. 275 * The domain type handler has representations for all columns in the database, whereas the JDBC 276 * driver has a specific set of columns referenced by the SQL statement. 277 * For insert, the column number to field number mapping will map parameters to field numbers, 278 * e.g. INSERT INTO EMPLOYEE (id, name, age) VALUES (?, ?, ?) 279 * For select, the column number to field number mapping will map result set columns to field numbers, 280 * e.g. SELECT id, name, age FROM EMPLOYEE 281 */ initializeFieldNumberMap()282 private void initializeFieldNumberMap() { 283 // the index into the int[] is the 0-origin field number (columns in order of definition in the schema) 284 // the value is the index into the parameter bindings (columns in order of the sql insert statement) 285 String[] fieldNames = domainTypeHandler.getFieldNames(); 286 numberOfFields = fieldNames.length; 287 fieldNumberToColumnNumberMap = new int[numberOfFields]; 288 columnNumberToFieldNumberMap = new int[1 + columnNames.size()]; 289 for (int i= 0; i < numberOfFields; ++i) { 290 columnNameToFieldNumberMap.put(fieldNames[i], i); 291 int index = columnNames.indexOf(fieldNames[i]); 292 if (index >= 0) { 293 // index origin 1 for JDBC interfaces 294 fieldNumberToColumnNumberMap[i] = index + 1; 295 columnNumberToFieldNumberMap[index + 1] = i; 296 } else { 297 // field is not in column list 298 fieldNumberToColumnNumberMap[i] = -1; 299 } 300 } 301 // make sure all columns are fields and if not, throw an exception 302 for (String columnName: columnNames) { 303 if (columnNameToFieldNumberMap.get(columnName) == null) { 304 throw new ClusterJUserException( 305 local.message("ERR_Column_Name_Not_In_Table", columnName, 306 Arrays.toString(fieldNames), 307 domainTypeHandler.getTableName())); 308 } 309 } 310 if (logger.isDetailEnabled()) { 311 StringBuilder buffer = new StringBuilder(); 312 for (int i = 0; i < fieldNumberToColumnNumberMap.length; ++i) { 313 int columnNumber = fieldNumberToColumnNumberMap[i]; 314 buffer.append("field "); 315 buffer.append(i); 316 buffer.append(" mapped to "); 317 buffer.append(columnNumber); 318 buffer.append("["); 319 buffer.append(columnNumber == -1?"nothing":(columnNames.get(columnNumber - 1))); 320 buffer.append("];"); 321 } 322 logger.detail(buffer.toString()); 323 } 324 } 325 326 /** Create a value handler (part of the clusterj spi) to retrieve values from jdbc parameter bindings. 327 * @param parameterBindings the jdbc parameter bindings from prepared statements 328 * @param fieldNumberToParameterNumberMap map from field number to parameter number 329 * @param offset into the parameter bindings for this instance (used for batch execution) 330 * @return 331 */ getValueHandler(ParameterBindings parameterBindings, int[] fieldNumberToParameterNumberMap, int offset)332 protected ValueHandler getValueHandler(ParameterBindings parameterBindings, 333 int[] fieldNumberToParameterNumberMap, int offset) { 334 return new ValueHandlerImpl(parameterBindings, fieldNumberToParameterNumberMap, offset); 335 } 336 337 /** If detailed logging is enabled write the parameter bindings to the log. 338 * @param parameterBindings the jdbc parameter bindings 339 */ logParameterBindings(ParameterBindings parameterBindings)340 protected static void logParameterBindings(ParameterBindings parameterBindings) { 341 if (logger.isDetailEnabled()) { 342 int i = 0; 343 while (true) { 344 try { 345 String value = parameterBindings.getObject(++i).toString(); 346 // parameters are 1-origin per jdbc specification 347 logger.detail("parameterBinding: parameter " + i + " has value: " + value); 348 } catch (Exception e) { 349 // we don't know how many parameters are bound... 350 break; 351 } 352 } 353 } 354 } 355 356 /** Count the number of bound parameters. If this is a batch execution, then the 357 * number of bound parameters is the number of statements in the batch times the 358 * number of parameters per statement. 359 * If detailed logging is enabled write the parameter bindings to the log. 360 * @param parameterBindings the jdbc parameter bindings 361 */ countParameters(ParameterBindings parameterBindings)362 protected static int countParameters(ParameterBindings parameterBindings) { 363 int i = 0; 364 while (true) { 365 try { 366 ++i; 367 // parameters are 1-origin per jdbc specification 368 Object objectValue = parameterBindings.getObject(i); 369 if (logger.isDetailEnabled()) { 370 logger.detail("parameterBinding: parameter " + i 371 + " has value: " + objectValue 372 + " of type " + objectValue.getClass()); 373 } 374 } catch (Exception e) { 375 // we don't know how many parameters are bound... 376 break; 377 } 378 } 379 return i - 1; 380 } 381 382 } 383