001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: *
023: * Free Software Foundation, Inc.
024: * 59 Temple Place, Suite 330
025: * Boston, MA 02111-1307 USA
026: *
027: * @author Scott Ferguson
028: */
029:
030: package com.caucho.amber.query;
031:
032: import com.caucho.amber.entity.AmberEntityHome;
033: import com.caucho.amber.expr.AmberExpr;
034: import com.caucho.amber.expr.AndExpr;
035: import com.caucho.amber.expr.ArgExpr;
036: import com.caucho.amber.expr.EmbeddedExpr;
037: import com.caucho.amber.expr.JoinExpr;
038: import com.caucho.amber.expr.ManyToOneJoinExpr;
039: import com.caucho.amber.manager.AmberConnection;
040: import com.caucho.amber.table.LinkColumns;
041: import com.caucho.amber.table.Table;
042: import com.caucho.amber.type.EntityType;
043: import com.caucho.jdbc.JdbcMetaData;
044:
045: import java.sql.SQLException;
046: import java.util.ArrayList;
047: import java.util.HashMap;
048:
049: /**
050: * Represents an amber query
051: */
052: abstract public class AbstractQuery {
053: private String _sql;
054:
055: AmberExpr _where;
056: AmberExpr _having;
057:
058: protected ArrayList<FromItem> _fromList = new ArrayList<FromItem>();
059:
060: // jpa/0w22
061: // SELECT p.startMonth FROM TestBean o JOIN o.period p
062: // p is an alias to o.period (o.period is @Embedded)
063: // "p" -> "o.period"
064: protected HashMap<String, EmbeddedExpr> _embeddedAliases = new HashMap<String, EmbeddedExpr>();
065:
066: private ArgExpr[] _argList;
067:
068: // Map named parameters to JDBC ?,?,?.
069: // Ex: INSERT INTO test VALUES(:testId, :testName) is mapped as [0]->"testId", [1]->"testName"
070: // INSERT INTO test VALUES(:testName, :testName) is mapped as [0]->"testName", [1]->"testName"
071: // XXX: HashMap<String, ArrayList<Long>> would probably be an overkill.
072: //
073: private ArrayList<String> _preparedMapping = new ArrayList<String>();
074:
075: private JdbcMetaData _metaData;
076:
077: // jpa/1231
078: private boolean _hasSubQuery;
079:
080: AbstractQuery(String sql, JdbcMetaData metaData) {
081: _sql = sql;
082: _metaData = metaData;
083: }
084:
085: /**
086: * Returns the query string.
087: */
088: public String getQueryString() {
089: return _sql;
090: }
091:
092: /**
093: * Adds an embedded alias.
094: */
095: public void addEmbeddedAlias(String alias, EmbeddedExpr expr) {
096: _embeddedAliases.put(alias, expr);
097: }
098:
099: /**
100: * Gets the embedded aliases.
101: */
102: public HashMap<String, EmbeddedExpr> getEmbeddedAliases() {
103: return _embeddedAliases;
104: }
105:
106: /**
107: * Sets the from list.
108: */
109: public FromItem createFromItem(Table table, String name) {
110: return createFromItem(null, table, name);
111: }
112:
113: /**
114: * Sets the from list.
115: */
116: public FromItem createFromItem(EntityType entityType, Table table,
117: String name) {
118: FromItem item = new FromItem(entityType, table, name, _fromList
119: .size());
120:
121: item.setQuery(this );
122:
123: _fromList.add(item);
124:
125: return item;
126: }
127:
128: /**
129: * Creates a dependent from item
130: */
131: public FromItem createDependentFromItem(FromItem parent,
132: LinkColumns link, String name) {
133: for (int i = 0; i < _fromList.size(); i++) {
134: JoinExpr join = _fromList.get(i).getJoinExpr();
135:
136: if (join != null && join.isDependent(parent, link))
137: return _fromList.get(i);
138: }
139:
140: FromItem item = createFromItem(null, link.getSourceTable(),
141: name);
142:
143: JoinExpr join = new ManyToOneJoinExpr(link, item, parent);
144:
145: item.setJoinExpr(join);
146:
147: return item;
148: }
149:
150: /**
151: * Returns the from list.
152: */
153: public ArrayList<FromItem> getFromList() {
154: return _fromList;
155: }
156:
157: /**
158: * Gets the parent query.
159: */
160: public AbstractQuery getParentQuery() {
161: return null;
162: }
163:
164: /**
165: * Returns the prepared mapping.
166: */
167: public ArrayList<String> getPreparedMapping() {
168: return _preparedMapping;
169: }
170:
171: /**
172: * Returns the SQL.
173: */
174: public abstract String getSQL();
175:
176: /**
177: * initializes the query.
178: */
179: void init() throws QueryParseException {
180: if (_where instanceof AndExpr) {
181: AndExpr and = (AndExpr) _where;
182:
183: ArrayList<AmberExpr> components = and.getComponents();
184:
185: for (int i = components.size() - 1; i >= 0; i--) {
186: AmberExpr component = components.get(i);
187:
188: if (component instanceof JoinExpr) {
189: JoinExpr link = (JoinExpr) component;
190:
191: if (link.bindToFromItem()) {
192: components.remove(i);
193: }
194: }
195: }
196:
197: _where = and.getSingle();
198: }
199:
200: if (_having instanceof AndExpr) {
201: AndExpr and = (AndExpr) _having;
202:
203: ArrayList<AmberExpr> components = and.getComponents();
204:
205: for (int i = components.size() - 1; i >= 0; i--) {
206: AmberExpr component = components.get(i);
207:
208: if (component instanceof JoinExpr) {
209: JoinExpr link = (JoinExpr) component;
210:
211: if (link.bindToFromItem()) {
212: components.remove(i);
213: }
214: }
215: }
216:
217: _having = and.getSingle();
218: }
219:
220: // Rolls up unused from items from the left to the right.
221: // It's not necessary to roll up the rightmost items because
222: // they're only created if they're actually needed
223: for (int i = 0; i < _fromList.size(); i++) {
224: FromItem item = _fromList.get(i);
225:
226: JoinExpr join = item.getJoinExpr();
227:
228: if (join == null)
229: continue;
230:
231: // XXX: jpa/1173, jpa/1178
232: // if (getParentQuery() != null)
233: // break;
234:
235: FromItem joinParent = join.getJoinParent();
236: FromItem joinTarget = join.getJoinTarget();
237:
238: boolean isTarget = item == joinTarget;
239:
240: if (joinParent == null) {
241: } else if (joinParent.getJoinExpr() == null
242: && joinParent == joinTarget
243: && !usesFromData(joinParent)) {
244: _fromList.remove(joinParent);
245:
246: replaceJoin(join);
247:
248: // XXX:
249: item.setJoinExpr(null);
250: //item.setOuterJoin(false);
251: i = -1;
252:
253: AmberExpr joinWhere = join.getWhere();
254:
255: if (joinWhere != null)
256: _where = AndExpr.create(_where, joinWhere);
257: } else if (item == joinTarget && !isJoinParent(item)
258: && !usesFromData(item)) {
259:
260: boolean isManyToOne = false;
261: boolean isManyToMany = false;
262:
263: if (join instanceof ManyToOneJoinExpr) {
264: // jpa/0h1c
265: isManyToOne = true;
266:
267: // jpa/1144
268: ManyToOneJoinExpr manyToOneJoinExpr;
269: manyToOneJoinExpr = (ManyToOneJoinExpr) join;
270: isManyToMany = manyToOneJoinExpr.isManyToMany();
271: }
272:
273: // ejb/06u0, jpa/1144, jpa/0h1c, jpa/114g
274: if (isManyToMany
275: || (isManyToOne && !item.isInnerJoin())) {
276: // ejb/06u0 || isFromInnerJoin(item)))) {
277:
278: // Optimization for common children query:
279: // SELECT o FROM TestBean o WHERE o.parent.id=?
280: // jpa/0h1k
281: // jpa/114g as negative exists test
282:
283: // jpa/0h1m
284: if (i + 1 < _fromList.size()) {
285: FromItem subItem = _fromList.get(i + 1);
286:
287: JoinExpr nextJoin = subItem.getJoinExpr();
288:
289: if (nextJoin != null
290: && nextJoin instanceof ManyToOneJoinExpr) {
291: continue;
292: }
293: }
294:
295: _fromList.remove(item);
296:
297: replaceJoin(join);
298:
299: i = -1;
300:
301: AmberExpr joinWhere = join.getWhere();
302:
303: if (joinWhere != null)
304: _where = AndExpr.create(_where, joinWhere);
305: }
306: }
307: }
308:
309: for (int i = 0; i < _fromList.size(); i++) {
310: FromItem item = _fromList.get(i);
311:
312: if (item.isInnerJoin())
313: continue;
314:
315: if (item.getJoinExpr() == null)
316: continue;
317:
318: boolean isFromInner = isFromInnerJoin(item);
319:
320: item.setOuterJoin(!isFromInner);
321: }
322: }
323:
324: boolean isJoinParent(FromItem item) {
325: for (int i = 0; i < _fromList.size(); i++) {
326: FromItem subItem = _fromList.get(i);
327:
328: if (subItem.getJoinExpr() != null
329: && subItem.getJoinExpr().getJoinParent() == item) {
330: return true;
331: }
332: }
333:
334: return false;
335: }
336:
337: boolean isFromInnerJoin(FromItem item) {
338: return usesFrom(item, AmberExpr.IS_INNER_JOIN);
339: }
340:
341: boolean usesFromData(FromItem item) {
342: return usesFrom(item, AmberExpr.USES_DATA);
343: }
344:
345: /**
346: * Returns true if this query has a subquery.
347: */
348: public boolean hasSubQuery() {
349: return _hasSubQuery;
350: }
351:
352: /**
353: * Sets true if this query has a subquery.
354: */
355: public void setHasSubQuery(boolean hasSubQuery) {
356: _hasSubQuery = hasSubQuery;
357: }
358:
359: /**
360: * Returns true if the item must have at least one entry in the database.
361: */
362: public boolean exists(FromItem item) {
363: if (_where != null && _where.exists(item)) {
364: return true;
365: }
366:
367: return false;
368: }
369:
370: /**
371: * Returns true if the from item is used by the query.
372: */
373: public boolean usesFrom(FromItem item, int type) {
374: // jpa/1201
375: if (_where != null && _where.usesFrom(item, type)) {
376: return true;
377: }
378:
379: return false;
380: }
381:
382: void replaceJoin(JoinExpr join) {
383: if (_where != null) {
384: _where = _where.replaceJoin(join);
385: }
386: }
387:
388: /**
389: * Sets the arg list.
390: */
391: boolean setArgList(ArgExpr[] argList) {
392: _argList = argList;
393:
394: int n = argList.length;
395:
396: if (n > 0) {
397:
398: if (argList[0].getName() != null) {
399:
400: for (int i = 0; i < n; i++) {
401:
402: String name = argList[i].getName();
403:
404: if (name == null) {
405: _preparedMapping = null;
406: return false;
407: }
408:
409: _preparedMapping.add(name);
410: }
411: }
412: }
413:
414: return true;
415: }
416:
417: /**
418: * Returns the arg list.
419: */
420: public ArgExpr[] getArgList() {
421: return _argList;
422: }
423:
424: /**
425: * Generates update
426: */
427: void registerUpdates(CachedQuery query) {
428: for (int i = 0; i < _fromList.size(); i++) {
429: FromItem item = _fromList.get(i);
430:
431: AmberEntityHome home = item.getEntityHome();
432:
433: CacheUpdate update = new TableCacheUpdate(query);
434:
435: home.addUpdate(update);
436: }
437: }
438:
439: /**
440: * Returns the expire time.
441: */
442: public long getCacheMaxAge() {
443: return -1;
444: }
445:
446: /**
447: * Prepares before any update.
448: */
449: public void prepare(UserQuery userQuery, AmberConnection aConn)
450: throws SQLException {
451: }
452:
453: /**
454: * Any post-sql completion
455: */
456: public void complete(UserQuery userQuery, AmberConnection aConn)
457: throws SQLException {
458: }
459:
460: /**
461: * Returns the jdbc meta data, if available.
462: */
463: JdbcMetaData getMetaData() {
464: return _metaData;
465: }
466: }
|