/* 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); } }