]> source.dussan.org Git - jackcess.git/commitdiff
attempt to restore autonumbers on some add row failures
authorJames Ahlborn <jtahlborn@yahoo.com>
Tue, 26 Nov 2013 23:53:55 +0000 (23:53 +0000)
committerJames Ahlborn <jtahlborn@yahoo.com>
Tue, 26 Nov 2013 23:53:55 +0000 (23:53 +0000)
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@838 f203690c-595d-4dc9-a70b-905162fa7fd2

src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java
src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java
src/test/java/com/healthmarketscience/jackcess/IndexTest.java

index a6cba978f8deed169f0df8d2a1ce8ac93426593c..ae68a175d07a10acc15c9fedd45e6970f6542b46 100644 (file)
@@ -1993,6 +1993,11 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
      */
     public abstract Object getNext(Object prevRowValue);
 
+    /**
+     * Restores a previous autonumber generated by this generator.
+     */
+    public abstract void restoreLast(Object last);
+    
     /**
      * Returns the type of values generated by this generator.
      */
@@ -2015,6 +2020,13 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
       return getTable().getNextLongAutoNumber();
     }
 
+    @Override
+    public void restoreLast(Object last) {
+      if(last instanceof Integer) {
+        getTable().restoreLastLongAutoNumber((Integer)last);
+      }
+    }
+    
     @Override
     public DataType getType() {
       return DataType.LONG;
@@ -2039,6 +2051,11 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
       return _lastAutoNumber;
     }
 
+    @Override
+    public void restoreLast(Object last) {
+      _lastAutoNumber = null;
+    }
+    
     @Override
     public DataType getType() {
       return DataType.GUID;
@@ -2066,6 +2083,14 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
       return new ComplexValueForeignKeyImpl(ColumnImpl.this, nextComplexAutoNum);
     }
 
+    @Override
+    public void restoreLast(Object last) {
+      if(last instanceof ComplexValueForeignKey) {
+        getTable().restoreLastComplexTypeAutoNumber(
+            ((ComplexValueForeignKey)last).get());
+      }
+    }
+    
     @Override
     public DataType getType() {
       return DataType.COMPLEX_TYPE;
@@ -2090,6 +2115,11 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
       throw new UnsupportedOperationException();
     }
 
+    @Override
+    public void restoreLast(Object last) {
+      throw new UnsupportedOperationException();
+    }
+    
     @Override
     public DataType getType() {
       return _genType;
index 638f7fc9e347eed3bad5a7562cbf0fd30049ffa9..15dfaec843b35f8cf1838c09328c607a560aea76 100644 (file)
@@ -1503,6 +1503,7 @@ public class TableImpl implements Table
       ByteBuffer dataPage = null;
       int pageNumber = PageChannel.INVALID_PAGE_NUMBER;
       int updateCount = 0;
+      int autoNumAssignCount = 0;
       try {
 
         List<Object[]> dupeRows = null;
@@ -1529,6 +1530,7 @@ public class TableImpl implements Table
 
           // fill in autonumbers
           handleAutoNumbersForAdd(row);
+          ++autoNumAssignCount;
       
           // write the row of data to a temporary buffer
           ByteBuffer rowData = createRow(
@@ -1592,6 +1594,14 @@ public class TableImpl implements Table
 
       } catch(Exception rowWriteFailure) {
 
+        boolean isWriteFailure = isWriteFailure(rowWriteFailure);
+
+        if(!isWriteFailure && (autoNumAssignCount > updateCount)) {
+          // we assigned some autonumbers which won't get written.  attempt to
+          // recover them so we don't get ugly "holes"
+          restoreAutoNumbersFromAdd(rows.get(autoNumAssignCount - 1));
+        }
+        
         if(!isBatchWrite) {
           // just re-throw the original exception
           if(rowWriteFailure instanceof IOException) {
@@ -1601,7 +1611,7 @@ public class TableImpl implements Table
         }
 
         // attempt to resolve a partial batch write
-        if(isWriteFailure(rowWriteFailure)) {
+        if(isWriteFailure) {
 
           // we don't really know the status of any of the rows, so clear the
           // update count
@@ -2149,6 +2159,22 @@ public class TableImpl implements Table
     }
   }
 
+  /**
+   * Restores all autonumber column values from a failed add row.
+   */
+  private void restoreAutoNumbersFromAdd(Object[] row)
+    throws IOException
+  {
+    if(_autoNumColumns.isEmpty()) {
+      return;
+    }
+
+    for(ColumnImpl col : _autoNumColumns) {
+      // restore the last value from the row
+      col.getAutoNumberGenerator().restoreLast(col.getRowValue(row));
+    }
+  }
+
   private static void padRowBuffer(ByteBuffer buffer, int minRowSize,
                                    int trailerSize)
   {
@@ -2174,6 +2200,11 @@ public class TableImpl implements Table
     // gets the last used auto number (does not modify)
     return _lastLongAutoNumber;
   }
+
+  void restoreLastLongAutoNumber(int lastLongAutoNumber) {
+    // restores the last used auto number
+    _lastLongAutoNumber = lastLongAutoNumber - 1;
+  }
   
   int getNextComplexTypeAutoNumber() {
     // note, the saved value is the last one handed out, so pre-increment
@@ -2184,6 +2215,11 @@ public class TableImpl implements Table
     // gets the last used auto number (does not modify)
     return _lastComplexTypeAutoNumber;
   }
+
+  void restoreLastComplexTypeAutoNumber(int lastComplexTypeAutoNumber) {
+    // restores the last used auto number
+    _lastComplexTypeAutoNumber = lastComplexTypeAutoNumber - 1;
+  }
   
   @Override
   public String toString() {
index 623ec351d1302547c795c658c9efc3810681cb85..2b325a6fd1a26c3bb047dea6b94d61a54628485d 100644 (file)
@@ -593,6 +593,81 @@ public class IndexTest extends TestCase {
     }    
   }
 
+  public void testAutoNumberRecover() throws Exception
+  {
+    for (final FileFormat fileFormat : SUPPORTED_FILEFORMATS) {
+      Database db = create(fileFormat);
+
+      Table t = new TableBuilder("TestTable")
+        .addColumn(new ColumnBuilder("id", DataType.LONG).setAutoNumber(true))
+        .addColumn(new ColumnBuilder("data", DataType.TEXT))
+        .addIndex(new IndexBuilder(IndexBuilder.PRIMARY_KEY_NAME)
+                  .addColumns("id").setPrimaryKey())
+        .addIndex(new IndexBuilder("data_ind")
+                  .addColumns("data").setUnique())
+        .toTable(db);
+
+      for(int i = 1; i < 3; ++i) {
+        t.addRow(null, "row" + i);
+      }
+
+      try {
+        t.addRow(null, "row1");
+        fail("ConstraintViolationException should have been thrown");
+      } catch(ConstraintViolationException ce) {
+        // success
+      }
+      
+      t.addRow(null, "row3");
+
+      assertEquals(3, t.getRowCount());
+
+      List<Row> expectedRows =
+        createExpectedTable(
+            createExpectedRow(
+                "id", 1, "data", "row1"),
+            createExpectedRow(
+                "id", 2, "data", "row2"),
+            createExpectedRow(
+                "id", 3, "data", "row3"));
+
+      assertTable(expectedRows, t);
+
+      IndexCursor pkCursor = CursorBuilder.createCursor(t.getPrimaryKeyIndex());
+      assertCursor(expectedRows, pkCursor);
+
+      assertCursor(expectedRows, 
+                   CursorBuilder.createCursor(t.getIndex("data_ind")));
+
+      List<Object[]> batch = new ArrayList<Object[]>();
+      batch.add(new Object[]{null, "row4"});
+      batch.add(new Object[]{null, "row5"});
+      batch.add(new Object[]{null, "row3"});
+
+      try {
+        t.addRows(batch);
+        fail("BatchUpdateException should have been thrown");
+      } catch(BatchUpdateException be) {
+        // success
+        assertTrue(be.getCause() instanceof ConstraintViolationException);
+        assertEquals(2, be.getUpdateCount());
+      }
+
+      expectedRows = new ArrayList<Row>(expectedRows);
+      expectedRows.add(createExpectedRow("id", 4, "data", "row4"));
+      expectedRows.add(createExpectedRow("id", 5, "data", "row5"));
+
+      assertTable(expectedRows, t);
+      
+      assertCursor(expectedRows, pkCursor);
+
+      assertCursor(expectedRows, 
+                   CursorBuilder.createCursor(t.getIndex("data_ind")));
+
+      db.close();
+    }
+  }
+  
   private void doCheckForeignKeyIndex(Table ta, Index ia, Table tb)
     throws Exception
   {