git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@838 f203690c-595d-4dc9-a70b-905162fa7fd2tags/jackcess-2.0.2
@@ -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; |
@@ -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() { |
@@ -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 | |||
{ |