/* Copyright (c) 2001-2009, The HSQL Development Group
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the HSQL Development Group nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.hsqldb;
import org.hsqldb.HsqlNameManager.HsqlName;
import org.hsqldb.index.Index;
import org.hsqldb.index.IndexAVL;
import org.hsqldb.lib.ArrayUtil;
import org.hsqldb.navigator.RowIterator;
import org.hsqldb.persist.PersistentStore;
import org.hsqldb.types.Type;
/**
* The base of all HSQLDB table implementations.
*
* @author Fred Toussi (fredt@users dot sourceforge.net)
* @version 1.9.0
* @since 1.7.2
*/
public class TableBase {
// types of table
public static final int SYSTEM_TABLE = 0;
public static final int SYSTEM_SUBQUERY = 1;
public static final int TEMP_TABLE = 2;
public static final int MEMORY_TABLE = 3;
public static final int CACHED_TABLE = 4;
public static final int TEMP_TEXT_TABLE = 5;
public static final int TEXT_TABLE = 6;
public static final int VIEW_TABLE = 7;
public static final int RESULT_TABLE = 8;
public static final int TRANSITION_TABLE = 9;
public static final int FUNCTION_TABLE = 10;
//
public static final int SCOPE_STATEMENT = 11;
public static final int SCOPE_TRANSACTION = 12;
public static final int SCOPE_SESSION = 13;
public static final int SCOPE_FULL = 14;
//
public static final int COLUMNS_UNREFERENCED = 15;
public static final int COLUMNS_REFERENCED = 16;
//
public PersistentStore store;
public int persistenceScope;
public long persistenceId;
// columns in table
int[] primaryKeyCols; // column numbers for primary key
Type[] primaryKeyTypes;
int[] primaryKeyColsSequence; // {0,1,2,...}
//
//
Index[] indexList; // first index is the primary key index
public Database database;
int[] bestRowIdentifierCols; // column set for best index
boolean bestRowIdentifierStrict; // true if it has no nullable column
int[] bestIndexForColumn; // index of the 'best' index for each column
Index bestIndex; // the best index overall - null if there is no user-defined index
Index fullIndex; // index on all columns
boolean[] colNotNull; // nullability
Type[] colTypes; // types of columns
protected int columnCount;
//
int tableType;
protected boolean isReadOnly;
protected boolean isTemp;
protected boolean isCached;
protected boolean isText;
boolean isView;
public boolean isSessionBased;
protected boolean isSchemaBased;
protected boolean isLogged;
private boolean isTransactional = true;
boolean hasLobColumn;
//
TableBase() {}
//
public TableBase(Database database, int scope, int type, Type[] colTypes) {
tableType = type;
persistenceScope = scope;
isSessionBased = true;
persistenceId = database.persistentStoreCollection.getNextId();
this.database = database;
this.colTypes = colTypes;
columnCount = colTypes.length;
primaryKeyCols = new int[]{};
primaryKeyTypes = new Type[]{};
indexList = new Index[0];
createPrimaryIndex(primaryKeyCols, primaryKeyTypes, null);
}
public TableBase duplicate() {
TableBase copy = new TableBase();
copy.tableType = tableType;
copy.persistenceScope = persistenceScope;
copy.isSessionBased = isSessionBased;
copy.persistenceId = database.persistentStoreCollection.getNextId();
copy.database = database;
copy.colTypes = colTypes;
copy.columnCount = colTypes.length;
copy.primaryKeyCols = primaryKeyCols;
copy.primaryKeyTypes = primaryKeyTypes;
copy.indexList = indexList;
return copy;
}
public final int getTableType() {
return tableType;
}
public long getPersistenceId() {
return persistenceId;
}
int getId() {
return 0;
}
public final boolean onCommitPreserve() {
return persistenceScope == TableBase.SCOPE_SESSION;
}
public final RowIterator rowIterator(Session session) {
PersistentStore store = session.sessionData.getRowStore(this);
return getPrimaryIndex().firstRow(store);
}
public final RowIterator rowIterator(PersistentStore store) {
return getPrimaryIndex().firstRow(store);
}
public final int getIndexCount() {
return indexList.length;
}
public final Index getPrimaryIndex() {
return indexList[0];
}
public final Type[] getPrimaryKeyTypes() {
return primaryKeyTypes;
}
public final boolean hasPrimaryKey() {
return !(primaryKeyCols.length == 0);
}
public final int[] getPrimaryKey() {
return primaryKeyCols;
}
/**
* Returns an array of Type indicating the SQL type of the columns
*/
public final Type[] getColumnTypes() {
return colTypes;
}
/**
* Returns an index on all the columns
*/
public Index getFullIndex() {
return fullIndex;
}
/**
* Returns the Index object at the given index
*/
public final Index getIndex(int i) {
return indexList[i];
}
public Index[] getIndexes() {
return indexList;
}
/**
* Returns the indexes
*/
public final Index[] getIndexList() {
return indexList;
}
/**
* Returns empty boolean array.
*/
public final boolean[] getNewColumnCheckList() {
return new boolean[getColumnCount()];
}
/**
* Returns the count of all visible columns.
*/
public int getColumnCount() {
return columnCount;
}
/**
* Returns the count of all columns.
*/
public final int getDataColumnCount() {
return colTypes.length;
}
public boolean isTransactional() {
return isTransactional;
}
public void setTransactional(boolean value) {
isTransactional = value;
}
/**
* This method is called whenever there is a change to table structure and
* serves two porposes: (a) to reset the best set of columns that identify
* the rows of the table (b) to reset the best index that can be used
* to find rows of the table given a column value.
*
* (a) gives most weight to a primary key index, followed by a unique
* address with the lowest count of nullable columns. Otherwise there is
* no best row identifier.
*
* (b) finds for each column an index with a corresponding first column.
* It uses any type of visible index and accepts the one with the largest
* column count.
*
* bestIndex is the user defined, primary key, the first unique index, or
* the first non-unique index. NULL if there is no user-defined index.
*
*/
public final void setBestRowIdentifiers() {
int[] briCols = null;
int briColsCount = 0;
boolean isStrict = false;
int nNullCount = 0;
// ignore if called prior to completion of primary key construction
if (colNotNull == null) {
return;
}
bestIndex = null;
bestIndexForColumn = new int[colTypes.length];
ArrayUtil.fillArray(bestIndexForColumn, -1);
for (int i = 0; i < indexList.length; i++) {
Index index = indexList[i];
int[] cols = index.getColumns();
int colsCount = index.getVisibleColumns();
if (colsCount == 0) {
continue;
}
if (i == 0) {
isStrict = true;
}
if (bestIndexForColumn[cols[0]] == -1) {
bestIndexForColumn[cols[0]] = i;
} else {
Index existing = indexList[bestIndexForColumn[cols[0]]];
if (colsCount > existing.getColumns().length) {
bestIndexForColumn[cols[0]] = i;
}
}
if (!index.isUnique()) {
if (bestIndex == null) {
bestIndex = index;
}
continue;
}
int nnullc = 0;
for (int j = 0; j < colsCount; j++) {
if (colNotNull[cols[j]]) {
nnullc++;
}
}
if (bestIndex != null) {
bestIndex = index;
}
if (nnullc == colsCount) {
if (briCols == null || briColsCount != nNullCount
|| colsCount < briColsCount) {
// nothing found before ||
// found but has null columns ||
// found but has more columns than this index
briCols = cols;
briColsCount = colsCount;
nNullCount = colsCount;
isStrict = true;
}
continue;
} else if (isStrict) {
continue;
} else if (briCols == null || colsCount < briColsCount
|| nnullc > nNullCount) {
// nothing found before ||
// found but has more columns than this index||
// found but has fewer not null columns than this index
briCols = cols;
briColsCount = colsCount;
nNullCount = nnullc;
}
}
// remove rowID column from bestRowIdentiferCols
bestRowIdentifierCols = briCols == null
|| briColsCount == briCols.length ? briCols
: ArrayUtil
.arraySlice(briCols,
0, briColsCount);
bestRowIdentifierStrict = isStrict;
if (indexList[0].getColumnCount() > 0) {
bestIndex = indexList[0];
}
}
public final void createPrimaryIndex(int[] pkcols, Type[] pktypes,
HsqlName name) {
long id = database.persistentStoreCollection.getNextId();
Index newindex = new IndexAVL(name, id, this, pkcols, null, null,
pktypes, true, true, true, false);
try {
addIndex(newindex);
} catch (HsqlException e) {}
}
public final Index createAndAddIndexStructure(HsqlName name,
int[] columns, boolean[] descending, boolean[] nullsLast,
boolean unique, boolean constraint, boolean forward) {
Index newindex = createIndexStructure(name, columns, descending,
nullsLast, unique, constraint,
forward);
addIndex(newindex);
return newindex;
}
final Index createIndexStructure(HsqlName name, int[] columns,
boolean[] descending,
boolean[] nullsLast, boolean unique,
boolean constraint, boolean forward) {
if (primaryKeyCols == null) {
throw Error.runtimeError(ErrorCode.U_S0500, "createIndex");
}
int s = columns.length;
int[] cols = new int[s];
Type[] types = new Type[s];
for (int j = 0; j < s; j++) {
cols[j] = columns[j];
types[j] = colTypes[cols[j]];
}
long id = database.persistentStoreCollection.getNextId();
Index newIndex = new IndexAVL(name, id, this, cols, descending,
nullsLast, types, false, unique,
constraint, forward);
return newIndex;
}
final void addIndex(Index index) {
int i = 0;
for (; i < indexList.length; i++) {
Index current = indexList[i];
int order = index.getIndexOrderValue()
- current.getIndexOrderValue();
if (order < 0) {
break;
}
}
indexList = (Index[]) ArrayUtil.toAdjustedArray(indexList, index, i,
1);
for (i = 0; i < indexList.length; i++) {
indexList[i].setPosition(i);
}
if (store != null) {
try {
store.resetAccessorKeys(indexList);
} catch (HsqlException e) {
indexList = (Index[]) ArrayUtil.toAdjustedArray(indexList,
null, index.getPosition(), -1);
for (i = 0; i < indexList.length; i++) {
indexList[i].setPosition(i);
}
throw e;
}
}
setBestRowIdentifiers();
}
final void removeIndex(int position) {
setBestRowIdentifiers();
}
public final Object[] getEmptyRowData() {
return new Object[getDataColumnCount()];
}
/**
* Create new memory-resident index. For MEMORY and TEXT tables.
*/
public final Index createIndex(PersistentStore store, HsqlName name,
int[] columns, boolean[] descending,
boolean[] nullsLast, boolean unique,
boolean constraint, boolean forward) {
Index newIndex = createAndAddIndexStructure(name, columns, descending,
nullsLast, unique, constraint, forward);
return newIndex;
}
public void clearAllData(Session session) {
PersistentStore store = session.sessionData.getRowStore(this);
store.removeAll();
}
public void clearAllData(PersistentStore store) {
store.removeAll();
}
/**
* @todo - this is wrong, as it returns true when table has no rows,
* but not where it has rows that are not visible by session
* Returns true if the table has any rows at all.
*/
public final boolean isEmpty(Session session) {
if (getIndexCount() == 0) {
return true;
}
PersistentStore store = session.sessionData.getRowStore(this);
return getIndex(0).isEmpty(store);
}
public int getRowCount(PersistentStore store) {
return getPrimaryIndex().size(store);
}
}