Quadcap Embeddable Database

com/quadcap/sql/InExpression.java

Go to the documentation of this file.
00001 package com.quadcap.sql; 00002 00003 /* Copyright 1999 - 2003 Quadcap Software. All rights reserved. 00004 * 00005 * This software is distributed under the Quadcap Free Software License. 00006 * This software may be used or modified for any purpose, personal or 00007 * commercial. Open Source redistributions are permitted. Commercial 00008 * redistribution of larger works derived from, or works which bundle 00009 * this software requires a "Commercial Redistribution License"; see 00010 * http://www.quadcap.com/purchase. 00011 * 00012 * Redistributions qualify as "Open Source" under one of the following terms: 00013 * 00014 * Redistributions are made at no charge beyond the reasonable cost of 00015 * materials and delivery. 00016 * 00017 * Redistributions are accompanied by a copy of the Source Code or by an 00018 * irrevocable offer to provide a copy of the Source Code for up to three 00019 * years at the cost of materials and delivery. Such redistributions 00020 * must allow further use, modification, and redistribution of the Source 00021 * Code under substantially the same terms as this license. 00022 * 00023 * Redistributions of source code must retain the copyright notices as they 00024 * appear in each source code file, these license terms, and the 00025 * disclaimer/limitation of liability set forth as paragraph 6 below. 00026 * 00027 * Redistributions in binary form must reproduce this Copyright Notice, 00028 * these license terms, and the disclaimer/limitation of liability set 00029 * forth as paragraph 6 below, in the documentation and/or other materials 00030 * provided with the distribution. 00031 * 00032 * The Software is provided on an "AS IS" basis. No warranty is 00033 * provided that the Software is free of defects, or fit for a 00034 * particular purpose. 00035 * 00036 * Limitation of Liability. Quadcap Software shall not be liable 00037 * for any damages suffered by the Licensee or any third party resulting 00038 * from use of the Software. 00039 */ 00040 00041 import java.io.Externalizable; 00042 import java.io.IOException; 00043 import java.io.ObjectInput; 00044 import java.io.ObjectOutput; 00045 00046 import java.util.Enumeration; 00047 import java.util.Vector; 00048 00049 import java.sql.SQLException; 00050 00051 import com.quadcap.sql.index.Btree; 00052 00053 import com.quadcap.sql.types.Op; 00054 import com.quadcap.sql.types.Type; 00055 import com.quadcap.sql.types.TypeBoolean; 00056 import com.quadcap.sql.types.Value; 00057 import com.quadcap.sql.types.ValueBoolean; 00058 00059 import com.quadcap.util.Debug; 00060 import com.quadcap.util.Util; 00061 00062 /** 00063 * Expression implementing <b>IN </b><i>(list)</i>. 00064 * 00065 * @author Stan Bailes 00066 */ 00067 public class InExpression extends Expression implements Externalizable { 00068 Expression e = null; 00069 Expression f = null; 00070 boolean not = false; 00071 00072 /** 00073 * Private class to maintain per-session state for this expression 00074 */ 00075 class InSessionState implements StatementContext { 00076 boolean correlatedSubquery = true; 00077 Session session; 00078 Btree index = null; 00079 byte[] aByte = new byte[1]; 00080 00081 /** 00082 * Construct "IN" session state 00083 */ 00084 public InSessionState(Session session) { 00085 this.session = session; 00086 } 00087 00088 public boolean initialized() { 00089 return index != null; 00090 } 00091 00092 /** 00093 * Initialize "IN" session state from RHS cursor. 00094 */ 00095 void init(Expression f, Cursor cursor) throws SQLException { 00096 // Ok, there's probably a better way to do this, but the 00097 // idea is that if this is a correlated subquery, then 00098 // attempting to evaluate the query without reference to 00099 // the outer cursor will fail (by throwing an exception) 00100 Cursor d = f.getCursor(session, null); 00101 try { 00102 d.beforeFirst(); 00103 d.next(); 00104 Type eType = e.getType(session, cursor); 00105 Type fType = d.getColumn(1).getType(); 00106 if (eType.toString().equals(fType.toString())) { 00107 correlatedSubquery = false; 00108 } else { 00109 // not really a correlated query, per se, but if the types 00110 // don't match, our index-based optimization doesn't work. 00111 // XXX (Though we could fix this by performing the appropriate 00112 // XXX conversion when building the index...) 00113 correlatedSubquery = true; 00114 } 00115 } catch (Throwable t) { 00116 correlatedSubquery = true; 00117 } finally { 00118 d.close(); 00119 } 00120 if (!correlatedSubquery) { 00121 Cursor c = f.getCursor(session, cursor); 00122 try { 00123 this.index = session.makeTempTree(); 00124 c.beforeFirst(); 00125 if (c.getColumnCount() != 1) { 00126 throw new SQLException("'IN' comparator: Rank mismatch", 00127 "42000"); 00128 } 00129 while (c.next()) { 00130 Row fcrow = c.getRow(); 00131 Value fval = fcrow.item(1); 00132 if (!Value.isNull(fval)) { 00133 byte[] key = Value.bytes(fval); 00134 index.set(key, key.length, aByte, 0, 1); 00135 } 00136 } 00137 } catch (IOException ex) { 00138 throw DbException.wrapThrowable(ex); 00139 } finally { 00140 c.close(); 00141 } 00142 } 00143 } 00144 00145 /** 00146 * Initialize "IN" session state from RHS row. 00147 */ 00148 void init(Row r) throws SQLException { 00149 try { 00150 this.index = session.makeTempTree(); 00151 for (int i = 1; i <= r.size(); i++) { 00152 Value fval = r.item(i); 00153 byte[] key = Value.bytes(fval); 00154 index.set(key, key.length, aByte, 0, 1); 00155 } 00156 } catch (IOException ex) { 00157 throw DbException.wrapThrowable(ex); 00158 } 00159 } 00160 00161 /** Whenever you get around to finishing me is fine. */ 00162 public int priority() { return 4; } 00163 00164 /** Clean up any resources held by this context */ 00165 public void finish(boolean abort) throws IOException { 00166 try { 00167 if (index != null) { 00168 index.free(); 00169 } 00170 } finally { 00171 if (index != null) session.getDatabase().releaseTempFile(); 00172 index = null; 00173 } 00174 } 00175 00176 /** Get ready for another day */ 00177 public void reset() throws IOException { 00178 finish(false); 00179 } 00180 00181 /** Does RHS set contain this value? */ 00182 public boolean contains(Value eval, Expression f, Cursor cursor) throws SQLException { 00183 if (!correlatedSubquery) { 00184 byte[] ekey = Value.bytes(eval); 00185 try { 00186 return index.get(ekey, ekey.length, aByte) != -1; 00187 } catch (IOException ex) { 00188 throw DbException.wrapThrowable(ex); 00189 } 00190 } else { 00191 return matchCorrelated(f, cursor, eval); 00192 } 00193 } 00194 00195 /** Correlated sub-queries can't be short-circuited, alas. */ 00196 boolean matchCorrelated(Expression f, Cursor cursor, Value eval) 00197 throws SQLException 00198 { 00199 boolean match = false; 00200 Cursor c = f.getCursor(session, cursor); 00201 try { 00202 c.beforeFirst(); 00203 if (c.getColumnCount() != 1) { 00204 throw new SQLException("'IN' comparator: Rank mismatch", 00205 "42000"); 00206 } 00207 while (!match && c.next()) { 00208 Row fcrow = c.getRow(); 00209 Value fval = fcrow.item(1); 00210 match = Value.boolOp(Op.EQ, eval, fval); 00211 } 00212 } finally { 00213 c.close(); 00214 } 00215 return match; 00216 } 00217 00218 } 00219 00220 private InSessionState getSessionState(Session session) { 00221 InSessionState s = (InSessionState)session.getContext(this, false); 00222 if (s == null) { 00223 s = new InSessionState(session); 00224 session.putContext(this, false, s); 00225 } 00226 return s; 00227 } 00228 00229 /** 00230 * Default constructor 00231 */ 00232 public InExpression() {} 00233 00234 /** 00235 * Explicit constructor, called by SQL parser. 00236 */ 00237 public InExpression(Expression e, Expression f) { 00238 this.e = e; 00239 this.f = f; 00240 } 00241 00242 /** 00243 * "IN" always returns a scalar value. 00244 */ 00245 public int rank() { return 0; } 00246 00247 /** 00248 * "IN" always returns a scalar value. 00249 */ 00250 public Type getType(Session session, Cursor cursor) { 00251 return TypeBoolean.typeBoolean; 00252 } 00253 00254 /** 00255 * Implementation of IN varies based on the rank of the LHS 00256 */ 00257 public Value getValue(Session session, Cursor cursor) throws SQLException { 00258 switch (e.rank()) { 00259 case 0: 00260 return getValue1(session, cursor); 00261 case 1: 00262 return getValue2(session, cursor); 00263 default: 00264 throw new SQLException("bad rank (" + e.rank() + 00265 ") for left argument to IN", "42000"); 00266 } 00267 } 00268 00269 private Value getValue1(Session session, Cursor cursor) 00270 throws SQLException 00271 { 00272 Value eval = e.getValue(session, cursor); 00273 boolean match = false; 00274 switch (f.rank()) { 00275 case 0: 00276 throw new SQLException("'IN' comparator: Rank mismatch", "42000"); 00277 case 1: 00278 Row frow = f.getValues(session, cursor); 00279 for (int i = 1; !match && i <= frow.size(); i++) { 00280 Value fval = frow.item(i); 00281 match = Value.boolOp(Op.EQ, eval, fval); 00282 } 00283 break; 00284 case 2: 00285 InSessionState s = getSessionState(session); 00286 if (!s.initialized()) { 00287 s.init(f, cursor); 00288 } 00289 match = s.contains(eval, f, cursor); 00290 break; 00291 default: 00292 throw new SQLException("internal error, bad rank: " + f.rank(), 00293 "Q0004"); 00294 } 00295 match ^= not; 00296 return new ValueBoolean(match); 00297 } 00298 00299 00300 private Value getValue2(Session session, Cursor cursor) 00301 throws SQLException 00302 { 00303 Row erow = e.getValues(session, cursor); 00304 boolean match = false; 00305 switch (f.rank()) { 00306 case 0: 00307 case 1: 00308 throw new SQLException("'IN' comparator: Rank mismatch", "42000"); 00309 case 2: 00310 Cursor c = f.getCursor(session, cursor); 00311 try { 00312 if (c.getColumnCount() != cursor.getColumnCount()) { 00313 throw new SQLException("'IN' comparator: Rank mismatch", 00314 "42000"); 00315 } 00316 while (!match && c.next()) { 00317 Row frow = c.getRow(); 00318 match = matchRow(erow, frow); 00319 } 00320 } finally { 00321 c.close(); 00322 } 00323 break; 00324 default: 00325 throw new SQLException("internal error, bad rank: " + f.rank(), 00326 "Q0005"); 00327 } 00328 match ^= not; 00329 return new ValueBoolean(match); 00330 } 00331 00332 static boolean matchRow(Row erow, Row frow) throws SQLException { 00333 boolean match = true; 00334 for (int i = 1; match && i <= erow.size(); i++) { 00335 match = Value.boolOp(Op.EQ, erow.item(i), frow.item(i)); 00336 } 00337 return match; 00338 } 00339 00340 public void invert() { 00341 not = !not; 00342 } 00343 00344 public String toString() { 00345 String n = not ? "not " : ""; 00346 return "(" + n + e + " IN " + f + ")"; 00347 } 00348 00349 public Expression getLhs() { return e; } 00350 00351 public void visitSubExpressions(ExpressionVisitor ev) { 00352 ev.visit(e); 00353 ev.visit(f); 00354 } 00355 00356 public void readExternal(ObjectInput in) 00357 throws IOException, ClassNotFoundException 00358 { 00359 e = (Expression)in.readObject(); 00360 f = (Expression)in.readObject(); 00361 not = (in.read() == 1); 00362 } 00363 00364 public void writeExternal(ObjectOutput out) throws IOException { 00365 out.writeObject(e); 00366 out.writeObject(f); 00367 out.write(not ? 1 : 0); 00368 } 00369 }