/* * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, * and the EPL 1.0 (http://h2database.com/html/license.html). * Initial Developer: H2 Group */ package org.h2.table; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Set; import org.h2.api.ErrorCode; import org.h2.command.Prepared; import org.h2.constraint.Constraint; import org.h2.engine.Constants; import org.h2.engine.DbObject; import org.h2.engine.Right; import org.h2.engine.Session; import org.h2.engine.UndoLogRecord; import org.h2.expression.Expression; import org.h2.expression.ExpressionVisitor; import org.h2.index.Index; import org.h2.index.IndexType; import org.h2.message.DbException; import org.h2.message.Trace; import org.h2.result.Row; import org.h2.result.RowList; import org.h2.result.SearchRow; import org.h2.result.SimpleRow; import org.h2.result.SimpleRowValue; import org.h2.result.SortOrder; import org.h2.schema.Schema; import org.h2.schema.SchemaObjectBase; import org.h2.schema.Sequence; import org.h2.schema.TriggerObject; import org.h2.util.New; import org.h2.value.CompareMode; import org.h2.value.Value; import org.h2.value.ValueNull; /** * This is the base class for most tables. * A table contains a list of columns and a list of rows. */ //7个子类 //FunctionTable //MetaTable //RangeTable //MVTable //RegularTable //TableLink //TableView public abstract class Table extends SchemaObjectBase { /** * The table type that means this table is a regular persistent table. */ public static final int TYPE_CACHED = 0; /** * The table type that means this table is a regular persistent table. */ public static final int TYPE_MEMORY = 1; /** * The columns of this table. */ protected Column[] columns; /** * The compare mode used for this table. */ protected CompareMode compareMode; /** * Protected tables are not listed in the meta data and are excluded when * using the SCRIPT command. */ protected boolean isHidden; private final HashMap<String, Column> columnMap; private final boolean persistIndexes; private final boolean persistData; private ArrayList<TriggerObject> triggers; private ArrayList<Constraint> constraints; private ArrayList<Sequence> sequences; private ArrayList<TableView> views; private boolean checkForeignKeyConstraints = true; private boolean onCommitDrop, onCommitTruncate; private volatile Row nullRow; public Table(Schema schema, int id, String name, boolean persistIndexes, boolean persistData) { columnMap = schema.getDatabase().newStringMap(); initSchemaObjectBase(schema, id, name, Trace.TABLE); this.persistIndexes = persistIndexes; this.persistData = persistData; compareMode = schema.getDatabase().getCompareMode(); } @Override public void rename(String newName) { super.rename(newName); if (constraints != null) { for (int i = 0, size = constraints.size(); i < size; i++) { Constraint constraint = constraints.get(i); constraint.rebuild(); } } } public boolean isView() { return false; } /** * Lock the table for the given session. * This method waits until the lock is granted. * * @param session the session * @param exclusive true for write locks, false for read locks * @param forceLockEvenInMvcc lock even in the MVCC mode * @return true if the table was already exclusively locked by this session. * @throws DbException if a lock timeout occurred */ public abstract boolean lock(Session session, boolean exclusive, boolean forceLockEvenInMvcc); /** * Close the table object and flush changes. * * @param session the session */ public abstract void close(Session session); /** * Release the lock for this session. * * @param s the session */ public abstract void unlock(Session s); /** * Create an index for this table * * @param session the session * @param indexName the name of the index * @param indexId the id * @param cols the index columns * @param indexType the index type * @param create whether this is a new index * @param indexComment the comment * @return the index */ public abstract Index addIndex(Session session, String indexName, int indexId, IndexColumn[] cols, IndexType indexType, boolean create, String indexComment); /** * Get the given row. * * @param session the session * @param key the primary key * @return the row */ public Row getRow(Session session, long key) { return null; } /** * Remove a row from the table and all indexes. * * @param session the session * @param row the row */ public abstract void removeRow(Session session, Row row); /** * Remove all rows from the table and indexes. * * @param session the session */ public abstract void truncate(Session session); /** * Add a row to the table and all indexes. * * @param session the session * @param row the row * @throws DbException if a constraint was violated */ public abstract void addRow(Session session, Row row); /** * Commit an operation (when using multi-version concurrency). * * @param operation the operation * @param row the row */ public void commit(short operation, Row row) { // nothing to do } /** * Check if this table supports ALTER TABLE. * * @throws DbException if it is not supported */ public abstract void checkSupportAlter(); //只有MVTable和RegularTable支持 /** * Get the table type name * * @return the table type name */ public abstract TableType getTableType(); /** * Get the scan index to iterate through all rows. * * @param session the session * @return the index */ public abstract Index getScanIndex(Session session); /** * Get the scan index for this table. * * @param session the session * @param masks the search mask * @param filters the table filters * @param filter the filter index * @param sortOrder the sort order * @param allColumnsSet all columns * @return the scan index */ public Index getScanIndex(Session session, int[] masks, TableFilter[] filters, int filter, SortOrder sortOrder, HashSet<Column> allColumnsSet) { return getScanIndex(session); } /** * Get any unique index for this table if one exists. * * @return a unique index */ public abstract Index getUniqueIndex(); /** * Get all indexes for this table. * * @return the list of indexes */ public abstract ArrayList<Index> getIndexes(); /** * Get an index by name. * * @param indexName the index name to search for * @return the found index */ public Index getIndex(String indexName) { ArrayList<Index> indexes = getIndexes(); if (indexes != null) { for (int i = 0; i < indexes.size(); i++) { Index index = indexes.get(i); if (index.getName().equals(indexName)) { return index; } } } throw DbException.get(ErrorCode.INDEX_NOT_FOUND_1, indexName); } /** * Check if this table is locked exclusively. * * @return true if it is. */ public abstract boolean isLockedExclusively(); /** * Get the last data modification id. * * @return the modification id */ public abstract long getMaxDataModificationId(); /** * Check if the table is deterministic. * * @return true if it is */ public abstract boolean isDeterministic(); //表TableLink是false,FunctionTable、TableView要看具体情况,其他子类返回true /** * Check if the row count can be retrieved quickly. * * @return true if it can */ public abstract boolean canGetRowCount(); /** * Check if this table can be referenced. * * @return true if it can */ public boolean canReference() { return true; } /** * Check if this table can be dropped. * * @return true if it can */ public abstract boolean canDrop(); /** * Get the row count for this table. * * @param session the session * @return the row count */ public abstract long getRowCount(Session session); /** * Get the approximated row count for this table. * * @return the approximated row count */ public abstract long getRowCountApproximation(); public abstract long getDiskSpaceUsed(); /** * Get the row id column if this table has one. * * @return the row id column, or null */ public Column getRowIdColumn() { return null; } @Override public String getCreateSQLForCopy(Table table, String quotedName) { //只有TableView覆盖了 throw DbException.throwInternalError(toString()); } /** * Check whether the table (or view) contains no columns that prevent index * conditions to be used. For example, a view that contains the ROWNUM() * pseudo-column prevents this. * * @return true if the table contains no query-comparable column */ public boolean isQueryComparable() { return true; } /** * Add all objects that this table depends on to the hash set. * * @param dependencies the current set of dependencies */ public void addDependencies(HashSet<DbObject> dependencies) { if (dependencies.contains(this)) { // avoid endless recursion return; } if (sequences != null) { for (Sequence s : sequences) { dependencies.add(s); } } ExpressionVisitor visitor = ExpressionVisitor.getDependenciesVisitor(dependencies); for (Column col : columns) { col.isEverything(visitor); } if (constraints != null) { for (Constraint c : constraints) { c.isEverything(visitor); } } dependencies.add(this); } //Table的children有6种: index、constraint、trigger、sequence、view、right @Override public ArrayList<DbObject> getChildren() { ArrayList<DbObject> children = New.arrayList(); ArrayList<Index> indexes = getIndexes(); if (indexes != null) { children.addAll(indexes); } if (constraints != null) { children.addAll(constraints); } if (triggers != null) { children.addAll(triggers); } if (sequences != null) { children.addAll(sequences); } if (views != null) { children.addAll(views); } ArrayList<Right> rights = database.getAllRights(); for (Right right : rights) { if (right.getGrantedObject() == this) { children.add(right); } } return children; } protected void setColumns(Column[] columns) { this.columns = columns; if (columnMap.size() > 0) { columnMap.clear(); } for (int i = 0; i < columns.length; i++) { Column col = columns[i]; int dataType = col.getType(); if (dataType == Value.UNKNOWN) { throw DbException.get(ErrorCode.UNKNOWN_DATA_TYPE_1, col.getSQL()); } col.setTable(this, i); String columnName = col.getName(); if (columnMap.get(columnName) != null) { throw DbException.get(ErrorCode.DUPLICATE_COLUMN_NAME_1, columnName); } columnMap.put(columnName, col); } } /** * Rename a column of this table. * * @param column the column to rename * @param newName the new column name */ public void renameColumn(Column column, String newName) { for (Column c : columns) { if (c == column) { continue; } if (c.getName().equals(newName)) { throw DbException.get(ErrorCode.DUPLICATE_COLUMN_NAME_1, newName); } } columnMap.remove(column.getName()); column.rename(newName); columnMap.put(newName, column); } /** * Check if the table is exclusively locked by this session. * * @param session the session * @return true if it is */ public boolean isLockedExclusivelyBy(Session session) { //只有RegularTable、MVTable覆盖此方法 return false; } /** * Update a list of rows in this table. * * @param prepared the prepared statement * @param session the session * @param rows a list of row pairs of the form old row, new row, old row, * new row,... */ public void updateRows(Prepared prepared, Session session, RowList rows) { // in case we need to undo the update Session.Savepoint rollback = session.setSavepoint(); // remove the old rows int rowScanCount = 0; for (rows.reset(); rows.hasNext();) { if ((++rowScanCount & 127) == 0) { prepared.checkCanceled(); } Row o = rows.next(); rows.next(); try { //为什么不是先记撤消日志再删除行呢?因为如果这样的话假设删除行不成功,但是日志记成功了,当rollback时又按日志做insert操作 //此时就多了一条记录了, //那假设记撤消日志失败了呢? 这个不会出现的,因为session.log中进一步调用了org.h2.engine.UndoLog.add(UndoLogRecord) //这个UndoLog.add方法的第一行就把UndoLogRecord增加到records中,只要严格确保在出现任何异常前先加入records, //那么在rollback中就能找到之前被删除的行。 removeRow(session, o); } catch (DbException e) { if (e.getErrorCode() == ErrorCode.CONCURRENT_UPDATE_1) { session.rollbackTo(rollback, false); session.startStatementWithinTransaction(); rollback = session.setSavepoint(); } throw e; } session.log(this, UndoLogRecord.DELETE, o); } //int c=0; // add the new rows for (rows.reset(); rows.hasNext();) { //if(c++>2) throw DbException.get(ErrorCode.CANNOT_DROP_2); if ((++rowScanCount & 127) == 0) { prepared.checkCanceled(); } rows.next(); Row n = rows.next(); try { addRow(session, n); } catch (DbException e) { if (e.getErrorCode() == ErrorCode.CONCURRENT_UPDATE_1) { session.rollbackTo(rollback, false); session.startStatementWithinTransaction(); rollback = session.setSavepoint(); } throw e; } session.log(this, UndoLogRecord.INSERT, n); } } public ArrayList<TableView> getViews() { return views; } @Override public void removeChildrenAndResources(Session session) { while (views != null && views.size() > 0) { TableView view = views.get(0); views.remove(0); database.removeSchemaObject(session, view); } while (triggers != null && triggers.size() > 0) { TriggerObject trigger = triggers.get(0); triggers.remove(0); database.removeSchemaObject(session, trigger); } while (constraints != null && constraints.size() > 0) { Constraint constraint = constraints.get(0); constraints.remove(0); database.removeSchemaObject(session, constraint); } for (Right right : database.getAllRights()) { if (right.getGrantedObject() == this) { database.removeDatabaseObject(session, right); } } database.removeMeta(session, getId()); // must delete sequences later (in case there is a power failure // before removing the table object) while (sequences != null && sequences.size() > 0) { Sequence sequence = sequences.get(0); sequences.remove(0); // only remove if no other table depends on this sequence // this is possible when calling ALTER TABLE ALTER COLUMN if (database.getDependentTable(sequence, this) == null) { database.removeSchemaObject(session, sequence); } } } /** * Check that these columns are not referenced by a multi-column constraint * or multi-column index. If it is, an exception is thrown. Single-column * references and indexes are dropped. * * @param session the session * @param columnsToDrop the columns to drop * @throws DbException if the column is referenced by multi-column * constraints or indexes */ public void dropMultipleColumnsConstraintsAndIndexes(Session session, ArrayList<Column> columnsToDrop) { HashSet<Constraint> constraintsToDrop = New.hashSet(); if (constraints != null) { for (Column col : columnsToDrop) { for (int i = 0, size = constraints.size(); i < size; i++) { Constraint constraint = constraints.get(i); HashSet<Column> columns = constraint.getReferencedColumns(this); if (!columns.contains(col)) { continue; } if (columns.size() == 1) { constraintsToDrop.add(constraint); } else { throw DbException.get( ErrorCode.COLUMN_IS_REFERENCED_1, constraint.getSQL()); } } } } HashSet<Index> indexesToDrop = New.hashSet(); ArrayList<Index> indexes = getIndexes(); if (indexes != null) { for (Column col : columnsToDrop) { for (int i = 0, size = indexes.size(); i < size; i++) { Index index = indexes.get(i); if (index.getCreateSQL() == null) { continue; } if (index.getColumnIndex(col) < 0) { continue; } if (index.getColumns().length == 1) { indexesToDrop.add(index); } else { throw DbException.get( ErrorCode.COLUMN_IS_REFERENCED_1, index.getSQL()); } } } } for (Constraint c : constraintsToDrop) { session.getDatabase().removeSchemaObject(session, c); } for (Index i : indexesToDrop) { // the index may already have been dropped when dropping the // constraint if (getIndexes().contains(i)) { session.getDatabase().removeSchemaObject(session, i); } } } public Row getTemplateRow() { return database.createRow(new Value[columns.length], Row.MEMORY_CALCULATE); } /** * Get a new simple row object. * * @param singleColumn if only one value need to be stored * @return the simple row object */ public SearchRow getTemplateSimpleRow(boolean singleColumn) { if (singleColumn) { return new SimpleRowValue(columns.length); } return new SimpleRow(new Value[columns.length]); } Row getNullRow() { Row row = nullRow; if (row == null) { // Here can be concurrently produced more than one row, but it must // be ok. Value[] values = new Value[columns.length]; Arrays.fill(values, ValueNull.INSTANCE); nullRow = row = database.createRow(values, 1); } return row; } public Column[] getColumns() { return columns; } @Override public int getType() { return DbObject.TABLE_OR_VIEW; } /** * Get the column at the given index. * * @param index the column index (0, 1,...) * @return the column */ public Column getColumn(int index) { return columns[index]; } /** * Get the column with the given name. * * @param columnName the column name * @return the column * @throws DbException if the column was not found */ public Column getColumn(String columnName) { Column column = columnMap.get(columnName); if (column == null) { throw DbException.get(ErrorCode.COLUMN_NOT_FOUND_1, columnName); } return column; } /** * Does the column with the given name exist? * * @param columnName the column name * @return true if the column exists */ public boolean doesColumnExist(String columnName) { return columnMap.containsKey(columnName); } /** * Get the best plan for the given search mask. * * @param session the session * @param masks per-column comparison bit masks, null means 'always false', * see constants in IndexCondition * @param filters all joined table filters * @param filter the current table filter index * @param sortOrder the sort order * @param allColumnsSet the set of all columns * @return the plan item */ public PlanItem getBestPlanItem(Session session, int[] masks, TableFilter[] filters, int filter, SortOrder sortOrder, HashSet<Column> allColumnsSet) { PlanItem item = new PlanItem(); item.setIndex(getScanIndex(session)); item.cost = item.getIndex().getCost(session, null, filters, filter, null, allColumnsSet); Trace t = session.getTrace(); if (t.isDebugEnabled()) { t.debug("Table : potential plan item cost {0} index {1}", item.cost, item.getIndex().getPlanSQL()); } ArrayList<Index> indexes = getIndexes(); IndexHints indexHints = getIndexHints(filters, filter); if (indexes != null && masks != null) { //indexes[0]是ScanIndex,所以可以跳过,从1开始 for (int i = 1, size = indexes.size(); i < size; i++) { Index index = indexes.get(i); if (isIndexExcludedByHints(indexHints, index)) { continue; } double cost = index.getCost(session, masks, filters, filter, sortOrder, allColumnsSet); if (t.isDebugEnabled()) { t.debug("Table : potential plan item cost {0} index {1}", cost, index.getPlanSQL()); } if (cost < item.cost) { item.cost = cost; item.setIndex(index); } } } return item; } private static boolean isIndexExcludedByHints(IndexHints indexHints, Index index) { return indexHints != null && !indexHints.allowIndex(index); } private static IndexHints getIndexHints(TableFilter[] filters, int filter) { return filters == null ? null : filters[filter].getIndexHints(); } /** * Get the primary key index if there is one, or null if there is none. * * @return the primary key index or null */ public Index findPrimaryKey() { ArrayList<Index> indexes = getIndexes(); if (indexes != null) { for (int i = 0, size = indexes.size(); i < size; i++) { Index idx = indexes.get(i); if (idx.getIndexType().isPrimaryKey()) { return idx; } } } return null; } public Index getPrimaryKey() { Index index = findPrimaryKey(); if (index != null) { return index; } throw DbException.get(ErrorCode.INDEX_NOT_FOUND_1, Constants.PREFIX_PRIMARY_KEY); } /** * Validate all values in this row, convert the values if required, and * update the sequence values if required. This call will also set the * default values if required and set the computed column if there are any. * * @param session the session * @param row the row */ public void validateConvertUpdateSequence(Session session, Row row) { for (int i = 0; i < columns.length; i++) { Value value = row.getValue(i); Column column = columns[i]; Value v2; if (column.getComputed()) { // force updating the value value = null; v2 = column.computeValue(session, row); } v2 = column.validateConvertUpdateSequence(session, value); if (v2 != value) { row.setValue(i, v2); } } } private static void remove(ArrayList<? extends DbObject> list, DbObject obj) { if (list != null) { int i = list.indexOf(obj); if (i >= 0) { list.remove(i); } } } /** * Remove the given index from the list. * * @param index the index to remove */ public void removeIndex(Index index) { ArrayList<Index> indexes = getIndexes(); if (indexes != null) { remove(indexes, index); if (index.getIndexType().isPrimaryKey()) { for (Column col : index.getColumns()) { col.setPrimaryKey(false); } } } } /** * Remove the given view from the list. * * @param view the view to remove */ public void removeView(TableView view) { remove(views, view); } /** * Remove the given constraint from the list. * * @param constraint the constraint to remove */ public void removeConstraint(Constraint constraint) { remove(constraints, constraint); } /** * Remove a sequence from the table. Sequences are used as identity columns. * * @param sequence the sequence to remove */ public final void removeSequence(Sequence sequence) { remove(sequences, sequence); } /** * Remove the given trigger from the list. * * @param trigger the trigger to remove */ public void removeTrigger(TriggerObject trigger) { remove(triggers, trigger); } /** * Add a view to this table. * * @param view the view to add */ public void addView(TableView view) { views = add(views, view); } /** * Add a constraint to the table. * * @param constraint the constraint to add */ public void addConstraint(Constraint constraint) { if (constraints == null || constraints.indexOf(constraint) < 0) { constraints = add(constraints, constraint); } } public ArrayList<Constraint> getConstraints() { return constraints; } /** * Add a sequence to this table. * * @param sequence the sequence to add */ public void addSequence(Sequence sequence) { sequences = add(sequences, sequence); } /** * Add a trigger to this table. * * @param trigger the trigger to add */ public void addTrigger(TriggerObject trigger) { triggers = add(triggers, trigger); } private static <T> ArrayList<T> add(ArrayList<T> list, T obj) { if (list == null) { list = New.arrayList(); } // self constraints are two entries in the list list.add(obj); return list; } /** * Fire the triggers for this table. * * @param session the session * @param type the trigger type * @param beforeAction whether 'before' triggers should be called */ public void fire(Session session, int type, boolean beforeAction) { if (triggers != null) { for (TriggerObject trigger : triggers) { trigger.fire(session, type, beforeAction); } } } /** * Check whether this table has a select trigger. * * @return true if it has */ public boolean hasSelectTrigger() { if (triggers != null) { for (TriggerObject trigger : triggers) { if (trigger.isSelectTrigger()) { return true; } } } return false; } /** * Check if row based triggers or constraints are defined. * In this case the fire after and before row methods need to be called. * * @return if there are any triggers or rows defined */ public boolean fireRow() { return (constraints != null && constraints.size() > 0) || (triggers != null && triggers.size() > 0); } /** * Fire all triggers that need to be called before a row is updated. * * @param session the session * @param oldRow the old data or null for an insert * @param newRow the new data or null for a delete * @return true if no further action is required (for 'instead of' triggers) */ public boolean fireBeforeRow(Session session, Row oldRow, Row newRow) { boolean done = fireRow(session, oldRow, newRow, true, false); fireConstraints(session, oldRow, newRow, true); return done; } private void fireConstraints(Session session, Row oldRow, Row newRow, boolean before) { if (constraints != null) { // don't use enhanced for loop to avoid creating objects for (int i = 0, size = constraints.size(); i < size; i++) { Constraint constraint = constraints.get(i); if (constraint.isBefore() == before) { constraint.checkRow(session, this, oldRow, newRow); } } } } /** * Fire all triggers that need to be called after a row is updated. * * @param session the session * @param oldRow the old data or null for an insert * @param newRow the new data or null for a delete * @param rollback when the operation occurred within a rollback */ public void fireAfterRow(Session session, Row oldRow, Row newRow, boolean rollback) { fireRow(session, oldRow, newRow, false, rollback); if (!rollback) { fireConstraints(session, oldRow, newRow, false); } } private boolean fireRow(Session session, Row oldRow, Row newRow, boolean beforeAction, boolean rollback) { if (triggers != null) { for (TriggerObject trigger : triggers) { boolean done = trigger.fireRow(session, oldRow, newRow, beforeAction, rollback); if (done) { return true; } } } return false; } public boolean isGlobalTemporary() { return false; } /** * Check if this table can be truncated. * * @return true if it can */ public boolean canTruncate() { return false; } /** * Enable or disable foreign key constraint checking for this table. * * @param session the session * @param enabled true if checking should be enabled * @param checkExisting true if existing rows must be checked during this * call */ public void setCheckForeignKeyConstraints(Session session, boolean enabled, boolean checkExisting) { if (enabled && checkExisting) { if (constraints != null) { for (Constraint c : constraints) { c.checkExistingData(session); } } } checkForeignKeyConstraints = enabled; } public boolean getCheckForeignKeyConstraints() { return checkForeignKeyConstraints; } /** * Get the index that has the given column as the first element. * This method returns null if no matching index is found. * * @param column the column * @return the index or null */ public Index getIndexForColumn(Column column) { ArrayList<Index> indexes = getIndexes(); if (indexes != null) { for (int i = 1, size = indexes.size(); i < size; i++) { Index index = indexes.get(i); if (index.canGetFirstOrLast()) { int idx = index.getColumnIndex(column); if (idx == 0) { return index; } } } } return null; } public boolean getOnCommitDrop() { return onCommitDrop; } public void setOnCommitDrop(boolean onCommitDrop) { this.onCommitDrop = onCommitDrop; } public boolean getOnCommitTruncate() { return onCommitTruncate; } public void setOnCommitTruncate(boolean onCommitTruncate) { this.onCommitTruncate = onCommitTruncate; } /** * If the index is still required by a constraint, transfer the ownership to * it. Otherwise, the index is removed. * * @param session the session * @param index the index that is no longer required */ public void removeIndexOrTransferOwnership(Session session, Index index) { boolean stillNeeded = false; if (constraints != null) { for (Constraint cons : constraints) { if (cons.usesIndex(index)) { cons.setIndexOwner(index); database.updateMeta(session, cons); stillNeeded = true; } } } if (!stillNeeded) { database.removeSchemaObject(session, index); } } /** * Check if a deadlock occurred. This method is called recursively. There is * a circle if the session to be tested has already being visited. If this * session is part of the circle (if it is the clash session), the method * must return an empty object array. Once a deadlock has been detected, the * methods must add the session to the list. If this session is not part of * the circle, or if no deadlock is detected, this method returns null. * * @param session the session to be tested for * @param clash set with sessions already visited, and null when starting * verification * @param visited set with sessions already visited, and null when starting * verification * @return an object array with the sessions involved in the deadlock, or * null */ public ArrayList<Session> checkDeadlock(Session session, Session clash, Set<Session> visited) { return null; } public boolean isPersistIndexes() { return persistIndexes; } public boolean isPersistData() { return persistData; } /** * Compare two values with the current comparison mode. The values may be of * different type. * * @param a the first value * @param b the second value * @return 0 if both values are equal, -1 if the first value is smaller, and * 1 otherwise */ public int compareTypeSafe(Value a, Value b) { if (a == b) { return 0; } int dataType = Value.getHigherOrder(a.getType(), b.getType()); a = a.convertTo(dataType); b = b.convertTo(dataType); return a.compareTypeSafe(b, compareMode); } public CompareMode getCompareMode() { return compareMode; } /** * Tests if the table can be written. Usually, this depends on the * database.checkWritingAllowed method, but some tables (eg. TableLink) * overwrite this default behaviour. */ public void checkWritingAllowed() { database.checkWritingAllowed(); } /** * Get or generate a default value for the given column. * * @param session the session * @param column the column * @return the value */ public Value getDefaultValue(Session session, Column column) { Expression defaultExpr = column.getDefaultExpression(); Value v; if (defaultExpr == null) { v = column.validateConvertUpdateSequence(session, null); } else { v = defaultExpr.getValue(session); } return column.convert(v); } @Override public boolean isHidden() { return isHidden; } public void setHidden(boolean hidden) { this.isHidden = hidden; } public boolean isMVStore() { return false; } }