Quellcode durchsuchen

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. fixes feature #32

git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@941 f203690c-595d-4dc9-a70b-905162fa7fd2
tags/jackcess-2.1.1
James Ahlborn vor 9 Jahren
Ursprung
Commit
e8616e6476

+ 6
- 0
src/changes/changes.xml Datei anzeigen

@@ -9,6 +9,12 @@
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">

+ 32
- 0
src/main/java/com/healthmarketscience/jackcess/Database.java Datei anzeigen

@@ -117,6 +117,13 @@ public interface Database extends Iterable<Table>, Closeable, Flushable
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_
@@ -392,6 +399,31 @@ public interface Database extends Iterable<Table>, Closeable, Flushable
*/
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_

+ 14
- 0
src/main/java/com/healthmarketscience/jackcess/Table.java Datei anzeigen

@@ -98,6 +98,20 @@ public interface Table extends Iterable<Row>
*/
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_

+ 96
- 13
src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java Datei anzeigen

@@ -151,7 +151,10 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
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 */
@@ -1843,7 +1846,17 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
* <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.
@@ -1867,11 +1880,26 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
}

@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) {
@@ -1897,12 +1925,21 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
}

@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;
@@ -1925,14 +1962,54 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
}

@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
@@ -1963,7 +2040,13 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
}

@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();
}


+ 30
- 0
src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java Datei anzeigen

@@ -301,6 +301,8 @@ public class DatabaseImpl implements Database
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 */
@@ -501,6 +503,7 @@ public class DatabaseImpl implements Database
_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);
@@ -662,6 +665,18 @@ public class DatabaseImpl implements Database
_enforceForeignKeys = newEnforceForeignKeys;
}

public boolean isAllowAutoNumberInsert() {
return _allowAutoNumInsert;
}

public void setAllowAutoNumberInsert(Boolean allowAutoNumInsert) {
if(allowAutoNumInsert == null) {
allowAutoNumInsert = getDefaultAllowAutoNumberInsert();
}
_allowAutoNumInsert = allowAutoNumInsert;
}


public ColumnValidatorFactory getColumnValidatorFactory() {
return _validatorFactory;
}
@@ -1743,6 +1758,21 @@ public class DatabaseImpl implements Database
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.

+ 140
- 48
src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java Datei anzeigen

@@ -38,7 +38,6 @@ import com.healthmarketscience.jackcess.Column;
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;
@@ -166,6 +165,9 @@ public class TableImpl implements Table
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;

@@ -356,6 +358,15 @@ public class TableImpl implements Table
return _tableDefPageNumber;
}

public boolean isAllowAutoNumberInsert() {
return ((_allowAutoNumInsert != null) ? (boolean)_allowAutoNumInsert :
getDatabase().isAllowAutoNumberInsert());
}

public void setAllowAutoNumberInsert(Boolean allowAutoNumInsert) {
_allowAutoNumInsert = allowAutoNumInsert;
}

/**
* @usage _advanced_method_
*/
@@ -1079,7 +1090,6 @@ public class TableImpl implements Table

/**
* @param buffer Buffer to write to
* @param columns List of Columns in the table
*/
private static void writeTableDefinitionHeader(
TableCreator creator, ByteBuffer buffer, int totalTableDefSize)
@@ -1499,7 +1509,7 @@ public class TableImpl implements Table
/**
* 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)
@@ -1516,6 +1526,8 @@ public class TableImpl implements Table
int pageNumber = PageChannel.INVALID_PAGE_NUMBER;
int updateCount = 0;
int autoNumAssignCount = 0;
WriteRowState writeRowState =
(!_autoNumColumns.isEmpty() ? new WriteRowState() : null);
try {

List<Object[]> dupeRows = null;
@@ -1549,7 +1561,7 @@ public class TableImpl implements Table
}

// fill in autonumbers
handleAutoNumbersForAdd(row);
handleAutoNumbersForAdd(row, writeRowState);
++autoNumAssignCount;
// write the row of data to a temporary buffer
@@ -1758,45 +1770,44 @@ public class TableImpl implements Table

// 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,
@@ -2178,32 +2189,79 @@ public class TableImpl implements Table
}

/**
* 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.
*/
@@ -2246,6 +2304,12 @@ public class TableImpl implements Table
return _lastLongAutoNumber;
}

void adjustLongAutoNumber(int inLongAutoNumber) {
if(inLongAutoNumber > _lastLongAutoNumber) {
_lastLongAutoNumber = inLongAutoNumber;
}
}

void restoreLastLongAutoNumber(int lastLongAutoNumber) {
// restores the last used auto number
_lastLongAutoNumber = lastLongAutoNumber - 1;
@@ -2261,6 +2325,12 @@ public class TableImpl implements Table
return _lastComplexTypeAutoNumber;
}

void adjustComplexTypeAutoNumber(int inComplexTypeAutoNumber) {
if(inComplexTypeAutoNumber > _lastComplexTypeAutoNumber) {
_lastComplexTypeAutoNumber = inComplexTypeAutoNumber;
}
}

void restoreLastComplexTypeAutoNumber(int lastComplexTypeAutoNumber) {
// restores the last used auto number
_lastComplexTypeAutoNumber = lastComplexTypeAutoNumber - 1;
@@ -2501,10 +2571,31 @@ public class TableImpl implements Table
}

/**
* 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;
@@ -2560,6 +2651,7 @@ public class TableImpl implements Table
}
public void reset() {
resetAutoNumber();
_finalRowId = null;
_finalRowBuffer = null;
_rowsOnHeaderPage = 0;

+ 0
- 76
src/test/java/com/healthmarketscience/jackcess/DatabaseTest.java Datei anzeigen

@@ -619,82 +619,6 @@ public class DatabaseTest extends TestCase
}
}

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);

+ 3
- 3
src/test/java/com/healthmarketscience/jackcess/TestUtil.java Datei anzeigen

@@ -254,17 +254,17 @@ public class TestUtil
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));
}


+ 499
- 0
src/test/java/com/healthmarketscience/jackcess/impl/AutoNumberTest.java Datei anzeigen

@@ -0,0 +1,499 @@
/*
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();
}
}

}

Laden…
Abbrechen
Speichern