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.ByteArrayOutputStream;
042: import java.io.BufferedInputStream;
043: import java.io.BufferedOutputStream;
044: import java.io.Externalizable;
045: import java.io.IOException;
046: import java.io.InputStream;
047: import java.io.ObjectInput;
048: import java.io.ObjectOutput;
049: import java.io.OutputStream;
050:
051: import java.util.BitSet;
052: import java.util.Hashtable;
053: import java.util.Vector;
054:
055: import java.sql.ResultSet;
056: import java.sql.ResultSetMetaData;
057: import java.sql.SQLException;
058:
059: import com.quadcap.sql.io.ObjectInputStream;
060: import com.quadcap.sql.io.ObjectOutputStream;
061:
062: import com.quadcap.sql.index.Btree;
063:
064: import com.quadcap.sql.file.BlockFile;
065: import com.quadcap.sql.file.ByteUtil;
066: import com.quadcap.sql.file.Datafile;
067: import com.quadcap.sql.file.Page;
068: import com.quadcap.sql.file.PageManager;
069: import com.quadcap.sql.file.RandomAccess;
070: import com.quadcap.sql.file.RandomAccessOutputStream;
071: import com.quadcap.sql.file.SubPageManager;
072:
073: import com.quadcap.sql.types.Type;
074: import com.quadcap.sql.types.TypeBlob;
075: import com.quadcap.sql.types.TypeClob;
076: import com.quadcap.sql.types.Value;
077:
078: import com.quadcap.util.ConfigNumber;
079: import com.quadcap.util.Debug;
080: import com.quadcap.util.Util;
081:
082: /**
083: * A single SQL base table.
084: *
085: * @author Stan Bailes
086: */
087: public class Table extends TupleImpl implements Relation,
088: Externalizable {
089: // bit 0: table insert
090:
091: int modifiers = 0;
092: Constraint[] constraints = new Constraint[0];
093: transient boolean underConstruction = false;
094:
095: // init: 0, if blobs: 1, else 2;
096: transient int hasBlobs = 0;
097:
098: /**
099: * Table modifiers, bit fields. If temporary, either global or local
100: * must be specified.
101: */
102: public static final int TEMPORARY = 1;
103: public static final int LOCAL = 2;
104: public static final int GLOBAL = 4;
105: public static final int STATEMENT_TEMP = 8;
106:
107: /**
108: * For in-memory mode, the table identity
109: */
110: long tableIdentity = 1;
111:
112: /**
113: * Default constructor (required for deserialization)
114: */
115: public Table() {
116: }
117:
118: /**
119: * Explicit constructor
120: */
121: public Table(String tableName, int modifiers) {
122: super (tableName);
123: this .modifiers = modifiers;
124: }
125:
126: /**
127: * Get/Set the value of the 'is under construction' flag. This is used to
128: * determine when ADD CONSTRAINT really has work to do ;-)
129: */
130: public boolean isUnderConstruction() {
131: return underConstruction;
132: }
133:
134: /**
135: * Set the value of the 'is under construction' flag.
136: */
137: public void setUnderConstruction(boolean v) {
138: underConstruction = v;
139: }
140:
141: /**
142: * Return the modifier bits for this table
143: */
144: public int getModifiers() {
145: return modifiers;
146: }
147:
148: /**
149: * Are any of this table's columns BLOB (or CLOB) types?
150: */
151: public boolean hasBlobs() throws SQLException {
152: if (hasBlobs == 0) {
153: hasBlobs = 2;
154: for (int i = 1; hasBlobs == 2 && i <= getColumnCount(); i++) {
155: Column c = getColumn(i);
156: Type t = c.getType();
157: if (t instanceof TypeBlob || t instanceof TypeClob) {
158: hasBlobs = 1;
159: }
160: }
161: }
162: return hasBlobs == 1;
163: }
164:
165: /**
166: * Read me from a stream
167: */
168: public void readExternal(ObjectInput in) throws IOException,
169: ClassNotFoundException {
170: super .readExternal(in);
171: constraints = new Constraint[in.readInt()];
172: for (int i = 0; i < constraints.length; i++) {
173: constraints[i] = (Constraint) in.readObject();
174: try {
175: constraints[i].setTable(this );
176: } catch (SQLException ex) {
177: Debug.print(ex);
178: }
179: }
180: }
181:
182: /**
183: * Write me to a stream
184: */
185: public void writeExternal(ObjectOutput out) throws IOException {
186: super .writeExternal(out);
187: out.writeInt(constraints.length);
188: for (int i = 0; i < constraints.length; i++) {
189: out.writeObject(constraints[i]);
190: }
191: }
192:
193: /**
194: * Add a column to the table; called during initial table construction
195: * only.
196: */
197: public void addColumn(Column col) throws SQLException {
198: super .addColumn(col);
199: Vector v = col.getConstraints();
200: if (v != null) {
201: for (int i = 0; i < v.size(); i++) {
202: Constraint c = (Constraint) v.elementAt(i);
203: c.setColumn(col);
204: }
205: }
206: }
207:
208: /**
209: * Return the specified table constraint (zero-based)
210: */
211: final public Constraint getConstraint(int num) {
212: return constraints[num];
213: }
214:
215: /**
216: * Return the number of table constraints
217: */
218: final public int getNumConstraints() {
219: return constraints.length;
220: }
221:
222: /**
223: * Get the constraint with the specified name
224: *
225: * XXX Maybe we should profile this sucker. A hashtable?
226: */
227: public Constraint getConstraint(String name) {
228: for (int i = 0; i < constraints.length; i++) {
229: Constraint c = constraints[i];
230: if (c.getName().equals(name)) {
231: return c;
232: }
233: }
234: return null;
235: }
236:
237: /**
238: * Return the table's primary key constraint, if it is defined,
239: * otherwise return null.
240: */
241: public UniqueConstraint getPrimaryKey() {
242: UniqueConstraint uc = null;
243: for (int i = 0; i < constraints.length; i++) {
244: Constraint c = constraints[i];
245: if (c instanceof PrimaryKeyConstraint) {
246: return (PrimaryKeyConstraint) c;
247: } else if (uc == null && c instanceof UniqueConstraint) {
248: uc = (UniqueConstraint) c;
249: }
250: }
251: return uc;
252: }
253:
254: /**
255: * Generate a unique, semi-meaningful, name for this constratint,
256: * in case the user didn't specify one.
257: */
258: public void nameConstraint(Constraint c) {
259: String name = c.getName();
260: if (name == null) {
261: name = c.getClass().getName();
262: int idx = name.lastIndexOf('.');
263: if (idx > 0)
264: name = name.substring(idx + 1);
265: name += "_";
266: name += Integer.toString(constraints.length);
267: c.setName(name);
268: }
269: }
270:
271: /**
272: * Add a table constraint
273: */
274: public void addConstraint(Constraint c) throws SQLException {
275: Constraint[] nc = new Constraint[constraints.length + 1];
276: boolean added = false;
277: for (int i = 0; i < constraints.length; i++) {
278: Constraint ci = constraints[i];
279: if (added) {
280: nc[i + 1] = ci;
281: } else {
282: if (c.getPriority() >= ci.getPriority()) {
283: nc[i] = ci;
284: } else {
285: nc[i] = c;
286: nc[i + 1] = ci;
287: added = true;
288: }
289: }
290: }
291: if (!added)
292: nc[constraints.length] = c;
293: nameConstraint(c);
294: constraints = nc;
295: c.setTable(this );
296: final int notnull = ResultSetMetaData.columnNoNulls;
297: if (c instanceof NotNullConstraint
298: || c instanceof PrimaryKeyConstraint) {
299: int[] cols = c.getColumns();
300: for (int i = 0; i < cols.length; i++) {
301: getColumn(cols[i]).setNullable(notnull);
302: }
303: }
304: }
305:
306: /**
307: * Delete the specified table constraint.
308: */
309: public void deleteConstraint(String name) throws SQLException,
310: IOException {
311: int off = 0;
312: for (int i = 0; i < constraints.length; i++) {
313: if (constraints[i].getName().equals(name)) {
314: off++;
315: } else if (off > 0) {
316: constraints[i - off] = constraints[i];
317: }
318: }
319: if (off > 0) {
320: Constraint[] nc = new Constraint[constraints.length - off];
321: System.arraycopy(constraints, 0, nc, 0, nc.length);
322: constraints = nc;
323: } else {
324: throw new SQLException("No constraint: " + name
325: + " for table " + getName(), "42000");
326: }
327: }
328:
329: /**
330: * Delete a column from this table, and reset any column-constraint
331: * mappings
332: */
333: public void deleteColumn(int c) throws SQLException, IOException {
334: super .deleteColumn(c);
335: resetColumnConstraints();
336: }
337:
338: /**
339: * Reset any constraint-column mapping (typically after a column
340: * has been added or dropped)
341: */
342: public void resetColumnConstraints() throws SQLException {
343: final int num = getNumConstraints();
344: for (int i = 0; i < num; i++) {
345: Constraint con = getConstraint(i);
346: con.resetColumns();
347: }
348: }
349:
350: /**
351: * Convenience method to write a new row to the database
352: */
353: public static final long putRow(Session session, Tuple t, Row row)
354: throws SQLException, IOException {
355: return session.getDatabase().putRow(session, session.getFile(),
356: t, row);
357: }
358:
359: // /**
360: // * Update the specified row
361: // */
362: // public final void writeRow(Session session, long rowId, Row row)
363: // throws SQLException, IOException
364: // {
365: // //#ifdef TRACE
366: // if (Trace.bit(11)) {
367: // Debug.println("Table[" + getName() + "].putRow(" + row + ")");
368: // }
369: // //#endif
370: // byte[] buf = LazyRow.writeRow(session, this, row);
371: // session.getFile().updateBytes(rowId, buf);
372: // }
373:
374: // /**
375: // * Write (an already serialized) row to the database
376: // */
377: // public final static void writeRow(BlockFile file, long rowId, byte[] buf)
378: // throws SQLException, IOException
379: // {
380: // final RandomAccess ra = file.getStream(rowId);
381: // ra.resize(buf.length);
382: // ra.write(0, buf, 0, buf.length);
383: // }
384:
385: static final String strip(String s) {
386: int idx = s.lastIndexOf(".");
387: if (idx >= 0)
388: s = s.substring(idx + 1);
389: return s;
390: }
391:
392: Row getRow(Database db, long rowId) throws IOException,
393: SQLException {
394: LazyRow row = new LazyRow(getColumnCount());
395: db.getRow(rowId, row, false);
396: return row;
397: }
398:
399: /**
400: * Return a cursor on this table
401: */
402: public Cursor getCursor(Session session, Expression where,
403: String asName, Cursor outer) throws SQLException {
404: try {
405: session.getTableReadLock(getName());
406: Hashtable t = getNames(where);
407: IndexConstraint con = getIndexForNames(session, t);
408: String qualName = asName;
409: if (qualName == null)
410: qualName = getName();
411: Cursor c = new IndexCursor(this , session, con, where,
412: qualName, outer);
413: return c;
414: } catch (IOException e) {
415: throw DbException.wrapThrowable(e);
416: }
417: }
418:
419: public Cursor getCursor(Session session, Expression where,
420: String asName) throws SQLException {
421: return getCursor(session, where, asName, null);
422: }
423:
424: public IndexCursor getCursor(Session session, IndexConstraint notMe)
425: throws SQLException {
426: try {
427: session.getTableReadLock(getName());
428: IndexConstraint con = getAnyIndexBut(session, notMe);
429: if (con == null)
430: return null;
431: return new IndexCursor(this , session, con, null, getName(),
432: null);
433: } catch (IOException e) {
434: throw DbException.wrapThrowable(e);
435: }
436: }
437:
438: class GetNames implements ExpressionVisitor {
439: Hashtable t = new Hashtable();
440:
441: public void visit(Expression ex) {
442: String name = ex.getName();
443: if (name != null) {
444: t.put(name, name);
445: } else {
446: ex.visitSubExpressions(this );
447: }
448: }
449: }
450:
451: Hashtable getNames(Expression ex) {
452: GetNames get = new GetNames();
453: if (ex != null)
454: get.visit(ex);
455: return get.t;
456: }
457:
458: final IndexConstraint getAnyIndex(Session session)
459: throws IOException, SQLException {
460: return getIndexConstraint(session, null, null);
461: }
462:
463: final IndexConstraint getIndexForNames(Session session,
464: Hashtable names) throws IOException, SQLException {
465: return getIndexConstraint(session, names, null);
466: }
467:
468: final IndexConstraint getAnyIndexBut(Session session,
469: IndexConstraint notMe) throws IOException, SQLException {
470: return getIndexConstraint(session, null, notMe);
471: }
472:
473: IndexConstraint getIndexConstraint(Session session,
474: Hashtable names, IndexConstraint notMe) throws IOException,
475: SQLException {
476: // try to find, in this order:
477: // a unique constraint matching an item in the where clause
478: UniqueConstraint wcon = null;
479: // any index constraint matching an item in the where clause
480: IndexConstraint icon = null;
481: // any unique constraint
482: UniqueConstraint ucon = null;
483: // any index
484: IndexConstraint acon = null;
485:
486: String sc = "";
487: String nm = getName();
488: int idx = nextUnquotedPeriod(nm);
489: if (idx >= 0) {
490: sc = nm.substring(0, idx);
491: nm = nm.substring(idx + 1);
492: }
493:
494: for (int ci = 0; ci < constraints.length; ci++) {
495: Constraint con = constraints[ci];
496: if (con == notMe)
497: continue;
498: if (con instanceof IndexConstraint) {
499: acon = (IndexConstraint) con;
500: Vector cnames = con.getColumnNames();
501: if (cnames == null) {
502: // bad news, skip out the back
503: //#ifdef DEBUG
504: throw new SQLException(
505: "Index constraint has no columns: " + con);
506: //#else
507: //- continue;
508: //#endif
509: }
510: boolean matched = false;
511: if (names != null) {
512: matched = true;
513: for (int i = 0; matched && i < cnames.size(); i++) {
514: String cnam = cnames.get(i).toString();
515: if (names.get(cnam) == null
516: && names.get(nm + "." + cnam) == null
517: && names
518: .get(sc + "." + nm + "." + cnam) == null) {
519: matched = false;
520: }
521: }
522: }
523: if (matched) {
524: if (acon instanceof UniqueConstraint) {
525: wcon = (UniqueConstraint) acon;
526: } else {
527: icon = acon;
528: }
529: } else {
530: if (acon instanceof UniqueConstraint) {
531: ucon = (UniqueConstraint) acon;
532: }
533: }
534: }
535: }
536:
537: if (wcon != null)
538: return wcon;
539: if (icon != null)
540: return icon;
541: if (ucon != null)
542: return ucon;
543: if (acon != null)
544: return acon;
545:
546: // Don't throw an exception when trying to create first index.
547: if (notMe == null) {
548: throw new SQLException("No index", "42000");
549: } else {
550: return null;
551: }
552: }
553:
554: UniqueConstraint getIndexForColumns(int[] cols) throws SQLException {
555: BitSet match = new BitSet();
556: for (int i = 0; i < cols.length; i++) {
557: match.set(cols[i]);
558: }
559: for (int i = 0; i < constraints.length; i++) {
560: Constraint con = constraints[i];
561: if (con instanceof UniqueConstraint) {
562: int[] tcols = con.getColumns();
563: BitSet tmatch = new BitSet();
564: for (int j = 0; j < tcols.length; j++) {
565: tmatch.set(tcols[j]);
566: }
567: if (match.equals(tmatch))
568: return (UniqueConstraint) con;
569: }
570: }
571: return null;
572: }
573:
574: /**
575: * My SQL type
576: */
577: public String getType() {
578: if ((modifiers & TEMPORARY) != 0) {
579: if ((modifiers & GLOBAL) != 0) {
580: return "GLOBAL TEMPORARY";
581: }
582: if ((modifiers & LOCAL) != 0) {
583: return "LOCAL TEMPORARY";
584: }
585: }
586: return "TABLE";
587: }
588:
589: /** (haiku-comment)
590: * I am a table
591: * and therefore updateable
592: * @return true not false
593: */
594: public boolean isUpdatable() {
595: return true;
596: }
597:
598: //#ifdef DEBUG
599: /**
600: * String representation for debugging
601: */
602: public String toString() {
603: StringBuffer sb = new StringBuffer(super .toString());
604: sb.append("Constraints:\n");
605: for (int i = 0; i < constraints.length; i++) {
606: Constraint c = constraints[i];
607: sb.append(" ");
608: sb.append(String.valueOf(c));
609: sb.append("\n");
610: }
611: return sb.toString();
612: }
613:
614: //#endif
615:
616: public void insertRow(Session session, Row row)
617: throws SQLException, IOException {
618: TableOps.insertRow(session, this, row);
619: }
620:
621: }
|