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.ArrayList;
047: import java.util.HashMap;
048: import java.util.Iterator;
049: import java.util.Vector;
050:
051: import java.sql.SQLException;
052:
053: import antlr.RecognitionException;
054:
055: import com.quadcap.sql.types.Op;
056:
057: import com.quadcap.util.Debug;
058:
059: /**
060: * A table expression representing a single join operation.
061: *
062: * @author Stan Bailes
063: */
064: public class JoinedTable extends TableExpression implements
065: Externalizable {
066: int op = -1;
067: TableExpression a = null;
068: TableExpression b = null;
069: Tuple tuple = null;
070: Expression onExpression = null;
071: /** Vector<String> **/
072: Vector usingList = null;
073:
074: /**
075: * Default public constructor
076: */
077: public JoinedTable() {
078: }
079:
080: /**
081: * Either a cross join or a natural join over any common columns.
082: */
083: public JoinedTable(int op, TableExpression a, TableExpression b) {
084: this .op = op;
085: this .a = a;
086: this .b = b;
087: }
088:
089: /**
090: * Join 'ON' on
091: */
092: public void setOnExpression(Expression on) {
093: this .onExpression = on;
094: }
095:
096: /**
097: * Join 'USING' column list
098: */
099: public void setUsingList(Vector usingList) {
100: this .usingList = usingList;
101: }
102:
103: /**
104: * Verify the correctness of the join expression -- the grammar can't
105: * catch all of the possible errors.
106: */
107: /*{joinedTable.xml-100}
108: * <section name="Semantic Constraints">
109: *
110: * <p>The syntax as specified here will accept certain join expressions
111: * that are illegal; as a result there are additional semantic checks
112: * performed on all join expressions:</p>
113: *
114: * <ul>
115: * <li>At most one of the {<code>NATURAL, UNION, ON, USING</code>} may be
116: * specified in a join expression.</li>
117: * <li>If neither <code>NATURAL</code> or <code>UNION</code> is specified,
118: * then
119: * either <code>ON</code> or <code>USING</code> must be specified.</li>
120: * </ul>
121: * </section>
122: */
123: public void checkSyntax() throws RecognitionException {
124: if ((op & Op.NATURAL) != 0) {
125: if (usingList != null) {
126: throw new RecognitionException(
127: "Can't specify both NATURAL and " + "USING");
128: }
129: if (onExpression != null) {
130: throw new RecognitionException(
131: "Can't specify both NATURAL and ON");
132: }
133: } else if (op == Op.LEFT || op == Op.RIGHT || op == Op.FULL
134: || op == Op.INNER) {
135: if (usingList == null && onExpression == null) {
136: throw new RecognitionException(Op.toString(op)
137: + " join requires ON or USING " + "clause");
138: }
139: } else if (op == Op.UNION) {
140: if (usingList != null) {
141: throw new RecognitionException(
142: "Can't specify both UNION and USING");
143: }
144: if (onExpression != null) {
145: throw new RecognitionException(
146: "Can't specify both UNION and ON");
147: }
148: }
149: }
150:
151: /**
152: * Override base method to propagate to base tables.
153: */
154: public void setWhere(Expression where) {
155: this .where = where;
156: a.setWhere(where);
157: b.setWhere(where);
158: }
159:
160: public int rank() {
161: return 2;
162: }
163:
164: public boolean isUpdatable() {
165: return false;
166: }
167:
168: public void getBaseTables(Vector v) {
169: a.getBaseTables(v);
170: b.getBaseTables(v);
171: }
172:
173: public void visitSubExpressions(ExpressionVisitor ev) {
174: if (a != null)
175: ev.visit(a);
176: if (b != null)
177: ev.visit(b);
178: }
179:
180: String toString(Iterator iter) {
181: StringBuffer sb = new StringBuffer();
182: while (iter.hasNext()) {
183: if (sb.length() > 0)
184: sb.append(", ");
185: sb.append(iter.next().toString());
186: }
187: return sb.toString();
188: }
189:
190: static String isa(Object x) {
191: return x == null ? "null" : x.getClass().getName() + ":" + x;
192: }
193:
194: public Cursor getCursor(Session session, Cursor outer)
195: throws SQLException {
196: int nop = op & ~Op.NATURAL;
197: int[] map = null;
198:
199: Cursor ra = a.getCursor(session, outer);
200: Cursor rb = b.getCursor(session, outer);
201:
202: int size = ra.getColumnCount() + rb.getColumnCount();
203:
204: if ((op & Op.NATURAL) != 0) {
205: getNaturalJoinColumns(ra, rb);
206: }
207:
208: int usize = usingList == null ? 0 : usingList.size();
209: map = new int[size - usize];
210: Tuple jtl = mapColumns(ra, rb, map, true);
211: JoinMapRow jrl = new JoinMapRow(map);
212:
213: map = new int[size - usize];
214: Tuple jtr = mapColumns(ra, rb, map, false);
215: JoinMapRow jrr = new JoinMapRow(map);
216:
217: Cursor ret = null;
218: Cursor left = null;
219: Cursor right = null;
220: if (onExpression != null) {
221: where = new BinaryExpression(Op.AND, where, onExpression);
222: }
223:
224: switch (nop) {
225: case Op.CROSS:
226: case Op.INNER:
227: ret = getCrossCursor(session, outer, ra, rb, where, jtl,
228: jrl, false /* left */, true /* inner */);
229: break;
230: case Op.LEFT:
231: ret = getCrossCursor(session, outer, ra, rb, where, jtl,
232: jrl, true /* left */, true /* inner */);
233: break;
234: case Op.RIGHT:
235: ret = getCrossCursor(session, outer, rb, ra, where, jtr,
236: jrr, true /* left */, true /* inner */);
237: break;
238: case Op.FULL:
239: left = getCrossCursor(session, outer, ra, rb, where, jtl,
240: jrl, true /* left */, true /* inner */);
241: right = getCrossCursor(session, outer, rb, ra, where, jtr,
242: jrr, true /* left */, false /* inner */);
243: break;
244: case Op.UNION:
245: ret = new JoinUnionCursor(session, outer, ra, rb, where,
246: jtl, jrl);
247: break;
248: default:
249: throw new SQLException("Bad join type: " + nop);
250: }
251: if (left != null && right != null) {
252: MultiCursor mc = new MultiCursor(session, left);
253: mc.appendCursor(session, right);
254: ret = mc;
255: }
256: if (ret == null) {
257: throw new SQLException("Bad join");
258: }
259: return ret;
260: }
261:
262: Cursor getCrossCursor(Session session, Cursor outer, Cursor ca,
263: Cursor cb, Expression where, Tuple jt, JoinMapRow jrl,
264: boolean left, boolean inner) throws SQLException {
265: int[][] cols = usingList != null ? getUsingColumns(ca, cb)
266: : new Analyze(session, where).getJoinColumns(ca, cb);
267:
268: Cursor ret = null;
269: if (cols == null) {
270: //Debug.println("CROSS");
271: ret = new JoinCrossCursor(session, outer, ca, cb, where,
272: jt, jrl, left, inner);
273: } else {
274: //Debug.println("INNER");
275: int[] aCols = cols[0];
276: int[] bCols = cols[1];
277: ret = new JoinInnerCursor(session, outer, ca, aCols, cb,
278: bCols, where, jt, jrl, left, inner);
279: }
280: return ret;
281: }
282:
283: int[][] getUsingColumns(Tuple a, Tuple b) throws SQLException {
284: int[][] ret = null;
285: if (usingList != null) {
286: int len = usingList.size();
287: ret = new int[2][len];
288: for (int i = 0; i < len; i++) {
289: String u = usingList.get(i).toString();
290: Column ca = a.getColumn(u);
291: Column cb = b.getColumn(u);
292: ret[0][i] = ca.getColumn();
293: ret[1][i] = cb.getColumn();
294: }
295: }
296: return ret;
297: }
298:
299: void getNaturalJoinColumns(Tuple ta, Tuple tb) throws SQLException {
300: usingList = new Vector();
301: HashMap t = new HashMap();
302: for (int i = 1; i <= tb.getColumnCount(); i++) {
303: Column cb = tb.getColumn(i);
304: t.put(cb.getShortName(), cb);
305: }
306: for (int i = 1; i <= ta.getColumnCount(); i++) {
307: Column ca = ta.getColumn(i);
308: if (t.containsKey(ca.getShortName())) {
309: usingList.addElement(ca.getShortName());
310: }
311: }
312: }
313:
314: /**
315: * Build the tuple for this join operation
316: */
317: private final Tuple mapColumns(Tuple ra, Tuple rb, int[] map,
318: boolean left) throws SQLException {
319: HashMap uMap = null;
320: TupleImpl ti = new TupleImpl();
321: int usize = usingList == null ? 0 : usingList.size();
322:
323: // First, add the common columns, where we drop the table names.
324: if (usize > 0) {
325: Tuple outer = left ? ra : rb;
326: uMap = new HashMap();
327: int[] umap = outer.mapColumns(usingList);
328: for (int i = 0; i < umap.length; i++) {
329: int u = umap[i];
330: Column ci = outer.getColumn(u);
331: uMap.put(ci.getShortName(), "");
332: Column col = new Column(ci.getShortName(), ci);
333: col.setJoinColumn(true);
334: ti.addColumn(col);
335: map[i] = 0 - u;
336: }
337: }
338:
339: // Then the remaining columns from 'A'
340: int base = usize;
341: for (int i = 1; i <= ra.getColumnCount(); i++) {
342: Column ci = ra.getColumn(i);
343: if (uMap == null || !uMap.containsKey(ci.getShortName())) {
344: int c = ci.getColumn();
345: if (map[base] == 0) {
346: map[base++] = left ? 0 - c : c;
347: ti.addColumn(ci.getName(), ci.getType());
348: }
349: }
350: }
351:
352: // And the remaining columns from 'B'
353: for (int i = 1; i <= rb.getColumnCount(); i++) {
354: Column ci = rb.getColumn(i);
355: if (uMap == null || !uMap.containsKey(ci.getShortName())) {
356: int c = ci.getColumn();
357: if (map[base] == 0) {
358: map[base++] = left ? c : 0 - c;
359: ti.addColumn(ci.getName(), ci.getType());
360: }
361: }
362: }
363: return ti;
364: }
365:
366: public void readExternal(ObjectInput in) throws IOException,
367: ClassNotFoundException {
368: this .op = in.readInt();
369: this .a = (TableExpression) in.readObject();
370: this .b = (TableExpression) in.readObject();
371: this .onExpression = (Expression) in.readObject();
372: this .usingList = (Vector) in.readObject();
373: }
374:
375: public void writeExternal(ObjectOutput out) throws IOException {
376: out.writeInt(op);
377: out.writeObject(a);
378: out.writeObject(b);
379: out.writeObject(onExpression);
380: out.writeObject(usingList);
381: }
382:
383: public String toString() {
384: StringBuffer sb = new StringBuffer(a.toString());
385:
386: if ((op & Op.NATURAL) != 0)
387: sb.append(" NATURAL");
388: op &= ~Op.NATURAL;
389: sb.append(' ');
390: sb.append(Op.toString(op));
391: sb.append(" JOIN ");
392: sb.append(b.toString());
393: return sb.toString();
394: }
395:
396: //#ifdef DEBUG
397: public String name() {
398: StringBuffer sb = new StringBuffer('(');
399: sb.append(a.name());
400: sb.append("\n ");
401: sb.append(Op.toString(op));
402: sb.append("\n");
403: sb.append(b.name());
404: sb.append(')');
405: return sb.toString();
406: }
407: //#endif
408: }
|