*/
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.
*/
return getTable().getNextLongAutoNumber();
}
+ @Override
+ public void restoreLast(Object last) {
+ if(last instanceof Integer) {
+ getTable().restoreLastLongAutoNumber((Integer)last);
+ }
+ }
+
@Override
public DataType getType() {
return DataType.LONG;
return _lastAutoNumber;
}
+ @Override
+ public void restoreLast(Object last) {
+ _lastAutoNumber = null;
+ }
+
@Override
public DataType getType() {
return DataType.GUID;
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;
throw new UnsupportedOperationException();
}
+ @Override
+ public void restoreLast(Object last) {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public DataType getType() {
return _genType;
ByteBuffer dataPage = null;
int pageNumber = PageChannel.INVALID_PAGE_NUMBER;
int updateCount = 0;
+ int autoNumAssignCount = 0;
try {
List<Object[]> dupeRows = null;
// fill in autonumbers
handleAutoNumbersForAdd(row);
+ ++autoNumAssignCount;
// write the row of data to a temporary buffer
ByteBuffer rowData = createRow(
} 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) {
}
// 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
}
}
+ /**
+ * 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)
{
// 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
// 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() {
}
}
+ 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
{