001: /*
002: * Copyright 2002 (C) TJDO.
003: * All rights reserved.
004: *
005: * This software is distributed under the terms of the TJDO License version 1.0.
006: * See the terms of the TJDO License in the documentation provided with this software.
007: *
008: * $Id: JDOQLQuery.java,v 1.10 2004/01/18 05:46:55 jackknifebarber Exp $
009: */
010:
011: package com.triactive.jdo.store;
012:
013: import com.triactive.jdo.PersistenceManager;
014: import java.math.BigInteger;
015: import java.math.BigDecimal;
016: import java.sql.Connection;
017: import java.sql.PreparedStatement;
018: import java.sql.ResultSet;
019: import java.sql.SQLException;
020: import java.util.ArrayList;
021: import java.util.Collection;
022: import java.util.HashMap;
023: import java.util.Map;
024: import java.util.StringTokenizer;
025: import javax.jdo.Extent;
026: import javax.jdo.JDODataStoreException;
027: import javax.jdo.JDOFatalInternalException;
028: import javax.jdo.JDOUnsupportedOptionException;
029: import javax.jdo.JDOUserException;
030: import org.apache.log4j.Category;
031:
032: /**
033: * A JDO query that uses the default JQOQL language.
034: *
035: * @author <a href="mailto:mmartin5@austin.rr.com">Mike Martin</a>
036: * @version $Revision: 1.10 $
037: *
038: * @see Query
039: */
040:
041: public class JDOQLQuery extends Query {
042: private static final Category LOG = Category
043: .getInstance(JDOQLQuery.class);
044: private static final Category CLOG = Category
045: .getInstance(Compiler.class);
046:
047: private transient Queryable candidates = null;
048:
049: private transient QueryStatement queryStmt = null;
050: private transient Query.ResultObjectFactory rof = null;
051:
052: /**
053: * Constructs a new query instance having no persistence manager. The
054: * resulting query cannot be executed, but can be used to create a new
055: * query instance having the same criteria.
056: */
057:
058: public JDOQLQuery() {
059: this (null, null);
060: }
061:
062: /**
063: * Constructs a new query instance that uses the given persistence manager.
064: *
065: * @param pm the associated persistence manager for this query.
066: */
067:
068: public JDOQLQuery(PersistenceManager pm, StoreManager storeMgr) {
069: this (pm, storeMgr, null);
070: }
071:
072: /**
073: * Constructs a new query instance having the same criteria as the given
074: * query.
075: *
076: * @param q the query from which to copy criteria.
077: */
078:
079: public JDOQLQuery(PersistenceManager pm, StoreManager storeMgr,
080: JDOQLQuery q) {
081: super (pm, storeMgr);
082:
083: if (q == null) {
084: candidateClass = null;
085: filter = null;
086: imports = null;
087: variables = null;
088: parameters = null;
089: ordering = null;
090: } else {
091: candidateClass = q.candidateClass;
092: filter = q.filter;
093: imports = q.imports;
094: variables = q.variables;
095: parameters = q.parameters;
096: ordering = q.ordering;
097: }
098: }
099:
100: protected void discardCompiled() {
101: super .discardCompiled();
102:
103: queryStmt = null;
104: rof = null;
105: }
106:
107: public boolean equals(Object obj) {
108: if (obj == this )
109: return true;
110:
111: if (!(obj instanceof JDOQLQuery) || !super .equals(obj))
112: return false;
113:
114: return true;
115: }
116:
117: /**
118: * Set the candidate Extent to query.
119: *
120: * @param pcs the Candidate Extent.
121: *
122: * @see javax.jdo.Query#setCandidates(javax.jdo.Extent)
123: */
124:
125: public void setCandidates(Extent pcs) {
126: setCandidatesInternal(pcs);
127: }
128:
129: /**
130: * Set the candidate Collection to query.
131: *
132: * @param pcs the Candidate collection.
133: *
134: * @see javax.jdo.Query#setCandidates(java.util.Collection)
135: */
136:
137: public void setCandidates(Collection pcs) {
138: setCandidatesInternal(pcs);
139: }
140:
141: private void setCandidatesInternal(Object pcs) {
142: if (!(pcs instanceof Queryable))
143: throw new JDOUnsupportedOptionException(
144: "Object not queryable: " + pcs.getClass().getName());
145:
146: discardCompiled();
147:
148: candidates = (Queryable) pcs;
149:
150: if (candidateClass == null)
151: candidateClass = candidates.getCandidateClass();
152: }
153:
154: /**
155: * Verify the elements of the query and provide a hint to the query to
156: * prepare and optimize an execution plan.
157: *
158: * @see javax.jdo.Query#compile
159: */
160:
161: public void compile() {
162: super .compile();
163: isCompiled = true;
164: }
165:
166: private void compile(Map parameters) {
167: discardCompiled();
168: super .compile();
169:
170: try {
171: if (CLOG.isDebugEnabled()) {
172: CLOG.debug("Filter = " + filter);
173: CLOG.debug("Ordering = " + ordering);
174: }
175:
176: long startTime = System.currentTimeMillis();
177:
178: Compiler c = new Compiler(parameters);
179:
180: queryStmt = c.compileQueryStatement();
181: rof = candidates.newResultObjectFactory(queryStmt);
182:
183: if (CLOG.isDebugEnabled())
184: CLOG.debug("Compile time = "
185: + (System.currentTimeMillis() - startTime)
186: + " ms: " + queryStmt);
187:
188: isCompiled = true;
189: } finally {
190: if (!isCompiled)
191: discardCompiled();
192: }
193: }
194:
195: /**
196: * Execute the query and return the filtered Collection.
197: *
198: * @param parameters the Map containing all of the parameters.
199: *
200: * @return the filtered Collection.
201: *
202: * @see javax.jdo.Query#executeWithMap(Map)
203: * @see #executeWithArray(Object[] parameters)
204: */
205:
206: public Object executeWithMap(Map parameters) {
207: compile(parameters);
208:
209: QueryResult qr = null;
210:
211: try {
212: Connection conn = pm.getConnection(false);
213:
214: try {
215: PreparedStatement ps = queryStmt.toStatementText()
216: .prepareStatement(pm, conn);
217:
218: try {
219: long startTime = System.currentTimeMillis();
220:
221: ResultSet rs = ps.executeQuery();
222:
223: if (LOG.isDebugEnabled())
224: LOG
225: .debug("Time = "
226: + (System.currentTimeMillis() - startTime)
227: + " ms: " + queryStmt);
228:
229: try {
230: qr = new QueryResult(this , rof, rs);
231: } finally {
232: if (qr == null)
233: rs.close();
234: }
235: } finally {
236: if (qr == null)
237: ps.close();
238: }
239: } finally {
240: pm.releaseConnection(conn);
241: }
242: } catch (SQLException e) {
243: throw dba.newDataStoreException("Error executing query: "
244: + queryStmt, e);
245: }
246:
247: queryResults.add(qr);
248:
249: return qr;
250: }
251:
252: public class Compiler {
253: private final Map parameters;
254: private final HashMap expressionsByVariableName = new HashMap();
255: private QueryStatement qs = null;
256: private Parser p = null;
257:
258: public Compiler(Map parameters) {
259: if (parameters != null
260: && parameters.size() != parameterNames.size())
261: throw new JDOUserException(
262: "Incorrect number of parameters: "
263: + parameters.size() + ", s/b "
264: + parameterNames.size());
265:
266: this .parameters = parameters;
267: }
268:
269: public void bindVariable(String name, SQLExpression expr) {
270: SQLExpression previousExpr = (SQLExpression) expressionsByVariableName
271: .put(name, expr);
272:
273: if (previousExpr != null)
274: throw new JDOFatalInternalException("Error binding "
275: + name + " to " + expr
276: + ", previously bound to " + previousExpr);
277: }
278:
279: public QueryStatement compileQueryStatement() {
280: if (candidates == null) {
281: if (candidateClass == null)
282: throw new JDOUserException(
283: "No candidate class provided");
284:
285: candidates = (Queryable) pm.getExtent(candidateClass,
286: true);
287: }
288:
289: qs = candidates.newQueryStatement(candidateClass);
290:
291: if (filter != null && filter.length() > 0) {
292: p = new Parser(filter, parsedImports);
293: SQLExpression expr = compileExpression();
294:
295: if (!p.parseEOS())
296: throw new ExpressionSyntaxException(
297: "Invalid expression");
298:
299: if (!(expr instanceof BooleanExpression))
300: throw new JDOUserException(
301: "Filter expression does not yield boolean result: "
302: + filter);
303:
304: qs.andCondition((BooleanExpression) expr);
305: }
306:
307: if (ordering != null && ordering.length() > 0) {
308: StringTokenizer t1 = new StringTokenizer(ordering, ",");
309:
310: int n = t1.countTokens();
311: SQLExpression[] orderExprs = new SQLExpression[n];
312: boolean[] descending = new boolean[n];
313:
314: for (n = 0; t1.hasMoreTokens(); ++n) {
315: StringTokenizer t2 = new StringTokenizer(t1
316: .nextToken(), " ");
317:
318: if (t2.countTokens() != 2)
319: throw new JDOUserException(
320: "Invalid order specification: "
321: + ordering);
322:
323: String expression = t2.nextToken();
324: String direction = t2.nextToken();
325:
326: p = new Parser(expression, parsedImports);
327: orderExprs[n] = compileExpression();
328:
329: if (!p.parseEOS())
330: throw new ExpressionSyntaxException(
331: "Invalid expression");
332:
333: if (direction.equals("ascending"))
334: descending[n] = false;
335: else if (direction.equals("descending"))
336: descending[n] = true;
337: else
338: throw new JDOUserException(
339: "Invalid order direction: " + ordering);
340: }
341:
342: qs.setOrdering(orderExprs, descending);
343: }
344:
345: return qs;
346: }
347:
348: private SQLExpression compileExpression() {
349: return compileConditionalOrExpression();
350: }
351:
352: private SQLExpression compileConditionalOrExpression() {
353: SQLExpression expr = compileConditionalAndExpression();
354:
355: while (p.parseString("||"))
356: expr = expr.ior(compileConditionalAndExpression());
357:
358: return expr;
359: }
360:
361: private SQLExpression compileConditionalAndExpression() {
362: SQLExpression expr = compileInclusiveOrExpression();
363:
364: while (p.parseString("&&"))
365: expr = expr.and(compileInclusiveOrExpression());
366:
367: return expr;
368: }
369:
370: private SQLExpression compileInclusiveOrExpression() {
371: SQLExpression expr = compileExclusiveOrExpression();
372:
373: while (p.parseChar('|', '|'))
374: expr = expr.ior(compileExclusiveOrExpression());
375:
376: return expr;
377: }
378:
379: private SQLExpression compileExclusiveOrExpression() {
380: SQLExpression expr = compileAndExpression();
381:
382: while (p.parseChar('^'))
383: expr = expr.eor(compileExclusiveOrExpression());
384:
385: return expr;
386: }
387:
388: private SQLExpression compileAndExpression() {
389: SQLExpression expr = compileEqualityExpression();
390:
391: while (p.parseChar('&', '&'))
392: expr = expr.and(compileEqualityExpression());
393:
394: return expr;
395: }
396:
397: private SQLExpression compileEqualityExpression() {
398: SQLExpression expr = compileRelationalExpression();
399:
400: for (;;) {
401: if (p.parseString("=="))
402: expr = expr.eq(compileRelationalExpression());
403: else if (p.parseString("!="))
404: expr = expr.noteq(compileRelationalExpression());
405: else
406: break;
407: }
408:
409: return expr;
410: }
411:
412: private SQLExpression compileRelationalExpression() {
413: SQLExpression expr = compileAdditiveExpression();
414:
415: for (;;) {
416: if (p.parseString("<="))
417: expr = expr.lteq(compileAdditiveExpression());
418: else if (p.parseString(">="))
419: expr = expr.gteq(compileAdditiveExpression());
420: else if (p.parseChar('<'))
421: expr = expr.lt(compileAdditiveExpression());
422: else if (p.parseChar('>'))
423: expr = expr.gt(compileAdditiveExpression());
424: else
425: break;
426: }
427:
428: return expr;
429: }
430:
431: private SQLExpression compileAdditiveExpression() {
432: SQLExpression expr = compileMultiplicativeExpression();
433:
434: for (;;) {
435: if (p.parseChar('+'))
436: expr = expr.add(compileMultiplicativeExpression());
437: else if (p.parseChar('-'))
438: expr = expr.sub(compileMultiplicativeExpression());
439: else
440: break;
441: }
442:
443: return expr;
444: }
445:
446: private SQLExpression compileMultiplicativeExpression() {
447: SQLExpression expr = compileUnaryExpression();
448:
449: for (;;) {
450: if (p.parseChar('*'))
451: expr = expr.mul(compileUnaryExpression());
452: else if (p.parseChar('/'))
453: expr = expr.div(compileUnaryExpression());
454: else if (p.parseChar('%'))
455: expr = expr.mod(compileUnaryExpression());
456: else
457: break;
458: }
459:
460: return expr;
461: }
462:
463: private SQLExpression compileUnaryExpression() {
464: SQLExpression expr;
465:
466: if (p.parseChar('+'))
467: expr = compileUnaryExpression();
468: else if (p.parseChar('-'))
469: expr = compileUnaryExpression().neg();
470: else
471: expr = compileUnaryExpressionNotPlusMinus();
472:
473: return expr;
474: }
475:
476: private SQLExpression compileUnaryExpressionNotPlusMinus() {
477: SQLExpression expr;
478:
479: if (p.parseChar('~'))
480: expr = compileUnaryExpression().com();
481: else if (p.parseChar('!'))
482: expr = compileUnaryExpression().not();
483: else if ((expr = compileCastExpression()) == null)
484: expr = compilePrimary();
485:
486: return expr;
487: }
488:
489: private SQLExpression compileCastExpression() {
490: Class type;
491:
492: if ((type = p.parseCast()) == null)
493: return null;
494:
495: return compileUnaryExpression().cast(type);
496: }
497:
498: private SQLExpression compilePrimary() {
499: SQLExpression expr = compileLiteral();
500:
501: if (expr == null) {
502: if (p.parseChar('(')) {
503: expr = compileExpression();
504:
505: if (!p.parseChar(')'))
506: throw new ExpressionSyntaxException(
507: "')' expected");
508: } else
509: expr = compileIdentifier();
510:
511: while (p.parseChar('.')) {
512: String id = p.parseIdentifier();
513:
514: if (id == null)
515: throw new ExpressionSyntaxException(
516: "Identifier expected");
517:
518: if (p.parseChar('(')) {
519: ArrayList args = new ArrayList();
520:
521: if (!p.parseChar(')')) {
522: do {
523: args.add(compileExpression());
524: } while (p.parseChar(','));
525:
526: if (!p.parseChar(')'))
527: throw new ExpressionSyntaxException(
528: "')' expected");
529: }
530:
531: expr = expr.callMethod(id, args);
532: } else
533: expr = expr.accessField(id);
534: }
535: }
536:
537: return expr;
538: }
539:
540: private SQLExpression compileLiteral() {
541: Class litType;
542: Object litValue;
543:
544: String sLiteral;
545: BigDecimal fLiteral;
546: BigInteger iLiteral;
547: Character cLiteral;
548: Boolean bLiteral;
549:
550: if ((sLiteral = p.parseStringLiteral()) != null) {
551: litType = String.class;
552: litValue = sLiteral;
553: } else if ((fLiteral = p.parseFloatingPointLiteral()) != null) {
554: litType = BigDecimal.class;
555: litValue = fLiteral;
556: } else if ((iLiteral = p.parseIntegerLiteral()) != null) {
557: litType = Long.class;
558: litValue = new Long(iLiteral.longValue());
559: } else if ((cLiteral = p.parseCharacterLiteral()) != null) {
560: litType = Character.class;
561: litValue = cLiteral;
562: } else if ((bLiteral = p.parseBooleanLiteral()) != null) {
563: litType = Boolean.class;
564: litValue = bLiteral;
565: } else if (p.parseNullLiteral())
566: return new NullLiteral(qs);
567: else
568: return null;
569:
570: Mapping m = dba.getMapping(litType);
571: return m.newSQLLiteral(qs, litValue);
572: }
573:
574: private SQLExpression compileIdentifier() {
575: SQLExpression expr;
576: String id = p.parseIdentifier();
577:
578: if (id == null)
579: throw new ExpressionSyntaxException(
580: "Identifier expected");
581:
582: if (parameterNames.contains(id)) {
583: Mapping m = dba.getMapping((Class) parameterTypesByName
584: .get(id));
585:
586: if (!parameters.containsKey(id))
587: throw new JDOUserException("Required parameter "
588: + id + " not provided");
589:
590: Object parameterValue = parameters.get(id);
591:
592: if (parameterValue == null)
593: expr = new NullLiteral(qs);
594: else
595: expr = m.newSQLLiteral(qs, parameterValue);
596: } else if (variableNames.contains(id)) {
597: expr = (SQLExpression) expressionsByVariableName
598: .get(id);
599:
600: if (expr == null)
601: expr = new UnboundVariable(qs, id,
602: (Class) variableTypesByName.get(id), this );
603: } else
604: expr = qs.getDefaultTableExpression()
605: .newFieldExpression(id);
606:
607: return expr;
608: }
609:
610: private class ExpressionSyntaxException extends
611: JDOUserException {
612: public ExpressionSyntaxException(String msg) {
613: super (msg + " at character " + (p.getIndex() + 1)
614: + " in \"" + p.getInput() + '"');
615: }
616: }
617: }
618: }
|