summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJames Ahlborn <jtahlborn@yahoo.com>2008-03-11 20:40:13 +0000
committerJames Ahlborn <jtahlborn@yahoo.com>2008-03-11 20:40:13 +0000
commit379669147b0118a39d432568c6a3b6cb6c454e6d (patch)
tree936137912fba3a7a39b836a6dd87180b40563209
parentcd8798cb54ad84e55e13ed2831a9512003fa7ecc (diff)
downloadjackcess-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.xml3
-rw-r--r--src/java/com/healthmarketscience/jackcess/Cursor.java79
-rw-r--r--src/java/com/healthmarketscience/jackcess/CursorBuilder.java29
-rw-r--r--src/java/com/healthmarketscience/jackcess/Database.java282
-rw-r--r--src/java/com/healthmarketscience/jackcess/Index.java11
-rw-r--r--src/java/com/healthmarketscience/jackcess/Relationship.java109
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);
+ }
+
+}