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