2 Copyright (c) 2009, 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.core.query;
20 import com.mysql.clusterj.ClusterJException;
21 import com.mysql.clusterj.ClusterJFatalInternalException;
22 import com.mysql.clusterj.ClusterJUserException;
23 import com.mysql.clusterj.Query;
25 import com.mysql.clusterj.core.query.PredicateImpl.ScanType;
26 import com.mysql.clusterj.core.spi.DomainFieldHandler;
27 import com.mysql.clusterj.core.spi.DomainTypeHandler;
28 import com.mysql.clusterj.core.spi.QueryExecutionContext;
29 import com.mysql.clusterj.core.spi.SessionSPI;
30 import com.mysql.clusterj.core.spi.ValueHandler;
32 import com.mysql.clusterj.core.store.Index;
33 import com.mysql.clusterj.core.store.IndexOperation;
34 import com.mysql.clusterj.core.store.IndexScanOperation;
35 import com.mysql.clusterj.core.store.Operation;
36 import com.mysql.clusterj.core.store.ResultData;
37 import com.mysql.clusterj.core.store.ScanOperation;
39 import com.mysql.clusterj.core.util.I18NHelper;
40 import com.mysql.clusterj.core.util.Logger;
41 import com.mysql.clusterj.core.util.LoggerFactoryService;
43 import com.mysql.clusterj.query.Predicate;
44 import com.mysql.clusterj.query.PredicateOperand;
45 import com.mysql.clusterj.query.QueryDefinition;
46 import com.mysql.clusterj.query.QueryDomainType;
48 import java.util.ArrayList;
49 import java.util.HashMap;
50 import java.util.List;
53 public class QueryDomainTypeImpl<T> implements QueryDomainType<T> {
55 /** My message translator */
56 static final I18NHelper local = I18NHelper.getInstance(QueryDomainTypeImpl.class);
59 static final Logger logger = LoggerFactoryService.getFactory().getInstance(QueryDomainTypeImpl.class);
62 protected Class<T> cls;
64 /** My DomainTypeHandler. */
65 protected DomainTypeHandler<T> domainTypeHandler;
67 /** My where clause. */
68 protected PredicateImpl where;
70 /** My parameters. These encapsulate the parameter names not the values. */
71 protected Map<String, ParameterImpl> parameters =
72 new HashMap<String, ParameterImpl>();
74 /** My properties. These encapsulate the property names not the values. */
75 protected Map<String, PropertyImpl> properties =
76 new HashMap<String, PropertyImpl>();
78 public QueryDomainTypeImpl(DomainTypeHandler<T> domainTypeHandler, Class<T> cls) {
80 this.domainTypeHandler = domainTypeHandler;
83 public QueryDomainTypeImpl(DomainTypeHandler<T> domainTypeHandler) {
84 this.domainTypeHandler = domainTypeHandler;
87 public PredicateOperand get(String propertyName) {
88 // if called multiple times for the same property,
89 // return the same PropertyImpl instance
90 PropertyImpl property = properties.get(propertyName);
91 if (property != null) {
94 DomainFieldHandler fmd = domainTypeHandler.getFieldHandler(propertyName);
95 property = new PropertyImpl(this, fmd);
96 properties.put(propertyName, property);
101 /** Set the where clause. Mark parameters used by this query.
102 * @param predicate the predicate
103 * @return the query definition (this)
105 public QueryDefinition<T> where(Predicate predicate) {
106 if (predicate == null) {
107 throw new ClusterJUserException(
108 local.message("ERR_Query_Where_Must_Not_Be_Null"));
110 if (!(predicate instanceof PredicateImpl)) {
111 throw new UnsupportedOperationException(
112 local.message("ERR_NotImplemented"));
114 // if a previous where clause, unmark the parameters
116 where.unmarkParameters();
119 this.where = (PredicateImpl)predicate;
120 where.markParameters();
124 public PredicateOperand param(String parameterName) {
125 final ParameterImpl parameter = parameters.get(parameterName);
126 if (parameter != null) {
129 ParameterImpl result = new ParameterImpl(this, parameterName);
130 parameters.put(parameterName, result);
135 /** Convenience method to negate a predicate.
136 * @param predicate the predicate to negate
137 * @return the inverted predicate
139 public Predicate not(Predicate predicate) {
140 return predicate.not();
143 /** Query.getResultList delegates to this method.
145 * @return the results of executing the query
147 public List<T> getResultList(QueryExecutionContext context) {
148 assertAllParametersBound(context);
150 SessionSPI session = context.getSession();
151 session.startAutoTransaction();
152 // set up results and table information
153 List<T> resultList = new ArrayList<T>();
156 ResultData resultData = getResultData(context);
157 // put the result data into the result list
158 while (resultData.next()) {
159 T row = (T) session.newInstance(cls);
160 ValueHandler handler =domainTypeHandler.getValueHandler(row);
161 // set values from result set into object
162 domainTypeHandler.objectSetValues(resultData, handler);
165 session.endAutoTransaction();
167 } catch (ClusterJException ex) {
168 session.failAutoTransaction();
170 } catch (Exception ex) {
171 session.failAutoTransaction();
172 throw new ClusterJException(
173 local.message("ERR_Exception_On_Query"), ex);
177 /** Execute the query and return the result data. The type of operation
178 * (primary key lookup, unique key lookup, index scan, or table scan)
179 * depends on the where clause and the bound parameter values.
181 * @param context the query context, including the bound parameters
182 * @return the raw result data from the query
183 * @throws ClusterJUserException if not all parameters are bound
185 public ResultData getResultData(QueryExecutionContext context) {
186 SessionSPI session = context.getSession();
187 // execute query based on what kind of scan is needed
188 // if no where clause, scan the entire table
189 CandidateIndexImpl index = where==null?
190 CandidateIndexImpl.getIndexForNullWhereClause():
191 where.getBestCandidateIndex(context);
192 ScanType scanType = index.getScanType();
193 Map<String, Object> explain = newExplain(index, scanType);
194 context.setExplain(explain);
195 ResultData result = null;
201 // perform a select operation
202 Operation op = session.getSelectOperation(domainTypeHandler.getStoreTable());
203 // set key values into the operation
204 index.operationSetKeys(context, op);
205 // set the expected columns into the operation
206 domainTypeHandler.operationGetValues(op);
207 // execute the select and get results
208 result = op.resultData();
213 storeIndex = index.getStoreIndex();
214 if (logger.isDetailEnabled()) logger.detail("Using index scan with index " + index.getIndexName());
215 IndexScanOperation op;
216 // perform an index scan operation
217 if (index.isMultiRange()) {
218 op = session.getIndexScanOperationMultiRange(storeIndex, domainTypeHandler.getStoreTable());
221 op = session.getIndexScanOperation(storeIndex, domainTypeHandler.getStoreTable());
224 // set the expected columns into the operation
225 domainTypeHandler.operationGetValues(op);
226 // set the bounds into the operation
227 index.operationSetBounds(context, op);
228 // set additional filter conditions
229 where.filterCmpValue(context, op);
230 // execute the scan and get results
231 result = op.resultData();
236 if (logger.isDetailEnabled()) logger.detail("Using table scan");
237 // perform a table scan operation
238 ScanOperation op = session.getTableScanOperation(domainTypeHandler.getStoreTable());
239 // set the expected columns into the operation
240 domainTypeHandler.operationGetValues(op);
241 // set the bounds into the operation
243 where.filterCmpValue(context, op);
245 // execute the scan and get results
246 result = op.resultData();
251 storeIndex = index.getStoreIndex();
252 if (logger.isDetailEnabled()) logger.detail("Using unique lookup with index " + index.getIndexName());
253 // perform a unique lookup operation
254 IndexOperation op = session.getUniqueIndexOperation(storeIndex, domainTypeHandler.getStoreTable());
255 // set the keys of the indexName into the operation
256 where.operationEqual(context, op);
257 // set the expected columns into the operation
258 //domainTypeHandler.operationGetValuesExcept(op, indexName);
259 domainTypeHandler.operationGetValues(op);
260 // execute the select and get results
261 result = op.resultData();
266 session.failAutoTransaction();
267 throw new ClusterJFatalInternalException(
268 local.message("ERR_Illegal_Scan_Type", scanType));
270 context.deleteFilters();
274 /** Delete the instances that satisfy the query and return the number
275 * of instances deleted. The type of operation used to find the instances
276 * (primary key lookup, unique key lookup, index scan, or table scan)
277 * depends on the where clause and the bound parameter values.
279 * @param context the query context, including the bound parameters
280 * @return the number of instances deleted
281 * @throws ClusterJUserException if not all parameters are bound
283 public int deletePersistentAll(QueryExecutionContext context) {
284 SessionSPI session = context.getSession();
285 // calculate what kind of scan is needed
286 // if no where clause, scan the entire table
287 CandidateIndexImpl index = where==null?
288 CandidateIndexImpl.getIndexForNullWhereClause():
289 where.getBestCandidateIndex(context);
290 ScanType scanType = index.getScanType();
291 Map<String, Object> explain = newExplain(index, scanType);
292 context.setExplain(explain);
296 session.startAutoTransaction();
302 // perform a delete by primary key operation
303 if (logger.isDetailEnabled()) logger.detail("Using delete by primary key.");
304 Operation op = session.getDeleteOperation(domainTypeHandler.getStoreTable());
305 // set key values into the operation
306 index.operationSetKeys(context, op);
307 // execute the delete operation
308 session.executeNoCommit(false, true);
309 errorCode = op.errorCode();
310 // a non-zero result means the row was not deleted
311 result = (errorCode == 0?1:0);
316 storeIndex = index.getStoreIndex();
317 if (logger.isDetailEnabled()) logger.detail(
318 "Using delete by unique key " + index.getIndexName());
319 // perform a delete by unique key operation
320 IndexOperation op = session.getUniqueIndexDeleteOperation(storeIndex,
321 domainTypeHandler.getStoreTable());
322 // set the keys of the indexName into the operation
323 where.operationEqual(context, op);
324 // execute the delete operation
325 session.executeNoCommit(false, true);
326 errorCode = op.errorCode();
327 // a non-zero result means the row was not deleted
328 result = (errorCode == 0?1:0);
333 storeIndex = index.getStoreIndex();
334 if (logger.isDetailEnabled()) logger.detail(
335 "Using delete by index scan with index " + index.getIndexName());
336 // perform an index scan operation
337 IndexScanOperation op = session.getIndexScanDeleteOperation(storeIndex,
338 domainTypeHandler.getStoreTable());
339 // set the expected columns into the operation
340 domainTypeHandler.operationGetValues(op);
341 // set the bounds into the operation
342 index.operationSetBounds(context, op);
343 // set additional filter conditions
344 where.filterCmpValue(context, op);
345 // delete results of the scan; don't abort if no row found
346 result = session.deletePersistentAll(op, false);
351 if (logger.isDetailEnabled()) logger.detail("Using delete by table scan");
352 // perform a table scan operation
353 ScanOperation op = session.getTableScanDeleteOperation(domainTypeHandler.getStoreTable());
354 // set the expected columns into the operation
355 domainTypeHandler.operationGetValues(op);
356 // set the bounds into the operation
358 where.filterCmpValue(context, op);
360 // delete results of the scan; don't abort if no row found
361 result = session.deletePersistentAll(op, false);
366 throw new ClusterJFatalInternalException(
367 local.message("ERR_Illegal_Scan_Type", scanType));
369 context.deleteFilters();
370 session.endAutoTransaction();
372 } catch (ClusterJException e) {
373 session.failAutoTransaction();
375 } catch (Exception e) {
376 session.failAutoTransaction();
377 throw new ClusterJException(local.message("ERR_Exception_On_Query"), e);
381 protected CandidateIndexImpl[] createCandidateIndexes() {
382 return domainTypeHandler.createCandidateIndexes();
385 /** Explain how this query will be or was executed and store
386 * the result in the context.
388 * @param context the context, including bound parameters
390 public void explain(QueryExecutionContext context) {
391 assertAllParametersBound(context);
392 CandidateIndexImpl index = where==null?
393 CandidateIndexImpl.getIndexForNullWhereClause():
394 where.getBestCandidateIndex(context);
395 ScanType scanType = index.getScanType();
396 Map<String, Object> explain = newExplain(index, scanType);
397 context.setExplain(explain);
400 /** Create a new explain for this query.
401 * @param index the index used
402 * @param scanType the scan type
403 * @return the explain
405 protected Map<String, Object> newExplain(CandidateIndexImpl index,
407 Map<String, Object> explain = new HashMap<String, Object>();
408 explain.put(Query.SCAN_TYPE, scanType.toString());
409 explain.put(Query.INDEX_USED, index.getIndexName());
413 /** Assert that all parameters used by this query are bound.
414 * @param context the context, including the parameter map
415 * @throws ClusterJUserException if not all parameters are bound
417 protected void assertAllParametersBound(QueryExecutionContext context) {
419 // Make sure all marked parameters (used in the query) are bound.
420 for (ParameterImpl param: parameters.values()) {
421 if (param.isMarkedAndUnbound(context)) {
422 throw new ClusterJUserException(
423 local.message("ERR_Parameter_Not_Bound", param.getName()));
429 public Class<T> getType() {