]> source.dussan.org Git - jackcess.git/commitdiff
Allow optional direct insert/update of autonumber values. This is disabled by defaul...
authorJames Ahlborn <jtahlborn@yahoo.com>
Fri, 1 May 2015 00:45:53 +0000 (00:45 +0000)
committerJames Ahlborn <jtahlborn@yahoo.com>
Fri, 1 May 2015 00:45:53 +0000 (00:45 +0000)
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@941 f203690c-595d-4dc9-a70b-905162fa7fd2

src/changes/changes.xml
src/main/java/com/healthmarketscience/jackcess/Database.java
src/main/java/com/healthmarketscience/jackcess/Table.java
src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java
src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java
src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java
src/test/java/com/healthmarketscience/jackcess/DatabaseTest.java
src/test/java/com/healthmarketscience/jackcess/TestUtil.java
src/test/java/com/healthmarketscience/jackcess/impl/AutoNumberTest.java [new file with mode: 0644]

index 7d52603c5feb812a5fe03e072347d2d8a2fbab26..00681675393ba57537f82a341a88c730076d9c0b 100644 (file)
@@ -9,6 +9,12 @@
         Load linked table info from system table when reading databases with
         unsupported sort orders.
       </action>
+      <action dev="jahlborn" type="update" system="SourceForge2Features"
+              issue="32">
+        Allow optional direct insert/update of autonumber values.  This is
+        disabled by default, but can be selectively enabled per-jvm (using
+        system property), per-database, and per-table.
+      </action>
     </release>
     <release version="2.1.0" date="2015-04-16" 
              description="Relicense to Apache License">
index 410ce0d927cf27a60178dd427e4afb1fd1419c9a..1e6d6cfbaaab309ed9d63270f3e357f58567ec93 100644 (file)
@@ -117,6 +117,13 @@ public interface Database extends Iterable<Table>, Closeable, Flushable
   public static final String FK_ENFORCE_PROPERTY = 
     "com.healthmarketscience.jackcess.enforceForeignKeys";
 
+  /** system property which can be used to set the default allow auto number
+   * insert policy.  Defaults to {@code false}.
+   * @usage _general_field_
+   */
+  public static final String ALLOW_AUTONUM_INSERT_PROPERTY = 
+    "com.healthmarketscience.jackcess.allowAutoNumberInsert";
+
   /**
    * Enum which indicates which version of Access created the database.
    * @usage _general_class_
@@ -392,6 +399,31 @@ public interface Database extends Iterable<Table>, Closeable, Flushable
    */
   public void setEnforceForeignKeys(Boolean newEnforceForeignKeys);
 
+  /**
+   * Gets current allow auto number insert policy.  By default, jackcess does
+   * not allow auto numbers to be inserted or updated directly (they are
+   * always handled internally by the Table).  Setting this policy to {@code
+   * true} allows the caller to optionally set the value explicitly when
+   * adding or updating rows (if a value is not provided, it will still be
+   * handled internally by the Table).  This value can be set database-wide
+   * using {@link #setAllowAutoNumberInsert} and/or on a per-table basis using
+   * {@link Table#setAllowAutoNumberInsert} (and/or on a jvm-wide using the
+   * {@link #ALLOW_AUTONUM_INSERT_PROPERTY} system property).  Note that
+   * <i>enabling this feature should be done with care</i> to reduce the
+   * chances of screwing up the database.
+   * 
+   * @usage _intermediate_method_
+   */
+  public boolean isAllowAutoNumberInsert();
+
+  /**
+   * Sets the new auto number insert policy for the database (unless
+   * overridden at the Table level).  If {@code null}, resets to the default
+   * value.
+   * @usage _intermediate_method_
+   */
+  public void setAllowAutoNumberInsert(Boolean allowAutoNumInsert);
+
   /**
    * Gets currently configured ColumnValidatorFactory (always non-{@code null}).
    * @usage _intermediate_method_
index 660449110db6618378942498c6d02ebc2e46425f..bbb68852843f640e8bf4b310302653599f26c330 100644 (file)
@@ -98,6 +98,20 @@ public interface Table extends Iterable<Row>
    */
   public void setErrorHandler(ErrorHandler newErrorHandler);
 
+  /**
+   * Gets the currently configured auto number insert policy.
+   * @see Database#isAllowAutoNumberInsert
+   * @usage _intermediate_method_
+   */
+  public boolean isAllowAutoNumberInsert();
+
+  /**
+   * Sets the new auto number insert policy for the Table.  If {@code null},
+   * resets to using the policy configured at the Database level.
+   * @usage _intermediate_method_
+   */
+  public void setAllowAutoNumberInsert(Boolean allowAutoNumInsert);
+
   /**
    * @return All of the columns in this table (unmodifiable List)
    * @usage _general_method_
index d6c090892f298ef6c02f170ecf2cb25d5bea3e56..b4b7cc26dca1e77c2e4c857e6e8eef23c60ad606 100644 (file)
@@ -151,7 +151,10 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
   private static final char MIN_COMPRESS_CHAR = 1;
   private static final char MAX_COMPRESS_CHAR = 0xFF;
 
-  
+  /** auto numbers must be > 0 */
+  static final int INVALID_AUTO_NUMBER = 0;
+
+
   /** owning table */
   private final TableImpl _table;
   /** Whether or not the column is of variable length */
@@ -1843,7 +1846,17 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
      * <i>Warning, calling this externally will result in this value being
      * "lost" for the table.</i>
      */
-    public abstract Object getNext(Object prevRowValue);
+    public abstract Object getNext(TableImpl.WriteRowState writeRowState);
+
+    /**
+     * Returns a valid autonumber for this generator.
+     * <p>
+     * <i>Warning, calling this externally may result in this value being
+     * "lost" for the table.</i>
+     */
+    public abstract Object handleInsert(
+        TableImpl.WriteRowState writeRowState, Object inRowValue) 
+      throws IOException;
 
     /**
      * Restores a previous autonumber generated by this generator.
@@ -1867,11 +1880,26 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
     }
 
     @Override
-    public Object getNext(Object prevRowValue) {
+    public Object getNext(TableImpl.WriteRowState writeRowState) {
       // the table stores the last long autonumber used
       return getTable().getNextLongAutoNumber();
     }
 
+    @Override
+    public Object handleInsert(TableImpl.WriteRowState writeRowState,
+                               Object inRowValue) 
+      throws IOException
+    {
+      int inAutoNum = toNumber(inRowValue).intValue();
+      if(inAutoNum <= INVALID_AUTO_NUMBER) {
+        throw new IOException(withErrorContext(
+                "Invalid auto number value " + inAutoNum));
+      }
+      // the table stores the last long autonumber used
+      getTable().adjustLongAutoNumber(inAutoNum);
+      return inAutoNum;
+    }
+
     @Override
     public void restoreLast(Object last) {
       if(last instanceof Integer) {
@@ -1897,12 +1925,21 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
     }
 
     @Override
-    public Object getNext(Object prevRowValue) {
+    public Object getNext(TableImpl.WriteRowState writeRowState) {
       // format guids consistently w/ Column.readGUIDValue()
       _lastAutoNumber = "{" + UUID.randomUUID() + "}";
       return _lastAutoNumber;
     }
 
+    @Override
+    public Object handleInsert(TableImpl.WriteRowState writeRowState,
+                               Object inRowValue) 
+      throws IOException
+    {
+      _lastAutoNumber = toCharSequence(inRowValue);
+      return _lastAutoNumber;
+    }
+
     @Override
     public void restoreLast(Object last) {
       _lastAutoNumber = null;
@@ -1925,14 +1962,54 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
     }
 
     @Override
-    public Object getNext(Object prevRowValue) {
-      int nextComplexAutoNum =
-        ((prevRowValue == null) ?
-         // the table stores the last ComplexType autonumber used
-         getTable().getNextComplexTypeAutoNumber() :
-         // same value is shared across all ComplexType values in a row
-         ((ComplexValueForeignKey)prevRowValue).get());
-      return new ComplexValueForeignKeyImpl(ColumnImpl.this, nextComplexAutoNum);
+    public Object getNext(TableImpl.WriteRowState writeRowState) {
+      // same value is shared across all ComplexType values in a row
+      int nextComplexAutoNum = writeRowState.getComplexAutoNumber();
+      if(nextComplexAutoNum <= INVALID_AUTO_NUMBER) {
+        // the table stores the last ComplexType autonumber used
+        nextComplexAutoNum = getTable().getNextComplexTypeAutoNumber();
+        writeRowState.setComplexAutoNumber(nextComplexAutoNum);
+      }
+      return new ComplexValueForeignKeyImpl(ColumnImpl.this, 
+                                            nextComplexAutoNum);
+    }
+
+    @Override
+    public Object handleInsert(TableImpl.WriteRowState writeRowState,
+                               Object inRowValue) 
+      throws IOException
+    {
+      ComplexValueForeignKey inComplexFK = null;
+      if(inRowValue instanceof ComplexValueForeignKey) {
+        inComplexFK = (ComplexValueForeignKey)inRowValue;
+      } else {
+        inComplexFK = new ComplexValueForeignKeyImpl(
+            ColumnImpl.this, toNumber(inRowValue).intValue());
+      }
+
+      if(inComplexFK.getColumn() != ColumnImpl.this) {
+        throw new IOException(withErrorContext(
+                "Wrong column for complex value foreign key, found " +
+                inComplexFK.getColumn().getName()));
+      }
+      if(inComplexFK.get() < 1) {
+        throw new IOException(withErrorContext(
+                "Invalid complex value foreign key value " + inComplexFK.get()));
+      }
+      // same value is shared across all ComplexType values in a row
+      int prevRowValue = writeRowState.getComplexAutoNumber();
+      if(prevRowValue <= INVALID_AUTO_NUMBER) {
+        writeRowState.setComplexAutoNumber(inComplexFK.get());
+      } else if(prevRowValue != inComplexFK.get()) {
+        throw new IOException(withErrorContext(
+                "Inconsistent complex value foreign key values: found " +
+                prevRowValue + ", given " + inComplexFK));
+      }
+
+      // the table stores the last ComplexType autonumber used
+      getTable().adjustComplexTypeAutoNumber(inComplexFK.get());
+
+      return inComplexFK;
     }
 
     @Override
@@ -1963,7 +2040,13 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
     }
 
     @Override
-    public Object getNext(Object prevRowValue) {
+    public Object getNext(TableImpl.WriteRowState writeRowState) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Object handleInsert(TableImpl.WriteRowState writeRowState,
+                               Object inRowValue) {
       throw new UnsupportedOperationException();
     }
 
index 47acb105ed4f599b71216b8a43f5e4c69e1a133f..8cf809dcb6cd9da0fda1b155a95f906c7e43a67d 100644 (file)
@@ -301,6 +301,8 @@ public class DatabaseImpl implements Database
   private Table.ColumnOrder _columnOrder;
   /** whether or not enforcement of foreign-keys is enabled */
   private boolean _enforceForeignKeys;
+  /** whether or not auto numbers can be directly inserted by the user */
+  private boolean _allowAutoNumInsert;
   /** factory for ColumnValidators */
   private ColumnValidatorFactory _validatorFactory = SimpleColumnValidatorFactory.INSTANCE;
   /** cache of in-use tables */
@@ -501,6 +503,7 @@ public class DatabaseImpl implements Database
     _charset = ((charset == null) ? getDefaultCharset(_format) : charset);
     _columnOrder = getDefaultColumnOrder();
     _enforceForeignKeys = getDefaultEnforceForeignKeys();
+    _allowAutoNumInsert = getDefaultAllowAutoNumberInsert();
     _fileFormat = fileFormat;
     _pageChannel = new PageChannel(channel, closeChannel, _format, autoSync);
     _timeZone = ((timeZone == null) ? getDefaultTimeZone() : timeZone);
@@ -662,6 +665,18 @@ public class DatabaseImpl implements Database
     _enforceForeignKeys = newEnforceForeignKeys;
   }
 
+  public boolean isAllowAutoNumberInsert() {
+    return _allowAutoNumInsert;
+  }
+
+  public void setAllowAutoNumberInsert(Boolean allowAutoNumInsert) {
+    if(allowAutoNumInsert == null) {
+      allowAutoNumInsert = getDefaultAllowAutoNumberInsert();
+    }
+    _allowAutoNumInsert = allowAutoNumInsert;
+  }
+
+
   public ColumnValidatorFactory getColumnValidatorFactory() {
     return _validatorFactory;
   }
@@ -1743,6 +1758,21 @@ public class DatabaseImpl implements Database
     return true;
   }
   
+  /**
+   * Returns the default allow auto number insert policy.  This defaults to
+   * {@code false}, but can be overridden using the system
+   * property {@value com.healthmarketscience.jackcess.Database#ALLOW_AUTONUM_INSERT_PROPERTY}.
+   * @usage _advanced_method_
+   */
+  public static boolean getDefaultAllowAutoNumberInsert()
+  {
+    String prop = System.getProperty(ALLOW_AUTONUM_INSERT_PROPERTY);
+    if(prop != null) {
+      return Boolean.TRUE.toString().equalsIgnoreCase(prop);
+    }
+    return false;
+  }
+  
   /**
    * Copies the given InputStream to the given channel using the most
    * efficient means possible.
index 0e0488dcb764ecb51f6cf920a65600e21cb294fb..e2953160417ee99c64c86ea36ee778d04ba68534 100644 (file)
@@ -38,7 +38,6 @@ import com.healthmarketscience.jackcess.Column;
 import com.healthmarketscience.jackcess.ColumnBuilder;
 import com.healthmarketscience.jackcess.ConstraintViolationException;
 import com.healthmarketscience.jackcess.CursorBuilder;
-import com.healthmarketscience.jackcess.DataType;
 import com.healthmarketscience.jackcess.IndexBuilder;
 import com.healthmarketscience.jackcess.JackcessException;
 import com.healthmarketscience.jackcess.PropertyMap;
@@ -166,6 +165,9 @@ public class TableImpl implements Table
   private PropertyMap _props;
   /** properties group for this table (and columns) */
   private PropertyMaps _propertyMaps;
+  /** optional flag indicating whether or not auto numbers can be directly
+      inserted by the user */
+  private Boolean _allowAutoNumInsert;
   /** foreign-key enforcer for this table */
   private final FKEnforcer _fkEnforcer;
 
@@ -356,6 +358,15 @@ public class TableImpl implements Table
     return _tableDefPageNumber;
   }
 
+  public boolean isAllowAutoNumberInsert() {
+    return ((_allowAutoNumInsert != null) ? (boolean)_allowAutoNumInsert :
+            getDatabase().isAllowAutoNumberInsert());
+  }
+
+  public void setAllowAutoNumberInsert(Boolean allowAutoNumInsert) {
+    _allowAutoNumInsert = allowAutoNumInsert;
+  }
+
   /**
    * @usage _advanced_method_
    */
@@ -1079,7 +1090,6 @@ public class TableImpl implements Table
 
   /**
    * @param buffer Buffer to write to
-   * @param columns List of Columns in the table
    */
   private static void writeTableDefinitionHeader(
       TableCreator creator, ByteBuffer buffer, int totalTableDefSize)
@@ -1499,7 +1509,7 @@ public class TableImpl implements Table
   /**
    * Add multiple rows to this table, only writing to disk after all
    * rows have been written, and every time a data page is filled.
-   * @param inRows List of Object[] row values
+   * @param rows List of Object[] row values
    */
   private List<? extends Object[]> addRows(List<? extends Object[]> rows,
                                            final boolean isBatchWrite)
@@ -1516,6 +1526,8 @@ public class TableImpl implements Table
       int pageNumber = PageChannel.INVALID_PAGE_NUMBER;
       int updateCount = 0;
       int autoNumAssignCount = 0;
+      WriteRowState writeRowState = 
+        (!_autoNumColumns.isEmpty() ? new WriteRowState() : null);
       try {
 
         List<Object[]> dupeRows = null;
@@ -1549,7 +1561,7 @@ public class TableImpl implements Table
           }
 
           // fill in autonumbers
-          handleAutoNumbersForAdd(row);
+          handleAutoNumbersForAdd(row, writeRowState);
           ++autoNumAssignCount;
       
           // write the row of data to a temporary buffer
@@ -1758,45 +1770,44 @@ public class TableImpl implements Table
 
       // handle various value massaging activities
       for(ColumnImpl column : _columns) {
-        
-        Object rowValue = null;
+
         if(column.isAutoNumber()) {
-          
-          // fill in any auto-numbers (we don't allow autonumber values to be
-          // modified)
-          rowValue = getRowColumn(getFormat(), rowBuffer, column, rowState, null);
-          
-        } else {
+          // handle these separately (below)
+          continue;
+        }
 
-          rowValue = column.getRowValue(row);
-          if(rowValue == Column.KEEP_VALUE) {
+        Object rowValue = column.getRowValue(row);
+        if(rowValue == Column.KEEP_VALUE) {
             
-            // fill in any "keep value" fields (restore old value)
-            rowValue = getRowColumn(getFormat(), rowBuffer, column, rowState,
-                                    keepRawVarValues);
+          // fill in any "keep value" fields (restore old value)
+          rowValue = getRowColumn(getFormat(), rowBuffer, column, rowState,
+                                  keepRawVarValues);
             
-          } else {
-
-            // set oldValue to something that could not possibly be a real value
-            Object oldValue = Column.KEEP_VALUE;
-            if(_indexColumns.contains(column)) {
-              // read (old) row value to help update indexes
-              oldValue = getRowColumn(getFormat(), rowBuffer, column, rowState, null);
-            } else {
-              oldValue = rowState.getRowCacheValue(column.getColumnIndex());
-            }
+        } else {
 
-            // if the old value was passed back in, we don't need to validate
-            if(oldValue != rowValue) {
-              // pass input value through column validator
-              rowValue = column.validate(rowValue);
-            } 
+          // set oldValue to something that could not possibly be a real value
+          Object oldValue = Column.KEEP_VALUE;
+          if(_indexColumns.contains(column)) {
+            // read (old) row value to help update indexes
+            oldValue = getRowColumn(getFormat(), rowBuffer, column, rowState,
+                                    null);
+          } else {
+            oldValue = rowState.getRowCacheValue(column.getColumnIndex());
           }
+
+          // if the old value was passed back in, we don't need to validate
+          if(oldValue != rowValue) {
+            // pass input value through column validator
+            rowValue = column.validate(rowValue);
+          } 
         }
 
         column.setRowValue(row, rowValue);
       }
 
+      // fill in autonumbers
+      handleAutoNumbersForUpdate(row, rowBuffer, rowState);
+
       // generate new row bytes
       ByteBuffer newRowData = createRow(
           row, _writeRowBufferH.getPageBuffer(getPageChannel()), oldRowSize,
@@ -2178,32 +2189,79 @@ public class TableImpl implements Table
   }
 
   /**
-   * Fill in all autonumber column values.
+   * Fill in all autonumber column values for add.
    */
-  private void handleAutoNumbersForAdd(Object[] row)
+  private void handleAutoNumbersForAdd(Object[] row, WriteRowState writeRowState)
     throws IOException
   {
     if(_autoNumColumns.isEmpty()) {
       return;
     }
-    Object complexAutoNumber = null;
+
+    boolean enableInsert = isAllowAutoNumberInsert();
+    writeRowState.resetAutoNumber();
     for(ColumnImpl col : _autoNumColumns) {
-      // ignore given row value, use next autonumber
+
+      // ignore input row value, use original row value (unless explicitly
+      // enabled)
+      Object inRowValue = getInputAutoNumberRowValue(enableInsert, col, row);
+
       ColumnImpl.AutoNumberGenerator autoNumGen = col.getAutoNumberGenerator();
-      Object rowValue = null;
-      if(autoNumGen.getType() != DataType.COMPLEX_TYPE) {
-        rowValue = autoNumGen.getNext(null);
-      } else {
-        // complex type auto numbers are shared across all complex columns
-        // in the row
-        complexAutoNumber = autoNumGen.getNext(complexAutoNumber);
-        rowValue = complexAutoNumber;
-      }
+      Object rowValue = ((inRowValue == null) ? 
+                         autoNumGen.getNext(writeRowState) :
+                         autoNumGen.handleInsert(writeRowState, inRowValue));
+
       col.setRowValue(row, rowValue);
     }
   }
   
+  /**
+   * Fill in all autonumber column values for update.
+   */
+  private void handleAutoNumbersForUpdate(Object[] row, ByteBuffer rowBuffer,
+                                          RowState rowState)
+    throws IOException
+  {
+    if(_autoNumColumns.isEmpty()) {
+      return;
+    }
+
+    boolean enableInsert = isAllowAutoNumberInsert();
+    rowState.resetAutoNumber();
+    for(ColumnImpl col : _autoNumColumns) {
+
+      // ignore input row value, use original row value (unless explicitly
+      // enabled)
+      Object inRowValue = getInputAutoNumberRowValue(enableInsert, col, row);
+
+      Object rowValue = 
+        ((inRowValue == null) ?
+         getRowColumn(getFormat(), rowBuffer, col, rowState, null) :
+         col.getAutoNumberGenerator().handleInsert(rowState, inRowValue));
+
+      col.setRowValue(row, rowValue);
+    }
+  }
+
+  /**
+   * Optionally get the input autonumber row value for the given column from
+   * the given row if one was provided.
+   */
+  private static Object getInputAutoNumberRowValue(
+      boolean enableInsert, ColumnImpl col, Object[] row)
+  {
+    if(!enableInsert) {
+      return null;
+    }
+
+    Object inRowValue = col.getRowValue(row);
+    if((inRowValue == Column.KEEP_VALUE) || (inRowValue == Column.AUTO_NUMBER)) {
+      // these "special" values both behave like nothing was given
+      inRowValue = null;
+    }
+    return inRowValue;
+  }
+  
   /**
    * Restores all autonumber column values from a failed add row.
    */
@@ -2246,6 +2304,12 @@ public class TableImpl implements Table
     return _lastLongAutoNumber;
   }
 
+  void adjustLongAutoNumber(int inLongAutoNumber) {
+    if(inLongAutoNumber > _lastLongAutoNumber) {
+      _lastLongAutoNumber = inLongAutoNumber;
+    }
+  }
+
   void restoreLastLongAutoNumber(int lastLongAutoNumber) {
     // restores the last used auto number
     _lastLongAutoNumber = lastLongAutoNumber - 1;
@@ -2261,6 +2325,12 @@ public class TableImpl implements Table
     return _lastComplexTypeAutoNumber;
   }
 
+  void adjustComplexTypeAutoNumber(int inComplexTypeAutoNumber) {
+    if(inComplexTypeAutoNumber > _lastComplexTypeAutoNumber) {
+      _lastComplexTypeAutoNumber = inComplexTypeAutoNumber;
+    }
+  }
+
   void restoreLastComplexTypeAutoNumber(int lastComplexTypeAutoNumber) {
     // restores the last used auto number
     _lastComplexTypeAutoNumber = lastComplexTypeAutoNumber - 1;
@@ -2501,10 +2571,31 @@ public class TableImpl implements Table
   }
 
   /**
-   * Maintains the state of reading a row of data.
+   * Maintains state for writing a new row of data.
+   */
+  protected static class WriteRowState
+  {
+    private int _complexAutoNumber = ColumnImpl.INVALID_AUTO_NUMBER;
+
+    public int getComplexAutoNumber() {
+      return _complexAutoNumber;
+    }
+
+    public void setComplexAutoNumber(int complexAutoNumber) {
+      _complexAutoNumber = complexAutoNumber;
+    }
+
+    public void resetAutoNumber() {
+      _complexAutoNumber = ColumnImpl.INVALID_AUTO_NUMBER;
+    }
+  }
+
+  /**
+   * Maintains the state of reading/updating a row of data.
    * @usage _advanced_class_
    */
-  public final class RowState implements ErrorHandler.Location
+  public final class RowState extends WriteRowState
+    implements ErrorHandler.Location
   {
     /** Buffer used for reading the header row data pages */
     private final TempPageHolder _headerRowBufferH;
@@ -2560,6 +2651,7 @@ public class TableImpl implements Table
     }
     
     public void reset() {
+      resetAutoNumber();
       _finalRowId = null;
       _finalRowBuffer = null;
       _rowsOnHeaderPage = 0;
index 6deb3b919adbb731aaf01c5e85f0258b6318cc99..918271aa88b5ed6e29a67f04ec7319b1c49faa62 100644 (file)
@@ -619,82 +619,6 @@ public class DatabaseTest extends TestCase
     }
   }
 
-  public void testAutoNumber() throws Exception {
-    for (final FileFormat fileFormat : SUPPORTED_FILEFORMATS) {
-      Database db = createMem(fileFormat);
-
-      Table table = new TableBuilder("test")
-        .addColumn(new ColumnBuilder("a", DataType.LONG)
-                  .setAutoNumber(true))
-        .addColumn(new ColumnBuilder("b", DataType.TEXT))
-        .toTable(db);
-
-      doTestAutoNumber(table);
-
-      db.close();
-    }
-  }  
-
-  public void testAutoNumberPK() throws Exception {
-    for (final TestDB testDB : SUPPORTED_DBS_TEST) {
-      Database db = openMem(testDB);
-
-      Table table = db.getTable("Table3");
-
-      doTestAutoNumber(table);
-
-      db.close();
-    }
-  }  
-
-  private void doTestAutoNumber(Table table) throws Exception
-  {
-    Object[] row = {null, "row1"};
-    assertSame(row, table.addRow(row));
-    assertEquals(1, ((Integer)row[0]).intValue());
-    row = table.addRow(13, "row2");
-    assertEquals(2, ((Integer)row[0]).intValue());
-    row = table.addRow("flubber", "row3");
-    assertEquals(3, ((Integer)row[0]).intValue());
-
-    table.reset();
-
-    row = table.addRow(Column.AUTO_NUMBER, "row4");
-    assertEquals(4, ((Integer)row[0]).intValue());
-    row = table.addRow(Column.AUTO_NUMBER, "row5");
-    assertEquals(5, ((Integer)row[0]).intValue());
-
-    Object[] smallRow = {Column.AUTO_NUMBER};
-    row = table.addRow(smallRow);
-    assertNotSame(row, smallRow);
-    assertEquals(6, ((Integer)row[0]).intValue());    
-
-    table.reset();
-
-    List<? extends Map<String, Object>> expectedRows =
-      createExpectedTable(
-          createExpectedRow(
-              "a", 1,
-              "b", "row1"),
-          createExpectedRow(
-              "a", 2,
-              "b", "row2"),
-          createExpectedRow(
-              "a", 3,
-              "b", "row3"),
-          createExpectedRow(
-              "a", 4,
-              "b", "row4"),
-          createExpectedRow(
-              "a", 5,
-              "b", "row5"),
-          createExpectedRow(
-              "a", 6,
-              "b", null));
-
-    assertTable(expectedRows, table);    
-  }
-  
   public void testWriteAndReadDate() throws Exception {
     for (final FileFormat fileFormat : SUPPORTED_FILEFORMATS) {
       Database db = createMem(fileFormat);
index f7a24f0c6bd7b5fe7813616e256b1d549d42e62b..c6856bcac75fdfe17372bb6292937f37d1cc0251 100644 (file)
@@ -254,17 +254,17 @@ public class TestUtil
     return Arrays.<Row>asList(rows);
   }    
   
-  static void dumpDatabase(Database mdb) throws Exception {
+  public static void dumpDatabase(Database mdb) throws Exception {
     dumpDatabase(mdb, false);
   }
 
-  static void dumpDatabase(Database mdb, boolean systemTables)
+  public static void dumpDatabase(Database mdb, boolean systemTables)
     throws Exception
   {
     dumpDatabase(mdb, systemTables, new PrintWriter(System.out, true));
   }
 
-  static void dumpTable(Table table) throws Exception {
+  public static void dumpTable(Table table) throws Exception {
     dumpTable(table, new PrintWriter(System.out, true));
   }
 
diff --git a/src/test/java/com/healthmarketscience/jackcess/impl/AutoNumberTest.java b/src/test/java/com/healthmarketscience/jackcess/impl/AutoNumberTest.java
new file mode 100644 (file)
index 0000000..37eec77
--- /dev/null
@@ -0,0 +1,499 @@
+/*
+Copyright (c) 2015 James Ahlborn
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package com.healthmarketscience.jackcess.impl;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import com.healthmarketscience.jackcess.Column;
+import com.healthmarketscience.jackcess.ColumnBuilder;
+import com.healthmarketscience.jackcess.CursorBuilder;
+import com.healthmarketscience.jackcess.DataType;
+import com.healthmarketscience.jackcess.Database;
+import static com.healthmarketscience.jackcess.Database.*;
+import com.healthmarketscience.jackcess.Row;
+import com.healthmarketscience.jackcess.Table;
+import com.healthmarketscience.jackcess.TableBuilder;
+import static com.healthmarketscience.jackcess.TestUtil.*;
+import com.healthmarketscience.jackcess.complex.ComplexValueForeignKey;
+import static com.healthmarketscience.jackcess.impl.JetFormatTest.*;
+import junit.framework.TestCase;
+
+/**
+ *
+ * @author James Ahlborn
+ */
+public class AutoNumberTest extends TestCase
+{
+
+  public AutoNumberTest(String name) throws Exception {
+    super(name);
+  }
+
+
+  public void testAutoNumber() throws Exception 
+  {
+    for (final FileFormat fileFormat : SUPPORTED_FILEFORMATS) {
+      Database db = createMem(fileFormat);
+
+      Table table = new TableBuilder("test")
+        .addColumn(new ColumnBuilder("a", DataType.LONG)
+                  .setAutoNumber(true))
+        .addColumn(new ColumnBuilder("b", DataType.TEXT))
+        .toTable(db);
+
+      doTestAutoNumber(table);
+
+      db.close();
+    }
+  }  
+
+  public void testAutoNumberPK() throws Exception 
+  {
+    for (final TestDB testDB : SUPPORTED_DBS_TEST) {
+      Database db = openMem(testDB);
+
+      Table table = db.getTable("Table3");
+
+      doTestAutoNumber(table);
+
+      db.close();
+    }
+  }  
+
+  private static void doTestAutoNumber(Table table) throws Exception
+  {
+    Object[] row = {null, "row1"};
+    assertSame(row, table.addRow(row));
+    assertEquals(1, ((Integer)row[0]).intValue());
+    row = table.addRow(13, "row2");
+    assertEquals(2, ((Integer)row[0]).intValue());
+    row = table.addRow("flubber", "row3");
+    assertEquals(3, ((Integer)row[0]).intValue());
+
+    table.reset();
+
+    row = table.addRow(Column.AUTO_NUMBER, "row4");
+    assertEquals(4, ((Integer)row[0]).intValue());
+    row = table.addRow(Column.AUTO_NUMBER, "row5");
+    assertEquals(5, ((Integer)row[0]).intValue());
+
+    Object[] smallRow = {Column.AUTO_NUMBER};
+    row = table.addRow(smallRow);
+    assertNotSame(row, smallRow);
+    assertEquals(6, ((Integer)row[0]).intValue());    
+
+    table.reset();
+
+    List<? extends Map<String, Object>> expectedRows =
+      createExpectedTable(
+          createExpectedRow(
+              "a", 1,
+              "b", "row1"),
+          createExpectedRow(
+              "a", 2,
+              "b", "row2"),
+          createExpectedRow(
+              "a", 3,
+              "b", "row3"),
+          createExpectedRow(
+              "a", 4,
+              "b", "row4"),
+          createExpectedRow(
+              "a", 5,
+              "b", "row5"),
+          createExpectedRow(
+              "a", 6,
+              "b", null));
+
+    assertTable(expectedRows, table);    
+  }
+
+  public void testAutoNumberGuid() throws Exception 
+  {
+    for (final FileFormat fileFormat : SUPPORTED_FILEFORMATS) {
+      Database db = createMem(fileFormat);
+
+      Table table = new TableBuilder("test")
+        .addColumn(new ColumnBuilder("a", DataType.GUID)
+                  .setAutoNumber(true))
+        .addColumn(new ColumnBuilder("b", DataType.TEXT))
+        .toTable(db);
+
+      Object[] row = {null, "row1"};
+      assertSame(row, table.addRow(row));
+      assertTrue(ColumnImpl.isGUIDValue(row[0]));
+      row = table.addRow(13, "row2");
+      assertTrue(ColumnImpl.isGUIDValue(row[0]));
+      row = table.addRow("flubber", "row3");
+      assertTrue(ColumnImpl.isGUIDValue(row[0]));
+
+      Object[] smallRow = {Column.AUTO_NUMBER};
+      row = table.addRow(smallRow);
+      assertNotSame(row, smallRow);
+      assertTrue(ColumnImpl.isGUIDValue(row[0]));
+
+      db.close();
+    }
+  }  
+
+  public void testInsertLongAutoNumber() throws Exception
+  {
+    for (final FileFormat fileFormat : SUPPORTED_FILEFORMATS) {
+      Database db = createMem(fileFormat);
+
+      Table table = new TableBuilder("test")
+        .addColumn(new ColumnBuilder("a", DataType.LONG)
+                  .setAutoNumber(true))
+        .addColumn(new ColumnBuilder("b", DataType.TEXT))
+        .toTable(db);
+
+      doTestInsertLongAutoNumber(table);
+
+      db.close();
+    }    
+  }
+
+  public void testInsertLongAutoNumberPK() throws Exception
+  {
+    for (final FileFormat fileFormat : SUPPORTED_FILEFORMATS) {
+      Database db = createMem(fileFormat);
+
+      Table table = new TableBuilder("test")
+        .addColumn(new ColumnBuilder("a", DataType.LONG)
+                  .setAutoNumber(true))
+        .addColumn(new ColumnBuilder("b", DataType.TEXT))
+        .setPrimaryKey("a")
+        .toTable(db);
+
+      doTestInsertLongAutoNumber(table);
+
+      db.close();
+    }    
+  }
+
+  private static void doTestInsertLongAutoNumber(Table table) throws Exception
+  {
+    assertFalse(table.getDatabase().isAllowAutoNumberInsert());
+    assertFalse(table.isAllowAutoNumberInsert());
+
+    Object[] row = {null, "row1"};
+    assertSame(row, table.addRow(row));
+    assertEquals(1, ((Integer)row[0]).intValue());
+    row = table.addRow(13, "row2");
+    assertEquals(2, ((Integer)row[0]).intValue());
+    row = table.addRow("flubber", "row3");
+    assertEquals(3, ((Integer)row[0]).intValue());
+
+    table.reset();
+
+    table.setAllowAutoNumberInsert(true);
+    assertFalse(table.getDatabase().isAllowAutoNumberInsert());
+    assertTrue(table.isAllowAutoNumberInsert());
+
+    Row row2 = CursorBuilder.findRow(
+        table, Collections.singletonMap("a", 2));
+    assertEquals("row2", row2.getString("b"));
+
+    table.deleteRow(row2);
+
+    row = table.addRow(Column.AUTO_NUMBER, "row4");
+    assertEquals(4, ((Integer)row[0]).intValue());
+
+    assertEquals(4, ((TableImpl)table).getLastLongAutoNumber());
+
+    row = table.addRow(2, "row2-redux");
+    assertEquals(2, ((Integer)row[0]).intValue());
+
+    assertEquals(4, ((TableImpl)table).getLastLongAutoNumber());
+
+    row2 = CursorBuilder.findRow(
+        table, Collections.singletonMap("a", 2));
+    assertEquals("row2-redux", row2.getString("b"));
+
+    row = table.addRow(13, "row13-mindthegap");
+    assertEquals(13, ((Integer)row[0]).intValue());
+
+    assertEquals(13, ((TableImpl)table).getLastLongAutoNumber());
+    
+    try {
+      table.addRow("not a number", "nope");
+      fail("NumberFormatException should have been thrown");
+    } catch(NumberFormatException e) {
+      // success
+    }
+
+    assertEquals(13, ((TableImpl)table).getLastLongAutoNumber());
+
+    try {
+      table.addRow(-10, "uh-uh");
+      fail("IOException should have been thrown");
+    } catch(IOException e) {
+      // success
+    }
+
+    row = table.addRow(Column.AUTO_NUMBER, "row14");
+    assertEquals(14, ((Integer)row[0]).intValue());
+
+    Row row13 = CursorBuilder.findRow(
+        table, Collections.singletonMap("a", 13));
+    assertEquals("row13-mindthegap", row13.getString("b"));
+
+    row13.put("a", "45");
+    row13 = table.updateRow(row13);
+    assertEquals(45, row13.get("a"));
+
+    assertEquals(45, ((TableImpl)table).getLastLongAutoNumber());
+
+    row13.put("a", -1);
+
+    try {
+      table.updateRow(row13);
+      fail("IOException should have been thrown");
+    } catch(IOException e) {
+      // success
+    }
+
+    assertEquals(45, ((TableImpl)table).getLastLongAutoNumber());
+
+    row13.put("a", 55);
+
+    table.setAllowAutoNumberInsert(null);
+
+    row13 = table.updateRow(row13);
+    assertEquals(45, row13.get("a"));
+
+    assertEquals(45, ((TableImpl)table).getLastLongAutoNumber());
+    
+  }
+
+  public void testInsertComplexAutoNumber() throws Exception
+  {
+    for(final TestDB testDB : TestDB.getSupportedForBasename(Basename.COMPLEX)) {
+      
+      Database db = openMem(testDB);
+
+      Table t1 = db.getTable("Table1");
+
+      assertFalse(t1.isAllowAutoNumberInsert());
+
+      int lastAutoNum = ((TableImpl)t1).getLastComplexTypeAutoNumber();
+
+      Object[] row = t1.addRow("arow");
+      ++lastAutoNum;
+      checkAllComplexAutoNums(lastAutoNum, row);
+
+      assertEquals(lastAutoNum, ((TableImpl)t1).getLastComplexTypeAutoNumber());
+
+      db.setAllowAutoNumberInsert(true);
+      assertTrue(db.isAllowAutoNumberInsert());
+      assertTrue(t1.isAllowAutoNumberInsert());
+
+      row = t1.addRow("anotherrow");
+      ++lastAutoNum;
+      checkAllComplexAutoNums(lastAutoNum, row);
+
+      assertEquals(lastAutoNum, ((TableImpl)t1).getLastComplexTypeAutoNumber());
+      
+      row = t1.addRow("row5", 5, null, null, 5, 5);
+      checkAllComplexAutoNums(5, row);
+
+      assertEquals(lastAutoNum, ((TableImpl)t1).getLastComplexTypeAutoNumber());
+
+      row = t1.addRow("row13", 13, null, null, 13, 13);
+      checkAllComplexAutoNums(13, row);
+
+      assertEquals(13, ((TableImpl)t1).getLastComplexTypeAutoNumber());
+
+      try {
+        t1.addRow("nope", "not a number");
+        fail("NumberFormatException should have been thrown");
+      } catch(NumberFormatException e) {
+        // success
+      }
+
+      assertEquals(13, ((TableImpl)t1).getLastComplexTypeAutoNumber());
+
+      try {
+        t1.addRow("uh-uh", -10);
+        fail("IOException should have been thrown");
+      } catch(IOException e) {
+        // success
+      }
+
+      assertEquals(13, ((TableImpl)t1).getLastComplexTypeAutoNumber());
+
+      try {
+        t1.addRow("wut", 6, null, null, 40, 42);
+        fail("IOException should have been thrown");
+      } catch(IOException e) {
+        // success
+      }
+
+      row = t1.addRow("morerows");
+      checkAllComplexAutoNums(14, row);
+
+      assertEquals(14, ((TableImpl)t1).getLastComplexTypeAutoNumber());
+      
+      Row row13 = CursorBuilder.findRow(
+          t1, Collections.singletonMap("id", "row13"));
+
+      row13.put("VersionHistory_F5F8918F-0A3F-4DA9-AE71-184EE5012880", "45");
+      row13.put("multi-value-data", "45");
+      row13.put("attach-data", "45");
+      row13 = t1.updateRow(row13);
+      checkAllComplexAutoNums(45, row13);
+
+      assertEquals(45, ((TableImpl)t1).getLastComplexTypeAutoNumber());
+      
+      row13.put("attach-data", -1);
+
+      try {
+        t1.updateRow(row13);
+        fail("IOException should have been thrown");
+      } catch(IOException e) {
+        // success
+      }
+
+      assertEquals(45, ((TableImpl)t1).getLastComplexTypeAutoNumber());
+
+      row13.put("attach-data", 55);
+
+      try {
+        t1.updateRow(row13);
+        fail("IOException should have been thrown");
+      } catch(IOException e) {
+        // success
+      }
+
+      assertEquals(45, ((TableImpl)t1).getLastComplexTypeAutoNumber());
+
+      row13.put("VersionHistory_F5F8918F-0A3F-4DA9-AE71-184EE5012880", 55);
+      row13.put("multi-value-data", 55);
+
+      db.setAllowAutoNumberInsert(null);
+
+      row13 = t1.updateRow(row13);
+      checkAllComplexAutoNums(45, row13);
+
+      assertEquals(45, ((TableImpl)t1).getLastComplexTypeAutoNumber());
+
+      db.close();
+    }
+  }
+
+  private static void checkAllComplexAutoNums(int expected, Object[] row)
+  {
+    assertEquals(expected, ((ComplexValueForeignKey)row[1]).get());
+    assertEquals(expected, ((ComplexValueForeignKey)row[4]).get());
+    assertEquals(expected, ((ComplexValueForeignKey)row[5]).get());
+  }
+
+  private static void checkAllComplexAutoNums(int expected, Row row)
+  {
+      assertEquals(expected, ((Number)row.get("VersionHistory_F5F8918F-0A3F-4DA9-AE71-184EE5012880")).intValue());
+      assertEquals(expected, ((Number)row.get("multi-value-data")).intValue());
+      assertEquals(expected, ((Number)row.get("attach-data")).intValue());
+  }
+
+  public void testInsertGuidAutoNumber() throws Exception 
+  {
+    for (final FileFormat fileFormat : SUPPORTED_FILEFORMATS) {
+      Database db = createMem(fileFormat);
+
+      Table table = new TableBuilder("test")
+        .addColumn(new ColumnBuilder("a", DataType.GUID)
+                  .setAutoNumber(true))
+        .addColumn(new ColumnBuilder("b", DataType.TEXT))
+        .toTable(db);
+
+      db.setAllowAutoNumberInsert(true);
+      table.setAllowAutoNumberInsert(false);
+      assertFalse(table.isAllowAutoNumberInsert());
+
+      Object[] row = {null, "row1"};
+      assertSame(row, table.addRow(row));
+      assertTrue(ColumnImpl.isGUIDValue(row[0]));
+      row = table.addRow(13, "row2");
+      assertTrue(ColumnImpl.isGUIDValue(row[0]));
+      row = table.addRow("flubber", "row3");
+      assertTrue(ColumnImpl.isGUIDValue(row[0]));
+
+      Object[] smallRow = {Column.AUTO_NUMBER};
+      row = table.addRow(smallRow);
+      assertNotSame(row, smallRow);
+      assertTrue(ColumnImpl.isGUIDValue(row[0]));
+
+      table.setAllowAutoNumberInsert(null);
+      assertTrue(table.isAllowAutoNumberInsert());
+      
+      Row row2 = CursorBuilder.findRow(
+          table, Collections.singletonMap("b", "row2"));
+      assertEquals("row2", row2.getString("b"));
+      
+      String row2Guid = row2.getString("a");
+      table.deleteRow(row2);
+
+      row = table.addRow(Column.AUTO_NUMBER, "row4");
+      assertTrue(ColumnImpl.isGUIDValue(row[0]));
+
+      row = table.addRow(row2Guid, "row2-redux");
+      assertEquals(row2Guid, row[0]);
+
+      row2 = CursorBuilder.findRow(
+          table, Collections.singletonMap("a", row2Guid));
+      assertEquals("row2-redux", row2.getString("b"));
+
+      try {
+        table.addRow("not a guid", "nope");
+        fail("IOException should have been thrown");
+      } catch(IOException e) {
+        // success
+      }
+
+      row = table.addRow(Column.AUTO_NUMBER, "row5");
+      assertTrue(ColumnImpl.isGUIDValue(row[0]));
+
+      row2Guid = UUID.randomUUID().toString();
+      row2.put("a", row2Guid);
+
+      row2 = table.updateRow(row2);
+      assertEquals(row2Guid, row2.get("a"));
+
+      row2.put("a", "not a guid");
+
+      try {
+        table.updateRow(row2);
+        fail("IOException should have been thrown");
+      } catch(IOException e) {
+        // success
+      }
+
+      table.setAllowAutoNumberInsert(false);
+
+      row2 = table.updateRow(row2);
+      assertTrue(ColumnImpl.isGUIDValue(row2.get("a")));
+      assertFalse(row2Guid.equals(row2.get("a")));
+
+      db.close();
+    }
+  }  
+
+}