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.Enumeration;
047: import java.util.Vector;
048:
049: import java.sql.SQLException;
050:
051: import com.quadcap.sql.index.Btree;
052:
053: import com.quadcap.sql.types.Op;
054: import com.quadcap.sql.types.Type;
055: import com.quadcap.sql.types.TypeBoolean;
056: import com.quadcap.sql.types.Value;
057: import com.quadcap.sql.types.ValueBoolean;
058:
059: import com.quadcap.util.Debug;
060: import com.quadcap.util.Util;
061:
062: /**
063: * Expression implementing <b>IN </b><i>(list)</i>.
064: *
065: * @author Stan Bailes
066: */
067: public class InExpression extends Expression implements Externalizable {
068: Expression e = null;
069: Expression f = null;
070: boolean not = false;
071:
072: /**
073: * Private class to maintain per-session state for this expression
074: */
075: class InSessionState implements StatementContext {
076: boolean correlatedSubquery = true;
077: Session session;
078: Btree index = null;
079: byte[] aByte = new byte[1];
080:
081: /**
082: * Construct "IN" session state
083: */
084: public InSessionState(Session session) {
085: this .session = session;
086: }
087:
088: public boolean initialized() {
089: return index != null;
090: }
091:
092: /**
093: * Initialize "IN" session state from RHS cursor.
094: */
095: void init(Expression f, Cursor cursor) throws SQLException {
096: // Ok, there's probably a better way to do this, but the
097: // idea is that if this is a correlated subquery, then
098: // attempting to evaluate the query without reference to
099: // the outer cursor will fail (by throwing an exception)
100: Cursor d = f.getCursor(session, null);
101: try {
102: d.beforeFirst();
103: d.next();
104: Type eType = e.getType(session, cursor);
105: Type fType = d.getColumn(1).getType();
106: if (eType.toString().equals(fType.toString())) {
107: correlatedSubquery = false;
108: } else {
109: // not really a correlated query, per se, but if the types
110: // don't match, our index-based optimization doesn't work.
111: // XXX (Though we could fix this by performing the appropriate
112: // XXX conversion when building the index...)
113: correlatedSubquery = true;
114: }
115: } catch (Throwable t) {
116: correlatedSubquery = true;
117: } finally {
118: d.close();
119: }
120: if (!correlatedSubquery) {
121: Cursor c = f.getCursor(session, cursor);
122: try {
123: this .index = session.makeTempTree();
124: c.beforeFirst();
125: if (c.getColumnCount() != 1) {
126: throw new SQLException(
127: "'IN' comparator: Rank mismatch",
128: "42000");
129: }
130: while (c.next()) {
131: Row fcrow = c.getRow();
132: Value fval = fcrow.item(1);
133: if (!Value.isNull(fval)) {
134: byte[] key = Value.bytes(fval);
135: index.set(key, key.length, aByte, 0, 1);
136: }
137: }
138: } catch (IOException ex) {
139: throw DbException.wrapThrowable(ex);
140: } finally {
141: c.close();
142: }
143: }
144: }
145:
146: /**
147: * Initialize "IN" session state from RHS row.
148: */
149: void init(Row r) throws SQLException {
150: try {
151: this .index = session.makeTempTree();
152: for (int i = 1; i <= r.size(); i++) {
153: Value fval = r.item(i);
154: byte[] key = Value.bytes(fval);
155: index.set(key, key.length, aByte, 0, 1);
156: }
157: } catch (IOException ex) {
158: throw DbException.wrapThrowable(ex);
159: }
160: }
161:
162: /** Whenever you get around to finishing me is fine. */
163: public int priority() {
164: return 4;
165: }
166:
167: /** Clean up any resources held by this context */
168: public void finish(boolean abort) throws IOException {
169: try {
170: if (index != null) {
171: index.free();
172: }
173: } finally {
174: if (index != null)
175: session.getDatabase().releaseTempFile();
176: index = null;
177: }
178: }
179:
180: /** Get ready for another day */
181: public void reset() throws IOException {
182: finish(false);
183: }
184:
185: /** Does RHS set contain this value? */
186: public boolean contains(Value eval, Expression f, Cursor cursor)
187: throws SQLException {
188: if (!correlatedSubquery) {
189: byte[] ekey = Value.bytes(eval);
190: try {
191: return index.get(ekey, ekey.length, aByte) != -1;
192: } catch (IOException ex) {
193: throw DbException.wrapThrowable(ex);
194: }
195: } else {
196: return matchCorrelated(f, cursor, eval);
197: }
198: }
199:
200: /** Correlated sub-queries can't be short-circuited, alas. */
201: boolean matchCorrelated(Expression f, Cursor cursor, Value eval)
202: throws SQLException {
203: boolean match = false;
204: Cursor c = f.getCursor(session, cursor);
205: try {
206: c.beforeFirst();
207: if (c.getColumnCount() != 1) {
208: throw new SQLException(
209: "'IN' comparator: Rank mismatch", "42000");
210: }
211: while (!match && c.next()) {
212: Row fcrow = c.getRow();
213: Value fval = fcrow.item(1);
214: match = Value.boolOp(Op.EQ, eval, fval);
215: }
216: } finally {
217: c.close();
218: }
219: return match;
220: }
221:
222: }
223:
224: private InSessionState getSessionState(Session session) {
225: InSessionState s = (InSessionState) session.getContext(this ,
226: false);
227: if (s == null) {
228: s = new InSessionState(session);
229: session.putContext(this , false, s);
230: }
231: return s;
232: }
233:
234: /**
235: * Default constructor
236: */
237: public InExpression() {
238: }
239:
240: /**
241: * Explicit constructor, called by SQL parser.
242: */
243: public InExpression(Expression e, Expression f) {
244: this .e = e;
245: this .f = f;
246: }
247:
248: /**
249: * "IN" always returns a scalar value.
250: */
251: public int rank() {
252: return 0;
253: }
254:
255: /**
256: * "IN" always returns a scalar value.
257: */
258: public Type getType(Session session, Cursor cursor) {
259: return TypeBoolean.typeBoolean;
260: }
261:
262: /**
263: * Implementation of IN varies based on the rank of the LHS
264: */
265: public Value getValue(Session session, Cursor cursor)
266: throws SQLException {
267: switch (e.rank()) {
268: case 0:
269: return getValue1(session, cursor);
270: case 1:
271: return getValue2(session, cursor);
272: default:
273: throw new SQLException("bad rank (" + e.rank()
274: + ") for left argument to IN", "42000");
275: }
276: }
277:
278: private Value getValue1(Session session, Cursor cursor)
279: throws SQLException {
280: Value eval = e.getValue(session, cursor);
281: boolean match = false;
282: switch (f.rank()) {
283: case 0:
284: throw new SQLException("'IN' comparator: Rank mismatch",
285: "42000");
286: case 1:
287: Row frow = f.getValues(session, cursor);
288: for (int i = 1; !match && i <= frow.size(); i++) {
289: Value fval = frow.item(i);
290: match = Value.boolOp(Op.EQ, eval, fval);
291: }
292: break;
293: case 2:
294: InSessionState s = getSessionState(session);
295: if (!s.initialized()) {
296: s.init(f, cursor);
297: }
298: match = s.contains(eval, f, cursor);
299: break;
300: default:
301: throw new SQLException("internal error, bad rank: "
302: + f.rank(), "Q0004");
303: }
304: match ^= not;
305: return new ValueBoolean(match);
306: }
307:
308: private Value getValue2(Session session, Cursor cursor)
309: throws SQLException {
310: Row erow = e.getValues(session, cursor);
311: boolean match = false;
312: switch (f.rank()) {
313: case 0:
314: case 1:
315: throw new SQLException("'IN' comparator: Rank mismatch",
316: "42000");
317: case 2:
318: Cursor c = f.getCursor(session, cursor);
319: try {
320: if (c.getColumnCount() != cursor.getColumnCount()) {
321: throw new SQLException(
322: "'IN' comparator: Rank mismatch", "42000");
323: }
324: while (!match && c.next()) {
325: Row frow = c.getRow();
326: match = matchRow(erow, frow);
327: }
328: } finally {
329: c.close();
330: }
331: break;
332: default:
333: throw new SQLException("internal error, bad rank: "
334: + f.rank(), "Q0005");
335: }
336: match ^= not;
337: return new ValueBoolean(match);
338: }
339:
340: static boolean matchRow(Row erow, Row frow) throws SQLException {
341: boolean match = true;
342: for (int i = 1; match && i <= erow.size(); i++) {
343: match = Value.boolOp(Op.EQ, erow.item(i), frow.item(i));
344: }
345: return match;
346: }
347:
348: public void invert() {
349: not = !not;
350: }
351:
352: public String toString() {
353: String n = not ? "not " : "";
354: return "(" + n + e + " IN " + f + ")";
355: }
356:
357: public Expression getLhs() {
358: return e;
359: }
360:
361: public void visitSubExpressions(ExpressionVisitor ev) {
362: ev.visit(e);
363: ev.visit(f);
364: }
365:
366: public void readExternal(ObjectInput in) throws IOException,
367: ClassNotFoundException {
368: e = (Expression) in.readObject();
369: f = (Expression) in.readObject();
370: not = (in.read() == 1);
371: }
372:
373: public void writeExternal(ObjectOutput out) throws IOException {
374: out.writeObject(e);
375: out.writeObject(f);
376: out.write(not ? 1 : 0);
377: }
378: }
|