/* Copyright (c) 1995-2000, The Hypersonic SQL 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 Hypersonic SQL 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 THE HYPERSONIC SQL GROUP, * 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. * * This software consists of voluntary contributions made by many individuals * on behalf of the Hypersonic SQL Group. * * * For work added by the HSQL Development Group: * * 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.index; import java.util.NoSuchElementException; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.hsqldb.Error; import org.hsqldb.ErrorCode; import org.hsqldb.HsqlNameManager; import org.hsqldb.HsqlNameManager.HsqlName; import org.hsqldb.OpTypes; import org.hsqldb.Row; import org.hsqldb.RowAVL; import org.hsqldb.SchemaObject; import org.hsqldb.Session; import org.hsqldb.Table; import org.hsqldb.TableBase; import org.hsqldb.Tokens; import org.hsqldb.lib.ArrayUtil; import org.hsqldb.lib.OrderedHashSet; import org.hsqldb.navigator.RowIterator; import org.hsqldb.persist.PersistentStore; import org.hsqldb.rights.Grantee; import org.hsqldb.types.Type; // fredt@users 20020221 - patch 513005 by sqlbob@users - corrections // fredt@users 20020225 - patch 1.7.0 - changes to support cascading deletes // tony_lai@users 20020820 - patch 595052 - better error message // fredt@users 20021205 - patch 1.7.2 - changes to method signature // fredt@users - patch 1.8.0 - reworked the interface and comparison methods // fredt@users - patch 1.8.0 - improved reliability for cached indexes // fredt@users - patch 1.9.0 - iterators and concurrency /** * Implementation of an AVL tree with parent pointers in nodes. Subclasses * of Node implement the tree node objects for memory or disk storage. An * Index has a root Node that is linked with other nodes using Java Object * references or file pointers, depending on Node implementation.<p> * An Index object also holds information on table columns (in the form of int * indexes) that are covered by it.<p> * * New class derived from Hypersonic SQL code and enhanced in HSQLDB. <p> * * @author Thomas Mueller (Hypersonic SQL Group) * @author Fred Toussi (fredt@users dot sourceforge.net) * @version 1.9.0 * @since Hypersonic SQL */ public class IndexAVL implements Index { // fields private final long persistenceId; private final HsqlName name; private final boolean[] colCheck; private final int[] colIndex; private final int[] defaultColMap; private final Type[] colTypes; private final boolean[] colDesc; private final boolean[] nullsLast; private final int[] pkCols; private final Type[] pkTypes; private final boolean isUnique; // DDL uniqueness private final boolean useRowId; private final boolean isConstraint; private final boolean isForward; private int depth; private static final IndexRowIterator emptyIterator = new IndexRowIterator(null, (PersistentStore) null, null, null); private final TableBase table; private int position; // ReadWriteLock lock = new ReentrantReadWriteLock(); Lock readLock = lock.readLock(); Lock writeLock = lock.writeLock(); // public static final Index[] emptyArray = new Index[]{}; /** * Set a node as child of another * * @param x parent node * @param isleft boolean * @param n child node * */ private static NodeAVL set(PersistentStore store, NodeAVL x, boolean isleft, NodeAVL n) { if (isleft) { x = x.setLeft(store, n); } else { x = x.setRight(store, n); } if (n != null) { n.setParent(store, x); } return x; } /** * Returns either child node * * @param x node * @param isleft boolean * * @return child node * */ private static NodeAVL child(PersistentStore store, NodeAVL x, boolean isleft) { return isleft ? x.getLeft(store) : x.getRight(store); } private static void getColumnList(Table t, int[] col, int len, StringBuffer a) { a.append('('); for (int i = 0; i < len; i++) { a.append(t.getColumn(col[i]).getName().statementName); if (i < len - 1) { a.append(','); } } a.append(')'); } /** * compares two full table rows based on a set of columns * * @param a a full row * @param b a full row * @param cols array of column indexes to compare * @param coltypes array of column types for the full row * * @return comparison result, -1,0,+1 */ public static int compareRows(Object[] a, Object[] b, int[] cols, Type[] coltypes) { int fieldcount = cols.length; for (int j = 0; j < fieldcount; j++) { int i = coltypes[cols[j]].compare(a[cols[j]], b[cols[j]]); if (i != 0) { return i; } } return 0; } /** * Constructor declaration * * @param name HsqlName of the index * @param id persistnece id * @param table table of the index * @param columns array of column indexes * @param descending boolean[] * @param nullsLast boolean[] * @param colTypes array of column types * @param unique is this a unique index * @param constraint does this index belonging to a constraint * @param forward is this an auto-index for an FK that refers to a table * defined after this table */ public IndexAVL(HsqlName name, long id, TableBase table, int[] columns, boolean[] descending, boolean[] nullsLast, Type[] colTypes, boolean pk, boolean unique, boolean constraint, boolean forward) { persistenceId = id; this.name = name; this.colIndex = columns; this.colTypes = colTypes; this.colDesc = descending == null ? new boolean[columns.length] : descending; this.nullsLast = nullsLast == null ? new boolean[columns.length] : nullsLast; isUnique = unique; isConstraint = constraint; isForward = forward; this.table = table; this.pkCols = table.getPrimaryKey(); this.pkTypes = table.getPrimaryKeyTypes(); useRowId = (!isUnique && pkCols.length == 0) || (colIndex.length == 0); colCheck = table.getNewColumnCheckList(); ArrayUtil.intIndexesToBooleanArray(colIndex, colCheck); defaultColMap = new int[columns.length]; ArrayUtil.fillSequence(defaultColMap); } // SchemaObject implementation public int getType() { return SchemaObject.INDEX; } public HsqlName getName() { return name; } public HsqlName getCatalogName() { return name.schema.schema; } public HsqlName getSchemaName() { return name.schema; } public Grantee getOwner() { return name.schema.owner; } public OrderedHashSet getReferences() { return new OrderedHashSet(); } public OrderedHashSet getComponents() { return null; } public void compile(Session session) {} public String getSQL() { StringBuffer sb = new StringBuffer(); sb = new StringBuffer(64); sb.append(Tokens.T_CREATE).append(' '); if (isUnique()) { sb.append(Tokens.T_UNIQUE).append(' '); } sb.append(Tokens.T_INDEX).append(' '); sb.append(getName().statementName); sb.append(' ').append(Tokens.T_ON).append(' '); sb.append(((Table) table).getName().getSchemaQualifiedStatementName()); int[] col = getColumns(); int len = getVisibleColumns(); getColumnList(((Table) table), col, len, sb); return sb.toString(); } // IndexInterface public RowIterator emptyIterator() { return emptyIterator; } public int getPosition() { return position; } public void setPosition(int position) { this.position = position; } public long getPersistenceId() { return persistenceId; } /** * Returns the count of visible columns used */ public int getVisibleColumns() { return colIndex.length; } /** * Returns the count of visible columns used */ public int getColumnCount() { return colIndex.length; } /** * Is this a UNIQUE index? */ public boolean isUnique() { return isUnique; } /** * Does this index belong to a constraint? */ public boolean isConstraint() { return isConstraint; } /** * Returns the array containing column indexes for index */ public int[] getColumns() { return colIndex; } /** * Returns the array containing column indexes for index */ public Type[] getColumnTypes() { return colTypes; } public boolean[] getColumnDesc() { return colDesc; } /** * Returns a value indicating the order of different types of index in * the list of indexes for a table. The position of the groups of Indexes * in the list in ascending order is as follows: * * primary key index * unique constraint indexes * autogenerated foreign key indexes for FK's that reference this table or * tables created before this table * user created indexes (CREATE INDEX) * autogenerated foreign key indexes for FK's that reference tables created * after this table * * Among a group of indexes, the order is based on the order of creation * of the index. * * @return ordinal value */ public int getIndexOrderValue() { if (isConstraint) { return isForward ? 4 : isUnique ? 0 : 1; } else { return 2; } } public boolean isForward() { return isForward; } /** * Returns the node count. */ public int size(PersistentStore store) { int count = 0; readLock.lock(); try { RowIterator it = firstRow(null, store); while (it.hasNext()) { it.getNextRow(); count++; } return count; } finally { readLock.unlock(); } } public int sizeEstimate(PersistentStore store) { return (int) (1L << depth); } public boolean isEmpty(PersistentStore store) { readLock.lock(); try { return getAccessor(store) == null; } finally { readLock.unlock(); } } public void checkIndex(PersistentStore store) { readLock.lock(); try { NodeAVL p = getAccessor(store); NodeAVL f = null; while (p != null) { f = p; checkNodes(store, p); p = p.getLeft(store); } p = f; while (f != null) { checkNodes(store, f); f = next(store, f); } } finally { readLock.unlock(); } } private void checkNodes(PersistentStore store, NodeAVL p) { NodeAVL l = p.getLeft(store); NodeAVL r = p.getRight(store); if (l != null && l.getBalance() == -2) { System.out.print("broken index - deleted"); } if (r != null && r.getBalance() == -2) { System.out.print("broken index -deleted"); } if (l != null && !p.equals(l.getParent(store))) { System.out.print("broken index - no parent"); } if (r != null && !p.equals(r.getParent(store))) { System.out.print("broken index - no parent"); } } /** * Insert a node into the index */ public void insert(Session session, PersistentStore store, Row row) { NodeAVL n; NodeAVL x; boolean isleft = true; int compare = -1; writeLock.lock(); try { n = getAccessor(store); x = n; if (n == null) { store.setAccessor(this, ((RowAVL) row).getNode(position)); return; } while (true) { Row currentRow = n.getRow(store); compare = compareRowForInsertOrDelete(session, row, currentRow); if (compare == 0) { throw Error.error(ErrorCode.X_23505); } isleft = compare < 0; x = n; n = child(store, x, isleft); if (n == null) { break; } } x = set(store, x, isleft, ((RowAVL) row).getNode(position)); balance(store, x, isleft); } finally { writeLock.unlock(); } } public void delete(PersistentStore store, Row row) { if (!row.isInMemory()) { row = (Row) store.get(row, false); } NodeAVL node = ((RowAVL) row).getNode(position); delete(store, node); } public void delete(PersistentStore store, NodeAVL x) { if (x == null) { return; } NodeAVL n; writeLock.lock(); try { if (x.getLeft(store) == null) { n = x.getRight(store); } else if (x.getRight(store) == null) { n = x.getLeft(store); } else { NodeAVL d = x; x = x.getLeft(store); while (true) { NodeAVL temp = x.getRight(store); if (temp == null) { break; } x = temp; } // x will be replaced with n later n = x.getLeft(store); // swap d and x int b = x.getBalance(); x = x.setBalance(store, d.getBalance()); d = d.setBalance(store, b); // set x.parent NodeAVL xp = x.getParent(store); NodeAVL dp = d.getParent(store); if (d.isRoot()) { store.setAccessor(this, x); } x = x.setParent(store, dp); if (dp != null) { if (dp.isRight(d)) { dp = dp.setRight(store, x); } else { dp = dp.setLeft(store, x); } } // relink d.parent, x.left, x.right if (d.equals(xp)) { d = d.setParent(store, x); if (d.isLeft(x)) { x = x.setLeft(store, d); NodeAVL dr = d.getRight(store); x = x.setRight(store, dr); } else { x = x.setRight(store, d); NodeAVL dl = d.getLeft(store); x = x.setLeft(store, dl); } } else { d = d.setParent(store, xp); xp = xp.setRight(store, d); NodeAVL dl = d.getLeft(store); NodeAVL dr = d.getRight(store); x = x.setLeft(store, dl); x = x.setRight(store, dr); } // apprently no-ops x.getRight(store).setParent(store, x); x.getLeft(store).setParent(store, x); // set d.left, d.right d = d.setLeft(store, n); if (n != null) { n = n.setParent(store, d); } d = d.setRight(store, null); x = d; } boolean isleft = x.isFromLeft(store); replace(store, x, n); n = x.getParent(store); x.delete(); while (n != null) { x = n; int sign = isleft ? 1 : -1; switch (x.getBalance() * sign) { case -1 : x = x.setBalance(store, 0); break; case 0 : x = x.setBalance(store, sign); return; case 1 : NodeAVL r = child(store, x, !isleft); int b = r.getBalance(); if (b * sign >= 0) { replace(store, x, r); x = set(store, x, !isleft, child(store, r, isleft)); r = set(store, r, isleft, x); if (b == 0) { x = x.setBalance(store, sign); r = r.setBalance(store, -sign); return; } x = x.setBalance(store, 0); r = r.setBalance(store, 0); x = r; } else { NodeAVL l = child(store, r, isleft); replace(store, x, l); b = l.getBalance(); r = set(store, r, isleft, child(store, l, !isleft)); l = set(store, l, !isleft, r); x = set(store, x, !isleft, child(store, l, isleft)); l = set(store, l, isleft, x); x = x.setBalance(store, (b == sign) ? -sign : 0); r = r.setBalance(store, (b == -sign) ? sign : 0); l = l.setBalance(store, 0); x = l; } } isleft = x.isFromLeft(store); n = x.getParent(store); } } finally { writeLock.unlock(); } } public boolean exists(Session session, PersistentStore store, Object[] rowdata, int[] rowColMap) { return findNode(session, store, rowdata, rowColMap, rowColMap.length) != null; } /** * Return the first node equal to the indexdata object. The rowdata has * the same column mapping as this index. * * @param session session object * @param store store object * @param rowdata array containing index column data * @param match count of columns to match * @return iterator */ public RowIterator findFirstRow(Session session, PersistentStore store, Object[] rowdata, int match) { NodeAVL node = findNode(session, store, rowdata, defaultColMap, match); return getIterator(session, store, node); } /** * Return the first node equal to the rowdata object. * The rowdata has the same column mapping as this table. * * @param session session object * @param store store object * @param rowdata array containing table row data * @return iterator */ public RowIterator findFirstRow(Session session, PersistentStore store, Object[] rowdata) { NodeAVL node = findNode(session, store, rowdata, colIndex, colIndex.length); return getIterator(session, store, node); } /** * Return the first node equal to the rowdata object. * The rowdata has the column mapping privided in rowColMap. * * @param session session object * @param store store object * @param rowdata array containing table row data * @return iterator */ public RowIterator findFirstRow(Session session, PersistentStore store, Object[] rowdata, int[] rowColMap) { NodeAVL node = findNode(session, store, rowdata, rowColMap, rowColMap.length); return getIterator(session, store, node); } /** * Finds the first node that is larger or equal to the given one based * on the first column of the index only. * * @param session session object * @param store store object * @param value value to match * @param compare comparison Expression type * * @return iterator */ public RowIterator findFirstRow(Session session, PersistentStore store, Object value, int compare) { readLock.lock(); try { if (compare == OpTypes.SMALLER || compare == OpTypes.SMALLER_EQUAL) { return findFirstRowNotNull(session, store); } boolean isEqual = compare == OpTypes.EQUAL || compare == OpTypes.IS_NULL; NodeAVL x = getAccessor(store); int iTest = 1; if (compare == OpTypes.GREATER) { iTest = 0; } if (value == null && !isEqual) { return emptyIterator; } // this method returns the correct node only with the following conditions boolean check = compare == OpTypes.GREATER || compare == OpTypes.EQUAL || compare == OpTypes.GREATER_EQUAL; if (!check) { Error.runtimeError(ErrorCode.U_S0500, "Index.findFirst"); } while (x != null) { boolean t = colTypes[0].compare( value, x.getRow(store).getData()[colIndex[0]]) >= iTest; if (t) { NodeAVL r = x.getRight(store); if (r == null) { break; } x = r; } else { NodeAVL l = x.getLeft(store); if (l == null) { break; } x = l; } } /* while (x != null && Column.compare(value, x.getData()[colIndex_0], colType_0) >= iTest) { x = next(x); } */ while (x != null) { Object colvalue = x.getRow(store).getData()[colIndex[0]]; int result = colTypes[0].compare(value, colvalue); if (result >= iTest) { x = next(store, x); } else { if (isEqual) { if (result != 0) { x = null; } } else if (colvalue == null) { x = next(store, x); continue; } break; } } // MVCC if (session == null || x == null) { return getIterator(session, store, x); } while (x != null) { Row row = x.getRow(store); if (compare == OpTypes.EQUAL && colTypes[0].compare( value, row.getData()[colIndex[0]]) != 0) { x = null; break; } if (session.database.txManager.canRead(session, row)) { break; } x = next(store, x); } return getIterator(session, store, x); } finally { readLock.unlock(); } } /** * Finds the first node where the data is not null. * * @return iterator */ public RowIterator findFirstRowNotNull(Session session, PersistentStore store) { readLock.lock(); try { NodeAVL x = getAccessor(store); while (x != null) { boolean t = colTypes[0].compare( null, x.getRow(store).getData()[colIndex[0]]) >= 0; if (t) { NodeAVL r = x.getRight(store); if (r == null) { break; } x = r; } else { NodeAVL l = x.getLeft(store); if (l == null) { break; } x = l; } } while (x != null) { Object colvalue = x.getRow(store).getData()[colIndex[0]]; if (colvalue == null) { x = next(store, x); } else { break; } } // MVCC while (session != null && x != null) { Row row = x.getRow(store); if (session.database.txManager.canRead(session, row)) { break; } x = next(store, x); } return getIterator(session, store, x); } finally { readLock.unlock(); } } /** * Returns the row for the first node of the index * * @return Iterator for first row */ public RowIterator firstRow(Session session, PersistentStore store) { int tempDepth = 0; readLock.lock(); try { NodeAVL x = getAccessor(store); NodeAVL l = x; while (l != null) { x = l; l = x.getLeft(store); tempDepth++; } while (session != null && x != null) { Row row = x.getRow(store); if (session.database.txManager.canRead(session, row)) { break; } x = next(store, x); } return getIterator(session, store, x); } finally { depth = tempDepth; readLock.unlock(); } } public RowIterator firstRow(PersistentStore store) { int tempDepth = 0; readLock.lock(); try { NodeAVL x = getAccessor(store); NodeAVL l = x; while (l != null) { x = l; l = x.getLeft(store); tempDepth++; } return getIterator(null, store, x); } finally { depth = tempDepth; readLock.unlock(); } } /** * Returns the row for the last node of the index * * @return last row */ public Row lastRow(Session session, PersistentStore store) { readLock.lock(); try { NodeAVL x = getAccessor(store); NodeAVL l = x; while (l != null) { x = l; l = x.getRight(store); } while (session != null && x != null) { Row row = x.getRow(store); if (session.database.txManager.canRead(session, row)) { break; } x = last(store, x); } return x == null ? null : x.getRow(store); } finally { readLock.unlock(); } } /** * Returns the node after the given one * * @param x node * * @return next node */ private NodeAVL next(Session session, PersistentStore store, NodeAVL x) { if (x == null) { return null; } readLock.lock(); try { while (true) { x = next(store, x); if (x == null) { return x; } if (session == null) { return x; } Row row = x.getRow(store); if (session.database.txManager.canRead(session, row)) { return x; } } } finally { readLock.unlock(); } } private NodeAVL next(PersistentStore store, NodeAVL x) { NodeAVL r = x.getRight(store); if (r != null) { x = r; NodeAVL l = x.getLeft(store); while (l != null) { x = l; l = x.getLeft(store); } return x; } NodeAVL ch = x; x = x.getParent(store); while (x != null && ch.equals(x.getRight(store))) { ch = x; x = x.getParent(store); } return x; } private NodeAVL last(PersistentStore store, NodeAVL x) { if (x == null) { return null; } readLock.lock(); try { NodeAVL left = x.getLeft(store); if (left != null) { x = left; NodeAVL right = x.getRight(store); while (right != null) { x = right; right = x.getRight(store); } return x; } NodeAVL ch = x; x = x.getParent(store); while (x != null && ch.equals(x.getLeft(store))) { ch = x; x = x.getParent(store); } return x; } finally { readLock.unlock(); } } /** * Replace x with n * * @param x node * @param n node */ private void replace(PersistentStore store, NodeAVL x, NodeAVL n) { if (x.isRoot()) { if (n != null) { n = n.setParent(store, null); } store.setAccessor(this, n); } else { set(store, x.getParent(store), x.isFromLeft(store), n); } } /** * Compares two table rows based on the columns of this index. The rowColMap * parameter specifies which columns of the other table are to be compared * with the colIndex columns of this index. The rowColMap can cover all * or only some columns of this index. * * @param a row from another table * @param rowColMap column indexes in the other table * @param b a full row in this table * * @return comparison result, -1,0,+1 */ public int compareRowNonUnique(Object[] a, int[] rowColMap, Object[] b) { int fieldcount = rowColMap.length; for (int j = 0; j < fieldcount; j++) { int i = colTypes[j].compare(a[rowColMap[j]], b[colIndex[j]]); if (i != 0) { return i; } } return 0; } public int compareRowNonUnique(Object[] a, int[] rowColMap, Object[] b, int fieldCount) { for (int j = 0; j < fieldCount; j++) { int i = colTypes[j].compare(a[rowColMap[j]], b[colIndex[j]]); if (i != 0) { return i; } } return 0; } /** * As above but use the index column data */ public int compareRowNonUnique(Object[] a, Object[] b, int fieldcount) { for (int j = 0; j < fieldcount; j++) { int i = colTypes[j].compare(a[j], b[colIndex[j]]); if (i != 0) { return i; } } return 0; } /** * Compare two rows of the table for inserting rows into unique indexes * Supports descending columns. * * @param newRow data * @param existingRow data * @return comparison result, -1,0,+1 */ private int compareRowForInsertOrDelete(Session session, Row newRow, Row existingRow) { Object[] a = newRow.getData(); Object[] b = existingRow.getData(); int j = 0; boolean hasNull = false; for (; j < colIndex.length; j++) { Object currentvalue = a[colIndex[j]]; Object othervalue = b[colIndex[j]]; int i = colTypes[j].compare(currentvalue, othervalue); boolean nulls = currentvalue == null || othervalue == null; if (i != 0) { if (colDesc[j] && !nulls) { i = -i; } if (nullsLast[j] && nulls) { i = -i; } return i; } if (currentvalue == null) { hasNull = true; } } if (isUnique && !useRowId && !hasNull) { if (session == null || session.database.txManager.canRead(session, existingRow)) { //* debug 190 // session.database.txManager.canRead(session, existingRow); return 0; } else { int difference = newRow.getPos() - existingRow.getPos(); return difference; } } for (j = 0; j < pkCols.length; j++) { Object currentvalue = a[pkCols[j]]; int i = pkTypes[j].compare(currentvalue, b[pkCols[j]]); if (i != 0) { return i; } } if (useRowId) { int difference = newRow.getPos() - existingRow.getPos(); if (difference < 0) { difference = -1; } else if (difference > 0) { difference = 1; } return difference; } if (session == null || session.database.txManager.canRead(session, existingRow)) { return 0; } else { int difference = newRow.getPos() - existingRow.getPos(); if (difference < 0) { difference = -1; } else if (difference > 0) { difference = 1; } return difference; } } /** * Finds a match with a row from a different table * * @param rowdata array containing data for the index columns * @param rowColMap map of the data to columns * @param first true if the first matching node is required, false if any node * @return matching node or null */ private NodeAVL findNode(Session session, PersistentStore store, Object[] rowdata, int[] rowColMap, int fieldCount) { readLock.lock(); try { NodeAVL x = getAccessor(store); NodeAVL n; NodeAVL result = null; while (x != null) { int i = this.compareRowNonUnique(rowdata, rowColMap, x.getRow(store).getData(), fieldCount); if (i == 0) { result = x; n = x.getLeft(store); } else if (i > 0) { n = x.getRight(store); } else { n = x.getLeft(store); } if (n == null) { break; } x = n; } // MVCC 190 if (session == null) { return result; } while (result != null) { Row row = result.getRow(store); if (compareRowNonUnique( rowdata, rowColMap, row.getData(), fieldCount) != 0) { result = null; break; } if (session.database.txManager.canRead(session, row)) { break; } result = next(store, result); } return result; } finally { readLock.unlock(); } } /** * Balances part of the tree after an alteration to the index. */ private void balance(PersistentStore store, NodeAVL x, boolean isleft) { while (true) { int sign = isleft ? 1 : -1; switch (x.getBalance() * sign) { case 1 : x = x.setBalance(store, 0); return; case 0 : x = x.setBalance(store, -sign); break; case -1 : NodeAVL l = child(store, x, isleft); if (l.getBalance() == -sign) { replace(store, x, l); x = set(store, x, isleft, child(store, l, !isleft)); l = set(store, l, !isleft, x); x = x.setBalance(store, 0); l = l.setBalance(store, 0); } else { NodeAVL r = child(store, l, !isleft); replace(store, x, r); l = set(store, l, !isleft, child(store, r, isleft)); r = set(store, r, isleft, l); x = set(store, x, isleft, child(store, r, !isleft)); r = set(store, r, !isleft, x); int rb = r.getBalance(); x = x.setBalance(store, (rb == -sign) ? sign : 0); l = l.setBalance(store, (rb == sign) ? -sign : 0); r = r.setBalance(store, 0); } return; } if (x.isRoot()) { return; } isleft = x.isFromLeft(store); x = x.getParent(store); } } private NodeAVL getAccessor(PersistentStore store) { NodeAVL node = (NodeAVL) store.getAccessor(this); return node; } private IndexRowIterator getIterator(Session session, PersistentStore store, NodeAVL x) { if (x == null) { return emptyIterator; } else { IndexRowIterator it = new IndexRowIterator(session, store, this, x); return it; } } public static final class IndexRowIterator implements RowIterator { final Session session; final PersistentStore store; final IndexAVL index; NodeAVL nextnode; Row lastrow; IndexRowIterator last; IndexRowIterator next; IndexRowIterator lastInSession; IndexRowIterator nextInSession; /** * When session == null, rows from all sessions are returned */ public IndexRowIterator(Session session, PersistentStore store, IndexAVL index, NodeAVL node) { this.session = session; this.store = store; this.index = index; if (index == null) { return; } nextnode = node; } public boolean hasNext() { return nextnode != null; } public Row getNextRow() { if (nextnode == null) { release(); return null; } lastrow = nextnode.getRow(store); nextnode = index.next(session, store, nextnode); if (nextnode == null) { release(); } return lastrow; } public void remove() { store.delete(lastrow); } public void release() {} public boolean setRowColumns(boolean[] columns) { return false; } public long getPos() { return nextnode.getPos(); } } /*************** VOLTDB *********************/ String getColumnNameList() { String columnNameList = ""; Table t2 = (Table) table; for (int j = 0; j < colIndex.length; ++j) { columnNameList += t2.getColumn(colIndex[j]).getName().statementName; if (j < colIndex.length - 1) { columnNameList += ","; } } return columnNameList; } /** * VoltDB added method to get a non-catalog-dependent * representation of this HSQLDB object. * @param session The current Session object may be needed to resolve * some names. * @param indent A string of whitespace to be prepended to every line * in the resulting XML. * @return XML, correctly indented, representing this object. */ @Override public String voltGetXML(Session session, String indent) { StringBuilder sb = new StringBuilder(); sb.append(indent).append("<index"); sb.append(" name='").append(getName().name).append("'"); sb.append(" columns='").append(getColumnNameList()).append("'"); sb.append(" unique='").append(isUnique() ? "true" : "false").append("'"); sb.append(">\n"); sb.append(indent).append("</index>\n"); return sb.toString(); } }