]> source.dussan.org Git - jackcess.git/commitdiff
add support for reading table relationships; minor refactor of table creation; minor...
authorJames Ahlborn <jtahlborn@yahoo.com>
Tue, 11 Mar 2008 20:40:13 +0000 (20:40 +0000)
committerJames Ahlborn <jtahlborn@yahoo.com>
Tue, 11 Mar 2008 20:40:13 +0000 (20:40 +0000)
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@266 f203690c-595d-4dc9-a70b-905162fa7fd2

src/changes/changes.xml
src/java/com/healthmarketscience/jackcess/Cursor.java
src/java/com/healthmarketscience/jackcess/CursorBuilder.java
src/java/com/healthmarketscience/jackcess/Database.java
src/java/com/healthmarketscience/jackcess/Index.java
src/java/com/healthmarketscience/jackcess/Relationship.java [new file with mode: 0644]

index 4c5b113d462e8f9ba497fa58c18ec2c09fb5c87b..e9826154198b97d2c1a7b45a42350aebaa4d5c04 100644 (file)
@@ -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">
index 1e6ce52fc9afa4fe69251af6b46bac011b23ae73..7835c936668877bad855d7c4deca64e60e879f64 100644 (file)
@@ -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;
         }
index 4688fac5b18d8b8cc81b701fb2dcb1758d503ee5..57b2f18d8c58b074de345cb2ff70c94f810b4181 100644 (file)
@@ -149,6 +149,31 @@ public class CursorBuilder {
     return this;
   }
 
+  /**
+   * 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>
@@ -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.
    */
index 568e8d6e7cced16a747934b99b852df7410d8664..be2587b3a24673ce317d48fc6496d8b1f24d8feb 100644 (file)
@@ -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);
       }
     }
 
@@ -364,23 +395,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)
    */
@@ -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
index 0146c2bd0969bbd8d837d285cd06565389c6b77a..fbf4390c567312b5031000b0c46967e0e7a72fa1 100644 (file)
@@ -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 (file)
index 0000000..580fca3
--- /dev/null
@@ -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);
+  }
+
+}