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.JoinExpr;
036: import com.caucho.amber.expr.KeyColumnExpr;
037: import com.caucho.amber.expr.LoadEntityExpr;
038: import com.caucho.amber.expr.ManyToOneJoinExpr;
039: import com.caucho.amber.table.Column;
040: import com.caucho.amber.type.EntityType;
041: import com.caucho.amber.type.SubEntityType;
042: import com.caucho.amber.type.Type;
043: import com.caucho.jdbc.JdbcMetaData;
044: import com.caucho.util.CharBuffer;
045:
046: import java.sql.SQLException;
047: import java.util.ArrayList;
048: import java.util.Map;
049:
050: /**
051: * Represents an Amber select query
052: */
053: public class SelectQuery extends AbstractQuery {
054: private AbstractQuery _parentQuery;
055:
056: private boolean _isDistinct;
057:
058: private ArrayList<AmberExpr> _resultList;
059:
060: private ArrayList<AmberExpr> _orderList;
061: private ArrayList<Boolean> _ascList;
062:
063: private ArrayList<AmberExpr> _groupList;
064:
065: private int _offset = -1;
066: private int _limit = -1;
067:
068: private Map<AmberExpr, String> _joinFetchMap;
069:
070: private String _sql;
071:
072: // SELECT NEW
073: private Class _constructorClass;
074:
075: private boolean _isTableReadOnly = false;
076: private long _cacheTimeout = -1;
077:
078: private boolean _hasFrom = true;
079:
080: SelectQuery(String query, JdbcMetaData metaData) {
081: super (query, metaData);
082: }
083:
084: /**
085: * Gets the (join) fetch map.
086: */
087: Map<AmberExpr, String> getJoinFetchMap() {
088: return _joinFetchMap;
089: }
090:
091: /**
092: * Sets the constructor class for SELECT NEW.
093: */
094: void setConstructorClass(Class cl) {
095: _constructorClass = cl;
096: }
097:
098: /**
099: * Gets the constructor class for SELECT NEW.
100: */
101: public Class getConstructorClass() {
102: return _constructorClass;
103: }
104:
105: /**
106: * Sets whether the query has a FROM clause or not.
107: */
108: void setHasFrom(boolean hasFrom) {
109: // The spec. is not clear about the FROM clause for
110: // Current_Date/Time/Timestamp functions.
111:
112: _hasFrom = hasFrom;
113: }
114:
115: /**
116: * Sets the parent query.
117: */
118: void setParentQuery(AbstractQuery parent) {
119: _parentQuery = parent;
120:
121: // jpa/0g40
122: if (parent != null) {
123: // jpa/1231
124: parent.setHasSubQuery(true);
125: }
126: }
127:
128: /**
129: * Gets the parent query.
130: */
131: public AbstractQuery getParentQuery() {
132: return _parentQuery;
133: }
134:
135: /**
136: * Sets true if distinct.
137: */
138: void setDistinct(boolean isDistinct) {
139: _isDistinct = isDistinct;
140: }
141:
142: /**
143: * Sets the result list.
144: */
145: void setResultList(ArrayList<AmberExpr> resultList) {
146: _resultList = resultList;
147: }
148:
149: /**
150: * Returns the result list.
151: */
152: public ArrayList<AmberExpr> getResultList() {
153: return _resultList;
154: }
155:
156: /**
157: * Returns the result type.
158: */
159: int getResultCount() {
160: return _resultList.size();
161: }
162:
163: /**
164: * Returns the result type.
165: */
166: Type getResultType(int index) {
167: AmberExpr expr = _resultList.get(index);
168:
169: return expr.getType();
170: }
171:
172: /**
173: * Sets the having expression
174: */
175: void setHaving(AmberExpr expr) {
176: _having = expr;
177: }
178:
179: /**
180: * Sets the where expression
181: */
182: void setWhere(AmberExpr expr) {
183: _where = expr;
184: }
185:
186: /**
187: * Sets the group by list.
188: */
189: void setGroupList(ArrayList<AmberExpr> groupList) {
190: _groupList = groupList;
191: }
192:
193: /**
194: * Sets the (join) fetch map.
195: */
196: void setJoinFetchMap(Map<AmberExpr, String> joinFetchMap) {
197: _joinFetchMap = joinFetchMap;
198: }
199:
200: /**
201: * Sets the order by list.
202: */
203: void setOrderList(ArrayList<AmberExpr> orderList,
204: ArrayList<Boolean> ascList) {
205: _orderList = orderList;
206: _ascList = ascList;
207: }
208:
209: /**
210: * Returns the id load sql
211: */
212: public String getSQL() {
213: return _sql;
214: }
215:
216: /**
217: * Returns the expire time.
218: */
219: public long getCacheMaxAge() {
220: return _cacheTimeout;
221: }
222:
223: /**
224: * Returns true for cacheable queries.
225: */
226: public boolean isCacheable() {
227: return 100L <= _cacheTimeout;
228: }
229:
230: /**
231: * Are the tables read-only
232: */
233: public boolean isTableReadOnly() {
234: return _isTableReadOnly;
235: }
236:
237: /**
238: * Sets the OFFSET value.
239: */
240: public void setOffset(int offset) {
241: _offset = offset;
242: }
243:
244: /**
245: * Gets the OFFSET value.
246: */
247: public int getOffset() {
248: return _offset;
249: }
250:
251: /**
252: * Sets the LIMIT value.
253: */
254: public void setLimit(int limit) {
255: _limit = limit;
256: }
257:
258: /**
259: * Gets the LIMIT value.
260: */
261: public int getLimit() {
262: return _limit;
263: }
264:
265: /**
266: * initializes the query.
267: */
268: void init() throws QueryParseException {
269: super .init();
270:
271: _cacheTimeout = Long.MAX_VALUE / 2;
272: _isTableReadOnly = true;
273: for (FromItem item : _fromList) {
274: EntityType type = item.getTableType();
275:
276: if (type != null) {
277: long timeout = type.getCacheTimeout();
278:
279: if (timeout < _cacheTimeout)
280: _cacheTimeout = timeout;
281:
282: if (!type.isReadOnly())
283: _isTableReadOnly = false;
284: } else {
285: // XXX: kills the cache?
286: _isTableReadOnly = false;
287: }
288: }
289:
290: _sql = generateLoadSQL();
291: }
292:
293: /**
294: * Returns true if the item must have at least one entry in the database.
295: */
296: public boolean exists(FromItem item) {
297: // jpa/0h1b vs jpa/114g
298: if (_where != null && _where.exists(item)) {
299: return true;
300: }
301:
302: if (_orderList != null) {
303: for (AmberExpr orderBy : _orderList) {
304: // jpa/1110
305: if (orderBy instanceof KeyColumnExpr
306: && orderBy.usesFrom(item,
307: AmberExpr.IS_INNER_JOIN, false))
308: return true;
309: }
310: }
311:
312: if (_groupList != null) {
313: for (AmberExpr groupBy : _groupList) {
314: if (groupBy instanceof KeyColumnExpr
315: && groupBy.usesFrom(item,
316: AmberExpr.IS_INNER_JOIN, false))
317: return true;
318: }
319: }
320:
321: if (_having != null && _having.exists(item))
322: return true;
323:
324: return false;
325: }
326:
327: /**
328: * Returns true if the from item is used by the query.
329: */
330: public boolean usesFrom(FromItem item, int type) {
331: for (int j = 0; j < _resultList.size(); j++) {
332: AmberExpr result = _resultList.get(j);
333:
334: if (result.usesFrom(item, type)) {
335: return true;
336: }
337: }
338:
339: if (_where != null && _where.usesFrom(item, type)) {
340: return true;
341: }
342:
343: if (_orderList != null) {
344: for (int j = 0; j < _orderList.size(); j++) {
345: AmberExpr order = _orderList.get(j);
346:
347: if (order.usesFrom(item, type)) {
348: return true;
349: }
350: }
351: }
352:
353: // jpa/1123
354: if (_groupList != null) {
355: for (int j = 0; j < _groupList.size(); j++) {
356: AmberExpr group = _groupList.get(j);
357:
358: // jpa/1123 if (group.usesFrom(item, type)) {
359: if (group.usesFrom(item, AmberExpr.IS_INNER_JOIN)) {
360: return true;
361: }
362: }
363:
364: if (_having != null && _having.usesFrom(item, type))
365: return true;
366: }
367:
368: return false;
369: }
370:
371: void replaceJoin(JoinExpr join) {
372: for (int i = 0; i < _resultList.size(); i++) {
373: AmberExpr result = _resultList.get(i);
374:
375: _resultList.set(i, result.replaceJoin(join));
376: }
377:
378: if (_where != null) {
379: _where = _where.replaceJoin(join);
380: }
381:
382: if (_orderList != null) {
383: for (int i = 0; i < _orderList.size(); i++) {
384: AmberExpr order = _orderList.get(i);
385:
386: _orderList.set(i, order.replaceJoin(join));
387: }
388: }
389: }
390:
391: public String generateLoadSQL() {
392: return generateLoadSQL(true);
393: }
394:
395: /**
396: * Generates the load SQL.
397: *
398: * @param fullSelect true if the load entity expressions
399: * should be fully loaded for all entity
400: * fields. Otherwise, only the entity id
401: * will be loaded: select o.id from ...
402: * It is implemented to optimize the SQL
403: * and allow for databases that only
404: * support single columns in subqueries.
405: * Derby is an example. An additional
406: * condition to generate only the o.id
407: * is the absence of group by. If there
408: * is a group by the full select will
409: * always be generated.
410: *
411: * See also com.caucho.amber.expr.ExistsExpr
412: *
413: * @return the load SQL.
414: */
415: public String generateLoadSQL(boolean fullSelect) {
416: CharBuffer cb = CharBuffer.allocate();
417:
418: cb.append("select ");
419:
420: if (_isDistinct)
421: cb.append(" distinct ");
422:
423: for (int i = 0; i < _resultList.size(); i++) {
424: if (i != 0)
425: cb.append(", ");
426:
427: AmberExpr expr = _resultList.get(i);
428:
429: if ((_groupList == null)
430: && (expr instanceof LoadEntityExpr))
431: ((LoadEntityExpr) expr).generateSelect(cb, fullSelect);
432: else
433: expr.generateSelect(cb);
434: }
435:
436: if (_hasFrom)
437: cb.append(" from ");
438:
439: // jpa/114f: reorder from list for left outer join
440: for (int i = 1; i < _fromList.size(); i++) {
441: FromItem item = _fromList.get(i);
442:
443: if (item.isOuterJoin()) {
444: JoinExpr join = item.getJoinExpr();
445:
446: if (join == null)
447: continue;
448:
449: FromItem parent = join.getJoinParent();
450:
451: int index = _fromList.indexOf(parent);
452:
453: if (index < 0)
454: continue;
455:
456: _fromList.remove(i);
457:
458: if (index < i)
459: index++;
460:
461: _fromList.add(index, item);
462: }
463: }
464:
465: boolean hasJoinExpr = false;
466: boolean isFirst = true;
467: for (int i = 0; i < _fromList.size(); i++) {
468: FromItem item = _fromList.get(i);
469:
470: // jpa/1178
471: if (getParentQuery() != null) {
472: ArrayList<FromItem> fromList = getParentQuery()
473: .getFromList();
474: if (fromList != null) {
475: if (fromList.contains(item)) {
476: hasJoinExpr = true;
477: continue;
478: }
479: }
480: }
481:
482: if (isFirst) {
483: isFirst = false;
484: } else {
485: if (item.isOuterJoin())
486: cb.append(" left outer join ");
487: else {
488: cb.append(", ");
489:
490: if (item.getJoinExpr() != null)
491: hasJoinExpr = true;
492: }
493: }
494:
495: cb.append(item.getTable().getName());
496: cb.append(" ");
497: cb.append(item.getName());
498:
499: if (item.getJoinExpr() != null && item.isOuterJoin()) {
500: cb.append(" on ");
501: item.getJoinExpr().generateJoin(cb);
502: }
503:
504: EntityType entityType = item.getEntityType();
505:
506: // jpa/0l44, jpa/0l12
507: /* XXX: jpa/0l47 move this to LoadExpr.generateSelect
508: if (entityType != null) {
509: Column discriminator = entityType.getDiscriminator();
510:
511: if (entityType instanceof SubEntityType &&
512: discriminator != null) {
513: // jpa/0l4b
514: // XXX: needs to use parser.createTableName()
515: FromItem discriminatorItem
516: = new FromItem((EntityType) entityType,
517: discriminator.getTable(),
518: item.getName() + "_disc",
519: ++i);
520:
521: discriminatorItem.setQuery(this);
522:
523: _fromList.add(i, discriminatorItem);
524:
525: cb.append(", ");
526: cb.append(discriminator.getTable().getName());
527: cb.append(' ');
528: cb.append(discriminatorItem.getName());
529: }
530: }
531: */
532: }
533:
534: // jpa/0l12
535: // if (hasJoinExpr || _where != null) {
536:
537: boolean hasExpr = false;
538:
539: for (int i = 0; i < _fromList.size(); i++) {
540: FromItem item = _fromList.get(i);
541:
542: AmberExpr expr = item.getJoinExpr();
543:
544: if (expr != null && !item.isOuterJoin()) {
545: if (hasExpr)
546: cb.append(" and ");
547: else {
548: cb.append(" where ");
549: hasExpr = true;
550: }
551:
552: expr.generateJoin(cb);
553: }
554:
555: EntityType entityType = item.getEntityType();
556:
557: // jpa/0l44
558: if (entityType != null) {
559: Column discriminator = entityType.getDiscriminator();
560:
561: // jpa/0l43
562: if (entityType instanceof SubEntityType
563: && discriminator != null) {
564: // jpa/0l12, jpa/0l4b
565:
566: if (item.getTable() == discriminator.getTable()) {
567: if (hasExpr)
568: cb.append(" and ");
569: else {
570: cb.append(" where ");
571: hasExpr = true;
572: }
573:
574: cb.append("(" + item.getName() + "."
575: + discriminator.getName() + " = ");
576: cb.append("'"
577: + entityType.getDiscriminatorValue()
578: + "')");
579: }
580: }
581: }
582: }
583:
584: if (_where != null) {
585: if (hasExpr)
586: cb.append(" and ");
587: else {
588: cb.append(" where ");
589: hasExpr = true;
590: }
591:
592: _where.generateWhere(cb);
593: }
594:
595: if (_groupList != null) {
596: cb.append(" group by ");
597:
598: for (int i = 0; i < _groupList.size(); i++) {
599: if (i != 0)
600: cb.append(", ");
601:
602: _groupList.get(i).generateSelect(cb);
603: }
604: }
605:
606: if (_having != null) {
607: hasExpr = false;
608:
609: cb.append(" having ");
610:
611: /*
612: for (int i = 0; i < _fromList.size(); i++) {
613: FromItem item = _fromList.get(i);
614: AmberExpr expr = item.getJoinExpr();
615:
616: if (expr != null && ! item.isOuterJoin()) {
617: if (hasExpr)
618: cb.append(" and ");
619: hasExpr = true;
620:
621: expr.generateJoin(cb);
622: }
623: }
624: */
625:
626: if (_having != null) {
627: if (hasExpr)
628: cb.append(" and ");
629: hasExpr = true;
630:
631: _having.generateHaving(cb);
632: }
633: }
634:
635: if (_orderList != null) {
636: cb.append(" order by ");
637:
638: for (int i = 0; i < _orderList.size(); i++) {
639: if (i != 0)
640: cb.append(", ");
641:
642: _orderList.get(i).generateSelect(cb);
643:
644: if (Boolean.FALSE.equals(_ascList.get(i)))
645: cb.append(" desc");
646: }
647: }
648:
649: return cb.toString();
650: }
651:
652: /**
653: * Returns true if modifying the given table modifies a cached query.
654: */
655: public boolean invalidateTable(String table) {
656: for (int i = _fromList.size() - 1; i >= 0; i--) {
657: FromItem from = _fromList.get(i);
658:
659: if (table.equals(from.getTable().getName()))
660: return true;
661: }
662:
663: return false;
664: }
665:
666: /**
667: * Debug view.
668: */
669: public String toString() {
670: return "SelectQuery[" + getQueryString() + "]";
671: }
672: }
|