]> source.dussan.org Git - jackcess.git/commitdiff
refactor table creation
authorJames Ahlborn <jtahlborn@yahoo.com>
Thu, 17 Nov 2011 04:39:41 +0000 (04:39 +0000)
committerJames Ahlborn <jtahlborn@yahoo.com>
Thu, 17 Nov 2011 04:39:41 +0000 (04:39 +0000)
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@596 f203690c-595d-4dc9-a70b-905162fa7fd2

src/java/com/healthmarketscience/jackcess/Column.java
src/java/com/healthmarketscience/jackcess/Database.java
src/java/com/healthmarketscience/jackcess/ImportFilter.java
src/java/com/healthmarketscience/jackcess/Index.java
src/java/com/healthmarketscience/jackcess/IndexBuilder.java
src/java/com/healthmarketscience/jackcess/IndexData.java
src/java/com/healthmarketscience/jackcess/Table.java
src/java/com/healthmarketscience/jackcess/TableCreator.java [new file with mode: 0644]

index 847fd2bad72800c6ae09464c2de783df03362ca4..32a67cece8606e1fac4336a8c36049e5a1a9d84c 100644 (file)
@@ -1988,10 +1988,10 @@ public class Column implements Comparable<Column> {
    * @param columns List of Columns to write definitions for
    */
   protected static void writeDefinitions(
-      ByteBuffer buffer, List<Column> columns, JetFormat format,
-      Charset charset)
+      TableCreator creator, ByteBuffer buffer)
     throws IOException
   {
+    List<Column> columns = creator.getColumns();
     short columnNumber = (short) 0;
     short fixedOffset = (short) 0;
     short variableOffset = (short) 0;
@@ -2021,7 +2021,7 @@ public class Column implements Comparable<Column> {
       if(col.getType().isTextual()) {
         // this will write 4 bytes (note we don't support writing dbs which
         // use the text code page)
-        writeSortOrder(buffer, col.getTextSortOrder(), format);
+        writeSortOrder(buffer, col.getTextSortOrder(), creator.getFormat());
       } else {
         if(col.getType().getHasScalePrecision()) {
           buffer.put(col.getPrecision());  // numeric precision
@@ -2054,11 +2054,11 @@ public class Column implements Comparable<Column> {
       columnNumber++;
       if (LOG.isDebugEnabled()) {
         LOG.debug("Creating new column def block\n" + ByteUtil.toHexString(
-            buffer, position, format.SIZE_COLUMN_DEF_BLOCK));
+                  buffer, position, creator.getFormat().SIZE_COLUMN_DEF_BLOCK));
       }
     }
     for (Column col : columns) {
-      Table.writeName(buffer, col.getName(), charset);
+      Table.writeName(buffer, col.getName(), creator.getCharset());
     }
   }
 
index 4eb0b9b388ca9d04dc368cc7537b1ce751ed2279..4f2f75379e992a53fd144f5772a1fd1a52e98e42 100644 (file)
@@ -47,10 +47,8 @@ import java.sql.SQLException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.ConcurrentModificationException;
 import java.util.Date;
-import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -1226,100 +1224,24 @@ public class Database
                           List<IndexBuilder> indexes)
     throws IOException
   {
-    validateIdentifierName(name, _format.MAX_TABLE_NAME_LENGTH, "table");
-    
-    if(getTable(name) != null) {
+    if(lookupTable(name) != null) {
       throw new IllegalArgumentException(
           "Cannot create table with name of existing table");
     }
-    
-    if(columns.isEmpty()) {
-      throw new IllegalArgumentException(
-          "Cannot create table with no columns");
-    }
-    if(columns.size() > _format.MAX_COLUMNS_PER_TABLE) {
-      throw new IllegalArgumentException(
-          "Cannot create table with more than " +
-          _format.MAX_COLUMNS_PER_TABLE + " columns");
-    }
-    
-    Column.SortOrder dbSortOrder = null;
-    try {
-      dbSortOrder = getDefaultSortOrder();
-    } catch(IOException e) {
-      // ignored, just use the jet format default
-    }
-
-    Set<String> colNames = new HashSet<String>();
-    // next, validate the column definitions
-    for(Column column : columns) {
-
-      // FIXME for now, we can't create complex columns
-      if(column.getType() == DataType.COMPLEX_TYPE) {
-        throw new UnsupportedOperationException(
-            "Complex column creation is not yet implemented");
-      }
-      
-      column.validate(_format);
-      if(!colNames.add(column.getName().toUpperCase())) {
-        throw new IllegalArgumentException("duplicate column name: " +
-                                           column.getName());
-      }
 
-      // set the sort order to the db default (if unspecified)
-      if(column.getType().isTextual() && (column.getTextSortOrder() == null)) {
-        column.setTextSortOrder(dbSortOrder);
-      }
-    }
-
-    List<Column> autoCols = Table.getAutoNumberColumns(columns);
-    if(autoCols.size() > 1) {
-      // for most autonumber types, we can only have one of each type
-      Set<DataType> autoTypes = EnumSet.noneOf(DataType.class);
-      for(Column c : autoCols) {
-        if(!c.getType().isMultipleAutoNumberAllowed() &&
-           !autoTypes.add(c.getType())) {
-          throw new IllegalArgumentException(
-              "Can have at most one AutoNumber column of type " + c.getType() +
-              " per table");
-        }
-      }
-    }
+    new TableCreator(this, name, columns, indexes).createTable();
+  }
 
-    if(indexes == null) {
-      indexes = Collections.emptyList();
-    }
-    if(!indexes.isEmpty()) {
-      // now, validate the indexes
-      Set<String> idxNames = new HashSet<String>();
-      boolean foundPk = false;
-      for(IndexBuilder index : indexes) {
-        index.validate(colNames);
-        if(!idxNames.add(index.getName().toUpperCase())) {
-          throw new IllegalArgumentException("duplicate index name: " +
-                                             index.getName());
-        }
-        if(index.isPrimaryKey()) {
-          if(foundPk) {
-            throw new IllegalArgumentException(
-                "found second primary key index: " + index.getName());
-          }
-          foundPk = true;
-        }
-      }
-    }
-    
-    //Write the tdef page to disk.
-    int tdefPageNumber = Table.writeTableDefinition(columns, indexes,
-                                                    _pageChannel, _format,
-                                                    getCharset());
-    
+  /**
+   * Adds a newly created table to the relevant internal database structures.
+   */
+  void addNewTable(String name, int tdefPageNumber) throws IOException {
     //Add this table to our internal list.
     addTable(name, Integer.valueOf(tdefPageNumber));
     
     //Add this table to system tables
     addToSystemCatalog(name, tdefPageNumber);
-    addToAccessControlEntries(tdefPageNumber);    
+    addToAccessControlEntries(tdefPageNumber);
   }
 
   /**
index 3cb14166b9864f49c5f256cd34a857cff1e1c8be..6bcc6202c6e4f3aeb0a5087cd34e4ce2ab05204e 100644 (file)
@@ -34,7 +34,7 @@ import java.util.List;
 
 /**
  * Interface which allows customization of the behavior of the
- * <code>Database<</code> import/copy methods.
+ * <code>Database</code> import/copy methods.
  *
  * @author James Ahlborn
  */
index 532b4e1a7bb080dcf89ff7875d3e548458b4651a..88b5237f500a2969bbf8921e4597eaed212bcab2 100644 (file)
@@ -29,7 +29,6 @@ package com.healthmarketscience.jackcess;
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -394,14 +393,15 @@ public class Index implements Comparable<Index> {
    * @param indexes List of IndexBuilders to write definitions for
    */
   protected static void writeDefinitions(
-      ByteBuffer buffer, List<IndexBuilder> indexes, Charset charset)
+      TableCreator creator, ByteBuffer buffer)
     throws IOException
   {
     // write logical index information
-    for(IndexBuilder idx : indexes) {
+    for(IndexBuilder idx : creator.getIndexes()) {
+      TableCreator.IndexState idxState = creator.getIndexState(idx);
       buffer.putInt(Table.MAGIC_TABLE_NUMBER); // seemingly constant magic value which matches the table def
-      buffer.putInt(idx.getIndexNumber()); // index num
-      buffer.putInt(idx.getIndexDataNumber()); // index data num
+      buffer.putInt(idxState.getIndexNumber()); // index num
+      buffer.putInt(idxState.getIndexDataNumber()); // index data num
       buffer.put((byte)0); // related table type
       buffer.putInt(INVALID_INDEX_NUMBER); // related index num
       buffer.putInt(0); // related table definition page number
@@ -412,8 +412,8 @@ public class Index implements Comparable<Index> {
     }
 
     // write index names
-    for(IndexBuilder idx : indexes) {
-      Table.writeName(buffer, idx.getName(), charset);
+    for(IndexBuilder idx : creator.getIndexes()) {
+      Table.writeName(buffer, idx.getName(), creator.getCharset());
     }
   }
 
index 11fa55677e5bc3adc3d79b7aeff00f31aa7d82e1..07ddd771663da6bb7d75f4886b2e58631fc8628e 100644 (file)
@@ -44,13 +44,6 @@ public class IndexBuilder
   /** the names and orderings of the indexed columns */
   private final List<Column> _columns = new ArrayList<Column>();
 
-  // used by table definition writing code
-  private int _indexNumber;
-  private int _indexDataNumber;
-  private byte _umapRowNumber;
-  private int _umapPageNumber;
-  private int _rootPageNumber;
-
   public IndexBuilder(String name) {
     _name = name;
   }
@@ -161,46 +154,6 @@ public class IndexBuilder
     }
   }
 
-  protected int getIndexNumber() {
-    return _indexNumber;
-  }
-
-  protected void setIndexNumber(int newIndexNumber) {
-    _indexNumber = newIndexNumber;
-  }
-
-  protected int getIndexDataNumber() {
-    return _indexDataNumber;
-  }
-
-  protected void setIndexDataNumber(int newIndexDataNumber) {
-    _indexDataNumber = newIndexDataNumber;
-  }
-
-  protected byte getUmapRowNumber() {
-    return _umapRowNumber;
-  }
-
-  protected void setUmapRowNumber(byte newUmapRowNumber) {
-    _umapRowNumber = newUmapRowNumber;
-  }
-
-  protected int getUmapPageNumber() {
-    return _umapPageNumber;
-  }
-
-  protected void setUmapPageNumber(int newUmapPageNumber) {
-    _umapPageNumber = newUmapPageNumber;
-  }
-
-  protected int getRootPageNumber() {
-    return _rootPageNumber;
-  }
-
-  protected void setRootPageNumber(int newRootPageNumber) {
-    _rootPageNumber = newRootPageNumber;
-  }
-
   /**
    * Information about a column in this index (name and ordering).
    */
index b9793156fdce61f09fa660464bb0130515870294..383bf24dcba38e081a13e51b70314fc28ea6d0ee 100644 (file)
@@ -438,10 +438,11 @@ public abstract class IndexData {
    * @param indexes List of IndexBuilders to write definitions for
    */
   protected static void writeRowCountDefinitions(
-      ByteBuffer buffer, int indexCount, JetFormat format)
+      TableCreator creator, ByteBuffer buffer)
   {
     // index row counts (empty data)
-    ByteUtil.forward(buffer, (indexCount * format.SIZE_INDEX_DEFINITION));
+    ByteUtil.forward(buffer, (creator.getIndexCount() *
+                              creator.getFormat().SIZE_INDEX_DEFINITION));
   }
 
   /**
@@ -450,15 +451,14 @@ public abstract class IndexData {
    * @param indexes List of IndexBuilders to write definitions for
    */
   protected static void writeDefinitions(
-      ByteBuffer buffer, List<Column> columns, List<IndexBuilder> indexes,
-      int tdefPageNumber, PageChannel pageChannel, JetFormat format)
+      TableCreator creator, ByteBuffer buffer)
     throws IOException
   {
-    ByteBuffer rootPageBuffer = pageChannel.createPageBuffer();
+    ByteBuffer rootPageBuffer = creator.getPageChannel().createPageBuffer();
     writeDataPage(rootPageBuffer, SimpleIndexData.NEW_ROOT_DATA_PAGE, 
-                  tdefPageNumber, format);
+                  creator.getTdefPageNumber(), creator.getFormat());
 
-    for(IndexBuilder idx : indexes) {
+    for(IndexBuilder idx : creator.getIndexes()) {
       buffer.putInt(MAGIC_INDEX_NUMBER); // seemingly constant magic value
 
       // write column information (always MAX_COLUMNS entries)
@@ -475,7 +475,7 @@ public abstract class IndexData {
           flags = idxCol.getFlags();
 
           // find actual table column number
-          for(Column col : columns) {
+          for(Column col : creator.getColumns()) {
             if(col.getName().equalsIgnoreCase(idxCol.getName())) {
               columnNumber = col.getColumnNumber();
               break;
@@ -492,13 +492,16 @@ public abstract class IndexData {
         buffer.put(flags); // column flags (e.g. ordering)
       }
 
-      buffer.put(idx.getUmapRowNumber()); // umap row
-      ByteUtil.put3ByteInt(buffer, idx.getUmapPageNumber()); // umap page
+      TableCreator.IndexState idxState = creator.getIndexState(idx);
+
+      buffer.put(idxState.getUmapRowNumber()); // umap row
+      ByteUtil.put3ByteInt(buffer, creator.getUmapPageNumber()); // umap page
 
       // write empty root index page
-      pageChannel.writePage(rootPageBuffer, idx.getRootPageNumber());
+      creator.getPageChannel().writePage(rootPageBuffer, 
+                                         idxState.getRootPageNumber());
 
-      buffer.putInt(idx.getRootPageNumber());
+      buffer.putInt(idxState.getRootPageNumber());
       buffer.putInt(0); // unknown
       buffer.put(idx.getFlags()); // index flags (unique, etc.)
       ByteUtil.forward(buffer, 5); // unknown
@@ -1007,7 +1010,8 @@ public abstract class IndexData {
   /**
    * Returns a new Entry of the correct type for the given data and page type.
    */
-  private Entry newEntry(ByteBuffer buffer, int entryLength, boolean isLeaf)
+  private static Entry newEntry(ByteBuffer buffer, int entryLength, 
+                                boolean isLeaf)
     throws IOException
   {
     if(isLeaf) {
index e11c7f9b91c0da7899e72753a3fc9b071e409f77..ba7b67598ce77a60725b6fe6897f12e5f91cbddd 100644 (file)
@@ -945,52 +945,35 @@ public class Table
   }
 
   /**
-   * Writes a new table defined by the given columns and indexes to the
-   * database.
-   * @return the first page of the new table's definition
+   * Writes a new table defined by the given TableCreator to the database.
    * @usage _advanced_method_
    */
-  public static int writeTableDefinition(
-      List<Column> columns, List<IndexBuilder> indexes,
-      PageChannel pageChannel, JetFormat format, Charset charset)
+  protected static void writeTableDefinition(TableCreator creator)
     throws IOException
   {
-    int indexCount = 0;
-    int logicalIndexCount = 0;
-    if(!indexes.isEmpty()) {
-      // sort out index numbers.  for now, these values will always match
-      // (until we support writing foreign key indexes)
-      for(IndexBuilder idx : indexes) {
-        idx.setIndexNumber(logicalIndexCount++);
-        idx.setIndexDataNumber(indexCount++);
-      }
-    }
-
-    // allocate first table def page
-    int tdefPageNumber = pageChannel.allocateNewPage();
-
     // first, create the usage map page
-    int usageMapPageNumber = 
-        createUsageMapDefinitionBuffer(indexes, pageChannel, format);
+    createUsageMapDefinitionBuffer(creator);
 
     // next, determine how big the table def will be (in case it will be more
     // than one page)
-    int idxDataLen = (indexCount * (format.SIZE_INDEX_DEFINITION + 
-                                    format.SIZE_INDEX_COLUMN_BLOCK)) + 
-      (logicalIndexCount * format.SIZE_INDEX_INFO_BLOCK);
+    JetFormat format = creator.getFormat();
+    int idxDataLen = (creator.getIndexCount() * 
+                      (format.SIZE_INDEX_DEFINITION + 
+                       format.SIZE_INDEX_COLUMN_BLOCK)) + 
+      (creator.getLogicalIndexCount() * format.SIZE_INDEX_INFO_BLOCK);
     int totalTableDefSize = format.SIZE_TDEF_HEADER +
-      (format.SIZE_COLUMN_DEF_BLOCK * columns.size()) + idxDataLen +
-      format.SIZE_TDEF_TRAILER;
+      (format.SIZE_COLUMN_DEF_BLOCK * creator.getColumns().size()) + 
+      idxDataLen + format.SIZE_TDEF_TRAILER;
 
     // total up the amount of space used by the column and index names (2
     // bytes per char + 2 bytes for the length)
-    for(Column col : columns) {
+    for(Column col : creator.getColumns()) {
       int nameByteLen = (col.getName().length() *
                          JetFormat.TEXT_FIELD_UNIT_SIZE);
       totalTableDefSize += nameByteLen + 2;
     }
     
-    for(IndexBuilder idx : indexes) {
+    for(IndexBuilder idx : creator.getIndexes()) {
       int nameByteLen = (idx.getName().length() *
                          JetFormat.TEXT_FIELD_UNIT_SIZE);
       totalTableDefSize += nameByteLen + 2;
@@ -998,25 +981,23 @@ public class Table
     
 
     // now, create the table definition
-    ByteBuffer buffer = pageChannel.createBuffer(Math.max(totalTableDefSize,
-                                                          format.PAGE_SIZE));
-    writeTableDefinitionHeader(buffer, columns, usageMapPageNumber,
-                               totalTableDefSize, indexCount, 
-                               logicalIndexCount, format);
+    PageChannel pageChannel = creator.getPageChannel();
+    ByteBuffer buffer = pageChannel .createBuffer(Math.max(totalTableDefSize,
+                                                           format.PAGE_SIZE));
+    writeTableDefinitionHeader(creator, buffer, totalTableDefSize);
 
-    if(indexCount > 0) {
+    if(creator.hasIndexes()) {
       // index row counts
-      IndexData.writeRowCountDefinitions(buffer, indexCount, format);
+      IndexData.writeRowCountDefinitions(creator, buffer);
     }
 
     // column definitions
-    Column.writeDefinitions(buffer, columns, format, charset); 
+    Column.writeDefinitions(creator, buffer); 
     
-    if(indexCount > 0) {
+    if(creator.hasIndexes()) {
       // index and index data definitions
-      IndexData.writeDefinitions(buffer, columns, indexes, tdefPageNumber,
-                                 pageChannel, format);
-      Index.writeDefinitions(buffer, indexes, charset);
+      IndexData.writeDefinitions(creator, buffer);
+      Index.writeDefinitions(creator, buffer);
     }
 
     //End of tabledef
@@ -1030,7 +1011,7 @@ public class Table
       buffer.putShort(format.OFFSET_FREE_SPACE,
                       (short)(buffer.remaining() - 8)); // overwrite page free space
       // Write the tdef page to disk.
-      pageChannel.writePage(buffer, tdefPageNumber);
+      pageChannel.writePage(buffer, creator.getTdefPageNumber());
       
     } else {
 
@@ -1047,7 +1028,7 @@ public class Table
           
           // this is the first page.  note, the first page already has the
           // page header, so no need to write it here
-          nextTdefPageNumber = tdefPageNumber;
+          nextTdefPageNumber = creator.getTdefPageNumber();
           
         } else {
 
@@ -1077,8 +1058,6 @@ public class Table
       }
         
     }
-       
-    return tdefPageNumber;
   }
 
   /**
@@ -1086,11 +1065,11 @@ public class Table
    * @param columns List of Columns in the table
    */
   private static void writeTableDefinitionHeader(
-      ByteBuffer buffer, List<Column> columns,
-      int usageMapPageNumber, int totalTableDefSize,
-      int indexCount, int logicalIndexCount, JetFormat format)
+      TableCreator creator, ByteBuffer buffer, int totalTableDefSize)
     throws IOException
   {
+    List<Column> columns = creator.getColumns();
+
     //Start writing the tdef
     writeTablePageHeader(buffer);
     buffer.putInt(totalTableDefSize);  //Length of table def
@@ -1105,17 +1084,17 @@ public class Table
     buffer.putShort((short) columns.size()); //Max columns a row will have
     buffer.putShort(Column.countVariableLength(columns));  //Number of variable columns in table
     buffer.putShort((short) columns.size()); //Number of columns in table
-    buffer.putInt(logicalIndexCount);  //Number of logical indexes in table
-    buffer.putInt(indexCount);  //Number of indexes in table
+    buffer.putInt(creator.getLogicalIndexCount());  //Number of logical indexes in table
+    buffer.putInt(creator.getIndexCount());  //Number of indexes in table
     buffer.put((byte) 0); //Usage map row number
-    ByteUtil.put3ByteInt(buffer, usageMapPageNumber);  //Usage map page number
+    ByteUtil.put3ByteInt(buffer, creator.getUmapPageNumber());  //Usage map page number
     buffer.put((byte) 1); //Free map row number
-    ByteUtil.put3ByteInt(buffer, usageMapPageNumber);  //Free map page number
+    ByteUtil.put3ByteInt(buffer, creator.getUmapPageNumber());  //Free map page number
     if (LOG.isDebugEnabled()) {
       int position = buffer.position();
       buffer.rewind();
       LOG.debug("Creating new table def block:\n" + ByteUtil.toHexString(
-          buffer, format.SIZE_TDEF_HEADER));
+                buffer, creator.getFormat().SIZE_TDEF_HEADER));
       buffer.position(position);
     }
   }
@@ -1149,13 +1128,13 @@ public class Table
    * row 0, the "pages with free space" map is in row 1.  Index usage maps are
    * in subsequent rows.
    */
-  private static int createUsageMapDefinitionBuffer(
-      List<IndexBuilder> indexes, PageChannel pageChannel, JetFormat format)
+  private static void createUsageMapDefinitionBuffer(TableCreator creator)
     throws IOException
   {
     // 2 table usage maps plus 1 for each index
-    int umapNum = 2 + indexes.size();
+    int umapNum = 2 + creator.getIndexCount();
 
+    JetFormat format = creator.getFormat();
     int usageMapRowLength = format.OFFSET_USAGE_MAP_START +
       format.USAGE_MAP_TABLE_BYTE_LENGTH;
     int freeSpace = format.DATA_PAGE_INITIAL_FREE_SPACE
@@ -1166,8 +1145,9 @@ public class Table
       throw new IOException("FIXME attempting to write too many indexes");
     }
 
-    int umapPageNumber = pageChannel.allocateNewPage();
+    int umapPageNumber = creator.getUmapPageNumber();
 
+    PageChannel pageChannel = creator.getPageChannel();
     ByteBuffer rtn = pageChannel.createPageBuffer();
     rtn.put(PageTypes.DATA);
     rtn.put((byte) 0x1);  //Unknown
@@ -1190,19 +1170,20 @@ public class Table
       rowStart -= usageMapRowLength;
     }
 
-    if(!indexes.isEmpty()) {
+    if(creator.hasIndexes()) {
       
-      for(int i = 0; i < indexes.size(); ++i) {
-        IndexBuilder idx = indexes.get(i);
+      for(int i = 0; i < creator.getIndexes().size(); ++i) {
+        IndexBuilder idx = creator.getIndexes().get(i);
 
         // allocate root page for the index
         int rootPageNumber = pageChannel.allocateNewPage();
         int umapRowNum = i + 2;
 
         // stash info for later use
-        idx.setRootPageNumber(rootPageNumber);
-        idx.setUmapRowNumber((byte)umapRowNum);
-        idx.setUmapPageNumber(umapPageNumber);
+        TableCreator.IndexState idxState = creator.getIndexState(idx);
+        idxState.setRootPageNumber(rootPageNumber);
+        idxState.setUmapRowNumber((byte)umapRowNum);
+        idxState.setUmapPageNumber(umapPageNumber);
 
         // index map definition, including initial root page
         rtn.putShort(getRowStartOffset(umapRowNum, format), (short)rowStart);
@@ -1211,12 +1192,10 @@ public class Table
         rtn.put(rowStart + 5, (byte)1);
 
         rowStart -= usageMapRowLength;
-      }      
+      }
     }
 
     pageChannel.writePage(rtn, umapPageNumber);
-
-    return umapPageNumber;
   }
     
   /**
diff --git a/src/java/com/healthmarketscience/jackcess/TableCreator.java b/src/java/com/healthmarketscience/jackcess/TableCreator.java
new file mode 100644 (file)
index 0000000..556227c
--- /dev/null
@@ -0,0 +1,273 @@
+/*
+Copyright (c) 2011 James Ahlborn
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
+USA
+*/
+
+package com.healthmarketscience.jackcess;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Helper class used to maintain state during table creation.
+ *
+ * @author James Ahlborn
+ * @usage _advanced_class_
+ */
+class TableCreator 
+{
+  private final Database _database;
+  private final String _name;
+  private final List<Column> _columns;
+  private final List<IndexBuilder> _indexes;
+  private final Map<IndexBuilder,IndexState> _indexStates = 
+    new HashMap<IndexBuilder,IndexState>();
+  private int _tdefPageNumber = PageChannel.INVALID_PAGE_NUMBER;
+  private int _umapPageNumber = PageChannel.INVALID_PAGE_NUMBER;
+  private int _indexCount;
+  private int _logicalIndexCount;
+
+  public TableCreator(Database database, String name, List<Column> columns,
+                      List<IndexBuilder> indexes) {
+    _database = database;
+    _name = name;
+    _columns = columns;
+    _indexes = ((indexes != null) ? indexes : 
+                Collections.<IndexBuilder>emptyList());
+  }
+
+  public JetFormat getFormat() {
+    return _database.getFormat();
+  }
+
+  public PageChannel getPageChannel() {
+    return _database.getPageChannel();
+  }
+
+  public Charset getCharset() {
+    return _database.getCharset();
+  }
+
+  public int getTdefPageNumber() {
+    return _tdefPageNumber;
+  }
+
+  public int getUmapPageNumber() {
+    return _umapPageNumber;
+  }
+
+  public List<Column> getColumns() {
+    return _columns;
+  }
+
+  public List<IndexBuilder> getIndexes() {
+    return _indexes;
+  }
+
+  public boolean hasIndexes() {
+    return !_indexes.isEmpty();
+  }
+
+  public int getIndexCount() {
+    return _indexCount;
+  }
+
+  public int getLogicalIndexCount() {
+    return _logicalIndexCount;
+  }
+
+  public IndexState getIndexState(IndexBuilder idx) {
+    return _indexStates.get(idx);
+  }
+
+  public int reservePageNumber() throws IOException {
+    return getPageChannel().allocateNewPage();
+  }
+
+  /**
+   * Creates the table in the database.
+   * @usage _advanced_method_
+   */
+  public void createTable() throws IOException {
+
+    validate();
+
+    if(hasIndexes()) {
+      // sort out index numbers.  for now, these values will always match
+      // (until we support writing foreign key indexes)
+      for(IndexBuilder idx : _indexes) {
+        IndexState idxState = new IndexState();
+        idxState.setIndexNumber(_logicalIndexCount++);
+        idxState.setIndexDataNumber(_indexCount++);
+        _indexStates.put(idx, idxState);
+      }
+    }
+
+    // reserve some pages
+    _tdefPageNumber = reservePageNumber();
+    _umapPageNumber = reservePageNumber();
+    
+    //Write the tdef page to disk.
+    Table.writeTableDefinition(this);
+
+    // update the database with the new table info
+    _database.addNewTable(_name, _tdefPageNumber);
+  }
+
+  /**
+   * Validates the new table information before attempting creation.
+   */
+  private void validate() {
+
+    Database.validateIdentifierName(
+        _name, getFormat().MAX_TABLE_NAME_LENGTH, "table");
+    
+    if((_columns == null) || _columns.isEmpty()) {
+      throw new IllegalArgumentException(
+          "Cannot create table with no columns");
+    }
+    if(_columns.size() > getFormat().MAX_COLUMNS_PER_TABLE) {
+      throw new IllegalArgumentException(
+          "Cannot create table with more than " +
+          getFormat().MAX_COLUMNS_PER_TABLE + " columns");
+    }
+    
+    Column.SortOrder dbSortOrder = null;
+    try {
+      dbSortOrder = _database.getDefaultSortOrder();
+    } catch(IOException e) {
+      // ignored, just use the jet format default
+    }
+
+    Set<String> colNames = new HashSet<String>();
+    // next, validate the column definitions
+    for(Column column : _columns) {
+
+      // FIXME for now, we can't create complex columns
+      if(column.getType() == DataType.COMPLEX_TYPE) {
+        throw new UnsupportedOperationException(
+            "Complex column creation is not yet implemented");
+      }
+      
+      column.validate(getFormat());
+      if(!colNames.add(column.getName().toUpperCase())) {
+        throw new IllegalArgumentException("duplicate column name: " +
+                                           column.getName());
+      }
+
+      // set the sort order to the db default (if unspecified)
+      if(column.getType().isTextual() && (column.getTextSortOrder() == null)) {
+        column.setTextSortOrder(dbSortOrder);
+      }
+    }
+
+    List<Column> autoCols = Table.getAutoNumberColumns(_columns);
+    if(autoCols.size() > 1) {
+      // for most autonumber types, we can only have one of each type
+      Set<DataType> autoTypes = EnumSet.noneOf(DataType.class);
+      for(Column c : autoCols) {
+        if(!c.getType().isMultipleAutoNumberAllowed() &&
+           !autoTypes.add(c.getType())) {
+          throw new IllegalArgumentException(
+              "Can have at most one AutoNumber column of type " + c.getType() +
+              " per table");
+        }
+      }
+    }
+
+    if(hasIndexes()) {
+      // now, validate the indexes
+      Set<String> idxNames = new HashSet<String>();
+      boolean foundPk = false;
+      for(IndexBuilder index : _indexes) {
+        index.validate(colNames);
+        if(!idxNames.add(index.getName().toUpperCase())) {
+          throw new IllegalArgumentException("duplicate index name: " +
+                                             index.getName());
+        }
+        if(index.isPrimaryKey()) {
+          if(foundPk) {
+            throw new IllegalArgumentException(
+                "found second primary key index: " + index.getName());
+          }
+          foundPk = true;
+        }
+      }
+    }
+  }
+
+  /**
+   * Maintains additional state used during index creation.
+   * @usage _advanced_class_
+   */
+  static final class IndexState
+  {
+    private int _indexNumber;
+    private int _indexDataNumber;
+    private byte _umapRowNumber;
+    private int _umapPageNumber;
+    private int _rootPageNumber;
+
+    public int getIndexNumber() {
+      return _indexNumber;
+    }
+
+    public void setIndexNumber(int newIndexNumber) {
+      _indexNumber = newIndexNumber;
+    }
+
+    public int getIndexDataNumber() {
+      return _indexDataNumber;
+    }
+
+    public void setIndexDataNumber(int newIndexDataNumber) {
+      _indexDataNumber = newIndexDataNumber;
+    }
+
+    public byte getUmapRowNumber() {
+      return _umapRowNumber;
+    }
+
+    public void setUmapRowNumber(byte newUmapRowNumber) {
+      _umapRowNumber = newUmapRowNumber;
+    }
+
+    public int getUmapPageNumber() {
+      return _umapPageNumber;
+    }
+
+    public void setUmapPageNumber(int newUmapPageNumber) {
+      _umapPageNumber = newUmapPageNumber;
+    }
+
+    public int getRootPageNumber() {
+      return _rootPageNumber;
+    }
+
+    public void setRootPageNumber(int newRootPageNumber) {
+      _rootPageNumber = newRootPageNumber;
+    }
+    
+  }
+}