Quadcap Embeddable Database

com/quadcap/sql/SelectExpression.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.Vector; 00047 00048 import java.sql.SQLException; 00049 00050 import com.quadcap.sql.types.Op; 00051 import com.quadcap.sql.types.Value; 00052 import com.quadcap.sql.types.ValueLong; 00053 import com.quadcap.sql.types.ValueNull; 00054 00055 import com.quadcap.util.Debug; 00056 00057 /** 00058 * A table expression representing a <b>SELECT</b> clause. 00059 * 00060 * @author Stan Bailes 00061 */ 00062 00063 public class SelectExpression extends TableExpression 00064 implements Externalizable 00065 { 00066 boolean distinct = false; 00067 Vector items = null; 00068 Vector from = null; 00069 Vector groupBy = null; 00070 Expression having = null; 00071 Vector aggregates = null; 00072 00073 /** 00074 * Default constructor 00075 */ 00076 public SelectExpression() {} 00077 00078 /** 00079 * The typical 'select from table where' stuff 00080 */ 00081 public SelectExpression(String tableName, Expression where) { 00082 Vector v = new Vector(); 00083 v.addElement(new SelectFromTable(tableName)); 00084 this.setFrom(v); 00085 this.setWhere(where); 00086 } 00087 00088 /** 00089 * Get the FROM list 00090 */ 00091 public Vector getFrom() { 00092 return from; 00093 } 00094 00095 /** 00096 * Set the DISTINCT flag 00097 */ 00098 public void setDistinct(boolean b) { this.distinct = b; } 00099 00100 /** 00101 * Set the select items (i.e., the colums returned from this select) 00102 */ 00103 public void setItems(Vector v) { this.items = v; } 00104 00105 /** 00106 * Set the from list (a list of tables is implicitly joined) 00107 */ 00108 public void setFrom(Vector v) { this.from = v; } 00109 00110 /** 00111 * Set the GROUP BY clause as a list of names 00112 */ 00113 public void setGroupBy(Vector v) { this.groupBy = v; } 00114 00115 /** 00116 * Set the HAVING expression 00117 */ 00118 public void setHaving(Expression e) { this.having = e; } 00119 00120 /** 00121 * We're a table 00122 */ 00123 public int rank() { return 2; } 00124 00125 /** 00126 * Well, a bunch of things can prevent us from being updateable. 00127 * Here are the ones we know about: 00128 */ 00129 public boolean isUpdatable() { 00130 if (groupBy != null) return false; 00131 if (having != null) return false; 00132 if (distinct) return false; 00133 if (from == null || from.size() > 1) return false; 00134 if (aggregates != null && aggregates.size() > 0) return false; 00135 00136 TableExpression t = (TableExpression)from.elementAt(0); 00137 if (!t.isUpdatable()) return false; 00138 00139 return true; // Good luck, sir. 00140 } 00141 00142 /** 00143 * Cross-join multiple tables in 'from' clause. 00144 * 00145 * TODO: 00146 * First, tables which have selection constraintsfind pairs 00147 * of tables that have candidate join columns and greedily join 00148 * them. Next, pick tables which have matching non-key join 00149 * columns and join them Finally, join all the rest. 00150 */ 00151 TableExpression scheduleJoin() throws SQLException { 00152 while (from.size() > 1) { 00153 TableExpression a = (TableExpression)from.elementAt(0); 00154 TableExpression b = (TableExpression)from.elementAt(1); 00155 JoinedTable t = new JoinedTable(Op.CROSS, a, b); 00156 //t.setOnExpression(where); 00157 from.removeElementAt(0); 00158 from.setElementAt(t, 0); 00159 } 00160 TableExpression item = (TableExpression)from.elementAt(0); 00161 return item; 00162 } 00163 00164 /** 00165 * This is the main entry point to this madness. Our handling of 00166 * selects is fairly literal, and pretty brute force. Things work 00167 * much better if there are appropriate indexes to use based on the 00168 * query..... 00169 */ 00170 public Cursor getCursor(Session session, Cursor cur) throws SQLException { 00171 if (from == null) { 00172 Row row = new Row(items.size()); 00173 TupleImpl t = new TupleImpl(); 00174 for (int i = 0; i < items.size(); i++) { 00175 SelectItem item = (SelectItem)items.elementAt(i); 00176 Expression expr = item.getExpression(); 00177 Value v = expr.getValue(session, cur); 00178 String name = item.getAsName(); 00179 if (name == null) name = "Column" + i; 00180 t.addColumn(name, v.getType()); 00181 row.set(i+1, expr.getValue(session, cur)); 00182 } 00183 return new StaticCursor(session, t, row); 00184 } 00185 TableExpression tab = null; 00186 00187 if (from.size() > 1) { 00188 tab = scheduleJoin(); 00189 } else { 00190 tab = (TableExpression)from.elementAt(0); 00191 } 00192 00193 // give the join or query a chance to optimize 00194 tab.setWhere(where); 00195 00196 Cursor c = tab.getCursor(session, cur); 00197 if (where != null) { 00198 // Actually, we could avoid creating a predicate cursor 00199 // in cases where the upstream index cursor is already 00200 // sufficient. 00201 c = new PredicateCursor(session, c, where); 00202 } 00203 if (groupBy != null) { 00204 GroupByCursor gc = new GroupByCursor(session, c, groupBy); 00205 c = gc; 00206 Vector tItems = items; // why alias items here? XX 00207 if (having != null) { 00208 int cnt = (items == null) ? 0 : items.size(); 00209 tItems = new Vector(cnt + 1); 00210 for (int i = 0; i < cnt; i++) { 00211 tItems.addElement(items.elementAt(i)); 00212 } 00213 tItems.addElement(new SelectItem(having)); 00214 } 00215 if (isAggregate(session, tItems)) { 00216 ItemsCursor ic = new ItemsCursor(session, gc, tItems); 00217 c = new AggregateCursor(session, ic, gc, aggregates); 00218 if (having != null) { 00219 c = new HavingCursor(session, c); 00220 } 00221 } else if (tItems != null) { 00222 ItemsCursor ic = new ItemsCursor(session, gc, tItems); 00223 c = ic; 00224 c = new AggregateCursor(session, ic, gc, aggregates); 00225 if (having != null) { 00226 c = new HavingCursor(session, c); 00227 } 00228 } 00229 } else { 00230 if (isAggregate(session, items)) { 00231 Cursor optimized = optimizeAggregate(session, c, items); 00232 if (optimized != null) { 00233 c = optimized; 00234 } else { 00235 ItemsCursor ic = new ItemsCursor(session, c, items); 00236 c = new AggregateCursor(session, ic, null, aggregates); 00237 } 00238 } else { 00239 if (items != null) { 00240 c = new ItemsCursor(session, c, items); 00241 } 00242 } 00243 } 00244 if (distinct) { 00245 c = new DistinctCursor(session, c); 00246 } 00247 00248 if (cur != null) c.setOuterCursor(cur); 00249 //#ifdef DEBUG 00250 showCursor(c); 00251 //#endif 00252 return c; 00253 } 00254 00255 /** 00256 * Visitor class used to analyze the select expression for aggregate usage 00257 */ 00258 class IsAggregate implements ExpressionVisitor { 00259 Session session = null; 00260 Vector aggregates = null; 00261 boolean seenName = false; 00262 boolean seenAggregate = false; 00263 00264 public IsAggregate(Session session) { 00265 this.session = session; 00266 } 00267 00268 public void visit(Expression sub) { 00269 if (sub instanceof AggregateExpression) { 00270 seenAggregate = true; 00271 //try { 00272 // ((AggregateExpression)sub).reset(session); 00273 //} catch (IOException e) { 00274 // Debug.print(e); 00275 //} 00276 if (aggregates == null) { 00277 aggregates = new Vector(); 00278 } 00279 aggregates.addElement(sub); 00280 } else { 00281 seenName |= (sub instanceof NameExpression); 00282 sub.visitSubExpressions(this); 00283 } 00284 } 00285 00286 void reset() { 00287 seenName = seenAggregate = false; 00288 } 00289 } 00290 00291 /** 00292 * Check the list of select items to see if any of them are aggregate 00293 * expressions. (Side effect: set 'aggregates' variable.) 00294 */ 00295 boolean isAggregate(Session session, Vector items) throws SQLException { 00296 if (items == null) return false; 00297 int cnt = 0; 00298 IsAggregate isVis = new IsAggregate(session); 00299 for (int i = 0; i < items.size(); i++) { 00300 SelectItem item = (SelectItem)items.elementAt(i); 00301 Expression expression = item.getExpression(); 00302 isVis.reset(); 00303 isVis.visit(expression); 00304 if (isVis.seenAggregate && isVis.seenName && groupBy == null) { 00305 throw new SQLException("Expressions can't mix aggregate and " + 00306 "non-aggregate values", "42000"); 00307 } 00308 if (isVis.seenAggregate) cnt++; 00309 } 00310 this.aggregates = isVis.aggregates; 00311 if (cnt == 0) return false; 00312 return true; 00313 } 00314 00315 /** 00316 * Helper to create a cursor containing a single constant value. 00317 */ 00318 Cursor staticCursor(Session session, Cursor c, Vector items, Value v) 00319 throws SQLException 00320 { 00321 Row r = new Row(1); 00322 r.set(1, v); 00323 ItemsCursor it = new ItemsCursor(session, c, items); 00324 return new StaticCursor(session, it, r); 00325 } 00326 00327 /** 00328 * Identify cases of MIN(column) or MAX(column) on indexed columns. 00329 * Compute the value directly and replace the index cursor with a 00330 * static cursor containing the value. 00331 */ 00332 Cursor optimizeAggregate(Session session, Cursor c, Vector items) 00333 throws SQLException 00334 { 00335 Cursor ret = null; 00336 if (c instanceof IndexCursor) { 00337 IndexCursor ic = (IndexCursor)c; 00338 if (items.size() == 1) { 00339 SelectItem item = (SelectItem)items.get(0); 00340 Expression te = item.getExpression(); 00341 if (te instanceof AggregateExpression) { 00342 AggregateExpression ag = (AggregateExpression)te; 00343 if (ag.isMin() || ag.isMax()) { 00344 Expression inner = ag.getInnerExpression(); 00345 if (inner instanceof NameExpression) { 00346 NameExpression nam = (NameExpression)inner; 00347 Column col = ic.getConstraint().getColumn(0); 00348 if (nam.getName().equals(col.getName())) { 00349 // This is the lucky special case. MAX or MIN 00350 // on an indexed column. 00351 if (c.absolute(ag.isMax() ? -1 : 1)) { 00352 Row r = c.getRow(); 00353 ret = staticCursor(session, c, items, 00354 r.item(1)); 00355 c.close(); 00356 } 00357 } 00358 } 00359 } else if (ag.isCount() && ag.all && c.size() > 0) { 00360 // another easy case is count(*) 00361 ret = staticCursor(session, c, items, 00362 new ValueLong(c.size())); 00363 c.close(); 00364 } 00365 } 00366 } 00367 } 00368 return ret; 00369 } 00370 00371 /** 00372 * Return the expression as a scalar value. We try. If there's only 00373 * a single column in the result, we just return the column's value for 00374 * the first row of the result. Otherwise, return ValueNull. 00375 */ 00376 public Value getValue(Session session, Cursor cur) throws SQLException { 00377 Value ret = ValueNull.valueNull; 00378 Cursor cursor = getCursor(session, cur); 00379 try { 00380 if (cursor.next()) { 00381 Row row = cursor.getRow(); 00382 if (row.size() == 1) { 00383 ret = row.item(1); 00384 } 00385 if (cursor.next()) { 00386 throw new SQLException("Cardinality violation", "21000"); 00387 } 00388 } 00389 return ret; 00390 } finally { 00391 cursor.close(); 00392 } 00393 } 00394 00395 /** 00396 * Get the base tables that participate in this select expression 00397 */ 00398 public void getBaseTables(Vector v) { 00399 for (int i = 0; i < from.size(); i++) { 00400 ((TableExpression)from.elementAt(i)).getBaseTables(v); 00401 } 00402 } 00403 00404 /** 00405 * Expression implementation... 00406 */ 00407 public void visitSubExpressions(ExpressionVisitor ev) { 00408 if (where != null) ev.visit(where); 00409 } 00410 00411 /** 00412 * Visitor class to set the WHERE clause on all subtables in this SELECT 00413 */ 00414 class AndWhere implements ExpressionVisitor { 00415 Expression where; 00416 AndWhere(Expression where) { this.where = where; } 00417 public void visit(Expression sub) { 00418 if (sub instanceof TableExpression) { 00419 TableExpression te = (TableExpression)sub; 00420 if (!te.anded) { 00421 Expression oldW = te.getWhere(); 00422 if (oldW != null) { 00423 where = new BinaryExpression(Op.AND, where, oldW); 00424 } 00425 te.setWhere(where); 00426 te.anded = true; 00427 } 00428 sub.visitSubExpressions(this); 00429 } 00430 } 00431 } 00432 00433 /** 00434 * Nope, I'm not a boolean 00435 * 00436 * @exception RuntimeException is thrown 00437 */ 00438 public void invert() { 00439 throw new RuntimeException( 00440 "invert not implemented for SelectExpression"); 00441 } 00442 00443 /** 00444 * Read me from a stream 00445 */ 00446 public void readExternal(ObjectInput in) 00447 throws IOException, ClassNotFoundException 00448 { 00449 this.distinct = (in.read() == 1); 00450 this.items = (Vector)in.readObject(); 00451 this.from = (Vector)in.readObject(); 00452 this.where = (Expression)in.readObject(); 00453 this.groupBy = (Vector)in.readObject(); 00454 this.having = (Expression)in.readObject(); 00455 } 00456 00457 /** 00458 * Write me to a stream 00459 */ 00460 public void writeExternal(ObjectOutput out) throws IOException { 00461 out.write(distinct ? 1 : 0); 00462 out.writeObject(items); 00463 out.writeObject(from); 00464 out.writeObject(where); 00465 out.writeObject(groupBy); 00466 out.writeObject(having); 00467 } 00468 00469 /** 00470 * Return a string representation for debugging purposes 00471 */ 00472 public String toString() { 00473 StringBuffer sb = new StringBuffer("SELECT "); 00474 if (items == null) { 00475 sb.append("*"); 00476 } else { 00477 for (int i = 0; i < items.size(); i++) { 00478 if (i > 0) sb.append(','); 00479 sb.append(items.elementAt(i).toString()); 00480 } 00481 } 00482 if (from != null) { 00483 sb.append(" FROM "); 00484 for (int i = 0; i < from.size(); i++) { 00485 if (i > 0) sb.append(','); 00486 sb.append(from.elementAt(i).toString()); 00487 } 00488 } 00489 if (where != null) { 00490 sb.append(" WHERE "); 00491 sb.append(where.toString()); 00492 } 00493 if (groupBy != null) { 00494 sb.append(" GROUP BY "); 00495 for (int i = 0; i < groupBy.size(); i++) { 00496 if (i > 0) sb.append(','); 00497 sb.append(groupBy.elementAt(i).toString()); 00498 } 00499 } 00500 if (having != null) { 00501 sb.append(" HAVING "); 00502 sb.append(having.toString()); 00503 } 00504 return sb.toString(); 00505 } 00506 00507 //#ifdef DEBUG 00508 /** 00509 * Display a cursor (under trace control) 00510 */ 00511 public static final void showCursor(Cursor c) { 00512 try { 00513 if (Trace.bit(2)) { 00514 Debug.println(c.toString()); 00515 } 00516 } catch (Exception e) { 00517 Debug.print(e); 00518 } 00519 } 00520 00521 /** 00522 * Display c cursor (or not) 00523 */ 00524 public static void showCursor(Cursor c, boolean really) { 00525 try { 00526 if (really) { 00527 Debug.println("\n" + c); 00528 } 00529 } catch (Throwable e) { 00530 Debug.print(e); 00531 } 00532 } 00533 00534 public String name() { 00535 return toString(); 00536 } 00537 //#endif 00538 00539 00540 } 00541