From: James Ahlborn Date: Tue, 26 Nov 2013 23:53:55 +0000 (+0000) Subject: attempt to restore autonumbers on some add row failures X-Git-Tag: jackcess-2.0.2~3 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=a3c4f6214b309667186d783daac2ea1351da30d2;p=jackcess.git attempt to restore autonumbers on some add row failures git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@838 f203690c-595d-4dc9-a70b-905162fa7fd2 --- diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java index a6cba97..ae68a17 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java @@ -1993,6 +1993,11 @@ public class ColumnImpl implements Column, Comparable { */ 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 { 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 { 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 { 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 { throw new UnsupportedOperationException(); } + @Override + public void restoreLast(Object last) { + throw new UnsupportedOperationException(); + } + @Override public DataType getType() { return _genType; diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java index 638f7fc..15dfaec 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java @@ -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 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() { diff --git a/src/test/java/com/healthmarketscience/jackcess/IndexTest.java b/src/test/java/com/healthmarketscience/jackcess/IndexTest.java index 623ec35..2b325a6 100644 --- a/src/test/java/com/healthmarketscience/jackcess/IndexTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/IndexTest.java @@ -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 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 batch = new ArrayList(); + 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(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 {