Coverage Report - com.healthmarketscience.jackcess.Database
 
Classes in this File Line Coverage Branch Coverage Complexity
Database
85%
240/283
75%
113/150
0
Database$1
N/A
N/A
0
Database$TableInfo
100%
5/5
N/A
0
Database$TableIterator
64%
7/11
50%
1/2
0
 
 1  
 /*
 2  
 Copyright (c) 2005 Health Market Science, Inc.
 3  
 
 4  
 This library is free software; you can redistribute it and/or
 5  
 modify it under the terms of the GNU Lesser General Public
 6  
 License as published by the Free Software Foundation; either
 7  
 version 2.1 of the License, or (at your option) any later version.
 8  
 
 9  
 This library is distributed in the hope that it will be useful,
 10  
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 11  
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 12  
 Lesser General Public License for more details.
 13  
 
 14  
 You should have received a copy of the GNU Lesser General Public
 15  
 License along with this library; if not, write to the Free Software
 16  
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 17  
 USA
 18  
 
 19  
 You can contact Health Market Science at info@healthmarketscience.com
 20  
 or at the following address:
 21  
 
 22  
 Health Market Science
 23  
 2700 Horizon Drive
 24  
 Suite 200
 25  
 King of Prussia, PA 19406
 26  
 */
 27  
 
 28  
 package com.healthmarketscience.jackcess;
 29  
 
 30  
 import java.io.BufferedReader;
 31  
 import java.io.Closeable;
 32  
 import java.io.File;
 33  
 import java.io.FileNotFoundException;
 34  
 import java.io.FileReader;
 35  
 import java.io.Flushable;
 36  
 import java.io.IOException;
 37  
 import java.io.RandomAccessFile;
 38  
 import java.nio.ByteBuffer;
 39  
 import java.nio.channels.Channels;
 40  
 import java.nio.channels.FileChannel;
 41  
 import java.sql.ResultSet;
 42  
 import java.sql.ResultSetMetaData;
 43  
 import java.sql.SQLException;
 44  
 import java.util.ArrayList;
 45  
 import java.util.Arrays;
 46  
 import java.util.Collection;
 47  
 import java.util.ConcurrentModificationException;
 48  
 import java.util.Date;
 49  
 import java.util.HashMap;
 50  
 import java.util.HashSet;
 51  
 import java.util.Iterator;
 52  
 import java.util.LinkedList;
 53  
 import java.util.List;
 54  
 import java.util.Map;
 55  
 import java.util.NoSuchElementException;
 56  
 import java.util.Set;
 57  
 
 58  
 import org.apache.commons.lang.builder.ToStringBuilder;
 59  
 import org.apache.commons.logging.Log;
 60  
 import org.apache.commons.logging.LogFactory;
 61  
 
 62  
 /**
 63  
  * An Access database.
 64  
  * <p>
 65  
  * There is now experimental, optional support for large indexes (disabled by
 66  
  * default).  This optional support can be enabled via a few different means:
 67  
  * <ul>
 68  
  * <li>Setting the system property {@value #USE_BIG_INDEX_PROPERTY} to
 69  
  *     {@code "true"} will enable "large" index support accross the jvm</li>
 70  
  * <li>Calling {@link #setUseBigIndex} with {@code true} on a Database
 71  
  *     instance will enable "large" index support for all tables subsequently
 72  
  *     created from that instance</li>
 73  
  * <li>Calling {@link #getTable(String,boolean)} can selectively
 74  
  *     enable/disable "large" index support on a per-table basis (overriding
 75  
  *     any Database or system property setting)</li>
 76  
  * </ul>
 77  
  *
 78  
  * @author Tim McCune
 79  
  */
 80  
 public class Database
 81  
   implements Iterable<Table>, Closeable, Flushable
 82  
 {
 83  
   
 84  1
   private static final Log LOG = LogFactory.getLog(Database.class);
 85  
 
 86  
   /** this is the default "userId" used if we cannot find existing info.  this
 87  
       seems to be some standard "Admin" userId for access files */
 88  1
   private static final byte[] SYS_DEFAULT_SID = new byte[2];
 89  
   static {
 90  1
     SYS_DEFAULT_SID[0] = (byte) 0xA6;
 91  1
     SYS_DEFAULT_SID[1] = (byte) 0x33;
 92  
   }
 93  
 
 94  
   /** default value for the auto-sync value ({@code true}).  this is slower,
 95  
       but leaves more chance of a useable database in the face of failures. */
 96  
   public static final boolean DEFAULT_AUTO_SYNC = true;
 97  
 
 98  
   /** system property which can be used to make big index support the
 99  
       default. */
 100  
   public static final String USE_BIG_INDEX_PROPERTY =
 101  
     "com.healthmarketscience.jackcess.bigIndex";
 102  
   
 103  
   /** Batch commit size for copying other result sets into this database */
 104  
   private static final int COPY_TABLE_BATCH_SIZE = 200;
 105  
   
 106  
   /** System catalog always lives on page 2 */
 107  
   private static final int PAGE_SYSTEM_CATALOG = 2;
 108  
   /** Name of the system catalog */
 109  
   private static final String TABLE_SYSTEM_CATALOG = "MSysObjects";
 110  
 
 111  
   /** this is the access control bit field for created tables.  the value used
 112  
       is equivalent to full access (Visual Basic DAO PermissionEnum constant:
 113  
       dbSecFullAccess) */
 114  1
   private static final Integer SYS_FULL_ACCESS_ACM = 1048575;
 115  
 
 116  
   /** ACE table column name of the actual access control entry */
 117  
   private static final String ACE_COL_ACM = "ACM";
 118  
   /** ACE table column name of the inheritable attributes flag */
 119  
   private static final String ACE_COL_F_INHERITABLE = "FInheritable";
 120  
   /** ACE table column name of the relevant objectId */
 121  
   private static final String ACE_COL_OBJECT_ID = "ObjectId";
 122  
   /** ACE table column name of the relevant userId */
 123  
   private static final String ACE_COL_SID = "SID";
 124  
 
 125  
   /** Relationship table column name of the column count */
 126  
   private static final String REL_COL_COLUMN_COUNT = "ccolumn";
 127  
   /** Relationship table column name of the flags */
 128  
   private static final String REL_COL_FLAGS = "grbit";
 129  
   /** Relationship table column name of the index of the columns */
 130  
   private static final String REL_COL_COLUMN_INDEX = "icolumn";
 131  
   /** Relationship table column name of the "to" column name */
 132  
   private static final String REL_COL_TO_COLUMN = "szColumn";
 133  
   /** Relationship table column name of the "to" table name */
 134  
   private static final String REL_COL_TO_TABLE = "szObject";
 135  
   /** Relationship table column name of the "from" column name */
 136  
   private static final String REL_COL_FROM_COLUMN = "szReferencedColumn";
 137  
   /** Relationship table column name of the "from" table name */
 138  
   private static final String REL_COL_FROM_TABLE = "szReferencedObject";
 139  
   /** Relationship table column name of the relationship */
 140  
   private static final String REL_COL_NAME = "szRelationship";
 141  
   
 142  
   /** System catalog column name of the page on which system object definitions
 143  
       are stored */
 144  
   private static final String CAT_COL_ID = "Id";
 145  
   /** System catalog column name of the name of a system object */
 146  
   private static final String CAT_COL_NAME = "Name";
 147  
   private static final String CAT_COL_OWNER = "Owner";
 148  
   /** System catalog column name of a system object's parent's id */
 149  
   private static final String CAT_COL_PARENT_ID = "ParentId";
 150  
   /** System catalog column name of the type of a system object */
 151  
   private static final String CAT_COL_TYPE = "Type";
 152  
   /** System catalog column name of the date a system object was created */
 153  
   private static final String CAT_COL_DATE_CREATE = "DateCreate";
 154  
   /** System catalog column name of the date a system object was updated */
 155  
   private static final String CAT_COL_DATE_UPDATE = "DateUpdate";
 156  
   /** System catalog column name of the flags column */
 157  
   private static final String CAT_COL_FLAGS = "Flags";
 158  
   
 159  
   /** Empty database template for creating new databases */
 160  
   private static final String EMPTY_MDB = "com/healthmarketscience/jackcess/empty.mdb";
 161  
   /** Prefix for column or table names that are reserved words */
 162  
   private static final String ESCAPE_PREFIX = "x";
 163  
   /** Prefix that flags system tables */
 164  
   private static final String PREFIX_SYSTEM = "MSys";
 165  
   /** Name of the system object that is the parent of all tables */
 166  
   private static final String SYSTEM_OBJECT_NAME_TABLES = "Tables";
 167  
   /** Name of the table that contains system access control entries */
 168  
   private static final String TABLE_SYSTEM_ACES = "MSysACEs";
 169  
   /** Name of the table that contains table relationships */
 170  
   private static final String TABLE_SYSTEM_RELATIONSHIPS = "MSysRelationships";
 171  
   /** System object type for table definitions */
 172  1
   private static final Short TYPE_TABLE = (short) 1;
 173  
 
 174  
   /** the columns to read when reading system catalog initially */
 175  1
   private static Collection<String> SYSTEM_CATALOG_COLUMNS =
 176  
     new HashSet<String>(Arrays.asList(CAT_COL_NAME, CAT_COL_TYPE, CAT_COL_ID));
 177  
   
 178  
   
 179  
   /**
 180  
    * All of the reserved words in Access that should be escaped when creating
 181  
    * table or column names
 182  
    */
 183  1
   private static final Set<String> RESERVED_WORDS = new HashSet<String>();
 184  
   static {
 185  
     //Yup, there's a lot.
 186  1
     RESERVED_WORDS.addAll(Arrays.asList(
 187  
        "add", "all", "alphanumeric", "alter", "and", "any", "application", "as",
 188  
        "asc", "assistant", "autoincrement", "avg", "between", "binary", "bit",
 189  
        "boolean", "by", "byte", "char", "character", "column", "compactdatabase",
 190  
        "constraint", "container", "count", "counter", "create", "createdatabase",
 191  
        "createfield", "creategroup", "createindex", "createobject", "createproperty",
 192  
        "createrelation", "createtabledef", "createuser", "createworkspace",
 193  
        "currency", "currentuser", "database", "date", "datetime", "delete",
 194  
        "desc", "description", "disallow", "distinct", "distinctrow", "document",
 195  
        "double", "drop", "echo", "else", "end", "eqv", "error", "exists", "exit",
 196  
        "false", "field", "fields", "fillcache", "float", "float4", "float8",
 197  
        "foreign", "form", "forms", "from", "full", "function", "general",
 198  
        "getobject", "getoption", "gotopage", "group", "group by", "guid", "having",
 199  
        "idle", "ieeedouble", "ieeesingle", "if", "ignore", "imp", "in", "index",
 200  
        "indexes", "inner", "insert", "inserttext", "int", "integer", "integer1",
 201  
        "integer2", "integer4", "into", "is", "join", "key", "lastmodified", "left",
 202  
        "level", "like", "logical", "logical1", "long", "longbinary", "longtext",
 203  
        "macro", "match", "max", "min", "mod", "memo", "module", "money", "move",
 204  
        "name", "newpassword", "no", "not", "null", "number", "numeric", "object",
 205  
        "oleobject", "off", "on", "openrecordset", "option", "or", "order", "outer",
 206  
        "owneraccess", "parameter", "parameters", "partial", "percent", "pivot",
 207  
        "primary", "procedure", "property", "queries", "query", "quit", "real",
 208  
        "recalc", "recordset", "references", "refresh", "refreshlink",
 209  
        "registerdatabase", "relation", "repaint", "repairdatabase", "report",
 210  
        "reports", "requery", "right", "screen", "section", "select", "set",
 211  
        "setfocus", "setoption", "short", "single", "smallint", "some", "sql",
 212  
        "stdev", "stdevp", "string", "sum", "table", "tabledef", "tabledefs",
 213  
        "tableid", "text", "time", "timestamp", "top", "transform", "true", "type",
 214  
        "union", "unique", "update", "user", "value", "values", "var", "varp",
 215  
        "varbinary", "varchar", "where", "with", "workspace", "xor", "year", "yes",
 216  
        "yesno"
 217  
     ));
 218  1
   }
 219  
   
 220  
   /** Buffer to hold database pages */
 221  
   private ByteBuffer _buffer;
 222  
   /** ID of the Tables system object */
 223  
   private Integer _tableParentId;
 224  
   /** Format that the containing database is in */
 225  
   private final JetFormat _format;
 226  
   /**
 227  
    * Map of UPPERCASE table names to page numbers containing their definition
 228  
    * and their stored table name.
 229  
    */
 230  67
   private Map<String, TableInfo> _tableLookup =
 231  
     new HashMap<String, TableInfo>();
 232  
   /** set of table names as stored in the mdb file, created on demand */
 233  
   private Set<String> _tableNames;
 234  
   /** Reads and writes database pages */
 235  
   private final PageChannel _pageChannel;
 236  
   /** System catalog table */
 237  
   private Table _systemCatalog;
 238  
   /** System access control entries table */
 239  
   private Table _accessControlEntries;
 240  
   /** page number of the system relationships table */
 241  
   private Integer _relationshipsPageNumber;
 242  
   /** System relationships table (initialized on first use) */
 243  
   private Table _relationships;
 244  
   /** SIDs to use for the ACEs added for new tables */
 245  67
   private final List<byte[]> _newTableSIDs = new ArrayList<byte[]>();
 246  
   /** for now, "big index support" is optional */
 247  
   private boolean _useBigIndex;
 248  
   
 249  
   /**
 250  
    * Open an existing Database.  If the existing file is not writeable, the
 251  
    * file will be opened read-only.  Auto-syncing is enabled for the returned
 252  
    * Database.
 253  
    * @param mdbFile File containing the database
 254  
    */
 255  
   public static Database open(File mdbFile) throws IOException {
 256  0
     return open(mdbFile, false);
 257  
   }
 258  
   
 259  
   /**
 260  
    * Open an existing Database.  If the existing file is not writeable or the
 261  
    * readOnly flag is <code>true</code>, the file will be opened read-only.
 262  
    * Auto-syncing is enabled for the returned Database.
 263  
    * @param mdbFile File containing the database
 264  
    * @param readOnly iff <code>true</code>, force opening file in read-only
 265  
    *                 mode
 266  
    */
 267  
   public static Database open(File mdbFile, boolean readOnly)
 268  
     throws IOException
 269  
   {
 270  0
     return open(mdbFile, readOnly, DEFAULT_AUTO_SYNC);
 271  
   }
 272  
   
 273  
   /**
 274  
    * Open an existing Database.  If the existing file is not writeable or the
 275  
    * readOnly flag is <code>true</code>, the file will be opened read-only.
 276  
    * @param mdbFile File containing the database
 277  
    * @param readOnly iff <code>true</code>, force opening file in read-only
 278  
    *                 mode
 279  
    * @param autoSync whether or not to enable auto-syncing on write.  if
 280  
    *                 {@code true}, writes will be immediately flushed to disk.
 281  
    *                 This leaves the database in a (fairly) consistent state
 282  
    *                 on each write, but can be very inefficient for many
 283  
    *                 updates.  if {@code false}, flushing to disk happens at
 284  
    *                 the jvm's leisure, which can be much faster, but may
 285  
    *                 leave the database in an inconsistent state if failures
 286  
    *                 are encountered during writing.
 287  
    */
 288  
   public static Database open(File mdbFile, boolean readOnly, boolean autoSync)
 289  
     throws IOException
 290  
   {    
 291  44
     if(!mdbFile.exists() || !mdbFile.canRead()) {
 292  1
       throw new FileNotFoundException("given file does not exist: " + mdbFile);
 293  
     }
 294  43
     return new Database(openChannel(mdbFile,
 295  
                                     (!mdbFile.canWrite() || readOnly)),
 296  
                         autoSync);
 297  
   }
 298  
   
 299  
   /**
 300  
    * Create a new Database
 301  
    * @param mdbFile Location to write the new database to.  <b>If this file
 302  
    *    already exists, it will be overwritten.</b>
 303  
    */
 304  
   public static Database create(File mdbFile) throws IOException {
 305  0
     return create(mdbFile, DEFAULT_AUTO_SYNC);
 306  
   }
 307  
   
 308  
   /**
 309  
    * Create a new Database
 310  
    * @param mdbFile Location to write the new database to.  <b>If this file
 311  
    *    already exists, it will be overwritten.</b>
 312  
    * @param autoSync whether or not to enable auto-syncing on write.  if
 313  
    *                 {@code true}, writes will be immediately flushed to disk.
 314  
    *                 This leaves the database in a (fairly) consistent state
 315  
    *                 on each write, but can be very inefficient for many
 316  
    *                 updates.  if {@code false}, flushing to disk happens at
 317  
    *                 the jvm's leisure, which can be much faster, but may
 318  
    *                 leave the database in an inconsistent state if failures
 319  
    *                 are encountered during writing.
 320  
    */
 321  
   public static Database create(File mdbFile, boolean autoSync)
 322  
     throws IOException
 323  
   {    
 324  24
     FileChannel channel = openChannel(mdbFile, false);
 325  24
     channel.truncate(0);
 326  24
     channel.transferFrom(Channels.newChannel(
 327  
         Thread.currentThread().getContextClassLoader().getResourceAsStream(
 328  
             EMPTY_MDB)), 0, Integer.MAX_VALUE);
 329  24
     return new Database(channel, autoSync);
 330  
   }
 331  
   
 332  
   private static FileChannel openChannel(File mdbFile, boolean readOnly)
 333  
     throws FileNotFoundException
 334  
   {
 335  67
     String mode = (readOnly ? "r" : "rw");
 336  67
     return new RandomAccessFile(mdbFile, mode).getChannel();
 337  
   }
 338  
   
 339  
   /**
 340  
    * Create a new database by reading it in from a FileChannel.
 341  
    * @param channel File channel of the database.  This needs to be a
 342  
    *    FileChannel instead of a ReadableByteChannel because we need to
 343  
    *    randomly jump around to various points in the file.
 344  
    */
 345  
   protected Database(FileChannel channel, boolean autoSync) throws IOException
 346  67
   {
 347  67
     _format = JetFormat.getFormat(channel);
 348  67
     _pageChannel = new PageChannel(channel, _format, autoSync);
 349  
     // note, it's slighly sketchy to pass ourselves along partially
 350  
     // constructed, but only our _format and _pageChannel refs should be
 351  
     // needed
 352  67
     _pageChannel.initialize(this);
 353  67
     _buffer = _pageChannel.createPageBuffer();
 354  67
     readSystemCatalog();
 355  67
   }
 356  
   
 357  
   public PageChannel getPageChannel() {
 358  228696
     return _pageChannel;
 359  
   }
 360  
 
 361  
   public JetFormat getFormat() {
 362  311478
     return _format;
 363  
   }
 364  
   
 365  
   /**
 366  
    * @return The system catalog table
 367  
    */
 368  
   public Table getSystemCatalog() {
 369  0
     return _systemCatalog;
 370  
   }
 371  
   
 372  
   /**
 373  
    * @return The system Access Control Entries table
 374  
    */
 375  
   public Table getAccessControlEntries() {
 376  0
     return _accessControlEntries;
 377  
   }
 378  
 
 379  
   /**
 380  
    * Whether or not big index support is enabled for tables.
 381  
    */
 382  
   public boolean doUseBigIndex() {
 383  298
     return _useBigIndex;
 384  
   }
 385  
 
 386  
   /**
 387  
    * Set whether or not big index support is enabled for tables.
 388  
    */
 389  
   public void setUseBigIndex(boolean useBigIndex) {
 390  0
     _useBigIndex = useBigIndex;
 391  0
   }
 392  
   
 393  
   /**
 394  
    * Read the system catalog
 395  
    */
 396  
   private void readSystemCatalog() throws IOException {
 397  67
     _systemCatalog = readTable(TABLE_SYSTEM_CATALOG, PAGE_SYSTEM_CATALOG,
 398  
                                defaultUseBigIndex());
 399  
     for(Map<String,Object> row :
 400  67
           Cursor.createCursor(_systemCatalog).iterable(
 401  
               SYSTEM_CATALOG_COLUMNS))
 402  
     {
 403  1326
       String name = (String) row.get(CAT_COL_NAME);
 404  1326
       if (name != null && TYPE_TABLE.equals(row.get(CAT_COL_TYPE))) {
 405  456
         if (!name.startsWith(PREFIX_SYSTEM)) {
 406  96
           addTable((String) row.get(CAT_COL_NAME), (Integer) row.get(CAT_COL_ID));
 407  360
         } else if(TABLE_SYSTEM_ACES.equals(name)) {
 408  67
           int pageNumber = (Integer)row.get(CAT_COL_ID);
 409  67
           _accessControlEntries = readTable(TABLE_SYSTEM_ACES, pageNumber,
 410  
                                             defaultUseBigIndex());
 411  67
         } else if(TABLE_SYSTEM_RELATIONSHIPS.equals(name)) {
 412  67
           _relationshipsPageNumber = (Integer)row.get(CAT_COL_ID);
 413  
         }
 414  870
       } else if (SYSTEM_OBJECT_NAME_TABLES.equals(name)) {
 415  67
         _tableParentId = (Integer) row.get(CAT_COL_ID);
 416  
       }
 417  1326
     }
 418  
 
 419  
     // check for required system values
 420  67
     if(_accessControlEntries == null) {
 421  0
       throw new IOException("Did not find required " + TABLE_SYSTEM_ACES +
 422  
                             " table");
 423  
     }
 424  67
     if(_tableParentId == null) {
 425  0
       throw new IOException("Did not find required parent table id");
 426  
     }
 427  
     
 428  67
     if (LOG.isDebugEnabled()) {
 429  0
       LOG.debug("Finished reading system catalog.  Tables: " +
 430  
                 getTableNames());
 431  
     }
 432  67
   }
 433  
   
 434  
   /**
 435  
    * @return The names of all of the user tables (String)
 436  
    */
 437  
   public Set<String> getTableNames() {
 438  3
     if(_tableNames == null) {
 439  3
       _tableNames = new HashSet<String>();
 440  3
       for(TableInfo tableInfo : _tableLookup.values()) {
 441  29
         _tableNames.add(tableInfo.tableName);
 442  
       }
 443  
     }
 444  3
     return _tableNames;
 445  
   }
 446  
 
 447  
   /**
 448  
    * @return an unmodifiable Iterator of the user Tables in this Database.
 449  
    * @throws IllegalStateException if an IOException is thrown by one of the
 450  
    *         operations, the actual exception will be contained within
 451  
    * @throws ConcurrentModificationException if a table is added to the
 452  
    *         database while an Iterator is in use.
 453  
    */
 454  
   public Iterator<Table> iterator() {
 455  1
     return new TableIterator();
 456  
   }
 457  
 
 458  
   /**
 459  
    * @param name Table name
 460  
    * @return The table, or null if it doesn't exist
 461  
    */
 462  
   public Table getTable(String name) throws IOException {
 463  163
     return getTable(name, defaultUseBigIndex());
 464  
   }
 465  
   
 466  
   /**
 467  
    * @param name Table name
 468  
    * @param useBigIndex whether or not "big index support" should be enabled
 469  
    *                    for the table (this value will override any other
 470  
    *                    settings)
 471  
    * @return The table, or null if it doesn't exist
 472  
    */
 473  
   public Table getTable(String name, boolean useBigIndex) throws IOException {
 474  
 
 475  163
     TableInfo tableInfo = lookupTable(name);
 476  
     
 477  163
     if ((tableInfo == null) || (tableInfo.pageNumber == null)) {
 478  29
       return null;
 479  
     }
 480  
 
 481  134
     return readTable(tableInfo.tableName, tableInfo.pageNumber, useBigIndex);
 482  
   }
 483  
   
 484  
   /**
 485  
    * Create a new table in this database
 486  
    * @param name Name of the table to create
 487  
    * @param columns List of Columns in the table
 488  
    */
 489  
   public void createTable(String name, List<Column> columns)
 490  
     throws IOException
 491  
   {
 492  28
     validateIdentifierName(name, _format.MAX_TABLE_NAME_LENGTH, "table");
 493  
     
 494  28
     if(getTable(name) != null) {
 495  1
       throw new IllegalArgumentException(
 496  
           "Cannot create table with name of existing table");
 497  
     }
 498  
     
 499  27
     if(columns.isEmpty()) {
 500  1
       throw new IllegalArgumentException(
 501  
           "Cannot create table with no columns");
 502  
     }
 503  26
     if(columns.size() > _format.MAX_COLUMNS_PER_TABLE) {
 504  0
       throw new IllegalArgumentException(
 505  
           "Cannot create table with more than " +
 506  
           _format.MAX_COLUMNS_PER_TABLE + " columns");
 507  
     }
 508  
     
 509  26
     Set<String> colNames = new HashSet<String>();
 510  
     // next, validate the column definitions
 511  26
     for(Column column : columns) {
 512  303
       column.validate(_format);
 513  301
       if(!colNames.add(column.getName().toUpperCase())) {
 514  1
         throw new IllegalArgumentException("duplicate column name: " +
 515  
                                            column.getName());
 516  
       }
 517  
     }
 518  
 
 519  23
     if(Table.countAutoNumberColumns(columns) > 1) {
 520  0
       throw new IllegalArgumentException(
 521  
           "Can have at most one AutoNumber column per table");
 522  
     }
 523  
     
 524  
     //Write the tdef page to disk.
 525  23
     int tdefPageNumber = Table.writeTableDefinition(columns, _pageChannel,
 526  
                                                     _format);
 527  
     
 528  
     //Add this table to our internal list.
 529  23
     addTable(name, Integer.valueOf(tdefPageNumber));
 530  
     
 531  
     //Add this table to system tables
 532  23
     addToSystemCatalog(name, tdefPageNumber);
 533  23
     addToAccessControlEntries(tdefPageNumber);    
 534  23
   }
 535  
 
 536  
   /**
 537  
    * Finds all the relationships in the database between the given tables.
 538  
    */
 539  
   public List<Relationship> getRelationships(Table table1, Table table2)
 540  
     throws IOException
 541  
   {
 542  
     // the relationships table does not get loaded until first accessed
 543  8
     if(_relationships == null) {
 544  1
       if(_relationshipsPageNumber == null) {
 545  0
         throw new IOException("Could not find system relationships table");
 546  
       }
 547  1
       _relationships = readTable(TABLE_SYSTEM_RELATIONSHIPS,
 548  
                                  _relationshipsPageNumber,
 549  
                                  defaultUseBigIndex());
 550  
     }
 551  
 
 552  8
     int nameCmp = table1.getName().compareTo(table2.getName());
 553  8
     if(nameCmp == 0) {
 554  1
       throw new IllegalArgumentException("Must provide two different tables");
 555  
     }
 556  7
     if(nameCmp > 0) {
 557  
       // we "order" the two tables given so that we will return a collection
 558  
       // of relationships in the same order regardless of whether we are given
 559  
       // (TableFoo, TableBar) or (TableBar, TableFoo).
 560  3
       Table tmp = table1;
 561  3
       table1 = table2;
 562  3
       table2 = tmp;
 563  
     }
 564  
       
 565  
 
 566  7
     List<Relationship> relationships = new ArrayList<Relationship>();
 567  7
     Cursor cursor = createCursorWithOptionalIndex(
 568  
         _relationships, REL_COL_FROM_TABLE, table1.getName());
 569  7
     collectRelationships(cursor, table1, table2, relationships);
 570  7
     cursor = createCursorWithOptionalIndex(
 571  
         _relationships, REL_COL_TO_TABLE, table1.getName());
 572  7
     collectRelationships(cursor, table2, table1, relationships);
 573  
     
 574  7
     return relationships;
 575  
   }
 576  
 
 577  
   /**
 578  
    * Finds the relationships matching the given from and to tables from the
 579