From 67833826ba430014698a030667561b537b79fe42 Mon Sep 17 00:00:00 2001 From: James Ahlborn Date: Sun, 3 Mar 2013 04:26:09 +0000 Subject: [PATCH] move Index internals to IndexImpl git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/branches/jackcess-2@670 f203690c-595d-4dc9-a70b-905162fa7fd2 --- .../healthmarketscience/jackcess/Cursor.java | 10 +- .../jackcess/CursorBuilder.java | 14 +- .../jackcess/FKEnforcer.java | 8 +- .../healthmarketscience/jackcess/Index.java | 442 +---------------- .../jackcess/IndexBuilder.java | 4 +- .../jackcess/IndexCursor.java | 12 +- .../jackcess/IndexData.java | 2 +- .../jackcess/IndexImpl.java | 465 ++++++++++++++++++ .../healthmarketscience/jackcess/Joiner.java | 18 +- .../healthmarketscience/jackcess/Table.java | 2 +- .../jackcess/TableImpl.java | 20 +- 11 files changed, 531 insertions(+), 466 deletions(-) create mode 100644 src/java/com/healthmarketscience/jackcess/IndexImpl.java diff --git a/src/java/com/healthmarketscience/jackcess/Cursor.java b/src/java/com/healthmarketscience/jackcess/Cursor.java index 162e799..2b6a9d2 100644 --- a/src/java/com/healthmarketscience/jackcess/Cursor.java +++ b/src/java/com/healthmarketscience/jackcess/Cursor.java @@ -118,7 +118,7 @@ public abstract class Cursor implements Iterable> * @param index index for the table which will define traversal order as * well as enhance certain lookups */ - public static Cursor createIndexCursor(TableImpl table, Index index) + public static Cursor createIndexCursor(TableImpl table, IndexImpl index) throws IOException { return IndexCursor.createCursor(table, index); @@ -140,7 +140,7 @@ public abstract class Cursor implements Iterable> * @param endRow the last row of data for the cursor (inclusive), or * {@code null} for the last entry */ - public static Cursor createIndexCursor(TableImpl table, Index index, + public static Cursor createIndexCursor(TableImpl table, IndexImpl index, Object[] startRow, Object[] endRow) throws IOException { @@ -165,7 +165,7 @@ public abstract class Cursor implements Iterable> * the last entry * @param endInclusive whether or not endRow is inclusive or exclusive */ - public static Cursor createIndexCursor(TableImpl table, Index index, + public static Cursor createIndexCursor(TableImpl table, IndexImpl index, Object[] startRow, boolean startInclusive, Object[] endRow, @@ -240,7 +240,7 @@ public abstract class Cursor implements Iterable> * @param rowPattern pattern to be used to find the row * @return the matching row or {@code null} if a match could not be found. */ - public static Map findRow(TableImpl table, Index index, + public static Map findRow(TableImpl table, IndexImpl index, Map rowPattern) throws IOException { @@ -269,7 +269,7 @@ public abstract class Cursor implements Iterable> * desired row * @return the matching row or {@code null} if a match could not be found. */ - public static Object findValue(TableImpl table, Index index, Column column, + public static Object findValue(TableImpl table, IndexImpl index, Column column, Column columnPattern, Object valuePattern) throws IOException { diff --git a/src/java/com/healthmarketscience/jackcess/CursorBuilder.java b/src/java/com/healthmarketscience/jackcess/CursorBuilder.java index f0d7348..df9b3c3 100644 --- a/src/java/com/healthmarketscience/jackcess/CursorBuilder.java +++ b/src/java/com/healthmarketscience/jackcess/CursorBuilder.java @@ -44,9 +44,9 @@ import java.util.List; */ public class CursorBuilder { /** the table which the cursor will traverse */ - private final Table _table; + private final TableImpl _table; /** optional index to use in traversal */ - private Index _index; + private IndexImpl _index; /** optional start row for an index cursor */ private Object[] _startRow; /** whether or not start row for an index cursor is inclusive */ @@ -63,7 +63,7 @@ public class CursorBuilder { private ColumnMatcher _columnMatcher; public CursorBuilder(Table table) { - _table = table; + _table = (TableImpl)table; } /** @@ -96,7 +96,7 @@ public class CursorBuilder { * Sets an index to use for the cursor. */ public CursorBuilder setIndex(Index index) { - _index = index; + _index = (IndexImpl)index; return this; } @@ -139,7 +139,7 @@ public class CursorBuilder { */ private CursorBuilder setIndexByColumns(List searchColumns) { boolean found = false; - for(Index index : _table.getIndexes()) { + for(IndexImpl index : _table.getIndexes()) { Collection indexColumns = index.getColumns(); if(indexColumns.size() != searchColumns.size()) { @@ -278,9 +278,9 @@ public class CursorBuilder { { Cursor cursor = null; if(_index == null) { - cursor = Cursor.createCursor((TableImpl)_table); + cursor = Cursor.createCursor(_table); } else { - cursor = Cursor.createIndexCursor((TableImpl)_table, _index, + cursor = Cursor.createIndexCursor(_table, _index, _startRow, _startRowInclusive, _endRow, _endRowInclusive); } diff --git a/src/java/com/healthmarketscience/jackcess/FKEnforcer.java b/src/java/com/healthmarketscience/jackcess/FKEnforcer.java index 8aa9f01..9e148a6 100644 --- a/src/java/com/healthmarketscience/jackcess/FKEnforcer.java +++ b/src/java/com/healthmarketscience/jackcess/FKEnforcer.java @@ -57,8 +57,8 @@ final class FKEnforcer // at this point, only init the index columns Set cols = new TreeSet(); - for(Index idx : _table.getIndexes()) { - Index.ForeignKeyReference ref = idx.getReference(); + for(IndexImpl idx : _table.getIndexes()) { + IndexImpl.ForeignKeyReference ref = idx.getReference(); if(ref != null) { // compile an ordered list of all columns in this table which are // involved in foreign key relationships with other tables @@ -88,8 +88,8 @@ final class FKEnforcer _primaryJoinersDoDel = new ArrayList(1); _secondaryJoiners = new ArrayList(1); - for(Index idx : _table.getIndexes()) { - Index.ForeignKeyReference ref = idx.getReference(); + for(IndexImpl idx : _table.getIndexes()) { + IndexImpl.ForeignKeyReference ref = idx.getReference(); if(ref != null) { Joiner joiner = Joiner.create(idx); diff --git a/src/java/com/healthmarketscience/jackcess/Index.java b/src/java/com/healthmarketscience/jackcess/Index.java index fec1bd9..d27e7e1 100644 --- a/src/java/com/healthmarketscience/jackcess/Index.java +++ b/src/java/com/healthmarketscience/jackcess/Index.java @@ -1,5 +1,5 @@ /* -Copyright (c) 2005 Health Market Science, Inc. +Copyright (c) 2013 James Ahlborn This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -15,207 +15,38 @@ You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -You can contact Health Market Science at info@healthmarketscience.com -or at the following address: - -Health Market Science -2700 Horizon Drive -Suite 200 -King of Prussia, PA 19406 */ package com.healthmarketscience.jackcess; import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.Collections; import java.util.List; -import java.util.Map; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - /** - * Access table (logical) index. Logical indexes are backed for IndexData, - * where one or more logical indexes could be backed by the same data. - * - * @author Tim McCune + * + * @author James Ahlborn */ -public class Index implements Comparable { - - protected static final Log LOG = LogFactory.getLog(Index.class); - - /** index type for primary key indexes */ - static final byte PRIMARY_KEY_INDEX_TYPE = (byte)1; - - /** index type for foreign key indexes */ - static final byte FOREIGN_KEY_INDEX_TYPE = (byte)2; - - /** flag for indicating that updates should cascade in a foreign key index */ - private static final byte CASCADE_UPDATES_FLAG = (byte)1; - /** flag for indicating that deletes should cascade in a foreign key index */ - private static final byte CASCADE_DELETES_FLAG = (byte)1; - - /** index table type for the "primary" table in a foreign key index */ - private static final byte PRIMARY_TABLE_TYPE = (byte)1; - - /** indicate an invalid index number for foreign key field */ - private static final int INVALID_INDEX_NUMBER = -1; - - /** the actual data backing this index (more than one index may be backed by - the same data */ - private final IndexData _data; - /** 0-based index number */ - private final int _indexNumber; - /** the type of the index */ - private final byte _indexType; - /** Index name */ - private String _name; - /** foreign key reference info, if any */ - private final ForeignKeyReference _reference; - - protected Index(ByteBuffer tableBuffer, List indexDatas, - JetFormat format) - throws IOException - { - - ByteUtil.forward(tableBuffer, format.SKIP_BEFORE_INDEX_SLOT); //Forward past Unknown - _indexNumber = tableBuffer.getInt(); - int indexDataNumber = tableBuffer.getInt(); - - // read foreign key reference info - byte relIndexType = tableBuffer.get(); - int relIndexNumber = tableBuffer.getInt(); - int relTablePageNumber = tableBuffer.getInt(); - byte cascadeUpdatesFlag = tableBuffer.get(); - byte cascadeDeletesFlag = tableBuffer.get(); - - _indexType = tableBuffer.get(); - - if((_indexType == FOREIGN_KEY_INDEX_TYPE) && - (relIndexNumber != INVALID_INDEX_NUMBER)) { - _reference = new ForeignKeyReference( - relIndexType, relIndexNumber, relTablePageNumber, - (cascadeUpdatesFlag == CASCADE_UPDATES_FLAG), - (cascadeDeletesFlag == CASCADE_DELETES_FLAG)); - } else { - _reference = null; - } - - ByteUtil.forward(tableBuffer, format.SKIP_AFTER_INDEX_SLOT); //Skip past Unknown - - _data = indexDatas.get(indexDataNumber); +public abstract class Index +{ - _data.addIndex(this); - } + public abstract Table getTable(); - public IndexData getIndexData() { - return _data; - } + public abstract String getName(); - public TableImpl getTable() { - return getIndexData().getTable(); - } - - public JetFormat getFormat() { - return getTable().getFormat(); - } + public abstract boolean isPrimaryKey(); - public PageChannel getPageChannel() { - return getTable().getPageChannel(); - } - - public int getIndexNumber() { - return _indexNumber; - } - - public byte getIndexFlags() { - return getIndexData().getIndexFlags(); - } - - public int getUniqueEntryCount() { - return getIndexData().getUniqueEntryCount(); - } - - public int getUniqueEntryCountOffset() { - return getIndexData().getUniqueEntryCountOffset(); - } - - public String getName() { - return _name; - } - - public void setName(String name) { - _name = name; - } - - public boolean isPrimaryKey() { - return _indexType == PRIMARY_KEY_INDEX_TYPE; - } - - public boolean isForeignKey() { - return _indexType == FOREIGN_KEY_INDEX_TYPE; - } - - public ForeignKeyReference getReference() { - return _reference; - } + public abstract boolean isForeignKey(); /** - * @return the Index referenced by this Index's ForeignKeyReference (if it - * has one), otherwise {@code null}. + * @return the Columns for this index (unmodifiable) */ - public Index getReferencedIndex() throws IOException { - - if(_reference == null) { - return null; - } - - TableImpl refTable = getTable().getDatabase().getTable( - _reference.getOtherTablePageNumber()); - - if(refTable == null) { - throw new IOException("Reference to missing table " + - _reference.getOtherTablePageNumber()); - } - - Index refIndex = null; - int idxNumber = _reference.getOtherIndexNumber(); - for(Index idx : refTable.getIndexes()) { - if(idx.getIndexNumber() == idxNumber) { - refIndex = idx; - break; - } - } - - if(refIndex == null) { - throw new IOException("Reference to missing index " + idxNumber + - " on table " + refTable.getName()); - } - - // finally verify that we found the expected index (should reference this - // index) - ForeignKeyReference otherRef = refIndex.getReference(); - if((otherRef == null) || - (otherRef.getOtherTablePageNumber() != - getTable().getTableDefPageNumber()) || - (otherRef.getOtherIndexNumber() != _indexNumber)) { - throw new IOException("Found unexpected index " + refIndex.getName() + - " on table " + refTable.getName() + - " with reference " + otherRef); - } - - return refIndex; - } + public abstract List getColumns(); /** - * Whether or not {@code null} values are actually recorded in the index. + * @return the Index referenced by this Index's ForeignKeyReference (if it + * has one), otherwise {@code null}. */ - public boolean shouldIgnoreNulls() { - return getIndexData().shouldIgnoreNulls(); - } + public abstract Index getReferencedIndex() throws IOException; /** * Whether or not index entries must be unique. @@ -229,250 +60,19 @@ public class Index implements Comparable { * case will violate the unique constraint * */ - public boolean isUnique() { - return getIndexData().isUnique(); - } - - /** - * Returns the Columns for this index (unmodifiable) - */ - public List getColumns() { - return getIndexData().getColumns(); - } - - /** - * Whether or not the complete index state has been read. - */ - public boolean isInitialized() { - return getIndexData().isInitialized(); - } - - /** - * Forces initialization of this index (actual parsing of index pages). - * normally, the index will not be initialized until the entries are - * actually needed. - */ - public void initialize() throws IOException { - getIndexData().initialize(); - } + public abstract boolean isUnique(); /** - * Writes the current index state to the database. - *

- * Forces index initialization. + * Information about a Column in an Index */ - public void update() throws IOException { - getIndexData().update(); - } + public interface ColumnInfo { - /** - * Adds a row to this index - *

- * Forces index initialization. - * - * @param row Row to add - * @param rowId rowId of the row to be added - */ - public void addRow(Object[] row, RowId rowId) - throws IOException - { - getIndexData().addRow(row, rowId); - } - - /** - * Removes a row from this index - *

- * Forces index initialization. - * - * @param row Row to remove - * @param rowId rowId of the row to be removed - */ - public void deleteRow(Object[] row, RowId rowId) - throws IOException - { - getIndexData().deleteRow(row, rowId); - } - - /** - * Gets a new cursor for this index. - *

- * Forces index initialization. - */ - public IndexData.EntryCursor cursor() - throws IOException - { - return cursor(null, true, null, true); - } - - /** - * Gets a new cursor for this index, narrowed to the range defined by the - * given startRow and endRow. - *

- * Forces index initialization. - * - * @param startRow the first row of data for the cursor, or {@code null} for - * the first entry - * @param startInclusive whether or not startRow is inclusive or exclusive - * @param endRow the last row of data for the cursor, or {@code null} for - * the last entry - * @param endInclusive whether or not endRow is inclusive or exclusive - */ - public IndexData.EntryCursor cursor(Object[] startRow, - boolean startInclusive, - Object[] endRow, - boolean endInclusive) - throws IOException - { - return getIndexData().cursor(startRow, startInclusive, endRow, - endInclusive); - } + public Column getColumn(); - /** - * Constructs an array of values appropriate for this index from the given - * column values, expected to match the columns for this index. - * @return the appropriate sparse array of data - * @throws IllegalArgumentException if the wrong number of values are - * provided - */ - public Object[] constructIndexRowFromEntry(Object... values) - { - return getIndexData().constructIndexRowFromEntry(values); - } - - /** - * Constructs an array of values appropriate for this index from the given - * column value. - * @return the appropriate sparse array of data or {@code null} if not all - * columns for this index were provided - */ - public Object[] constructIndexRow(String colName, Object value) - { - return constructIndexRow(Collections.singletonMap(colName, value)); - } - - /** - * Constructs an array of values appropriate for this index from the given - * column values. - * @return the appropriate sparse array of data or {@code null} if not all - * columns for this index were provided - */ - public Object[] constructIndexRow(Map row) - { - return getIndexData().constructIndexRow(row); - } - - @Override - public String toString() { - StringBuilder rtn = new StringBuilder(); - rtn.append("\tName: (").append(getTable().getName()).append(") ") - .append(_name); - rtn.append("\n\tNumber: ").append(_indexNumber); - rtn.append("\n\tIs Primary Key: ").append(isPrimaryKey()); - rtn.append("\n\tIs Foreign Key: ").append(isForeignKey()); - if(_reference != null) { - rtn.append("\n\tForeignKeyReference: ").append(_reference); - } - rtn.append(_data.toString()); - rtn.append("\n\n"); - return rtn.toString(); - } - - public int compareTo(Index other) { - if (_indexNumber > other.getIndexNumber()) { - return 1; - } else if (_indexNumber < other.getIndexNumber()) { - return -1; - } else { - return 0; - } - } + public boolean isAscending(); - /** - * Writes the logical index definitions into a table definition buffer. - * @param buffer Buffer to write to - * @param indexes List of IndexBuilders to write definitions for - */ - protected static void writeDefinitions( - TableCreator creator, ByteBuffer buffer) - throws IOException - { - // write logical index information - for(IndexBuilder idx : creator.getIndexes()) { - TableCreator.IndexState idxState = creator.getIndexState(idx); - buffer.putInt(TableImpl.MAGIC_TABLE_NUMBER); // seemingly constant magic value which matches the table def - buffer.putInt(idxState.getIndexNumber()); // index num - buffer.putInt(idxState.getIndexDataNumber()); // index data num - buffer.put((byte)0); // related table type - buffer.putInt(INVALID_INDEX_NUMBER); // related index num - buffer.putInt(0); // related table definition page number - buffer.put((byte)0); // cascade updates flag - buffer.put((byte)0); // cascade deletes flag - buffer.put(idx.getType()); // index type flags - buffer.putInt(0); // unknown - } - - // write index names - for(IndexBuilder idx : creator.getIndexes()) { - TableImpl.writeName(buffer, idx.getName(), creator.getCharset()); - } - } - - /** - * Information about a foreign key reference defined in an index (when - * referential integrity should be enforced). - */ - public static class ForeignKeyReference - { - private final byte _tableType; - private final int _otherIndexNumber; - private final int _otherTablePageNumber; - private final boolean _cascadeUpdates; - private final boolean _cascadeDeletes; + public int getColumnIndex(); - public ForeignKeyReference( - byte tableType, int otherIndexNumber, int otherTablePageNumber, - boolean cascadeUpdates, boolean cascadeDeletes) - { - _tableType = tableType; - _otherIndexNumber = otherIndexNumber; - _otherTablePageNumber = otherTablePageNumber; - _cascadeUpdates = cascadeUpdates; - _cascadeDeletes = cascadeDeletes; - } - - public byte getTableType() { - return _tableType; - } - - public boolean isPrimaryTable() { - return(getTableType() == PRIMARY_TABLE_TYPE); - } - - public int getOtherIndexNumber() { - return _otherIndexNumber; - } - - public int getOtherTablePageNumber() { - return _otherTablePageNumber; - } - - public boolean isCascadeUpdates() { - return _cascadeUpdates; - } - - public boolean isCascadeDeletes() { - return _cascadeDeletes; - } - - @Override - public String toString() { - return new StringBuilder() - .append("\n\t\tOther Index Number: ").append(_otherIndexNumber) - .append("\n\t\tOther Table Page Num: ").append(_otherTablePageNumber) - .append("\n\t\tIs Primary Table: ").append(isPrimaryTable()) - .append("\n\t\tIs Cascade Updates: ").append(isCascadeUpdates()) - .append("\n\t\tIs Cascade Deletes: ").append(isCascadeDeletes()) - .toString(); - } + public String getName(); } } diff --git a/src/java/com/healthmarketscience/jackcess/IndexBuilder.java b/src/java/com/healthmarketscience/jackcess/IndexBuilder.java index 07ddd77..1894315 100644 --- a/src/java/com/healthmarketscience/jackcess/IndexBuilder.java +++ b/src/java/com/healthmarketscience/jackcess/IndexBuilder.java @@ -61,7 +61,7 @@ public class IndexBuilder } public boolean isPrimaryKey() { - return (getType() == Index.PRIMARY_KEY_INDEX_TYPE); + return (getType() == IndexImpl.PRIMARY_KEY_INDEX_TYPE); } public boolean isUnique() { @@ -108,7 +108,7 @@ public class IndexBuilder * unique). */ public IndexBuilder setPrimaryKey() { - _type = Index.PRIMARY_KEY_INDEX_TYPE; + _type = IndexImpl.PRIMARY_KEY_INDEX_TYPE; return setUnique(); } diff --git a/src/java/com/healthmarketscience/jackcess/IndexCursor.java b/src/java/com/healthmarketscience/jackcess/IndexCursor.java index 016a9c6..b0b41c0 100644 --- a/src/java/com/healthmarketscience/jackcess/IndexCursor.java +++ b/src/java/com/healthmarketscience/jackcess/IndexCursor.java @@ -47,13 +47,13 @@ public class IndexCursor extends Cursor private final IndexDirHandler _reverseDirHandler = new ReverseIndexDirHandler(); /** logical index which this cursor is using */ - private final Index _index; + private final IndexImpl _index; /** Cursor over the entries of the relevant index */ private final IndexData.EntryCursor _entryCursor; /** column names for the index entry columns */ private Set _indexEntryPattern; - private IndexCursor(TableImpl table, Index index, + private IndexCursor(TableImpl table, IndexImpl index, IndexData.EntryCursor entryCursor) throws IOException { @@ -76,7 +76,7 @@ public class IndexCursor extends Cursor * @param index index for the table which will define traversal order as * well as enhance certain lookups */ - public static IndexCursor createCursor(TableImpl table, Index index) + public static IndexCursor createCursor(TableImpl table, IndexImpl index) throws IOException { return createCursor(table, index, null, null); @@ -99,7 +99,7 @@ public class IndexCursor extends Cursor * {@code null} for the last entry */ public static IndexCursor createCursor( - TableImpl table, Index index, Object[] startRow, Object[] endRow) + TableImpl table, IndexImpl index, Object[] startRow, Object[] endRow) throws IOException { return createCursor(table, index, startRow, true, endRow, true); @@ -123,7 +123,7 @@ public class IndexCursor extends Cursor * the last entry * @param endInclusive whether or not endRow is inclusive or exclusive */ - public static IndexCursor createCursor(TableImpl table, Index index, + public static IndexCursor createCursor(TableImpl table, IndexImpl index, Object[] startRow, boolean startInclusive, Object[] endRow, @@ -152,7 +152,7 @@ public class IndexCursor extends Cursor return cursor; } - public Index getIndex() { + public IndexImpl getIndex() { return _index; } diff --git a/src/java/com/healthmarketscience/jackcess/IndexData.java b/src/java/com/healthmarketscience/jackcess/IndexData.java index ca22be4..d6b411f 100644 --- a/src/java/com/healthmarketscience/jackcess/IndexData.java +++ b/src/java/com/healthmarketscience/jackcess/IndexData.java @@ -1270,7 +1270,7 @@ public class IndexData { * Information about the columns in an index. Also encodes new index * values. */ - public static abstract class ColumnDescriptor + public static abstract class ColumnDescriptor implements Index.ColumnInfo { private final Column _column; private final byte _flags; diff --git a/src/java/com/healthmarketscience/jackcess/IndexImpl.java b/src/java/com/healthmarketscience/jackcess/IndexImpl.java new file mode 100644 index 0000000..c34bf89 --- /dev/null +++ b/src/java/com/healthmarketscience/jackcess/IndexImpl.java @@ -0,0 +1,465 @@ +/* +Copyright (c) 2005 Health Market Science, Inc. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +USA + +You can contact Health Market Science at info@healthmarketscience.com +or at the following address: + +Health Market Science +2700 Horizon Drive +Suite 200 +King of Prussia, PA 19406 +*/ + +package com.healthmarketscience.jackcess; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + + +/** + * Access table (logical) index. Logical indexes are backed for IndexData, + * where one or more logical indexes could be backed by the same data. + * + * @author Tim McCune + */ +public class IndexImpl extends Index implements Comparable +{ + + protected static final Log LOG = LogFactory.getLog(Index.class); + + /** index type for primary key indexes */ + static final byte PRIMARY_KEY_INDEX_TYPE = (byte)1; + + /** index type for foreign key indexes */ + static final byte FOREIGN_KEY_INDEX_TYPE = (byte)2; + + /** flag for indicating that updates should cascade in a foreign key index */ + private static final byte CASCADE_UPDATES_FLAG = (byte)1; + /** flag for indicating that deletes should cascade in a foreign key index */ + private static final byte CASCADE_DELETES_FLAG = (byte)1; + + /** index table type for the "primary" table in a foreign key index */ + private static final byte PRIMARY_TABLE_TYPE = (byte)1; + + /** indicate an invalid index number for foreign key field */ + private static final int INVALID_INDEX_NUMBER = -1; + + /** the actual data backing this index (more than one index may be backed by + the same data */ + private final IndexData _data; + /** 0-based index number */ + private final int _indexNumber; + /** the type of the index */ + private final byte _indexType; + /** Index name */ + private String _name; + /** foreign key reference info, if any */ + private final ForeignKeyReference _reference; + + protected IndexImpl(ByteBuffer tableBuffer, List indexDatas, + JetFormat format) + throws IOException + { + + ByteUtil.forward(tableBuffer, format.SKIP_BEFORE_INDEX_SLOT); //Forward past Unknown + _indexNumber = tableBuffer.getInt(); + int indexDataNumber = tableBuffer.getInt(); + + // read foreign key reference info + byte relIndexType = tableBuffer.get(); + int relIndexNumber = tableBuffer.getInt(); + int relTablePageNumber = tableBuffer.getInt(); + byte cascadeUpdatesFlag = tableBuffer.get(); + byte cascadeDeletesFlag = tableBuffer.get(); + + _indexType = tableBuffer.get(); + + if((_indexType == FOREIGN_KEY_INDEX_TYPE) && + (relIndexNumber != INVALID_INDEX_NUMBER)) { + _reference = new ForeignKeyReference( + relIndexType, relIndexNumber, relTablePageNumber, + (cascadeUpdatesFlag == CASCADE_UPDATES_FLAG), + (cascadeDeletesFlag == CASCADE_DELETES_FLAG)); + } else { + _reference = null; + } + + ByteUtil.forward(tableBuffer, format.SKIP_AFTER_INDEX_SLOT); //Skip past Unknown + + _data = indexDatas.get(indexDataNumber); + + _data.addIndex(this); + } + + public IndexData getIndexData() { + return _data; + } + + @Override + public TableImpl getTable() { + return getIndexData().getTable(); + } + + public JetFormat getFormat() { + return getTable().getFormat(); + } + + public PageChannel getPageChannel() { + return getTable().getPageChannel(); + } + + public int getIndexNumber() { + return _indexNumber; + } + + public byte getIndexFlags() { + return getIndexData().getIndexFlags(); + } + + public int getUniqueEntryCount() { + return getIndexData().getUniqueEntryCount(); + } + + public int getUniqueEntryCountOffset() { + return getIndexData().getUniqueEntryCountOffset(); + } + + @Override + public String getName() { + return _name; + } + + public void setName(String name) { + _name = name; + } + + @Override + public boolean isPrimaryKey() { + return _indexType == PRIMARY_KEY_INDEX_TYPE; + } + + @Override + public boolean isForeignKey() { + return _indexType == FOREIGN_KEY_INDEX_TYPE; + } + + public ForeignKeyReference getReference() { + return _reference; + } + + @Override + public IndexImpl getReferencedIndex() throws IOException { + + if(_reference == null) { + return null; + } + + TableImpl refTable = getTable().getDatabase().getTable( + _reference.getOtherTablePageNumber()); + + if(refTable == null) { + throw new IOException("Reference to missing table " + + _reference.getOtherTablePageNumber()); + } + + IndexImpl refIndex = null; + int idxNumber = _reference.getOtherIndexNumber(); + for(IndexImpl idx : refTable.getIndexes()) { + if(idx.getIndexNumber() == idxNumber) { + refIndex = idx; + break; + } + } + + if(refIndex == null) { + throw new IOException("Reference to missing index " + idxNumber + + " on table " + refTable.getName()); + } + + // finally verify that we found the expected index (should reference this + // index) + ForeignKeyReference otherRef = refIndex.getReference(); + if((otherRef == null) || + (otherRef.getOtherTablePageNumber() != + getTable().getTableDefPageNumber()) || + (otherRef.getOtherIndexNumber() != _indexNumber)) { + throw new IOException("Found unexpected index " + refIndex.getName() + + " on table " + refTable.getName() + + " with reference " + otherRef); + } + + return refIndex; + } + + @Override + public boolean shouldIgnoreNulls() { + return getIndexData().shouldIgnoreNulls(); + } + + @Override + public boolean isUnique() { + return getIndexData().isUnique(); + } + + @Override + public List getColumns() { + return getIndexData().getColumns(); + } + + /** + * Whether or not the complete index state has been read. + */ + public boolean isInitialized() { + return getIndexData().isInitialized(); + } + + /** + * Forces initialization of this index (actual parsing of index pages). + * normally, the index will not be initialized until the entries are + * actually needed. + */ + public void initialize() throws IOException { + getIndexData().initialize(); + } + + /** + * Writes the current index state to the database. + *

+ * Forces index initialization. + */ + public void update() throws IOException { + getIndexData().update(); + } + + /** + * Adds a row to this index + *

+ * Forces index initialization. + * + * @param row Row to add + * @param rowId rowId of the row to be added + */ + public void addRow(Object[] row, RowId rowId) + throws IOException + { + getIndexData().addRow(row, rowId); + } + + /** + * Removes a row from this index + *

+ * Forces index initialization. + * + * @param row Row to remove + * @param rowId rowId of the row to be removed + */ + public void deleteRow(Object[] row, RowId rowId) + throws IOException + { + getIndexData().deleteRow(row, rowId); + } + + /** + * Gets a new cursor for this index. + *

+ * Forces index initialization. + */ + public IndexData.EntryCursor cursor() + throws IOException + { + return cursor(null, true, null, true); + } + + /** + * Gets a new cursor for this index, narrowed to the range defined by the + * given startRow and endRow. + *

+ * Forces index initialization. + * + * @param startRow the first row of data for the cursor, or {@code null} for + * the first entry + * @param startInclusive whether or not startRow is inclusive or exclusive + * @param endRow the last row of data for the cursor, or {@code null} for + * the last entry + * @param endInclusive whether or not endRow is inclusive or exclusive + */ + public IndexData.EntryCursor cursor(Object[] startRow, + boolean startInclusive, + Object[] endRow, + boolean endInclusive) + throws IOException + { + return getIndexData().cursor(startRow, startInclusive, endRow, + endInclusive); + } + + /** + * Constructs an array of values appropriate for this index from the given + * column values, expected to match the columns for this index. + * @return the appropriate sparse array of data + * @throws IllegalArgumentException if the wrong number of values are + * provided + */ + public Object[] constructIndexRowFromEntry(Object... values) + { + return getIndexData().constructIndexRowFromEntry(values); + } + + /** + * Constructs an array of values appropriate for this index from the given + * column value. + * @return the appropriate sparse array of data or {@code null} if not all + * columns for this index were provided + */ + public Object[] constructIndexRow(String colName, Object value) + { + return constructIndexRow(Collections.singletonMap(colName, value)); + } + + /** + * Constructs an array of values appropriate for this index from the given + * column values. + * @return the appropriate sparse array of data or {@code null} if not all + * columns for this index were provided + */ + public Object[] constructIndexRow(Map row) + { + return getIndexData().constructIndexRow(row); + } + + @Override + public String toString() { + StringBuilder rtn = new StringBuilder(); + rtn.append("\tName: (").append(getTable().getName()).append(") ") + .append(_name); + rtn.append("\n\tNumber: ").append(_indexNumber); + rtn.append("\n\tIs Primary Key: ").append(isPrimaryKey()); + rtn.append("\n\tIs Foreign Key: ").append(isForeignKey()); + if(_reference != null) { + rtn.append("\n\tForeignKeyReference: ").append(_reference); + } + rtn.append(_data.toString()); + rtn.append("\n\n"); + return rtn.toString(); + } + + public int compareTo(IndexImpl other) { + if (_indexNumber > other.getIndexNumber()) { + return 1; + } else if (_indexNumber < other.getIndexNumber()) { + return -1; + } else { + return 0; + } + } + + /** + * Writes the logical index definitions into a table definition buffer. + * @param buffer Buffer to write to + * @param indexes List of IndexBuilders to write definitions for + */ + protected static void writeDefinitions( + TableCreator creator, ByteBuffer buffer) + throws IOException + { + // write logical index information + for(IndexBuilder idx : creator.getIndexes()) { + TableCreator.IndexState idxState = creator.getIndexState(idx); + buffer.putInt(TableImpl.MAGIC_TABLE_NUMBER); // seemingly constant magic value which matches the table def + buffer.putInt(idxState.getIndexNumber()); // index num + buffer.putInt(idxState.getIndexDataNumber()); // index data num + buffer.put((byte)0); // related table type + buffer.putInt(INVALID_INDEX_NUMBER); // related index num + buffer.putInt(0); // related table definition page number + buffer.put((byte)0); // cascade updates flag + buffer.put((byte)0); // cascade deletes flag + buffer.put(idx.getType()); // index type flags + buffer.putInt(0); // unknown + } + + // write index names + for(IndexBuilder idx : creator.getIndexes()) { + TableImpl.writeName(buffer, idx.getName(), creator.getCharset()); + } + } + + /** + * Information about a foreign key reference defined in an index (when + * referential integrity should be enforced). + */ + public static class ForeignKeyReference + { + private final byte _tableType; + private final int _otherIndexNumber; + private final int _otherTablePageNumber; + private final boolean _cascadeUpdates; + private final boolean _cascadeDeletes; + + public ForeignKeyReference( + byte tableType, int otherIndexNumber, int otherTablePageNumber, + boolean cascadeUpdates, boolean cascadeDeletes) + { + _tableType = tableType; + _otherIndexNumber = otherIndexNumber; + _otherTablePageNumber = otherTablePageNumber; + _cascadeUpdates = cascadeUpdates; + _cascadeDeletes = cascadeDeletes; + } + + public byte getTableType() { + return _tableType; + } + + public boolean isPrimaryTable() { + return(getTableType() == PRIMARY_TABLE_TYPE); + } + + public int getOtherIndexNumber() { + return _otherIndexNumber; + } + + public int getOtherTablePageNumber() { + return _otherTablePageNumber; + } + + public boolean isCascadeUpdates() { + return _cascadeUpdates; + } + + public boolean isCascadeDeletes() { + return _cascadeDeletes; + } + + @Override + public String toString() { + return new StringBuilder() + .append("\n\t\tOther Index Number: ").append(_otherIndexNumber) + .append("\n\t\tOther Table Page Num: ").append(_otherTablePageNumber) + .append("\n\t\tIs Primary Table: ").append(isPrimaryTable()) + .append("\n\t\tIs Cascade Updates: ").append(isCascadeUpdates()) + .append("\n\t\tIs Cascade Deletes: ").append(isCascadeDeletes()) + .toString(); + } + } +} diff --git a/src/java/com/healthmarketscience/jackcess/Joiner.java b/src/java/com/healthmarketscience/jackcess/Joiner.java index dc3f4ba..cf25e29 100644 --- a/src/java/com/healthmarketscience/jackcess/Joiner.java +++ b/src/java/com/healthmarketscience/jackcess/Joiner.java @@ -34,12 +34,12 @@ import java.util.Map; */ public class Joiner { - private final Index _fromIndex; + private final IndexImpl _fromIndex; private final List _fromCols; private final IndexCursor _toCursor; private final Object[] _entryValues; - private Joiner(Index fromIndex, IndexCursor toCursor) + private Joiner(IndexImpl fromIndex, IndexCursor toCursor) { _fromIndex = fromIndex; _fromCols = _fromIndex.getColumns(); @@ -56,7 +56,7 @@ public class Joiner * @throws IllegalArgumentException if there is no relationship between the * given tables */ - public static Joiner create(Table fromTable, Table toTable) + public static Joiner create(TableImpl fromTable, TableImpl toTable) throws IOException { return create(fromTable.getForeignKeyIndex(toTable)); @@ -69,10 +69,10 @@ public class Joiner * * @param fromIndex the index backing one side of a foreign-key relationship */ - public static Joiner create(Index fromIndex) + public static Joiner create(IndexImpl fromIndex) throws IOException { - Index toIndex = fromIndex.getReferencedIndex(); + IndexImpl toIndex = fromIndex.getReferencedIndex(); IndexCursor toCursor = IndexCursor.createCursor( toIndex.getTable(), toIndex); // text lookups are always case-insensitive @@ -90,19 +90,19 @@ public class Joiner return create(getToTable(), getFromTable()); } - public Table getFromTable() { + public TableImpl getFromTable() { return getFromIndex().getTable(); } - public Index getFromIndex() { + public IndexImpl getFromIndex() { return _fromIndex; } - public Table getToTable() { + public TableImpl getToTable() { return getToCursor().getTable(); } - public Index getToIndex() { + public IndexImpl getToIndex() { return getToCursor().getIndex(); } diff --git a/src/java/com/healthmarketscience/jackcess/Table.java b/src/java/com/healthmarketscience/jackcess/Table.java index 2c0c3fd..1b7a359 100644 --- a/src/java/com/healthmarketscience/jackcess/Table.java +++ b/src/java/com/healthmarketscience/jackcess/Table.java @@ -102,7 +102,7 @@ public abstract class Table implements Iterable> * @return All of the Indexes on this table (unmodifiable List) * @usage _intermediate_method_ */ - public abstract List getIndexes(); + public abstract List getIndexes(); /** * @return the index with the given name diff --git a/src/java/com/healthmarketscience/jackcess/TableImpl.java b/src/java/com/healthmarketscience/jackcess/TableImpl.java index 35a5067..5459001 100644 --- a/src/java/com/healthmarketscience/jackcess/TableImpl.java +++ b/src/java/com/healthmarketscience/jackcess/TableImpl.java @@ -130,7 +130,7 @@ public class TableImpl extends Table private List _autoNumColumns; /** List of indexes on this table (multiple logical indexes may be backed by the same index data) */ - private final List _indexes = new ArrayList(); + private final List _indexes = new ArrayList(); /** List of index datas on this table (the actual backing data for an index) */ private final List _indexDatas = new ArrayList(); @@ -362,13 +362,13 @@ public class TableImpl extends Table } @Override - public List getIndexes() { + public List getIndexes() { return Collections.unmodifiableList(_indexes); } @Override - public Index getIndex(String name) { - for(Index index : _indexes) { + public IndexImpl getIndex(String name) { + for(IndexImpl index : _indexes) { if(index.getName().equalsIgnoreCase(name)) { return index; } @@ -378,8 +378,8 @@ public class TableImpl extends Table } @Override - public Index getPrimaryKeyIndex() { - for(Index index : _indexes) { + public IndexImpl getPrimaryKeyIndex() { + for(IndexImpl index : _indexes) { if(index.isPrimaryKey()) { return index; } @@ -389,8 +389,8 @@ public class TableImpl extends Table } @Override - public Index getForeignKeyIndex(Table otherTable) { - for(Index index : _indexes) { + public IndexImpl getForeignKeyIndex(Table otherTable) { + for(IndexImpl index : _indexes) { if(index.isForeignKey() && (index.getReference() != null) && (index.getReference().getOtherTablePageNumber() == ((TableImpl)otherTable).getTableDefPageNumber())) { @@ -940,7 +940,7 @@ public class TableImpl extends Table if(creator.hasIndexes()) { // index and index data definitions IndexData.writeDefinitions(creator, buffer); - Index.writeDefinitions(creator, buffer); + IndexImpl.writeDefinitions(creator, buffer); } //End of tabledef @@ -1246,7 +1246,7 @@ public class TableImpl extends Table // read logical index info (may be more logical indexes than index datas) for (int i = 0; i < _logicalIndexCount; i++) { - _indexes.add(new Index(tableBuffer, _indexDatas, getFormat())); + _indexes.add(new IndexImpl(tableBuffer, _indexDatas, getFormat())); } // read logical index names -- 2.39.5