1 /* 2 Copyright (c) 2009, 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.core.query; 26 27 import com.mysql.clusterj.ClusterJException; 28 import com.mysql.clusterj.ClusterJFatalInternalException; 29 import com.mysql.clusterj.ClusterJUserException; 30 import com.mysql.clusterj.Query; 31 32 import com.mysql.clusterj.core.query.PredicateImpl.ScanType; 33 import com.mysql.clusterj.core.spi.DomainFieldHandler; 34 import com.mysql.clusterj.core.spi.DomainTypeHandler; 35 import com.mysql.clusterj.core.spi.QueryExecutionContext; 36 import com.mysql.clusterj.core.spi.SessionSPI; 37 import com.mysql.clusterj.core.spi.ValueHandler; 38 39 import com.mysql.clusterj.core.store.Index; 40 import com.mysql.clusterj.core.store.IndexOperation; 41 import com.mysql.clusterj.core.store.IndexScanOperation; 42 import com.mysql.clusterj.core.store.Operation; 43 import com.mysql.clusterj.core.store.ResultData; 44 import com.mysql.clusterj.core.store.ScanOperation; 45 46 import com.mysql.clusterj.core.util.I18NHelper; 47 import com.mysql.clusterj.core.util.Logger; 48 import com.mysql.clusterj.core.util.LoggerFactoryService; 49 50 import com.mysql.clusterj.query.Predicate; 51 import com.mysql.clusterj.query.PredicateOperand; 52 import com.mysql.clusterj.query.QueryDefinition; 53 import com.mysql.clusterj.query.QueryDomainType; 54 55 import java.util.ArrayList; 56 import java.util.HashMap; 57 import java.util.List; 58 import java.util.Map; 59 60 public class QueryDomainTypeImpl<T> implements QueryDomainType<T> { 61 62 /** My message translator */ 63 static final I18NHelper local = I18NHelper.getInstance(QueryDomainTypeImpl.class); 64 65 /** My logger */ 66 static final Logger logger = LoggerFactoryService.getFactory().getInstance(QueryDomainTypeImpl.class); 67 68 /** My class. */ 69 protected Class<T> cls; 70 71 /** My DomainTypeHandler. */ 72 protected DomainTypeHandler<T> domainTypeHandler; 73 74 /** My where clause. */ 75 protected PredicateImpl where; 76 77 /** My parameters. These encapsulate the parameter names not the values. */ 78 protected Map<String, ParameterImpl> parameters = 79 new HashMap<String, ParameterImpl>(); 80 81 /** My properties. These encapsulate the property names not the values. */ 82 protected Map<String, PropertyImpl> properties = 83 new HashMap<String, PropertyImpl>(); 84 QueryDomainTypeImpl(DomainTypeHandler<T> domainTypeHandler, Class<T> cls)85 public QueryDomainTypeImpl(DomainTypeHandler<T> domainTypeHandler, Class<T> cls) { 86 this.cls = cls; 87 this.domainTypeHandler = domainTypeHandler; 88 } 89 QueryDomainTypeImpl(DomainTypeHandler<T> domainTypeHandler)90 public QueryDomainTypeImpl(DomainTypeHandler<T> domainTypeHandler) { 91 this.domainTypeHandler = domainTypeHandler; 92 } 93 get(String propertyName)94 public PredicateOperand get(String propertyName) { 95 // if called multiple times for the same property, 96 // return the same PropertyImpl instance 97 PropertyImpl property = properties.get(propertyName); 98 if (property != null) { 99 return property; 100 } else { 101 DomainFieldHandler fmd = domainTypeHandler.getFieldHandler(propertyName); 102 property = new PropertyImpl(this, fmd); 103 properties.put(propertyName, property); 104 return property; 105 } 106 } 107 108 /** Set the where clause. Mark parameters used by this query. 109 * @param predicate the predicate 110 * @return the query definition (this) 111 */ where(Predicate predicate)112 public QueryDefinition<T> where(Predicate predicate) { 113 if (predicate == null) { 114 throw new ClusterJUserException( 115 local.message("ERR_Query_Where_Must_Not_Be_Null")); 116 } 117 if (!(predicate instanceof PredicateImpl)) { 118 throw new UnsupportedOperationException( 119 local.message("ERR_NotImplemented")); 120 } 121 // if a previous where clause, unmark the parameters 122 if (where != null) { 123 where.unmarkParameters(); 124 where = null; 125 } 126 this.where = (PredicateImpl)predicate; 127 where.markParameters(); 128 return this; 129 } 130 param(String parameterName)131 public PredicateOperand param(String parameterName) { 132 final ParameterImpl parameter = parameters.get(parameterName); 133 if (parameter != null) { 134 return parameter; 135 } else { 136 ParameterImpl result = new ParameterImpl(this, parameterName); 137 parameters.put(parameterName, result); 138 return result; 139 } 140 } 141 142 /** Convenience method to negate a predicate. 143 * @param predicate the predicate to negate 144 * @return the inverted predicate 145 */ not(Predicate predicate)146 public Predicate not(Predicate predicate) { 147 return predicate.not(); 148 } 149 150 /** Query.getResultList delegates to this method. 151 * 152 * @return the results of executing the query 153 */ getResultList(QueryExecutionContext context)154 public List<T> getResultList(QueryExecutionContext context) { 155 assertAllParametersBound(context); 156 157 SessionSPI session = context.getSession(); 158 session.startAutoTransaction(); 159 // set up results and table information 160 List<T> resultList = new ArrayList<T>(); 161 try { 162 // execute the query 163 ResultData resultData = getResultData(context); 164 // put the result data into the result list 165 while (resultData.next()) { 166 T row = (T) session.newInstance(cls); 167 ValueHandler handler =domainTypeHandler.getValueHandler(row); 168 // set values from result set into object 169 domainTypeHandler.objectSetValues(resultData, handler); 170 resultList.add(row); 171 } 172 session.endAutoTransaction(); 173 return resultList; 174 } catch (ClusterJException ex) { 175 session.failAutoTransaction(); 176 throw ex; 177 } catch (Exception ex) { 178 session.failAutoTransaction(); 179 throw new ClusterJException( 180 local.message("ERR_Exception_On_Query"), ex); 181 } 182 } 183 184 /** Execute the query and return the result data. The type of operation 185 * (primary key lookup, unique key lookup, index scan, or table scan) 186 * depends on the where clause and the bound parameter values. 187 * 188 * @param context the query context, including the bound parameters 189 * @return the raw result data from the query 190 * @throws ClusterJUserException if not all parameters are bound 191 */ getResultData(QueryExecutionContext context)192 public ResultData getResultData(QueryExecutionContext context) { 193 SessionSPI session = context.getSession(); 194 // execute query based on what kind of scan is needed 195 // if no where clause, scan the entire table 196 CandidateIndexImpl index = where==null? 197 CandidateIndexImpl.getIndexForNullWhereClause(): 198 where.getBestCandidateIndex(context); 199 ScanType scanType = index.getScanType(); 200 Map<String, Object> explain = newExplain(index, scanType); 201 context.setExplain(explain); 202 ResultData result = null; 203 Index storeIndex; 204 205 switch (scanType) { 206 207 case PRIMARY_KEY: { 208 // perform a select operation 209 Operation op = session.getSelectOperation(domainTypeHandler.getStoreTable()); 210 // set key values into the operation 211 index.operationSetKeys(context, op); 212 // set the expected columns into the operation 213 domainTypeHandler.operationGetValues(op); 214 // execute the select and get results 215 result = op.resultData(); 216 break; 217 } 218 219 case INDEX_SCAN: { 220 storeIndex = index.getStoreIndex(); 221 if (logger.isDetailEnabled()) logger.detail("Using index scan with index " + index.getIndexName()); 222 IndexScanOperation op; 223 // perform an index scan operation 224 if (index.isMultiRange()) { 225 op = session.getIndexScanOperationMultiRange(storeIndex, domainTypeHandler.getStoreTable()); 226 227 } else { 228 op = session.getIndexScanOperation(storeIndex, domainTypeHandler.getStoreTable()); 229 230 } 231 // set the expected columns into the operation 232 domainTypeHandler.operationGetValues(op); 233 // set the bounds into the operation 234 index.operationSetBounds(context, op); 235 // set additional filter conditions 236 where.filterCmpValue(context, op); 237 // execute the scan and get results 238 result = op.resultData(); 239 break; 240 } 241 242 case TABLE_SCAN: { 243 if (logger.isDetailEnabled()) logger.detail("Using table scan"); 244 // perform a table scan operation 245 ScanOperation op = session.getTableScanOperation(domainTypeHandler.getStoreTable()); 246 // set the expected columns into the operation 247 domainTypeHandler.operationGetValues(op); 248 // set the bounds into the operation 249 if (where != null) { 250 where.filterCmpValue(context, op); 251 } 252 // execute the scan and get results 253 result = op.resultData(); 254 break; 255 } 256 257 case UNIQUE_KEY: { 258 storeIndex = index.getStoreIndex(); 259 if (logger.isDetailEnabled()) logger.detail("Using unique lookup with index " + index.getIndexName()); 260 // perform a unique lookup operation 261 IndexOperation op = session.getUniqueIndexOperation(storeIndex, domainTypeHandler.getStoreTable()); 262 // set the keys of the indexName into the operation 263 where.operationEqual(context, op); 264 // set the expected columns into the operation 265 //domainTypeHandler.operationGetValuesExcept(op, indexName); 266 domainTypeHandler.operationGetValues(op); 267 // execute the select and get results 268 result = op.resultData(); 269 break; 270 } 271 272 default: 273 session.failAutoTransaction(); 274 throw new ClusterJFatalInternalException( 275 local.message("ERR_Illegal_Scan_Type", scanType)); 276 } 277 context.deleteFilters(); 278 return result; 279 } 280 281 /** Delete the instances that satisfy the query and return the number 282 * of instances deleted. The type of operation used to find the instances 283 * (primary key lookup, unique key lookup, index scan, or table scan) 284 * depends on the where clause and the bound parameter values. 285 * 286 * @param context the query context, including the bound parameters 287 * @return the number of instances deleted 288 * @throws ClusterJUserException if not all parameters are bound 289 */ deletePersistentAll(QueryExecutionContext context)290 public int deletePersistentAll(QueryExecutionContext context) { 291 SessionSPI session = context.getSession(); 292 // calculate what kind of scan is needed 293 // if no where clause, scan the entire table 294 CandidateIndexImpl index = where==null? 295 CandidateIndexImpl.getIndexForNullWhereClause(): 296 where.getBestCandidateIndex(context); 297 ScanType scanType = index.getScanType(); 298 Map<String, Object> explain = newExplain(index, scanType); 299 context.setExplain(explain); 300 int result = 0; 301 int errorCode = 0; 302 Index storeIndex; 303 session.startAutoTransaction(); 304 305 try { 306 switch (scanType) { 307 308 case PRIMARY_KEY: { 309 // perform a delete by primary key operation 310 if (logger.isDetailEnabled()) logger.detail("Using delete by primary key."); 311 Operation op = session.getDeleteOperation(domainTypeHandler.getStoreTable()); 312 // set key values into the operation 313 index.operationSetKeys(context, op); 314 // execute the delete operation 315 session.executeNoCommit(false, true); 316 errorCode = op.errorCode(); 317 // a non-zero result means the row was not deleted 318 result = (errorCode == 0?1:0); 319 break; 320 } 321 322 case UNIQUE_KEY: { 323 storeIndex = index.getStoreIndex(); 324 if (logger.isDetailEnabled()) logger.detail( 325 "Using delete by unique key " + index.getIndexName()); 326 // perform a delete by unique key operation 327 IndexOperation op = session.getUniqueIndexDeleteOperation(storeIndex, 328 domainTypeHandler.getStoreTable()); 329 // set the keys of the indexName into the operation 330 where.operationEqual(context, op); 331 // execute the delete operation 332 session.executeNoCommit(false, true); 333 errorCode = op.errorCode(); 334 // a non-zero result means the row was not deleted 335 result = (errorCode == 0?1:0); 336 break; 337 } 338 339 case INDEX_SCAN: { 340 storeIndex = index.getStoreIndex(); 341 if (logger.isDetailEnabled()) logger.detail( 342 "Using delete by index scan with index " + index.getIndexName()); 343 // perform an index scan operation 344 IndexScanOperation op = session.getIndexScanDeleteOperation(storeIndex, 345 domainTypeHandler.getStoreTable()); 346 // set the expected columns into the operation 347 domainTypeHandler.operationGetValues(op); 348 // set the bounds into the operation 349 index.operationSetBounds(context, op); 350 // set additional filter conditions 351 where.filterCmpValue(context, op); 352 // delete results of the scan; don't abort if no row found 353 result = session.deletePersistentAll(op, false); 354 break; 355 } 356 357 case TABLE_SCAN: { 358 if (logger.isDetailEnabled()) logger.detail("Using delete by table scan"); 359 // perform a table scan operation 360 ScanOperation op = session.getTableScanDeleteOperation(domainTypeHandler.getStoreTable()); 361 // set the expected columns into the operation 362 domainTypeHandler.operationGetValues(op); 363 // set the bounds into the operation 364 if (where != null) { 365 where.filterCmpValue(context, op); 366 } 367 // delete results of the scan; don't abort if no row found 368 result = session.deletePersistentAll(op, false); 369 break; 370 } 371 372 default: 373 throw new ClusterJFatalInternalException( 374 local.message("ERR_Illegal_Scan_Type", scanType)); 375 } 376 context.deleteFilters(); 377 session.endAutoTransaction(); 378 return result; 379 } catch (ClusterJException e) { 380 session.failAutoTransaction(); 381 throw e; 382 } catch (Exception e) { 383 session.failAutoTransaction(); 384 throw new ClusterJException(local.message("ERR_Exception_On_Query"), e); 385 } 386 } 387 createCandidateIndexes()388 protected CandidateIndexImpl[] createCandidateIndexes() { 389 return domainTypeHandler.createCandidateIndexes(); 390 } 391 392 /** Explain how this query will be or was executed and store 393 * the result in the context. 394 * 395 * @param context the context, including bound parameters 396 */ explain(QueryExecutionContext context)397 public void explain(QueryExecutionContext context) { 398 assertAllParametersBound(context); 399 CandidateIndexImpl index = where==null? 400 CandidateIndexImpl.getIndexForNullWhereClause(): 401 where.getBestCandidateIndex(context); 402 ScanType scanType = index.getScanType(); 403 Map<String, Object> explain = newExplain(index, scanType); 404 context.setExplain(explain); 405 } 406 407 /** Create a new explain for this query. 408 * @param index the index used 409 * @param scanType the scan type 410 * @return the explain 411 */ newExplain(CandidateIndexImpl index, ScanType scanType)412 protected Map<String, Object> newExplain(CandidateIndexImpl index, 413 ScanType scanType) { 414 Map<String, Object> explain = new HashMap<String, Object>(); 415 explain.put(Query.SCAN_TYPE, scanType.toString()); 416 explain.put(Query.INDEX_USED, index.getIndexName()); 417 return explain; 418 } 419 420 /** Assert that all parameters used by this query are bound. 421 * @param context the context, including the parameter map 422 * @throws ClusterJUserException if not all parameters are bound 423 */ assertAllParametersBound(QueryExecutionContext context)424 protected void assertAllParametersBound(QueryExecutionContext context) { 425 if (where != null) { 426 // Make sure all marked parameters (used in the query) are bound. 427 for (ParameterImpl param: parameters.values()) { 428 if (param.isMarkedAndUnbound(context)) { 429 throw new ClusterJUserException( 430 local.message("ERR_Parameter_Not_Bound", param.getName())); 431 } 432 } 433 } 434 } 435 getType()436 public Class<T> getType() { 437 return cls; 438 } 439 440 } 441