001: package com.quadcap.sql;
002:
003: /* Copyright 1999 - 2003 Quadcap Software. All rights reserved.
004: *
005: * This software is distributed under the Quadcap Free Software License.
006: * This software may be used or modified for any purpose, personal or
007: * commercial. Open Source redistributions are permitted. Commercial
008: * redistribution of larger works derived from, or works which bundle
009: * this software requires a "Commercial Redistribution License"; see
010: * http://www.quadcap.com/purchase.
011: *
012: * Redistributions qualify as "Open Source" under one of the following terms:
013: *
014: * Redistributions are made at no charge beyond the reasonable cost of
015: * materials and delivery.
016: *
017: * Redistributions are accompanied by a copy of the Source Code or by an
018: * irrevocable offer to provide a copy of the Source Code for up to three
019: * years at the cost of materials and delivery. Such redistributions
020: * must allow further use, modification, and redistribution of the Source
021: * Code under substantially the same terms as this license.
022: *
023: * Redistributions of source code must retain the copyright notices as they
024: * appear in each source code file, these license terms, and the
025: * disclaimer/limitation of liability set forth as paragraph 6 below.
026: *
027: * Redistributions in binary form must reproduce this Copyright Notice,
028: * these license terms, and the disclaimer/limitation of liability set
029: * forth as paragraph 6 below, in the documentation and/or other materials
030: * provided with the distribution.
031: *
032: * The Software is provided on an "AS IS" basis. No warranty is
033: * provided that the Software is free of defects, or fit for a
034: * particular purpose.
035: *
036: * Limitation of Liability. Quadcap Software shall not be liable
037: * for any damages suffered by the Licensee or any third party resulting
038: * from use of the Software.
039: */
040:
041: import java.io.Externalizable;
042: import java.io.IOException;
043: import java.io.ObjectInput;
044: import java.io.ObjectOutput;
045:
046: import java.util.Vector;
047:
048: import java.sql.SQLException;
049:
050: import com.quadcap.sql.types.Op;
051: import com.quadcap.sql.types.Value;
052: import com.quadcap.sql.types.ValueLong;
053: import com.quadcap.sql.types.ValueNull;
054:
055: import com.quadcap.util.Debug;
056:
057: /**
058: * A table expression representing a <b>SELECT</b> clause.
059: *
060: * @author Stan Bailes
061: */
062:
063: public class SelectExpression extends TableExpression implements
064: Externalizable {
065: boolean distinct = false;
066: Vector items = null;
067: Vector from = null;
068: Vector groupBy = null;
069: Expression having = null;
070: Vector aggregates = null;
071:
072: /**
073: * Default constructor
074: */
075: public SelectExpression() {
076: }
077:
078: /**
079: * The typical 'select from table where' stuff
080: */
081: public SelectExpression(String tableName, Expression where) {
082: Vector v = new Vector();
083: v.addElement(new SelectFromTable(tableName));
084: this .setFrom(v);
085: this .setWhere(where);
086: }
087:
088: /**
089: * Get the FROM list
090: */
091: public Vector getFrom() {
092: return from;
093: }
094:
095: /**
096: * Set the DISTINCT flag
097: */
098: public void setDistinct(boolean b) {
099: this .distinct = b;
100: }
101:
102: /**
103: * Set the select items (i.e., the colums returned from this select)
104: */
105: public void setItems(Vector v) {
106: this .items = v;
107: }
108:
109: /**
110: * Set the from list (a list of tables is implicitly joined)
111: */
112: public void setFrom(Vector v) {
113: this .from = v;
114: }
115:
116: /**
117: * Set the GROUP BY clause as a list of names
118: */
119: public void setGroupBy(Vector v) {
120: this .groupBy = v;
121: }
122:
123: /**
124: * Set the HAVING expression
125: */
126: public void setHaving(Expression e) {
127: this .having = e;
128: }
129:
130: /**
131: * We're a table
132: */
133: public int rank() {
134: return 2;
135: }
136:
137: /**
138: * Well, a bunch of things can prevent us from being updateable.
139: * Here are the ones we know about:
140: */
141: public boolean isUpdatable() {
142: if (groupBy != null)
143: return false;
144: if (having != null)
145: return false;
146: if (distinct)
147: return false;
148: if (from == null || from.size() > 1)
149: return false;
150: if (aggregates != null && aggregates.size() > 0)
151: return false;
152:
153: TableExpression t = (TableExpression) from.elementAt(0);
154: if (!t.isUpdatable())
155: return false;
156:
157: return true; // Good luck, sir.
158: }
159:
160: /**
161: * Cross-join multiple tables in 'from' clause.
162: *
163: * TODO:
164: * First, tables which have selection constraintsfind pairs
165: * of tables that have candidate join columns and greedily join
166: * them. Next, pick tables which have matching non-key join
167: * columns and join them Finally, join all the rest.
168: */
169: TableExpression scheduleJoin() throws SQLException {
170: while (from.size() > 1) {
171: TableExpression a = (TableExpression) from.elementAt(0);
172: TableExpression b = (TableExpression) from.elementAt(1);
173: JoinedTable t = new JoinedTable(Op.CROSS, a, b);
174: //t.setOnExpression(where);
175: from.removeElementAt(0);
176: from.setElementAt(t, 0);
177: }
178: TableExpression item = (TableExpression) from.elementAt(0);
179: return item;
180: }
181:
182: /**
183: * This is the main entry point to this madness. Our handling of
184: * selects is fairly literal, and pretty brute force. Things work
185: * much better if there are appropriate indexes to use based on the
186: * query.....
187: */
188: public Cursor getCursor(Session session, Cursor cur)
189: throws SQLException {
190: if (from == null) {
191: Row row = new Row(items.size());
192: TupleImpl t = new TupleImpl();
193: for (int i = 0; i < items.size(); i++) {
194: SelectItem item = (SelectItem) items.elementAt(i);
195: Expression expr = item.getExpression();
196: Value v = expr.getValue(session, cur);
197: String name = item.getAsName();
198: if (name == null)
199: name = "Column" + i;
200: t.addColumn(name, v.getType());
201: row.set(i + 1, expr.getValue(session, cur));
202: }
203: return new StaticCursor(session, t, row);
204: }
205: TableExpression tab = null;
206:
207: if (from.size() > 1) {
208: tab = scheduleJoin();
209: } else {
210: tab = (TableExpression) from.elementAt(0);
211: }
212:
213: // give the join or query a chance to optimize
214: tab.setWhere(where);
215:
216: Cursor c = tab.getCursor(session, cur);
217: if (where != null) {
218: // Actually, we could avoid creating a predicate cursor
219: // in cases where the upstream index cursor is already
220: // sufficient.
221: c = new PredicateCursor(session, c, where);
222: }
223: if (groupBy != null) {
224: GroupByCursor gc = new GroupByCursor(session, c, groupBy);
225: c = gc;
226: Vector tItems = items; // why alias items here? XX
227: if (having != null) {
228: int cnt = (items == null) ? 0 : items.size();
229: tItems = new Vector(cnt + 1);
230: for (int i = 0; i < cnt; i++) {
231: tItems.addElement(items.elementAt(i));
232: }
233: tItems.addElement(new SelectItem(having));
234: }
235: if (isAggregate(session, tItems)) {
236: ItemsCursor ic = new ItemsCursor(session, gc, tItems);
237: c = new AggregateCursor(session, ic, gc, aggregates);
238: if (having != null) {
239: c = new HavingCursor(session, c);
240: }
241: } else if (tItems != null) {
242: ItemsCursor ic = new ItemsCursor(session, gc, tItems);
243: c = ic;
244: c = new AggregateCursor(session, ic, gc, aggregates);
245: if (having != null) {
246: c = new HavingCursor(session, c);
247: }
248: }
249: } else {
250: if (isAggregate(session, items)) {
251: Cursor optimized = optimizeAggregate(session, c, items);
252: if (optimized != null) {
253: c = optimized;
254: } else {
255: ItemsCursor ic = new ItemsCursor(session, c, items);
256: c = new AggregateCursor(session, ic, null,
257: aggregates);
258: }
259: } else {
260: if (items != null) {
261: c = new ItemsCursor(session, c, items);
262: }
263: }
264: }
265: if (distinct) {
266: c = new DistinctCursor(session, c);
267: }
268:
269: if (cur != null)
270: c.setOuterCursor(cur);
271: //#ifdef DEBUG
272: showCursor(c);
273: //#endif
274: return c;
275: }
276:
277: /**
278: * Visitor class used to analyze the select expression for aggregate usage
279: */
280: class IsAggregate implements ExpressionVisitor {
281: Session session = null;
282: Vector aggregates = null;
283: boolean seenName = false;
284: boolean seenAggregate = false;
285:
286: public IsAggregate(Session session) {
287: this .session = session;
288: }
289:
290: public void visit(Expression sub) {
291: if (sub instanceof AggregateExpression) {
292: seenAggregate = true;
293: //try {
294: // ((AggregateExpression)sub).reset(session);
295: //} catch (IOException e) {
296: // Debug.print(e);
297: //}
298: if (aggregates == null) {
299: aggregates = new Vector();
300: }
301: aggregates.addElement(sub);
302: } else {
303: seenName |= (sub instanceof NameExpression);
304: sub.visitSubExpressions(this );
305: }
306: }
307:
308: void reset() {
309: seenName = seenAggregate = false;
310: }
311: }
312:
313: /**
314: * Check the list of select items to see if any of them are aggregate
315: * expressions. (Side effect: set 'aggregates' variable.)
316: */
317: boolean isAggregate(Session session, Vector items)
318: throws SQLException {
319: if (items == null)
320: return false;
321: int cnt = 0;
322: IsAggregate isVis = new IsAggregate(session);
323: for (int i = 0; i < items.size(); i++) {
324: SelectItem item = (SelectItem) items.elementAt(i);
325: Expression expression = item.getExpression();
326: isVis.reset();
327: isVis.visit(expression);
328: if (isVis.seenAggregate && isVis.seenName
329: && groupBy == null) {
330: throw new SQLException(
331: "Expressions can't mix aggregate and "
332: + "non-aggregate values", "42000");
333: }
334: if (isVis.seenAggregate)
335: cnt++;
336: }
337: this .aggregates = isVis.aggregates;
338: if (cnt == 0)
339: return false;
340: return true;
341: }
342:
343: /**
344: * Helper to create a cursor containing a single constant value.
345: */
346: Cursor staticCursor(Session session, Cursor c, Vector items, Value v)
347: throws SQLException {
348: Row r = new Row(1);
349: r.set(1, v);
350: ItemsCursor it = new ItemsCursor(session, c, items);
351: return new StaticCursor(session, it, r);
352: }
353:
354: /**
355: * Identify cases of MIN(column) or MAX(column) on indexed columns.
356: * Compute the value directly and replace the index cursor with a
357: * static cursor containing the value.
358: */
359: Cursor optimizeAggregate(Session session, Cursor c, Vector items)
360: throws SQLException {
361: Cursor ret = null;
362: if (c instanceof IndexCursor) {
363: IndexCursor ic = (IndexCursor) c;
364: if (items.size() == 1) {
365: SelectItem item = (SelectItem) items.get(0);
366: Expression te = item.getExpression();
367: if (te instanceof AggregateExpression) {
368: AggregateExpression ag = (AggregateExpression) te;
369: if (ag.isMin() || ag.isMax()) {
370: Expression inner = ag.getInnerExpression();
371: if (inner instanceof NameExpression) {
372: NameExpression nam = (NameExpression) inner;
373: Column col = ic.getConstraint()
374: .getColumn(0);
375: if (nam.getName().equals(col.getName())) {
376: // This is the lucky special case. MAX or MIN
377: // on an indexed column.
378: if (c.absolute(ag.isMax() ? -1 : 1)) {
379: Row r = c.getRow();
380: ret = staticCursor(session, c,
381: items, r.item(1));
382: c.close();
383: }
384: }
385: }
386: } else if (ag.isCount() && ag.all && c.size() > 0) {
387: // another easy case is count(*)
388: ret = staticCursor(session, c, items,
389: new ValueLong(c.size()));
390: c.close();
391: }
392: }
393: }
394: }
395: return ret;
396: }
397:
398: /**
399: * Return the expression as a scalar value. We try. If there's only
400: * a single column in the result, we just return the column's value for
401: * the first row of the result. Otherwise, return ValueNull.
402: */
403: public Value getValue(Session session, Cursor cur)
404: throws SQLException {
405: Value ret = ValueNull.valueNull;
406: Cursor cursor = getCursor(session, cur);
407: try {
408: if (cursor.next()) {
409: Row row = cursor.getRow();
410: if (row.size() == 1) {
411: ret = row.item(1);
412: }
413: if (cursor.next()) {
414: throw new SQLException("Cardinality violation",
415: "21000");
416: }
417: }
418: return ret;
419: } finally {
420: cursor.close();
421: }
422: }
423:
424: /**
425: * Get the base tables that participate in this select expression
426: */
427: public void getBaseTables(Vector v) {
428: for (int i = 0; i < from.size(); i++) {
429: ((TableExpression) from.elementAt(i)).getBaseTables(v);
430: }
431: }
432:
433: /**
434: * Expression implementation...
435: */
436: public void visitSubExpressions(ExpressionVisitor ev) {
437: if (where != null)
438: ev.visit(where);
439: }
440:
441: /**
442: * Visitor class to set the WHERE clause on all subtables in this SELECT
443: */
444: class AndWhere implements ExpressionVisitor {
445: Expression where;
446:
447: AndWhere(Expression where) {
448: this .where = where;
449: }
450:
451: public void visit(Expression sub) {
452: if (sub instanceof TableExpression) {
453: TableExpression te = (TableExpression) sub;
454: if (!te.anded) {
455: Expression oldW = te.getWhere();
456: if (oldW != null) {
457: where = new BinaryExpression(Op.AND, where,
458: oldW);
459: }
460: te.setWhere(where);
461: te.anded = true;
462: }
463: sub.visitSubExpressions(this );
464: }
465: }
466: }
467:
468: /**
469: * Nope, I'm not a boolean
470: *
471: * @exception RuntimeException is thrown
472: */
473: public void invert() {
474: throw new RuntimeException(
475: "invert not implemented for SelectExpression");
476: }
477:
478: /**
479: * Read me from a stream
480: */
481: public void readExternal(ObjectInput in) throws IOException,
482: ClassNotFoundException {
483: this .distinct = (in.read() == 1);
484: this .items = (Vector) in.readObject();
485: this .from = (Vector) in.readObject();
486: this .where = (Expression) in.readObject();
487: this .groupBy = (Vector) in.readObject();
488: this .having = (Expression) in.readObject();
489: }
490:
491: /**
492: * Write me to a stream
493: */
494: public void writeExternal(ObjectOutput out) throws IOException {
495: out.write(distinct ? 1 : 0);
496: out.writeObject(items);
497: out.writeObject(from);
498: out.writeObject(where);
499: out.writeObject(groupBy);
500: out.writeObject(having);
501: }
502:
503: /**
504: * Return a string representation for debugging purposes
505: */
506: public String toString() {
507: StringBuffer sb = new StringBuffer("SELECT ");
508: if (items == null) {
509: sb.append("*");
510: } else {
511: for (int i = 0; i < items.size(); i++) {
512: if (i > 0)
513: sb.append(',');
514: sb.append(items.elementAt(i).toString());
515: }
516: }
517: if (from != null) {
518: sb.append(" FROM ");
519: for (int i = 0; i < from.size(); i++) {
520: if (i > 0)
521: sb.append(',');
522: sb.append(from.elementAt(i).toString());
523: }
524: }
525: if (where != null) {
526: sb.append(" WHERE ");
527: sb.append(where.toString());
528: }
529: if (groupBy != null) {
530: sb.append(" GROUP BY ");
531: for (int i = 0; i < groupBy.size(); i++) {
532: if (i > 0)
533: sb.append(',');
534: sb.append(groupBy.elementAt(i).toString());
535: }
536: }
537: if (having != null) {
538: sb.append(" HAVING ");
539: sb.append(having.toString());
540: }
541: return sb.toString();
542: }
543:
544: //#ifdef DEBUG
545: /**
546: * Display a cursor (under trace control)
547: */
548: public static final void showCursor(Cursor c) {
549: try {
550: if (Trace.bit(2)) {
551: Debug.println(c.toString());
552: }
553: } catch (Exception e) {
554: Debug.print(e);
555: }
556: }
557:
558: /**
559: * Display c cursor (or not)
560: */
561: public static void showCursor(Cursor c, boolean really) {
562: try {
563: if (really) {
564: Debug.println("\n" + c);
565: }
566: } catch (Throwable e) {
567: Debug.print(e);
568: }
569: }
570:
571: public String name() {
572: return toString();
573: }
574: //#endif
575:
576: }
|