001: /*
002: * Copyright (c) 1998 - 2005 Versant Corporation
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * Versant Corporation - initial API and implementation
010: */
011: package com.versant.core.jdbc.ejbql;
012:
013: import com.versant.core.ejb.query.*;
014: import com.versant.core.jdbc.sql.exp.*;
015: import com.versant.core.jdbc.sql.SqlDriver;
016: import com.versant.core.jdbc.metadata.JdbcClass;
017: import com.versant.core.jdbc.metadata.JdbcField;
018: import com.versant.core.jdbc.metadata.JdbcPolyRefField;
019: import com.versant.core.jdbc.fetch.FetchSpec;
020: import com.versant.core.jdbc.fetch.FopGetOID;
021: import com.versant.core.jdbc.fetch.FetchOpDataMainRS;
022: import com.versant.core.jdbc.fetch.FopGetState;
023: import com.versant.core.metadata.ClassMetaData;
024: import com.versant.core.metadata.FieldMetaData;
025: import com.versant.core.common.BindingSupportImpl;
026: import com.versant.core.common.Debug;
027:
028: import java.util.ArrayList;
029:
030: /**
031: * Walks EJBQL Node trees to produce SqlExp trees.
032: */
033: public class EJBQLNodeToSqlExp extends NodeVisitorAdapter {
034:
035: private ResolveContext rc;
036: private SqlDriver sqlDriver;
037:
038: private static final Object[] EMPTY = new Object[0];
039:
040: public EJBQLNodeToSqlExp(ResolveContext rc, SqlDriver sqlDriver) {
041: this .rc = rc;
042: this .sqlDriver = sqlDriver;
043: }
044:
045: /**
046: * Throw exception if we hit any unhandled nodes.
047: */
048: protected Object defaultArrive(Node node, Object msg) {
049: throw rc.createUserException("Not implemented: "
050: + node.getClass(), node);
051: }
052:
053: /**
054: * Convert resolved Node tree to a SqlExp tree. Returns nul if node is null.
055: */
056: public SqlExp toSqlExp(Node node, Object msg) {
057: if (node == null) {
058: return null;
059: } else {
060: return (SqlExp) node.arrive(this , msg);
061: }
062: }
063:
064: private RuntimeException notImplemented() {
065: return notImplemented("not implemented");
066: }
067:
068: private RuntimeException notImplemented(String msg) {
069: return BindingSupportImpl.getInstance().internal(msg);
070: }
071:
072: /**
073: * Invoke arrive on each entry in the list. Returns Object[0] if list is
074: * null.
075: */
076: private Object[] invokeArriveOnList(Node list, Object msg) {
077: if (list == null) {
078: return EMPTY;
079: }
080: if (list.getNext() == null) { // 1 node only
081: return new Object[] { toSqlExp(list, msg) };
082: } else {
083: ArrayList a = new ArrayList();
084: for (; list != null; list = list.getNext()) {
085: a.add(toSqlExp(list, msg));
086: }
087: return a.toArray();
088: }
089: }
090:
091: /**
092: * SELECT .. FROM .. WHERE .. GROUP BY .. HAVING .. ORDER BY expression
093: * that is not a subquery.
094: */
095: public Object arriveSelectNode(SelectNode node, Object msg) {
096: Object[] fromList = invokeArriveOnList(node.getFromList(), msg);
097: SelectExp se = (SelectExp) fromList[0];
098: if (fromList.length > 1) {
099: // todo combine into a single SelextExp
100: throw notImplemented();
101: }
102: se.fetchSpec = new FetchSpec(se, sqlDriver);
103: invokeArriveOnList(node.getSelectList(), msg);
104: se.whereExp = toSqlExp(node.getWhere(), msg);
105: return se;
106: }
107:
108: /**
109: * Declaration of identification variable in the FROM list e.g.
110: * 'FROM Order AS o, Product AS p'.
111: */
112: public Object arriveIdentificationVarNode(
113: IdentificationVarNode node, Object msg) {
114: // create SelectExp's linked with joins for whole nav tree
115: NavRoot root = node.getNavRoot();
116: ClassMetaData cmd = root.getNavClassMetaData();
117: JdbcClass jdbcClass = (JdbcClass) cmd.storeClass;
118: SelectExp se = new SelectExp();
119: se.table = jdbcClass.table;
120: root.storeObject = se;
121: return se;
122: }
123:
124: /**
125: * Path (e.g. o.customer.name, o).
126: */
127: public Object arrivePathNode(PathNode node, Object msg) {
128: switch (node.getParentType()) {
129: case PathNode.SELECT:
130: return pathNodeSelect(node);
131: case PathNode.WHERE:
132: return pathNodeWhere(node);
133: case PathNode.GROUP_BY:
134: case PathNode.ORDER_BY:
135: case PathNode.AGGREGATE:
136: case PathNode.CONSTRUCTOR:
137: case PathNode.JOIN:
138: case PathNode.COLLECTION_MEMBER:
139: }
140: return super .arrivePathNode(node, msg);
141: }
142:
143: private Object pathNodeSelect(PathNode node) {
144: FieldMetaData fmd = node.getFmd();
145: if (fmd == null) { // complete object
146: return arriveObjectNodeImp(node.getNavBase());
147: } else { // single field
148: throw notImplemented();
149: }
150: }
151:
152: public Object arriveObjectNode(ObjectNode node, Object msg) {
153: return arriveObjectNodeImp(node.getNavBase());
154: }
155:
156: private Object arriveObjectNodeImp(NavBase navBase) {
157: SelectExp se = (SelectExp) navBase.storeObject;
158: FetchSpec fetchSpec = se.fetchSpec;
159: ClassMetaData cmd = navBase.getNavClassMetaData();
160: FopGetOID fopGetOid = new FopGetOID(fetchSpec,
161: FetchOpDataMainRS.INSTANCE, cmd);
162: FopGetState fopGetState = new FopGetState(fetchSpec, fopGetOid
163: .getOutputData(), cmd.fetchGroups[0], true);
164: fetchSpec.addFetchOp(fopGetOid, true);
165: fetchSpec.addFetchOp(fopGetState, true);
166: // one of the above lines must change to false when we figure out
167: // how to link the State to the OID cleanly (OID field on State??)
168: return se;
169: }
170:
171: /**
172: * Get the SelectExp for node. This will create it and any joins required
173: * if it does not already exist. This actually creates all of the joins
174: * for the nodes NavRoot.
175: */
176: private SelectExp getNodeSelectExp(PathNode node) {
177: NavBase nav = node.getNavBase();
178: SelectExp se = (SelectExp) nav.storeObject;
179: if (se == null) {
180: createJoins(nav.getRoot());
181: se = (SelectExp) nav.storeObject;
182: if (Debug.DEBUG) {
183: if (se == null) {
184: throw BindingSupportImpl.getInstance().internal(
185: "nav.storeObject == null: " + node);
186: }
187: }
188: }
189: return se;
190: }
191:
192: /**
193: * Create all the joins for a tree of navigations starting at navRoot.
194: */
195: private void createJoins(NavBase from) {
196: SelectExp fromSE = (SelectExp) from.storeObject;
197: for (NavField to = from.getChildren(); to != null; to = to
198: .getNext()) {
199: if (to.storeObject != null) {
200: continue;
201: }
202: JdbcField jdbcField = (JdbcField) to.getFmd().storeField;
203: JdbcClass targetClass = (JdbcClass) to
204: .getNavClassMetaData().storeClass;
205: SelectExp se = new SelectExp();
206: se.table = targetClass.table;
207: se.jdbcField = jdbcField;
208: if (jdbcField instanceof JdbcPolyRefField) {
209: JdbcPolyRefField pf = (JdbcPolyRefField) jdbcField;
210: Join j = fromSE.addJoin(pf.refCols, se.table.pk, se);
211: // add a condition to check that the class-id column of
212: // the polyref matches the type used in the cast
213: j.appendJoinExp(pf.createClassIdMatchExp(fromSE,
214: targetClass.cmd));
215: } else {
216: fromSE
217: .addJoin(jdbcField.mainTableCols, se.table.pk,
218: se);
219: }
220: se.outer = to.isOuter();
221: to.storeObject = se;
222: createJoins(to);
223: }
224: }
225:
226: private Object pathNodeWhere(PathNode node) {
227: FieldMetaData fmd = node.getFmd();
228: if (fmd != null) {
229: JdbcField jdbcField = (JdbcField) fmd.storeField;
230: SelectExp se = getNodeSelectExp(node);
231: ColumnExp columnExp = jdbcField.toColumnExp(SelectExp
232: .createJoinToSuperTable(se, jdbcField), true);
233: return columnExp;
234: } else {
235: throw notImplemented();
236: }
237: }
238:
239: public Object arriveCompNode(CompNode node, Object msg) {
240: SqlExp left = toSqlExp(node.getLeft(), msg);
241: SqlExp right = toSqlExp(node.getRight(), left);
242: int op = node.getOp();
243: if (op == CompNode.EQ || op == CompNode.NE) {
244: if (op == CompNode.EQ) {
245: op = BinaryOpExp.EQUAL;
246: } else {
247: op = BinaryOpExp.NOT_EQUAL;
248: }
249: return SqlExp.createBinaryOpExp(left, op, right);
250: } else {
251: if (left.next == null && right.next == null) {
252: switch (op) {
253: case CompNode.GT:
254: op = BinaryOpExp.GT;
255: break;
256: case CompNode.LT:
257: op = BinaryOpExp.LT;
258: break;
259: case CompNode.GE:
260: op = BinaryOpExp.GE;
261: break;
262: case CompNode.LE:
263: op = BinaryOpExp.LE;
264: break;
265: default:
266: throw BindingSupportImpl.getInstance().internal(
267: "Unknown op: " + op);
268: }
269: return new BinaryOpExp(left, op, right);
270: }
271: throw BindingSupportImpl.getInstance().runtime(
272: "Expressions consisting of multiple columns may not be compared "
273: + "with >, <, >= or <=\n");
274: }
275: }
276:
277: public Object arriveLiteralNode(LiteralNode node, Object msg) {
278: String value;
279: int t;
280: switch (node.getType()) {
281: case LiteralNode.DOUBLE:
282: t = LiteralExp.TYPE_OTHER;
283: value = sqlDriver.toSqlLiteral(node.getDoubleValue());
284: break;
285: case LiteralNode.LONG:
286: t = LiteralExp.TYPE_OTHER;
287: value = sqlDriver.toSqlLiteral(node.getLongValue());
288: break;
289: case LiteralNode.STRING:
290: t = LiteralExp.TYPE_STRING;
291: value = node.getStringValue();
292: break;
293: case LiteralNode.BOOLEAN:
294: t = LiteralExp.TYPE_OTHER;
295: value = sqlDriver.toSqlLiteral(node.getBooleanValue());
296: break;
297: default:
298: throw BindingSupportImpl.getInstance().internal(
299: "Unknown literal type: " + node.getType());
300: }
301: return new LiteralExp(t, value);
302: }
303:
304: public Object arriveParameterNode(ParameterNode node, Object msg) {
305: if (msg != null && !(msg instanceof SqlExp)) {
306: throw BindingSupportImpl.getInstance().internal(
307: "expected SqlExp for msg");
308: }
309: SqlExp leftSibling = (SqlExp) msg;
310: SqlParamUsage u = new SqlParamUsage();
311: node.getUsage().storeObject = u;
312: if (leftSibling instanceof ColumnExp) {
313: ColumnExp left = (ColumnExp) leftSibling;
314: u.jdbcField = left.jdbcField;
315: u.col = left.col;
316: // Create extra ? in the SQL if our left sibling has multiple
317: // columns. Example: a reference to a composite pk class
318: // compared to a parameter.
319: SqlExp pos = u.expList = new ParamExp(u.jdbcType, u);
320: for (;;) {
321: if ((left = (ColumnExp) left.next) == null)
322: break;
323: pos = pos.next = new ParamExp(left.getJdbcType(), u);
324: }
325: } else {
326: if (leftSibling.next != null) {
327: throw BindingSupportImpl.getInstance().internal(
328: "Expression on left has more than one column and no "
329: + "field information");
330: }
331: u.expList = new ParamExp(leftSibling.getJdbcType(), u);
332: }
333: if (u.jdbcField == null) {
334: u.jdbcType = leftSibling.getJdbcType();
335: u.javaTypeCode = leftSibling.getJavaTypeCode();
336: u.classIndex = leftSibling.getClassIndex();
337: }
338: return u.expList;
339: }
340:
341: public Object arriveAndNode(AndNode node, Object msg) {
342: // Build the list of expressions to be AND'ed together.
343: SqlExp prev = null, first = null;
344: for (Node cn = node.getArgsList(); cn != null; cn = cn
345: .getNext()) {
346: SqlExp e = toSqlExp(cn, prev);
347: if (first == null) {
348: first = e;
349: } else if (prev != null) {
350: prev.next = e;
351: }
352: prev = e;
353: }
354:
355: // if there is now only one entry in the list then return it otherwise
356: // create a new AndExp for the list
357: if (first.next == null) {
358: return first;
359: } else {
360: return new AndExp(first);
361: }
362: }
363:
364: public Object arriveOrNode(OrNode node, Object msg) {
365: // Build the list of expressions to be OR'ed together.
366: SqlExp prev = null, first = null;
367: for (Node cn = node.getArgsList(); cn != null; cn = cn
368: .getNext()) {
369: SqlExp e = toSqlExp(cn, prev);
370: if (first == null) {
371: first = e;
372: } else if (prev != null) {
373: prev.next = e;
374: }
375: prev = e;
376: }
377:
378: // if there is now only one entry in the list then return it otherwise
379: // create a new OrExp for the list
380: if (first.next == null) {
381: return first;
382: } else {
383: return new OrExp(first);
384: }
385: }
386:
387: public Object arriveAddNode(AddNode node, Object msg) {
388: SqlExp left = processAddNodeChild(node.getLeft());
389: SqlExp right = processAddNodeChild(node.getRight());
390: left.next = right;
391: return new AddExp(left, new int[] { node.getOp() });
392: }
393:
394: private SqlExp processAddNodeChild(Node c) {
395: SqlExp e = toSqlExp(c, null);
396: if (e.next != null) {
397: throw BindingSupportImpl.getInstance().runtime(
398: "Expressions consisting of multiple columns may not be used"
399: + "with + or -");
400: }
401: return e;
402: }
403:
404: public Object arriveMultiplyNode(MultiplyNode node, Object msg) {
405: SqlExp left = processMultiplyNodeChild(node.getLeft());
406: SqlExp right = processMultiplyNodeChild(node.getLeft());
407: left.next = right;
408: return new AddExp(left, new int[] { node.getOp() });
409: }
410:
411: private SqlExp processMultiplyNodeChild(Node c) {
412: SqlExp e = toSqlExp(c, null);
413: if (e.next != null) {
414: throw BindingSupportImpl.getInstance().runtime(
415: "Expressions consisting of multiple columns may not be used"
416: + "with * or /");
417: }
418: return e;
419: }
420:
421: public Object arriveParenNode(ParenNode node, Object msg) {
422: SqlExp e = toSqlExp(node, msg);
423: if (e instanceof ParenExp) {
424: return e;
425: } else {
426: return new ParenExp(e);
427: }
428: }
429:
430: public Object arriveBetweenNode(BetweenNode node, Object msg) {
431: SqlExp arg = toSqlExp(node.getArg(), null);
432: SqlExp from = toSqlExp(node.getFrom(), null);
433: SqlExp to = toSqlExp(node.getTo(), null);
434: arg.next = from;
435: from.next = to;
436: return new BetweenExp(arg);
437: }
438:
439: public Object arriveNotNode(NotNode node, Object msg) {
440: SqlExp arg = toSqlExp(node.getExp(), null);
441: return new UnaryOpExp(arg, UnaryOpExp.OP_NOT);
442: }
443:
444: public Object arriveLikeNode(LikeNode node, Object msg) {
445: SqlExp arg = toSqlExp(node.getPath(), null);
446: SqlExp pattern = toSqlExp(node.getPattern(), null);
447: if (node.getEscape() != null) {
448: throw notImplemented("Escape character argument to LIKE is not implemented");
449: }
450: return new BinaryOpExp(arg, BinaryOpExp.LIKE, pattern);
451: }
452:
453: public Object arriveUnaryMinusNode(UnaryMinusNode node, Object msg) {
454: SqlExp e = toSqlExp(node, msg);
455: if (e.next != null) {
456: throw BindingSupportImpl.getInstance().runtime(
457: "Expressions consisting of multiple columns may not be used"
458: + "with unary -");
459: }
460: return new UnaryOpExp(e, UnaryOpExp.OP_MINUS);
461: }
462:
463: }
|