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">
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_
*/
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_
*/
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_
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 */
* <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.
}
@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) {
}
@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;
}
@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
}
@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();
}
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 */
_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);
_enforceForeignKeys = newEnforceForeignKeys;
}
+ public boolean isAllowAutoNumberInsert() {
+ return _allowAutoNumInsert;
+ }
+
+ public void setAllowAutoNumberInsert(Boolean allowAutoNumInsert) {
+ if(allowAutoNumInsert == null) {
+ allowAutoNumInsert = getDefaultAllowAutoNumberInsert();
+ }
+ _allowAutoNumInsert = allowAutoNumInsert;
+ }
+
+
public ColumnValidatorFactory getColumnValidatorFactory() {
return _validatorFactory;
}
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.
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;
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;
return _tableDefPageNumber;
}
+ public boolean isAllowAutoNumberInsert() {
+ return ((_allowAutoNumInsert != null) ? (boolean)_allowAutoNumInsert :
+ getDatabase().isAllowAutoNumberInsert());
+ }
+
+ public void setAllowAutoNumberInsert(Boolean allowAutoNumInsert) {
+ _allowAutoNumInsert = allowAutoNumInsert;
+ }
+
/**
* @usage _advanced_method_
*/
/**
* @param buffer Buffer to write to
- * @param columns List of Columns in the table
*/
private static void writeTableDefinitionHeader(
TableCreator creator, ByteBuffer buffer, int totalTableDefSize)
/**
* 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)
int pageNumber = PageChannel.INVALID_PAGE_NUMBER;
int updateCount = 0;
int autoNumAssignCount = 0;
+ WriteRowState writeRowState =
+ (!_autoNumColumns.isEmpty() ? new WriteRowState() : null);
try {
List<Object[]> dupeRows = null;
}
// fill in autonumbers
- handleAutoNumbersForAdd(row);
+ handleAutoNumbersForAdd(row, writeRowState);
++autoNumAssignCount;
// write the row of data to a temporary buffer
// 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,
}
/**
- * 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.
*/
return _lastLongAutoNumber;
}
+ void adjustLongAutoNumber(int inLongAutoNumber) {
+ if(inLongAutoNumber > _lastLongAutoNumber) {
+ _lastLongAutoNumber = inLongAutoNumber;
+ }
+ }
+
void restoreLastLongAutoNumber(int lastLongAutoNumber) {
// restores the last used auto number
_lastLongAutoNumber = lastLongAutoNumber - 1;
return _lastComplexTypeAutoNumber;
}
+ void adjustComplexTypeAutoNumber(int inComplexTypeAutoNumber) {
+ if(inComplexTypeAutoNumber > _lastComplexTypeAutoNumber) {
+ _lastComplexTypeAutoNumber = inComplexTypeAutoNumber;
+ }
+ }
+
void restoreLastComplexTypeAutoNumber(int lastComplexTypeAutoNumber) {
// restores the last used auto number
_lastComplexTypeAutoNumber = lastComplexTypeAutoNumber - 1;
}
/**
- * 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;
}
public void reset() {
+ resetAutoNumber();
_finalRowId = null;
_finalRowBuffer = null;
_rowsOnHeaderPage = 0;
}
}
- 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);
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));
}
--- /dev/null
+/*
+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();
+ }
+ }
+
+}