Quadcap Embeddable Database

com/quadcap/sql/Table.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.ByteArrayOutputStream; 00042 import java.io.BufferedInputStream; 00043 import java.io.BufferedOutputStream; 00044 import java.io.Externalizable; 00045 import java.io.IOException; 00046 import java.io.InputStream; 00047 import java.io.ObjectInput; 00048 import java.io.ObjectOutput; 00049 import java.io.OutputStream; 00050 00051 import java.util.BitSet; 00052 import java.util.Hashtable; 00053 import java.util.Vector; 00054 00055 import java.sql.ResultSet; 00056 import java.sql.ResultSetMetaData; 00057 import java.sql.SQLException; 00058 00059 import com.quadcap.sql.io.ObjectInputStream; 00060 import com.quadcap.sql.io.ObjectOutputStream; 00061 00062 import com.quadcap.sql.index.Btree; 00063 00064 import com.quadcap.sql.file.BlockFile; 00065 import com.quadcap.sql.file.ByteUtil; 00066 import com.quadcap.sql.file.Datafile; 00067 import com.quadcap.sql.file.Page; 00068 import com.quadcap.sql.file.PageManager; 00069 import com.quadcap.sql.file.RandomAccess; 00070 import com.quadcap.sql.file.RandomAccessOutputStream; 00071 import com.quadcap.sql.file.SubPageManager; 00072 00073 import com.quadcap.sql.types.Type; 00074 import com.quadcap.sql.types.TypeBlob; 00075 import com.quadcap.sql.types.TypeClob; 00076 import com.quadcap.sql.types.Value; 00077 00078 import com.quadcap.util.ConfigNumber; 00079 import com.quadcap.util.Debug; 00080 import com.quadcap.util.Util; 00081 00082 00083 /** 00084 * A single SQL base table. 00085 * 00086 * @author Stan Bailes 00087 */ 00088 public class Table extends TupleImpl implements Relation, Externalizable { 00089 // bit 0: table insert 00090 00091 int modifiers = 0; 00092 Constraint[] constraints = new Constraint[0]; 00093 transient boolean underConstruction = false; 00094 00095 // init: 0, if blobs: 1, else 2; 00096 transient int hasBlobs = 0; 00097 00098 /** 00099 * Table modifiers, bit fields. If temporary, either global or local 00100 * must be specified. 00101 */ 00102 public static final int TEMPORARY = 1; 00103 public static final int LOCAL = 2; 00104 public static final int GLOBAL = 4; 00105 public static final int STATEMENT_TEMP = 8; 00106 00107 /** 00108 * For in-memory mode, the table identity 00109 */ 00110 long tableIdentity = 1; 00111 00112 /** 00113 * Default constructor (required for deserialization) 00114 */ 00115 public Table() {} 00116 00117 /** 00118 * Explicit constructor 00119 */ 00120 public Table(String tableName, int modifiers) { 00121 super(tableName); 00122 this.modifiers = modifiers; 00123 } 00124 00125 /** 00126 * Get/Set the value of the 'is under construction' flag. This is used to 00127 * determine when ADD CONSTRAINT really has work to do ;-) 00128 */ 00129 public boolean isUnderConstruction() { return underConstruction; } 00130 00131 /** 00132 * Set the value of the 'is under construction' flag. 00133 */ 00134 public void setUnderConstruction(boolean v) { underConstruction = v; } 00135 00136 /** 00137 * Return the modifier bits for this table 00138 */ 00139 public int getModifiers() { return modifiers; } 00140 00141 /** 00142 * Are any of this table's columns BLOB (or CLOB) types? 00143 */ 00144 public boolean hasBlobs() throws SQLException { 00145 if (hasBlobs == 0) { 00146 hasBlobs = 2; 00147 for (int i = 1; hasBlobs == 2 && i <= getColumnCount(); i++) { 00148 Column c = getColumn(i); 00149 Type t = c.getType(); 00150 if (t instanceof TypeBlob || t instanceof TypeClob) { 00151 hasBlobs = 1; 00152 } 00153 } 00154 } 00155 return hasBlobs == 1; 00156 } 00157 00158 /** 00159 * Read me from a stream 00160 */ 00161 public void readExternal(ObjectInput in) 00162 throws IOException, ClassNotFoundException 00163 { 00164 super.readExternal(in); 00165 constraints = new Constraint[in.readInt()]; 00166 for (int i = 0; i < constraints.length; i++) { 00167 constraints[i] = (Constraint)in.readObject(); 00168 try { 00169 constraints[i].setTable(this); 00170 } catch (SQLException ex) { 00171 Debug.print(ex); 00172 } 00173 } 00174 } 00175 00176 /** 00177 * Write me to a stream 00178 */ 00179 public void writeExternal(ObjectOutput out) throws IOException { 00180 super.writeExternal(out); 00181 out.writeInt(constraints.length); 00182 for (int i = 0; i < constraints.length; i++) { 00183 out.writeObject(constraints[i]); 00184 } 00185 } 00186 00187 /** 00188 * Add a column to the table; called during initial table construction 00189 * only. 00190 */ 00191 public void addColumn(Column col) throws SQLException { 00192 super.addColumn(col); 00193 Vector v = col.getConstraints(); 00194 if (v != null) { 00195 for (int i = 0; i < v.size(); i++) { 00196 Constraint c = (Constraint)v.elementAt(i); 00197 c.setColumn(col); 00198 } 00199 } 00200 } 00201 00202 /** 00203 * Return the specified table constraint (zero-based) 00204 */ 00205 final public Constraint getConstraint(int num) { 00206 return constraints[num]; 00207 } 00208 00209 /** 00210 * Return the number of table constraints 00211 */ 00212 final public int getNumConstraints() { 00213 return constraints.length; 00214 } 00215 00216 /** 00217 * Get the constraint with the specified name 00218 * 00219 * XXX Maybe we should profile this sucker. A hashtable? 00220 */ 00221 public Constraint getConstraint(String name) { 00222 for (int i = 0; i < constraints.length; i++) { 00223 Constraint c = constraints[i]; 00224 if (c.getName().equals(name)) { 00225 return c; 00226 } 00227 } 00228 return null; 00229 } 00230 00231 /** 00232 * Return the table's primary key constraint, if it is defined, 00233 * otherwise return null. 00234 */ 00235 public UniqueConstraint getPrimaryKey() { 00236 UniqueConstraint uc = null; 00237 for (int i = 0; i < constraints.length; i++) { 00238 Constraint c = constraints[i]; 00239 if (c instanceof PrimaryKeyConstraint) { 00240 return (PrimaryKeyConstraint)c; 00241 } else if (uc == null && c instanceof UniqueConstraint) { 00242 uc = (UniqueConstraint)c; 00243 } 00244 } 00245 return uc; 00246 } 00247 00248 /** 00249 * Generate a unique, semi-meaningful, name for this constratint, 00250 * in case the user didn't specify one. 00251 */ 00252 public void nameConstraint(Constraint c) { 00253 String name = c.getName(); 00254 if (name == null) { 00255 name = c.getClass().getName(); 00256 int idx = name.lastIndexOf('.'); 00257 if (idx > 0) name = name.substring(idx+1); 00258 name += "_"; 00259 name += Integer.toString(constraints.length); 00260 c.setName(name); 00261 } 00262 } 00263 00264 /** 00265 * Add a table constraint 00266 */ 00267 public void addConstraint(Constraint c) throws SQLException { 00268 Constraint[] nc = new Constraint[constraints.length + 1]; 00269 boolean added = false; 00270 for (int i = 0; i < constraints.length; i++) { 00271 Constraint ci = constraints[i]; 00272 if (added) { 00273 nc[i+1] = ci; 00274 } else { 00275 if (c.getPriority() >= ci.getPriority()) { 00276 nc[i] = ci; 00277 } else { 00278 nc[i] = c; 00279 nc[i+1] = ci; 00280 added = true; 00281 } 00282 } 00283 } 00284 if (!added) nc[constraints.length] = c; 00285 nameConstraint(c); 00286 constraints = nc; 00287 c.setTable(this); 00288 final int notnull = ResultSetMetaData.columnNoNulls; 00289 if (c instanceof NotNullConstraint || 00290 c instanceof PrimaryKeyConstraint) { 00291 int[] cols = c.getColumns(); 00292 for (int i = 0; i < cols.length; i++) { 00293 getColumn(cols[i]).setNullable(notnull); 00294 } 00295 } 00296 } 00297 00298 /** 00299 * Delete the specified table constraint. 00300 */ 00301 public void deleteConstraint(String name) 00302 throws SQLException, IOException 00303 { 00304 int off = 0; 00305 for (int i = 0; i < constraints.length; i++) { 00306 if (constraints[i].getName().equals(name)) { 00307 off++; 00308 } else if (off > 0) { 00309 constraints[i-off] = constraints[i]; 00310 } 00311 } 00312 if (off > 0) { 00313 Constraint[] nc = new Constraint[constraints.length - off]; 00314 System.arraycopy(constraints, 0, nc, 0, nc.length); 00315 constraints = nc; 00316 } else { 00317 throw new SQLException("No constraint: " + name + " for table " + 00318 getName(), "42000"); 00319 } 00320 } 00321 00322 /** 00323 * Delete a column from this table, and reset any column-constraint 00324 * mappings 00325 */ 00326 public void deleteColumn(int c) throws SQLException, IOException { 00327 super.deleteColumn(c); 00328 resetColumnConstraints(); 00329 } 00330 00331 /** 00332 * Reset any constraint-column mapping (typically after a column 00333 * has been added or dropped) 00334 */ 00335 public void resetColumnConstraints() throws SQLException { 00336 final int num = getNumConstraints(); 00337 for (int i = 0; i < num; i++) { 00338 Constraint con = getConstraint(i); 00339 con.resetColumns(); 00340 } 00341 } 00342 00343 /** 00344 * Convenience method to write a new row to the database 00345 */ 00346 public static final long putRow(Session session, Tuple t, Row row) 00347 throws SQLException, IOException 00348 { 00349 return session.getDatabase().putRow(session, session.getFile(), t, row); 00350 } 00351 00352 // /** 00353 // * Update the specified row 00354 // */ 00355 // public final void writeRow(Session session, long rowId, Row row) 00356 // throws SQLException, IOException 00357 // { 00358 // //#ifdef TRACE 00359 // if (Trace.bit(11)) { 00360 // Debug.println("Table[" + getName() + "].putRow(" + row + ")"); 00361 // } 00362 // //#endif 00363 // byte[] buf = LazyRow.writeRow(session, this, row); 00364 // session.getFile().updateBytes(rowId, buf); 00365 // } 00366 00367 // /** 00368 // * Write (an already serialized) row to the database 00369 // */ 00370 // public final static void writeRow(BlockFile file, long rowId, byte[] buf) 00371 // throws SQLException, IOException 00372 // { 00373 // final RandomAccess ra = file.getStream(rowId); 00374 // ra.resize(buf.length); 00375 // ra.write(0, buf, 0, buf.length); 00376 // } 00377 00378 static final String strip(String s) { 00379 int idx = s.lastIndexOf("."); 00380 if (idx >= 0) s = s.substring(idx+1); 00381 return s; 00382 } 00383 00384 Row getRow(Database db, long rowId) throws IOException, SQLException { 00385 LazyRow row = new LazyRow(getColumnCount()); 00386 db.getRow(rowId, row, false); 00387 return row; 00388 } 00389 00390 /** 00391 * Return a cursor on this table 00392 */ 00393 public Cursor getCursor(Session session, Expression where, 00394 String asName, Cursor outer) 00395 throws SQLException 00396 { 00397 try { 00398 session.getTableReadLock(getName()); 00399 Hashtable t = getNames(where); 00400 IndexConstraint con = getIndexForNames(session, t); 00401 String qualName = asName; 00402 if (qualName == null) qualName = getName(); 00403 Cursor c = new IndexCursor(this, session, con, where, 00404 qualName, outer); 00405 return c; 00406 } catch (IOException e) { 00407 throw DbException.wrapThrowable(e); 00408 } 00409 } 00410 00411 public Cursor getCursor(Session session, Expression where, 00412 String asName) 00413 throws SQLException 00414 { 00415 return getCursor(session, where, asName, null); 00416 } 00417 00418 public IndexCursor getCursor(Session session, IndexConstraint notMe) 00419 throws SQLException 00420 { 00421 try { 00422 session.getTableReadLock(getName()); 00423 IndexConstraint con = getAnyIndexBut(session, notMe); 00424 if (con == null) return null; 00425 return new IndexCursor(this, session, con, null, getName(), null); 00426 } catch (IOException e) { 00427 throw DbException.wrapThrowable(e); 00428 } 00429 } 00430 00431 class GetNames implements ExpressionVisitor { 00432 Hashtable t = new Hashtable(); 00433 public void visit(Expression ex) { 00434 String name = ex.getName(); 00435 if (name != null) { 00436 t.put(name, name); 00437 } else { 00438 ex.visitSubExpressions(this); 00439 } 00440 } 00441 } 00442 00443 Hashtable getNames(Expression ex) { 00444 GetNames get = new GetNames(); 00445 if (ex != null) get.visit(ex); 00446 return get.t; 00447 } 00448 00449 final IndexConstraint getAnyIndex(Session session) 00450 throws IOException, SQLException 00451 { 00452 return getIndexConstraint(session, null, null); 00453 } 00454 00455 final IndexConstraint getIndexForNames(Session session, Hashtable names) 00456 throws IOException, SQLException 00457 { 00458 return getIndexConstraint(session, names, null); 00459 } 00460 00461 final IndexConstraint getAnyIndexBut(Session session, IndexConstraint notMe) 00462 throws IOException, SQLException 00463 { 00464 return getIndexConstraint(session, null, notMe); 00465 } 00466 00467 IndexConstraint getIndexConstraint(Session session, Hashtable names, 00468 IndexConstraint notMe) 00469 throws IOException, SQLException 00470 { 00471 // try to find, in this order: 00472 // a unique constraint matching an item in the where clause 00473 UniqueConstraint wcon = null; 00474 // any index constraint matching an item in the where clause 00475 IndexConstraint icon = null; 00476 // any unique constraint 00477 UniqueConstraint ucon = null; 00478 // any index 00479 IndexConstraint acon = null; 00480 00481 String sc = ""; 00482 String nm = getName(); 00483 int idx = nextUnquotedPeriod(nm); 00484 if (idx >= 0) { 00485 sc = nm.substring(0, idx); 00486 nm = nm.substring(idx+1); 00487 } 00488 00489 for (int ci = 0; ci < constraints.length; ci++) { 00490 Constraint con = constraints[ci]; 00491 if (con == notMe) continue; 00492 if (con instanceof IndexConstraint) { 00493 acon = (IndexConstraint)con; 00494 Vector cnames = con.getColumnNames(); 00495 if (cnames == null) { 00496 // bad news, skip out the back 00497 //#ifdef DEBUG 00498 throw new SQLException("Index constraint has no columns: "+ 00499 con); 00500 //#else 00501 //- continue; 00502 //#endif 00503 } 00504 boolean matched = false; 00505 if (names != null) { 00506 matched = true; 00507 for (int i = 0; matched && i < cnames.size(); i++) { 00508 String cnam = cnames.get(i).toString(); 00509 if (names.get(cnam) == null && 00510 names.get(nm + "." + cnam) == null && 00511 names.get(sc + "." + nm + "." + cnam) == null) { 00512 matched = false; 00513 } 00514 } 00515 } 00516 if (matched) { 00517 if (acon instanceof UniqueConstraint) { 00518 wcon = (UniqueConstraint)acon; 00519 } else { 00520 icon = acon; 00521 } 00522 } else { 00523 if (acon instanceof UniqueConstraint) { 00524 ucon = (UniqueConstraint)acon; 00525 } 00526 } 00527 } 00528 } 00529 00530 if (wcon != null) return wcon; 00531 if (icon != null) return icon; 00532 if (ucon != null) return ucon; 00533 if (acon != null) return acon; 00534 00535 // Don't throw an exception when trying to create first index. 00536 if (notMe == null) { 00537 throw new SQLException("No index", "42000"); 00538 } else { 00539 return null; 00540 } 00541 } 00542 00543 UniqueConstraint getIndexForColumns(int[] cols) throws SQLException { 00544 BitSet match = new BitSet(); 00545 for (int i = 0; i < cols.length; i++) { 00546 match.set(cols[i]); 00547 } 00548 for (int i = 0; i < constraints.length; i++) { 00549 Constraint con = constraints[i]; 00550 if (con instanceof UniqueConstraint) { 00551 int[] tcols = con.getColumns(); 00552 BitSet tmatch = new BitSet(); 00553 for (int j = 0; j < tcols.length; j++) { 00554 tmatch.set(tcols[j]); 00555 } 00556 if (match.equals(tmatch)) return (UniqueConstraint)con; 00557 } 00558 } 00559 return null; 00560 } 00561 00562 /** 00563 * My SQL type 00564 */ 00565 public String getType() { 00566 if ((modifiers & TEMPORARY) != 0) { 00567 if ((modifiers & GLOBAL) != 0) { 00568 return "GLOBAL TEMPORARY"; 00569 } 00570 if ((modifiers & LOCAL) != 0) { 00571 return "LOCAL TEMPORARY"; 00572 } 00573 } 00574 return "TABLE"; 00575 } 00576 00577 /** (haiku-comment) 00578 * I am a table 00579 * and therefore updateable 00580 * @return true not false 00581 */ 00582 public boolean isUpdatable() { return true; } 00583 00584 //#ifdef DEBUG 00585 /** 00586 * String representation for debugging 00587 */ 00588 public String toString() { 00589 StringBuffer sb = new StringBuffer(super.toString()); 00590 sb.append("Constraints:\n"); 00591 for (int i = 0; i < constraints.length; i++) { 00592 Constraint c = constraints[i]; 00593 sb.append(" "); 00594 sb.append(String.valueOf(c)); 00595 sb.append("\n"); 00596 } 00597 return sb.toString(); 00598 } 00599 //#endif 00600 00601 public void insertRow(Session session, Row row) 00602 throws SQLException, IOException 00603 { 00604 TableOps.insertRow(session, this, row); 00605 } 00606 00607 } 00608