001: /*
002: * Copyright 2004-2008 H2 Group. Licensed under the H2 License, Version 1.0
003: * (http://h2database.com/html/license.html).
004: * Initial Developer: H2 Group
005: */
006: package org.h2.table;
007:
008: import java.sql.SQLException;
009: import java.util.HashMap;
010: import java.util.HashSet;
011:
012: import org.h2.command.Prepared;
013: import org.h2.constant.ErrorCode;
014: import org.h2.constraint.Constraint;
015: import org.h2.engine.Constants;
016: import org.h2.engine.DbObject;
017: import org.h2.engine.Right;
018: import org.h2.engine.Session;
019: import org.h2.expression.ExpressionVisitor;
020: import org.h2.index.Index;
021: import org.h2.index.IndexType;
022: import org.h2.log.UndoLogRecord;
023: import org.h2.message.Message;
024: import org.h2.message.Trace;
025: import org.h2.result.Row;
026: import org.h2.result.RowList;
027: import org.h2.result.SearchRow;
028: import org.h2.result.SimpleRow;
029: import org.h2.result.SimpleRowValue;
030: import org.h2.result.SortOrder;
031: import org.h2.schema.Schema;
032: import org.h2.schema.SchemaObjectBase;
033: import org.h2.schema.Sequence;
034: import org.h2.schema.TriggerObject;
035: import org.h2.util.ObjectArray;
036: import org.h2.value.DataType;
037: import org.h2.value.Value;
038: import org.h2.value.ValueNull;
039:
040: /**
041: * This is the base class for most tables.
042: * A table contains a list of columns and a list of rows.
043: */
044: public abstract class Table extends SchemaObjectBase {
045:
046: public static final int TYPE_CACHED = 0, TYPE_MEMORY = 1;
047:
048: public static final String TABLE_LINK = "TABLE LINK";
049: public static final String SYSTEM_TABLE = "SYSTEM TABLE";
050: public static final String TABLE = "TABLE";
051: public static final String VIEW = "VIEW";
052:
053: /**
054: * The columns of this table.
055: */
056: protected Column[] columns;
057:
058: /**
059: * The amount of memory required for a row if all values would be very small.
060: */
061: protected int memoryPerRow;
062:
063: private final HashMap columnMap = new HashMap();
064: private final boolean persistent;
065: private ObjectArray triggers;
066: private ObjectArray constraints;
067: private ObjectArray sequences;
068: private ObjectArray views;
069: private boolean checkForeignKeyConstraints = true;
070: private boolean onCommitDrop, onCommitTruncate;
071: private Row nullRow;
072:
073: /**
074: * Lock the table for the given session.
075: * This method waits until the lock is granted.
076: *
077: * @param session the session
078: * @param exclusive true for write locks, false for read locks
079: * @param force lock even in the MVCC mode
080: * @throws SQLException if a lock timeout occured
081: */
082: public abstract void lock(Session session, boolean exclusive,
083: boolean force) throws SQLException;
084:
085: /**
086: * Close the table object and flush changes.
087: *
088: * @param session the session
089: */
090: public abstract void close(Session session) throws SQLException;
091:
092: /**
093: * Release the lock for this session.
094: *
095: * @param s the session
096: */
097: public abstract void unlock(Session s);
098:
099: /**
100: * Create an index for this table
101: *
102: * @param session the session
103: * @param indexName the name of the index
104: * @param indexId the id
105: * @param cols the index columns
106: * @param indexType the index type
107: * @param headPos the position of the head (if the index already exists)
108: * @param comment the comment
109: * @return the index
110: */
111: public abstract Index addIndex(Session session, String indexName,
112: int indexId, IndexColumn[] cols, IndexType indexType,
113: int headPos, String comment) throws SQLException;
114:
115: /**
116: * Remove a row from the table and all indexes.
117: *
118: * @param session the session
119: * @param row the row
120: */
121: public abstract void removeRow(Session session, Row row)
122: throws SQLException;
123:
124: /**
125: * Remove all rows from the table and indexes.
126: *
127: * @param session the session
128: */
129: public abstract void truncate(Session session) throws SQLException;
130:
131: /**
132: * Add a row to the table and all indexes.
133: *
134: * @param session the session
135: * @param row the row
136: * @throws SQLException if a constraint was violated
137: */
138: public abstract void addRow(Session session, Row row)
139: throws SQLException;
140:
141: /**
142: * Check if this table supports ALTER TABLE.
143: *
144: * @throws SQLException if it is not supported
145: */
146: public abstract void checkSupportAlter() throws SQLException;
147:
148: /**
149: * Get the table type name
150: *
151: * @return the table type name
152: */
153: public abstract String getTableType();
154:
155: /**
156: * Get the scan index to iterate through all rows.
157: *
158: * @param session the session
159: * @return the index
160: */
161: public abstract Index getScanIndex(Session session)
162: throws SQLException;
163:
164: /**
165: * Get any unique index for this table if one exists.
166: *
167: * @return a unique index
168: */
169: public abstract Index getUniqueIndex();
170:
171: /**
172: * Get all indexes for this table.
173: *
174: * @return the list of indexes
175: */
176: public abstract ObjectArray getIndexes();
177:
178: /**
179: * Check if this table is locked exclusively.
180: *
181: * @return true if it is.
182: */
183: public abstract boolean isLockedExclusively();
184:
185: /**
186: * Get the last data modification id.
187: *
188: * @return the modification id
189: */
190: public abstract long getMaxDataModificationId();
191:
192: /**
193: * Check if the row count can be retrieved quickly.
194: *
195: * @return true if it can
196: */
197: public abstract boolean canGetRowCount();
198:
199: /**
200: * Check if this table can be dropped.
201: *
202: * @return true if it can
203: */
204: public abstract boolean canDrop();
205:
206: /**
207: * Get the row count for this table.
208: *
209: * @param session the session
210: * @return the row count
211: */
212: public abstract long getRowCount(Session session)
213: throws SQLException;
214:
215: public Table(Schema schema, int id, String name, boolean persistent) {
216: super (schema, id, name, Trace.TABLE);
217: this .persistent = persistent;
218: }
219:
220: public String getCreateSQLForCopy(Table table, String quotedName) {
221: throw Message.getInternalError();
222: }
223:
224: public void addDependencies(HashSet dependencies) {
225: if (sequences != null) {
226: for (int i = 0; i < sequences.size(); i++) {
227: dependencies.add(sequences.get(i));
228: }
229: }
230: ExpressionVisitor visitor = ExpressionVisitor
231: .get(ExpressionVisitor.GET_DEPENDENCIES);
232: visitor.setDependencies(dependencies);
233: for (int i = 0; i < columns.length; i++) {
234: columns[i].isEverything(visitor);
235: }
236: }
237:
238: public ObjectArray getChildren() {
239: ObjectArray children = new ObjectArray();
240: ObjectArray indexes = getIndexes();
241: if (indexes != null) {
242: children.addAll(indexes);
243: }
244: if (constraints != null) {
245: children.addAll(constraints);
246: }
247: if (triggers != null) {
248: children.addAll(triggers);
249: }
250: if (sequences != null) {
251: children.addAll(sequences);
252: }
253: if (views != null) {
254: children.addAll(views);
255: }
256: ObjectArray rights = database.getAllRights();
257: for (int i = 0; i < rights.size(); i++) {
258: Right right = (Right) rights.get(i);
259: if (right.getGrantedTable() == this ) {
260: children.add(right);
261: }
262: }
263: return children;
264: }
265:
266: protected void setColumns(Column[] columns) throws SQLException {
267: this .columns = columns;
268: if (columnMap.size() > 0) {
269: columnMap.clear();
270: }
271: int memory = 0;
272: for (int i = 0; i < columns.length; i++) {
273: Column col = columns[i];
274: int dataType = col.getType();
275: if (dataType == Value.UNKNOWN) {
276: throw Message.getSQLException(
277: ErrorCode.UNKNOWN_DATA_TYPE_1, col.getSQL());
278: }
279: memory += DataType.getDataType(dataType).memory;
280: col.setTable(this , i);
281: String columnName = col.getName();
282: if (columnMap.get(columnName) != null) {
283: throw Message.getSQLException(
284: ErrorCode.DUPLICATE_COLUMN_NAME_1, columnName);
285: }
286: columnMap.put(columnName, col);
287: }
288: memoryPerRow = memory;
289: }
290:
291: public void renameColumn(Column column, String newName)
292: throws SQLException {
293: for (int i = 0; i < columns.length; i++) {
294: Column c = columns[i];
295: if (c == column) {
296: continue;
297: }
298: if (c.getName().equals(newName)) {
299: throw Message.getSQLException(
300: ErrorCode.DUPLICATE_COLUMN_NAME_1, newName);
301: }
302: }
303: columnMap.remove(column.getName());
304: column.rename(newName);
305: columnMap.put(newName, column);
306: }
307:
308: public boolean isLockExclusive(Session s) {
309: return false;
310: }
311:
312: public void updateRows(Prepared prepared, Session session,
313: RowList rows) throws SQLException {
314: // remove the old rows
315: for (rows.reset(); rows.hasNext();) {
316: prepared.checkCancelled();
317: Row o = rows.next();
318: rows.next();
319: removeRow(session, o);
320: session.log(this , UndoLogRecord.DELETE, o);
321: }
322: // add the new rows
323: for (rows.reset(); rows.hasNext();) {
324: prepared.checkCancelled();
325: rows.next();
326: Row n = rows.next();
327: addRow(session, n);
328: session.log(this , UndoLogRecord.INSERT, n);
329: }
330: }
331:
332: public void removeChildrenAndResources(Session session)
333: throws SQLException {
334: while (views != null && views.size() > 0) {
335: TableView view = (TableView) views.get(0);
336: views.remove(0);
337: database.removeSchemaObject(session, view);
338: }
339: while (triggers != null && triggers.size() > 0) {
340: TriggerObject trigger = (TriggerObject) triggers.get(0);
341: triggers.remove(0);
342: database.removeSchemaObject(session, trigger);
343: }
344: while (constraints != null && constraints.size() > 0) {
345: Constraint constraint = (Constraint) constraints.get(0);
346: constraints.remove(0);
347: database.removeSchemaObject(session, constraint);
348: }
349: ObjectArray rights = database.getAllRights();
350: for (int i = 0; i < rights.size(); i++) {
351: Right right = (Right) rights.get(i);
352: if (right.getGrantedTable() == this ) {
353: database.removeDatabaseObject(session, right);
354: }
355: }
356: database.removeMeta(session, getId());
357: // must delete sequences later (in case there is a power failure
358: // before removing the table object)
359: while (sequences != null && sequences.size() > 0) {
360: Sequence sequence = (Sequence) sequences.get(0);
361: sequences.remove(0);
362: if (!getTemporary()) {
363: database.removeSchemaObject(session, sequence);
364: }
365: }
366: }
367:
368: public void checkColumnIsNotReferenced(Column col)
369: throws SQLException {
370: for (int i = 0; constraints != null && i < constraints.size(); i++) {
371: Constraint constraint = (Constraint) constraints.get(i);
372: if (constraint.containsColumn(col)) {
373: throw Message.getSQLException(
374: ErrorCode.COLUMN_MAY_BE_REFERENCED_1,
375: constraint.getSQL());
376: }
377: }
378: ObjectArray indexes = getIndexes();
379: for (int i = 0; indexes != null && i < indexes.size(); i++) {
380: Index index = (Index) indexes.get(i);
381: if (index.getColumns().length == 1) {
382: continue;
383: }
384: if (index.getCreateSQL() == null) {
385: continue;
386: }
387: if (index.getColumnIndex(col) >= 0) {
388: throw Message.getSQLException(
389: ErrorCode.COLUMN_MAY_BE_REFERENCED_1, index
390: .getSQL());
391: }
392: }
393: }
394:
395: public Row getTemplateRow() {
396: return new Row(new Value[columns.length], memoryPerRow);
397: }
398:
399: public SearchRow getTemplateSimpleRow(boolean singleColumn) {
400: if (singleColumn) {
401: return new SimpleRowValue(columns.length);
402: } else {
403: return new SimpleRow(new Value[columns.length]);
404: }
405: }
406:
407: public Row getNullRow() {
408: synchronized (this ) {
409: if (nullRow == null) {
410: nullRow = new Row(new Value[columns.length], 0);
411: for (int i = 0; i < columns.length; i++) {
412: nullRow.setValue(i, ValueNull.INSTANCE);
413: }
414: }
415: return nullRow;
416: }
417: }
418:
419: public Column[] getColumns() {
420: return columns;
421: }
422:
423: public int getType() {
424: return DbObject.TABLE_OR_VIEW;
425: }
426:
427: public Column getColumn(int index) {
428: return columns[index];
429: }
430:
431: public Column getColumn(String columnName) throws SQLException {
432: Column column = (Column) columnMap.get(columnName);
433: if (column == null) {
434: throw Message.getSQLException(ErrorCode.COLUMN_NOT_FOUND_1,
435: columnName);
436: }
437: return column;
438: }
439:
440: /**
441: * @param masks - null means 'always false'
442: */
443: public PlanItem getBestPlanItem(Session session, int[] masks)
444: throws SQLException {
445: PlanItem item = new PlanItem();
446: item.setIndex(getScanIndex(session));
447: item.cost = item.getIndex().getCost(session, null);
448: ObjectArray indexes = getIndexes();
449: for (int i = 1; indexes != null && masks != null
450: && i < indexes.size(); i++) {
451: Index index = (Index) indexes.get(i);
452: double cost = index.getCost(session, masks);
453: if (cost < item.cost) {
454: item.cost = cost;
455: item.setIndex(index);
456: }
457: }
458: return item;
459: }
460:
461: public Index findPrimaryKey() throws SQLException {
462: ObjectArray indexes = getIndexes();
463: for (int i = 0; indexes != null && i < indexes.size(); i++) {
464: Index idx = (Index) indexes.get(i);
465: if (idx.getIndexType().isPrimaryKey()) {
466: return idx;
467: }
468: }
469: return null;
470: }
471:
472: public Index getPrimaryKey() throws SQLException {
473: Index index = findPrimaryKey();
474: if (index != null) {
475: return index;
476: }
477: throw Message.getSQLException(ErrorCode.INDEX_NOT_FOUND_1,
478: Constants.PREFIX_PRIMARY_KEY);
479: }
480:
481: public void validateConvertUpdateSequence(Session session, Row row)
482: throws SQLException {
483: for (int i = 0; i < columns.length; i++) {
484: Value value = row.getValue(i);
485: Column column = columns[i];
486: Value v2;
487: if (column.getComputed()) {
488: v2 = column.computeValue(session, row);
489: } else {
490: v2 = column.validateConvertUpdateSequence(session,
491: value);
492: }
493: if (v2 != value) {
494: row.setValue(i, v2);
495: }
496: }
497: }
498:
499: public boolean isPersistent() {
500: return persistent;
501: }
502:
503: private void remove(ObjectArray list, DbObject obj) {
504: if (list != null) {
505: int i = list.indexOf(obj);
506: if (i >= 0) {
507: list.remove(i);
508: }
509: }
510: }
511:
512: public void removeIndex(Index index) {
513: ObjectArray indexes = getIndexes();
514: if (indexes != null) {
515: remove(indexes, index);
516: if (index.getIndexType().isPrimaryKey()) {
517: Column[] cols = index.getColumns();
518: for (int i = 0; i < cols.length; i++) {
519: cols[i].setPrimaryKey(false);
520: }
521: }
522: }
523: }
524:
525: public void removeView(TableView view) {
526: remove(views, view);
527: }
528:
529: public void removeConstraint(Constraint constraint) {
530: remove(constraints, constraint);
531: }
532:
533: public void removeSequence(Session session, Sequence sequence) {
534: remove(sequences, sequence);
535: }
536:
537: public void removeTrigger(Session session, TriggerObject trigger) {
538: remove(triggers, trigger);
539: }
540:
541: public void addView(TableView view) {
542: views = add(views, view);
543: }
544:
545: public void addConstraint(Constraint constraint) {
546: if (constraints == null || constraints.indexOf(constraint) < 0) {
547: constraints = add(constraints, constraint);
548: }
549: }
550:
551: public ObjectArray getConstraints() {
552: return constraints;
553: }
554:
555: public void addSequence(Sequence sequence) {
556: sequences = add(sequences, sequence);
557: }
558:
559: public void addTrigger(TriggerObject trigger) {
560: triggers = add(triggers, trigger);
561: }
562:
563: private ObjectArray add(ObjectArray list, DbObject obj) {
564: if (list == null) {
565: list = new ObjectArray();
566: }
567: // self constraints are two entries in the list
568: // if(Database.CHECK) {
569: // if(list.indexOf(obj) >= 0) {
570: // throw Message.internal(
571: // "object already in list: " + obj.getName());
572: // }
573: // }
574: list.add(obj);
575: return list;
576: }
577:
578: public void fireBefore(Session session) throws SQLException {
579: // TODO trigger: for sql server compatibility,
580: // should send list of rows, not just 'the event'
581: fire(session, true);
582: }
583:
584: public void fireAfter(Session session) throws SQLException {
585: fire(session, false);
586: }
587:
588: private void fire(Session session, boolean beforeAction)
589: throws SQLException {
590: if (triggers != null) {
591: for (int i = 0; i < triggers.size(); i++) {
592: TriggerObject trigger = (TriggerObject) triggers.get(i);
593: trigger.fire(session, beforeAction);
594: }
595: }
596: }
597:
598: public boolean fireRow() {
599: return (constraints != null && constraints.size() > 0)
600: || (triggers != null && triggers.size() > 0);
601: }
602:
603: public void fireBeforeRow(Session session, Row oldRow, Row newRow)
604: throws SQLException {
605: fireRow(session, oldRow, newRow, true);
606: fireConstraints(session, oldRow, newRow, true);
607: }
608:
609: private void fireConstraints(Session session, Row oldRow,
610: Row newRow, boolean before) throws SQLException {
611: if (constraints != null) {
612: for (int i = 0; i < constraints.size(); i++) {
613: Constraint constraint = (Constraint) constraints.get(i);
614: if (constraint.isBefore() == before) {
615: constraint.checkRow(session, this , oldRow, newRow);
616: }
617: }
618: }
619: }
620:
621: public void fireAfterRow(Session session, Row oldRow, Row newRow)
622: throws SQLException {
623: fireRow(session, oldRow, newRow, false);
624: fireConstraints(session, oldRow, newRow, false);
625: }
626:
627: private void fireRow(Session session, Row oldRow, Row newRow,
628: boolean beforeAction) throws SQLException {
629: if (triggers != null) {
630: for (int i = 0; i < triggers.size(); i++) {
631: TriggerObject trigger = (TriggerObject) triggers.get(i);
632: trigger.fireRow(session, oldRow, newRow, beforeAction);
633: }
634: }
635: }
636:
637: public Column[] getColumns(String[] columnNames)
638: throws SQLException {
639: Column[] cols = new Column[columnNames.length];
640: for (int i = 0; i < cols.length; i++) {
641: cols[i] = getColumn(columnNames[i]);
642: }
643: return cols;
644: }
645:
646: public boolean getGlobalTemporary() {
647: return false;
648: }
649:
650: public boolean canTruncate() {
651: return false;
652: }
653:
654: public void setCheckForeignKeyConstraints(Session session,
655: boolean enabled, boolean checkExisting) throws SQLException {
656: if (enabled && checkExisting) {
657: for (int i = 0; constraints != null
658: && i < constraints.size(); i++) {
659: Constraint c = (Constraint) constraints.get(i);
660: c.checkExistingData(session);
661: }
662: }
663: checkForeignKeyConstraints = enabled;
664: }
665:
666: public boolean getCheckForeignKeyConstraints() {
667: return checkForeignKeyConstraints;
668: }
669:
670: public Index getIndexForColumn(Column column, boolean first) {
671: ObjectArray indexes = getIndexes();
672: for (int i = 1; indexes != null && i < indexes.size(); i++) {
673: Index index = (Index) indexes.get(i);
674: if (index.canGetFirstOrLast()) {
675: IndexColumn idxCol = index.getIndexColumns()[0];
676: if ((idxCol.sortType & SortOrder.DESCENDING) != 0
677: && (idxCol.sortType & SortOrder.NULLS_FIRST) == 0) {
678: // for descending sorted columns, if the NULLs
679: // are at the end, it does not work for some index types
680: continue;
681: }
682: int idx = index.getColumnIndex(column);
683: if (idx == 0) {
684: return index;
685: }
686: }
687: }
688: return null;
689: }
690:
691: public boolean isOnCommitDrop() {
692: return onCommitDrop;
693: }
694:
695: public void setOnCommitDrop(boolean onCommitDrop) {
696: this .onCommitDrop = onCommitDrop;
697: }
698:
699: public boolean isOnCommitTruncate() {
700: return onCommitTruncate;
701: }
702:
703: public void setOnCommitTruncate(boolean onCommitTruncate) {
704: this .onCommitTruncate = onCommitTruncate;
705: }
706:
707: public boolean isClustered() {
708: return false;
709: }
710:
711: /**
712: * If the index is still required by a constraint, transfer the ownership to
713: * it. Otherwise, the index is removed.
714: *
715: * @param session the session
716: * @param index the index that is no longer required
717: */
718: public void removeIndexOrTransferOwnership(Session session,
719: Index index) throws SQLException {
720: boolean stillNeeded = false;
721: for (int i = 0; constraints != null && i < constraints.size(); i++) {
722: Constraint cons = (Constraint) constraints.get(i);
723: if (cons.usesIndex(index)) {
724: cons.setIndexOwner(index);
725: database.update(session, cons);
726: stillNeeded = true;
727: }
728: }
729: if (!stillNeeded) {
730: database.removeSchemaObject(session, index);
731: }
732: }
733:
734: }
|