diff options
author | James Ahlborn <jtahlborn@yahoo.com> | 2008-03-11 20:40:13 +0000 |
---|---|---|
committer | James Ahlborn <jtahlborn@yahoo.com> | 2008-03-11 20:40:13 +0000 |
commit | 379669147b0118a39d432568c6a3b6cb6c454e6d (patch) | |
tree | 936137912fba3a7a39b836a6dd87180b40563209 | |
parent | cd8798cb54ad84e55e13ed2831a9512003fa7ecc (diff) | |
download | jackcess-379669147b0118a39d432568c6a3b6cb6c454e6d.tar.gz jackcess-379669147b0118a39d432568c6a3b6cb6c454e6d.zip |
add support for reading table relationships; minor refactor of table creation; minor features added to cursorbuilder
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@266 f203690c-595d-4dc9-a70b-905162fa7fd2
-rw-r--r-- | src/changes/changes.xml | 3 | ||||
-rw-r--r-- | src/java/com/healthmarketscience/jackcess/Cursor.java | 79 | ||||
-rw-r--r-- | src/java/com/healthmarketscience/jackcess/CursorBuilder.java | 29 | ||||
-rw-r--r-- | src/java/com/healthmarketscience/jackcess/Database.java | 282 | ||||
-rw-r--r-- | src/java/com/healthmarketscience/jackcess/Index.java | 11 | ||||
-rw-r--r-- | src/java/com/healthmarketscience/jackcess/Relationship.java | 109 |
6 files changed, 408 insertions, 105 deletions
diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 4c5b113..e982615 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -25,6 +25,9 @@ Modify Index update support so that it honors the "unique" and "ignoreNulls" properties for the Index. </action> + <action dev="jahlborn" type="update"> + Add support for reading table relationships. + </action> </release> <release version="1.1.12" date="2008-02-27"> <action dev="jahlborn" type="fix"> diff --git a/src/java/com/healthmarketscience/jackcess/Cursor.java b/src/java/com/healthmarketscience/jackcess/Cursor.java index 1e6ce52..7835c93 100644 --- a/src/java/com/healthmarketscience/jackcess/Cursor.java +++ b/src/java/com/healthmarketscience/jackcess/Cursor.java @@ -112,7 +112,7 @@ public abstract class Cursor implements Iterable<Map<String, Object>> { return createIndexCursor(table, index, null, null); } - + /** * Creates an indexed cursor for the given table, narrowed to the given * range. @@ -162,7 +162,7 @@ public abstract class Cursor implements Iterable<Map<String, Object>> /** * Convenience method for finding a specific row in a table which matches a - * given row "pattern. See {@link #findRow(Map)} for details on the + * given row "pattern". See {@link #findRow(Map)} for details on the * rowPattern. * * @param table the table to search @@ -182,7 +182,7 @@ public abstract class Cursor implements Iterable<Map<String, Object>> /** * Convenience method for finding a specific row in a table which matches a - * given row "pattern. See {@link #findRow(Column,Object)} for details on + * given row "pattern". See {@link #findRow(Column,Object)} for details on * the pattern. * <p> * Note, a {@code null} result value is ambiguous in that it could imply no @@ -210,7 +210,7 @@ public abstract class Cursor implements Iterable<Map<String, Object>> /** * Convenience method for finding a specific row in an indexed table which - * matches a given row "pattern. See {@link #findRow(Map)} for details on + * matches a given row "pattern". See {@link #findRow(Map)} for details on * the rowPattern. * * @param table the table to search @@ -231,7 +231,7 @@ public abstract class Cursor implements Iterable<Map<String, Object>> /** * Convenience method for finding a specific row in a table which matches a - * given row "pattern. See {@link #findRow(Column,Object)} for details on + * given row "pattern". See {@link #findRow(Column,Object)} for details on * the pattern. * <p> * Note, a {@code null} result value is ambiguous in that it could imply no @@ -703,6 +703,30 @@ public abstract class Cursor implements Iterable<Map<String, Object>> } } } + + /** + * Returns {@code true} if the current row matches the given pattern. + * @param columnPattern column from the table for this cursor which is being + * matched by the valuePattern + * @param valuePattern value which is tested for equality with the + * corresponding value in the current row + */ + public boolean currentRowMatches(Column columnPattern, Object valuePattern) + throws IOException + { + return ObjectUtils.equals(valuePattern, getCurrentRowValue(columnPattern)); + } + + /** + * Returns {@code true} if the current row matches the given pattern. + * @param rowPattern column names and values which must be equal to the + * corresponding values in the current row + */ + public boolean currentRowMatches(Map<String,Object> rowPattern) + throws IOException + { + return ObjectUtils.equals(rowPattern, getCurrentRow(rowPattern.keySet())); + } /** * Moves to the first row (as defined by the cursor) where the given column @@ -722,8 +746,7 @@ public abstract class Cursor implements Iterable<Map<String, Object>> { beforeFirst(); while(moveToNextRow()) { - if(ObjectUtils.equals(valuePattern, getCurrentRowValue(columnPattern))) - { + if(currentRowMatches(columnPattern, valuePattern)) { return true; } } @@ -745,9 +768,8 @@ public abstract class Cursor implements Iterable<Map<String, Object>> throws IOException { beforeFirst(); - Collection<String> columnNames = rowPattern.keySet(); while(moveToNextRow()) { - if(ObjectUtils.equals(rowPattern, getCurrentRow(columnNames))) { + if(currentRowMatches(rowPattern)) { return true; } } @@ -849,7 +871,7 @@ public abstract class Cursor implements Iterable<Map<String, Object>> protected abstract DirHandler getDirHandler(boolean moveForward); /** - * Row iterator for this table, supports modification. + * Row iterator for this table, unmodifiable. */ private final class RowIterator implements Iterator<Map<String, Object>> { @@ -1142,8 +1164,7 @@ public abstract class Cursor implements Iterable<Map<String, Object>> // either we found a row with the given value, or none exist in the // table restorePosition(new IndexPosition(startEntry)); - return ObjectUtils.equals(getCurrentRowValue(columnPattern), - valuePattern); + return currentRowMatches(columnPattern, valuePattern); } @Override @@ -1167,27 +1188,35 @@ public abstract class Cursor implements Iterable<Map<String, Object>> } restorePosition(new IndexPosition(startEntry)); - Map<String,Object> indexRowPattern = - new LinkedHashMap<String,Object>(); - for(Index.ColumnDescriptor idxCol : _entryCursor.getIndex().getColumns()) - { - indexRowPattern.put(idxCol.getName(), - rowValues[idxCol.getColumnNumber()]); + Map<String,Object> indexRowPattern = null; + if(rowPattern.size() == index.getColumns().size()) { + // the rowPattern matches our index columns exactly, so we can + // streamline our testing below + indexRowPattern = rowPattern; + } else { + // the rowPattern has more columns than just the index, so we need to + // do more work when testing below + indexRowPattern = + new LinkedHashMap<String,Object>(); + for(Index.ColumnDescriptor idxCol : index.getColumns()) { + indexRowPattern.put(idxCol.getName(), + rowValues[idxCol.getColumnNumber()]); + } } - + // there may be multiple columns which fit the pattern subset used by - // the index, so we need to keep checking until we no longer our index - // values no longer match + // the index, so we need to keep checking until our index values no + // longer match do { - if(!ObjectUtils.equals(getCurrentRow(indexRowPattern.keySet()), - indexRowPattern)) { + if(!currentRowMatches(indexRowPattern)) { // there are no more rows which could possibly match break; } - if(ObjectUtils.equals(getCurrentRow(rowPattern.keySet()), - rowPattern)) { + // note, if rowPattern == indexRowPattern, no need to do an extra + // comparison with the current row + if((rowPattern == indexRowPattern) || currentRowMatches(rowPattern)) { // found it! return true; } diff --git a/src/java/com/healthmarketscience/jackcess/CursorBuilder.java b/src/java/com/healthmarketscience/jackcess/CursorBuilder.java index 4688fac..57b2f18 100644 --- a/src/java/com/healthmarketscience/jackcess/CursorBuilder.java +++ b/src/java/com/healthmarketscience/jackcess/CursorBuilder.java @@ -150,6 +150,31 @@ public class CursorBuilder { } /** + * Sets the starting and ending row for a range based index cursor. + * <p> + * A valid index must be specified before calling this method. + */ + public CursorBuilder setSpecificRow(Object[] specificRow) { + setStartRow(specificRow); + setEndRow(specificRow); + return this; + } + + /** + * Sets the starting and ending row for a range based index cursor to the + * given entry (where the given values correspond to the index's columns). + * <p> + * A valid index must be specified before calling this method. + */ + public CursorBuilder setSpecificEntry(Object... specificEntry) { + if(specificEntry != null) { + setSpecificRow(_index.constructIndexRowFromEntry(specificEntry)); + } + return this; + } + + + /** * Sets the starting row for a range based index cursor. * <p> * A valid index must be specified before calling this method. @@ -161,7 +186,7 @@ public class CursorBuilder { /** * Sets the starting row for a range based index cursor to the given entry - * (where the given values correspond to the indexes columns). + * (where the given values correspond to the index's columns). * <p> * A valid index must be specified before calling this method. */ @@ -193,7 +218,7 @@ public class CursorBuilder { /** * Sets the ending row for a range based index cursor to the given entry - * (where the given values correspond to the indexes columns). + * (where the given values correspond to the index's columns). * <p> * A valid index must be specified before calling this method. */ diff --git a/src/java/com/healthmarketscience/jackcess/Database.java b/src/java/com/healthmarketscience/jackcess/Database.java index 568e8d6..be2587b 100644 --- a/src/java/com/healthmarketscience/jackcess/Database.java +++ b/src/java/com/healthmarketscience/jackcess/Database.java @@ -55,6 +55,7 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; +import org.apache.commons.lang.ObjectUtils; import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -87,33 +88,57 @@ public class Database /** System catalog always lives on page 2 */ private static final int PAGE_SYSTEM_CATALOG = 2; + /** Name of the system catalog */ + private static final String TABLE_SYSTEM_CATALOG = "MSysObjects"; /** this is the access control bit field for created tables. the value used is equivalent to full access (Visual Basic DAO PermissionEnum constant: dbSecFullAccess) */ private static final Integer SYS_FULL_ACCESS_ACM = 1048575; + + /** ACE table column name of the actual access control entry */ + private static final String ACE_COL_ACM = "ACM"; + /** ACE table column name of the inheritable attributes flag */ + private static final String ACE_COL_F_INHERITABLE = "FInheritable"; + /** ACE table column name of the relevant objectId */ + private static final String ACE_COL_OBJECT_ID = "ObjectId"; + /** ACE table column name of the relevant userId */ + private static final String ACE_COL_SID = "SID"; + + /** Relationship table column name of the column count */ + private static final String REL_COL_COLUMN_COUNT = "ccolumn"; + /** Relationship table column name of the flags */ + private static final String REL_COL_FLAGS = "grbit"; + /** Relationship table column name of the index of the columns */ + private static final String REL_COL_COLUMN_INDEX = "icolumn"; + /** Relationship table column name of the "to" column name */ + private static final String REL_COL_TO_COLUMN = "szColumn"; + /** Relationship table column name of the "to" table name */ + private static final String REL_COL_TO_TABLE = "szObject"; + /** Relationship table column name of the "from" column name */ + private static final String REL_COL_FROM_COLUMN = "szReferencedColumn"; + /** Relationship table column name of the "from" table name */ + private static final String REL_COL_FROM_TABLE = "szReferencedObject"; + /** Relationship table column name of the relationship */ + private static final String REL_COL_NAME = "szRelationship"; - private static final String COL_ACM = "ACM"; - /** System catalog column name of the date a system object was created */ - private static final String COL_DATE_CREATE = "DateCreate"; - /** System catalog column name of the date a system object was updated */ - private static final String COL_DATE_UPDATE = "DateUpdate"; - private static final String COL_F_INHERITABLE = "FInheritable"; - private static final String COL_FLAGS = "Flags"; - /** - * System catalog column name of the page on which system object definitions - * are stored - */ - private static final String COL_ID = "Id"; + /** System catalog column name of the page on which system object definitions + are stored */ + private static final String CAT_COL_ID = "Id"; /** System catalog column name of the name of a system object */ - private static final String COL_NAME = "Name"; - private static final String COL_OBJECT_ID = "ObjectId"; - private static final String COL_OWNER = "Owner"; + private static final String CAT_COL_NAME = "Name"; + private static final String CAT_COL_OWNER = "Owner"; /** System catalog column name of a system object's parent's id */ - private static final String COL_PARENT_ID = "ParentId"; - private static final String COL_SID = "SID"; + private static final String CAT_COL_PARENT_ID = "ParentId"; /** System catalog column name of the type of a system object */ - private static final String COL_TYPE = "Type"; + private static final String CAT_COL_TYPE = "Type"; + /** System catalog column name of the date a system object was created */ + private static final String CAT_COL_DATE_CREATE = "DateCreate"; + /** System catalog column name of the date a system object was updated */ + private static final String CAT_COL_DATE_UPDATE = "DateUpdate"; + /** System catalog column name of the flags column */ + private static final String CAT_COL_FLAGS = "Flags"; + /** Empty database template for creating new databases */ private static final String EMPTY_MDB = "com/healthmarketscience/jackcess/empty.mdb"; /** Prefix for column or table names that are reserved words */ @@ -124,12 +149,14 @@ public class Database private static final String SYSTEM_OBJECT_NAME_TABLES = "Tables"; /** Name of the table that contains system access control entries */ private static final String TABLE_SYSTEM_ACES = "MSysACEs"; + /** Name of the table that contains table relationships */ + private static final String TABLE_SYSTEM_RELATIONSHIPS = "MSysRelationships"; /** System object type for table definitions */ private static final Short TYPE_TABLE = (short) 1; /** the columns to read when reading system catalog initially */ private static Collection<String> SYSTEM_CATALOG_COLUMNS = - new HashSet<String>(Arrays.asList(COL_NAME, COL_TYPE, COL_ID)); + new HashSet<String>(Arrays.asList(CAT_COL_NAME, CAT_COL_TYPE, CAT_COL_ID)); /** @@ -193,6 +220,10 @@ public class Database private Table _systemCatalog; /** System access control entries table */ private Table _accessControlEntries; + /** page number of the system relationships table */ + private Integer _relationshipsPageNumber; + /** System relationships table (initialized on first use) */ + private Table _relationships; /** SIDs to use for the ACEs added for new tables */ private final List<byte[]> _newTableSIDs = new ArrayList<byte[]>(); @@ -318,6 +349,9 @@ public class Database return _systemCatalog; } + /** + * @return The system Access Control Entries table + */ public Table getAccessControlEntries() { return _accessControlEntries; } @@ -326,26 +360,23 @@ public class Database * Read the system catalog */ private void readSystemCatalog() throws IOException { - _pageChannel.readPage(_buffer, PAGE_SYSTEM_CATALOG); - byte pageType = _buffer.get(); - if (pageType != PageTypes.TABLE_DEF) { - throw new IOException("Looking for system catalog at page " + - PAGE_SYSTEM_CATALOG + ", but page type is " + pageType); - } - _systemCatalog = new Table(this, _buffer, PAGE_SYSTEM_CATALOG, - "System Catalog"); - Map<String,Object> row; - while ( (row = _systemCatalog.getNextRow(SYSTEM_CATALOG_COLUMNS)) != null) + _systemCatalog = readTable(TABLE_SYSTEM_CATALOG, PAGE_SYSTEM_CATALOG); + for(Map<String,Object> row : + Cursor.createCursor(_systemCatalog).iterable( + SYSTEM_CATALOG_COLUMNS)) { - String name = (String) row.get(COL_NAME); - if (name != null && TYPE_TABLE.equals(row.get(COL_TYPE))) { + String name = (String) row.get(CAT_COL_NAME); + if (name != null && TYPE_TABLE.equals(row.get(CAT_COL_TYPE))) { if (!name.startsWith(PREFIX_SYSTEM)) { - addTable((String) row.get(COL_NAME), (Integer) row.get(COL_ID)); - } else if (TABLE_SYSTEM_ACES.equals(name)) { - readAccessControlEntries(((Integer) row.get(COL_ID)).intValue()); + addTable((String) row.get(CAT_COL_NAME), (Integer) row.get(CAT_COL_ID)); + } else if(TABLE_SYSTEM_ACES.equals(name)) { + int pageNumber = (Integer)row.get(CAT_COL_ID); + _accessControlEntries = readTable(TABLE_SYSTEM_ACES, pageNumber); + } else if(TABLE_SYSTEM_RELATIONSHIPS.equals(name)) { + _relationshipsPageNumber = (Integer)row.get(CAT_COL_ID); } } else if (SYSTEM_OBJECT_NAME_TABLES.equals(name)) { - _tableParentId = (Integer) row.get(COL_ID); + _tableParentId = (Integer) row.get(CAT_COL_ID); } } @@ -365,23 +396,6 @@ public class Database } /** - * Read the system access control entries table - * @param pageNum Page number of the table def - */ - private void readAccessControlEntries(int pageNum) throws IOException { - ByteBuffer buffer = _pageChannel.createPageBuffer(); - _pageChannel.readPage(buffer, pageNum); - byte pageType = buffer.get(); - if (pageType != PageTypes.TABLE_DEF) { - throw new IOException("Looking for " + TABLE_SYSTEM_ACES + - " at page " + pageNum + - ", but page type is " + pageType); - } - _accessControlEntries = new Table(this, buffer, pageNum, - "Access Control Entries"); - } - - /** * @return The names of all of the user tables (String) */ public Set<String> getTableNames() { @@ -416,10 +430,8 @@ public class Database if ((tableInfo == null) || (tableInfo.pageNumber == null)) { return null; } - - int pageNumber = tableInfo.pageNumber.intValue(); - _pageChannel.readPage(_buffer, pageNumber); - return new Table(this, _buffer, pageNumber, tableInfo.tableName); + + return readTable(tableInfo.tableName, tableInfo.pageNumber); } /** @@ -461,13 +473,95 @@ public class Database addToSystemCatalog(name, tdefPageNumber); addToAccessControlEntries(tdefPageNumber); } + + /** + * Finds all the relationships in the database between the given tables. + */ + public List<Relationship> getRelationships(Table table1, Table table2) + throws IOException + { + // the relationships table does not get loaded until first accessed + if(_relationships == null) { + if(_relationshipsPageNumber == null) { + throw new IOException("Could not find system relationships table"); + } + _relationships = readTable(TABLE_SYSTEM_RELATIONSHIPS, + _relationshipsPageNumber); + } + + if(ObjectUtils.equals(table1.getName(), table2.getName())) { + throw new IllegalArgumentException("Must provide two different tables"); + } + + List<Relationship> relationships = new ArrayList<Relationship>(); + Cursor cursor = createCursorWithOptionalIndex( + _relationships, REL_COL_FROM_TABLE, table1.getName()); + collectRelationships(cursor, table1, table2, relationships); + cursor = createCursorWithOptionalIndex( + _relationships, REL_COL_TO_TABLE, table1.getName()); + collectRelationships(cursor, table2, table1, relationships); + return relationships; + } + + /** + * Finds the relationships matching the given from and to tables from the + * given cursor and adds them to the given list. + */ + private void collectRelationships( + Cursor cursor, Table fromTable, Table toTable, + List<Relationship> relationships) + { + for(Map<String,Object> row : cursor) { + String fromName = (String)row.get(REL_COL_FROM_TABLE); + String toName = (String)row.get(REL_COL_TO_TABLE); + + if(fromTable.getName().equals(fromName) && + toTable.getName().equals(toName)) + { + + String relName = (String)row.get(REL_COL_NAME); + + // found more info for a relationship. see if we already have some + // info for this relationship + Relationship rel = null; + for(Relationship tmp : relationships) { + if(tmp.getName().equals(relName)) { + rel = tmp; + break; + } + } + + if(rel == null) { + // new relationship + int numCols = (Integer)row.get(REL_COL_COLUMN_COUNT); + int flags = (Integer)row.get(REL_COL_FLAGS); + rel = new Relationship(relName, fromTable, toTable, + flags, numCols); + relationships.add(rel); + } + + // add column info + int colIdx = (Integer)row.get(REL_COL_COLUMN_INDEX); + Column fromCol = fromTable.getColumn( + (String)row.get(REL_COL_FROM_COLUMN)); + Column toCol = toTable.getColumn( + (String)row.get(REL_COL_TO_COLUMN)); + + rel.getFromColumns().set(colIdx, fromCol); + rel.getToColumns().set(colIdx, toCol); + } + } + } + /** * Add a new table to the system catalog * @param name Table name * @param pageNumber Page number that contains the table definition */ - private void addToSystemCatalog(String name, int pageNumber) throws IOException { + private void addToSystemCatalog(String name, int pageNumber) + throws IOException + { Object[] catalogRow = new Object[_systemCatalog.getColumnCount()]; int idx = 0; Date creationTime = new Date(); @@ -475,21 +569,20 @@ public class Database iter.hasNext(); idx++) { Column col = iter.next(); - if (COL_ID.equals(col.getName())) { + if (CAT_COL_ID.equals(col.getName())) { catalogRow[idx] = Integer.valueOf(pageNumber); - } else if (COL_NAME.equals(col.getName())) { + } else if (CAT_COL_NAME.equals(col.getName())) { catalogRow[idx] = name; - } else if (COL_TYPE.equals(col.getName())) { + } else if (CAT_COL_TYPE.equals(col.getName())) { catalogRow[idx] = TYPE_TABLE; - } else if (COL_DATE_CREATE.equals(col.getName()) || - COL_DATE_UPDATE.equals(col.getName())) - { + } else if (CAT_COL_DATE_CREATE.equals(col.getName()) || + CAT_COL_DATE_UPDATE.equals(col.getName())) { catalogRow[idx] = creationTime; - } else if (COL_PARENT_ID.equals(col.getName())) { + } else if (CAT_COL_PARENT_ID.equals(col.getName())) { catalogRow[idx] = _tableParentId; - } else if (COL_FLAGS.equals(col.getName())) { + } else if (CAT_COL_FLAGS.equals(col.getName())) { catalogRow[idx] = Integer.valueOf(0); - } else if (COL_OWNER.equals(col.getName())) { + } else if (CAT_COL_OWNER.equals(col.getName())) { byte[] owner = new byte[2]; catalogRow[idx] = owner; owner[0] = (byte) 0xcf; @@ -509,10 +602,10 @@ public class Database initNewTableSIDs(); } - Column acmCol = _accessControlEntries.getColumn(COL_ACM); - Column inheritCol = _accessControlEntries.getColumn(COL_F_INHERITABLE); - Column objIdCol = _accessControlEntries.getColumn(COL_OBJECT_ID); - Column sidCol = _accessControlEntries.getColumn(COL_SID); + Column acmCol = _accessControlEntries.getColumn(ACE_COL_ACM); + Column inheritCol = _accessControlEntries.getColumn(ACE_COL_F_INHERITABLE); + Column objIdCol = _accessControlEntries.getColumn(ACE_COL_OBJECT_ID); + Column sidCol = _accessControlEntries.getColumn(ACE_COL_SID); // construct a collection of ACE entries mimicing those of our parent, the // "Tables" system object @@ -533,13 +626,15 @@ public class Database */ private void initNewTableSIDs() throws IOException { - // search for ACEs matching the tableParentId. - // FIXME we could potentially use an index to do this, but index handling - // support is sketchy enough that i wouldn't want to trust it just now. - for(Map<String, Object> row : Cursor.createCursor(_accessControlEntries)) { - Integer objId = (Integer)row.get(COL_OBJECT_ID); + // search for ACEs matching the tableParentId. use the index on the + // objectId column if found (should be there) + Cursor cursor = createCursorWithOptionalIndex( + _accessControlEntries, ACE_COL_OBJECT_ID, _tableParentId); + + for(Map<String, Object> row : cursor) { + Integer objId = (Integer)row.get(ACE_COL_OBJECT_ID); if(_tableParentId.equals(objId)) { - _newTableSIDs.add((byte[])row.get(COL_SID)); + _newTableSIDs.add((byte[])row.get(ACE_COL_SID)); } } @@ -548,6 +643,41 @@ public class Database _newTableSIDs.add(SYS_DEFAULT_SID); } } + + /** + * Reads a table with the given name from the given pageNumber. + */ + private Table readTable(String name, int pageNumber) + throws IOException + { + _pageChannel.readPage(_buffer, pageNumber); + byte pageType = _buffer.get(0); + if (pageType != PageTypes.TABLE_DEF) { + throw new IOException("Looking for " + name + " at page " + pageNumber + + ", but page type is " + pageType); + } + return new Table(this, _buffer, pageNumber, name); + } + + /** + * Creates a Cursor restricted to the given column value if possible (using + * an existing index), otherwise a simple table cursor. + */ + private static Cursor createCursorWithOptionalIndex( + Table table, String colName, Object colValue) + throws IOException + { + try { + return new CursorBuilder(table) + .setIndexByColumns(table.getColumn(colName)) + .setSpecificEntry(colValue) + .toCursor(); + } catch(IllegalArgumentException e) { + LOG.info("Could not find expected index on table " + table.getName()); + } + // use table scan instead + return Cursor.createCursor(table); + } /** * Copy an existing JDBC ResultSet into a new table in this database diff --git a/src/java/com/healthmarketscience/jackcess/Index.java b/src/java/com/healthmarketscience/jackcess/Index.java index 0146c2b..fbf4390 100644 --- a/src/java/com/healthmarketscience/jackcess/Index.java +++ b/src/java/com/healthmarketscience/jackcess/Index.java @@ -643,15 +643,22 @@ public class Index implements Comparable<Index> { { initialize(); Position startPos = FIRST_POSITION; + byte[] startEntryBytes = null; if(startRow != null) { - Entry startEntry = new Entry(createEntryBytes(startRow), + startEntryBytes = createEntryBytes(startRow); + Entry startEntry = new Entry(startEntryBytes, (startInclusive ? RowId.FIRST_ROW_ID : RowId.LAST_ROW_ID)); startPos = new Position(FIRST_ENTRY_IDX, startEntry); } Position endPos = LAST_POSITION; if(endRow != null) { - Entry endEntry = new Entry(createEntryBytes(endRow), + // reuse startEntryBytes if startRow and endRow are same array. this is + // common for "lookup" code + byte[] endEntryBytes = ((startRow == endRow) ? + startEntryBytes : + createEntryBytes(endRow)); + Entry endEntry = new Entry(endEntryBytes, (endInclusive ? RowId.LAST_ROW_ID : RowId.FIRST_ROW_ID)); endPos = new Position(LAST_ENTRY_IDX, endEntry); diff --git a/src/java/com/healthmarketscience/jackcess/Relationship.java b/src/java/com/healthmarketscience/jackcess/Relationship.java new file mode 100644 index 0000000..580fca3 --- /dev/null +++ b/src/java/com/healthmarketscience/jackcess/Relationship.java @@ -0,0 +1,109 @@ +// Copyright (c) 2008 Health Market Science, Inc. + +package com.healthmarketscience.jackcess; + +import java.util.Collections; +import java.util.List; +import java.util.ArrayList; + +/** + * Information about a relationship between two tables in the database. + * + * @author James Ahlborn + */ +public class Relationship { + + /** flag indicating one-to-one relationship */ + private static final int ONE_TO_ONE_FLAG = 0x00000001; + /** flag indicating no referential integrity */ + private static final int NO_REFERENTIAL_INTEGRITY_FLAG = 0x00000002; + /** flag indicating cascading updates (requires referential integrity) */ + private static final int CASCADE_UPDATES_FLAG = 0x00000100; + /** flag indicating cascading deletes (requires referential integrity) */ + private static final int CASCADE_DELETES_FLAG = 0x00001000; + /** flag indicating left outer join */ + private static final int LEFT_OUTER_JOIN_FLAG = 0x01000000; + /** flag indicating right outer join */ + private static final int RIGHT_OUTER_JOIN_FLAG = 0x02000000; + + /** the name of this relationship */ + private final String _name; + /** the "from" table in this relationship */ + private final Table _fromTable; + /** the "to" table in this relationship */ + private final Table _toTable; + /** the columns in the "from" table in this relationship (aligned w/ + toColumns list) */ + private List<Column> _toColumns; + /** the columns in the "to" table in this relationship (aligned w/ + toColumns list) */ + private List<Column> _fromColumns; + /** the various flags describing this relationship */ + private final int _flags; + + public Relationship(String name, Table fromTable, Table toTable, int flags, + int numCols) + { + _name = name; + _fromTable = fromTable; + _fromColumns = new ArrayList<Column>( + Collections.nCopies(numCols, (Column)null)); + _toTable = toTable; + _toColumns = new ArrayList<Column>( + Collections.nCopies(numCols, (Column)null)); + _flags = flags; + } + + public String getName() { + return _name; + } + + public Table getFromTable() { + return _fromTable; + } + + public List<Column> getFromColumns() { + return _fromColumns; + } + + public Table getToTable() { + return _toTable; + } + + public List<Column> getToColumns() { + return _toColumns; + } + + public int getFlags() { + return _flags; + } + + public boolean isOneToOne() { + return hasFlag(ONE_TO_ONE_FLAG); + } + + public boolean hasReferentialIntegrity() { + return !hasFlag(NO_REFERENTIAL_INTEGRITY_FLAG); + } + + public boolean cascadeUpdates() { + return hasFlag(CASCADE_UPDATES_FLAG); + } + + public boolean cascadeDeletes() { + return hasFlag(CASCADE_DELETES_FLAG); + } + + public boolean isLeftOuterJoin() { + return hasFlag(LEFT_OUTER_JOIN_FLAG); + } + + public boolean isRightOuterJoin() { + return hasFlag(RIGHT_OUTER_JOIN_FLAG); + } + + private boolean hasFlag(int flagMask) { + return((getFlags() & flagMask) != 0); + } + +} |