Browse Source

reorg to prep for RelationshipBuilder; move remaining table creation logic from TableBuilder to TableCreator

git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/branches/mutateops@1002 f203690c-595d-4dc9-a70b-905162fa7fd2
tags/jackcess-2.1.5
James Ahlborn 7 years ago
parent
commit
060172ce30

+ 3
- 4
src/main/java/com/healthmarketscience/jackcess/ColumnBuilder.java View File

@@ -26,7 +26,7 @@ import com.healthmarketscience.jackcess.impl.DatabaseImpl;
import com.healthmarketscience.jackcess.impl.JetFormat;
import com.healthmarketscience.jackcess.impl.PropertyMapImpl;
import com.healthmarketscience.jackcess.impl.TableImpl;
import com.healthmarketscience.jackcess.impl.TableMutator;
import com.healthmarketscience.jackcess.impl.TableUpdater;

/**
* Builder style class for constructing a {@link Column}. See {@link
@@ -479,9 +479,8 @@ public class ColumnBuilder {
* Adds a new Column to the given Table with the currently configured
* attributes.
*/
public Column addToTable(Table table) throws IOException
{
return new TableMutator((TableImpl)table).addColumn(this);
public Column addToTable(Table table) throws IOException {
return new TableUpdater((TableImpl)table).addColumn(this);
}

private String withErrorContext(String msg) {

+ 3
- 4
src/main/java/com/healthmarketscience/jackcess/IndexBuilder.java View File

@@ -27,7 +27,7 @@ import com.healthmarketscience.jackcess.impl.IndexData;
import com.healthmarketscience.jackcess.impl.IndexImpl;
import com.healthmarketscience.jackcess.impl.JetFormat;
import com.healthmarketscience.jackcess.impl.TableImpl;
import com.healthmarketscience.jackcess.impl.TableMutator;
import com.healthmarketscience.jackcess.impl.TableUpdater;

/**
* Builder style class for constructing an {@link Index}. See {@link
@@ -199,9 +199,8 @@ public class IndexBuilder
* Adds a new Index to the given Table with the currently configured
* attributes.
*/
public Index addToTable(Table table) throws IOException
{
return new TableMutator((TableImpl)table).addIndex(this);
public Index addToTable(Table table) throws IOException {
return new TableUpdater((TableImpl)table).addIndex(this);
}

private String withErrorContext(String msg) {

+ 6
- 0
src/main/java/com/healthmarketscience/jackcess/Relationship.java View File

@@ -27,6 +27,10 @@ import java.util.List;
*/
public interface Relationship
{
public enum JoinType {
INNER, LEFT_OUTER, RIGHT_OUTER;
}
public String getName();
public Table getFromTable();
@@ -50,4 +54,6 @@ public interface Relationship
public boolean isLeftOuterJoin();

public boolean isRightOuterJoin();

public JoinType getJoinType();
}

+ 30
- 25
src/main/java/com/healthmarketscience/jackcess/TableBuilder.java View File

@@ -28,6 +28,7 @@ import java.util.Set;

import com.healthmarketscience.jackcess.impl.DatabaseImpl;
import com.healthmarketscience.jackcess.impl.PropertyMapImpl;
import com.healthmarketscience.jackcess.impl.TableCreator;

/**
* Builder style class for constructing a {@link Table}.
@@ -116,6 +117,9 @@ public class TableBuilder {
}
}

public String getName() {
return _name;
}

/**
* Adds a Column to the new table.
@@ -140,6 +144,10 @@ public class TableBuilder {
return this;
}

public List<ColumnBuilder> getColumns() {
return _columns;
}
/**
* Adds an IndexBuilder to the new table.
*/
@@ -154,6 +162,22 @@ public class TableBuilder {
return this;
}

/**
* Adds the Indexes to the new table.
*/
public TableBuilder addIndexes(Collection<? extends IndexBuilder> indexes) {
if(indexes != null) {
for(IndexBuilder col : indexes) {
addIndex(col);
}
}
return this;
}

public List<IndexBuilder> getIndexes() {
return _indexes;
}
/**
* Sets whether or not subsequently added columns will have their names
* automatically escaped
@@ -202,35 +226,16 @@ public class TableBuilder {
return this;
}

public Map<String,PropertyMap.Property> getProperties() {
return _props;
}

/**
* Creates a new Table in the given Database with the currently configured
* attributes.
*/
public Table toTable(Database db)
throws IOException
{
((DatabaseImpl)db).createTable(_name, _columns, _indexes);
Table table = db.getTable(_name);

boolean addedProps = false;
if(_props != null) {
table.getProperties().putAll(_props.values());
addedProps = true;
}
for(ColumnBuilder cb : _columns) {
Map<String,PropertyMap.Property> colProps = cb.getProperties();
if(colProps != null) {
table.getColumn(cb.getName()).getProperties().putAll(colProps.values());
addedProps = true;
}
}

// all table and column props are saved together
if(addedProps) {
table.getProperties().save();
}
return table;
public Table toTable(Database db) throws IOException {
return new TableCreator(((DatabaseImpl)db)).createTable(this);
}

/**

+ 4
- 4
src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java View File

@@ -1590,10 +1590,10 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
}

protected static void writeDefinition(
DBMutator mutator, ColumnBuilder col, ByteBuffer buffer)
TableMutator mutator, ColumnBuilder col, ByteBuffer buffer)
throws IOException
{
DBMutator.ColumnOffsets colOffsets = mutator.getColumnOffsets();
TableMutator.ColumnOffsets colOffsets = mutator.getColumnOffsets();

buffer.put(col.getType().getValue());
buffer.putInt(TableImpl.MAGIC_TABLE_NUMBER); //constant magic number
@@ -1671,10 +1671,10 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
}

protected static void writeColUsageMapDefinition(
DBMutator creator, ColumnBuilder lvalCol, ByteBuffer buffer)
TableMutator creator, ColumnBuilder lvalCol, ByteBuffer buffer)
throws IOException
{
DBMutator.ColumnState colState = creator.getColumnState(lvalCol);
TableMutator.ColumnState colState = creator.getColumnState(lvalCol);

buffer.putShort(lvalCol.getColumnNumber());

+ 2
- 209
src/main/java/com/healthmarketscience/jackcess/impl/DBMutator.java View File

@@ -18,23 +18,16 @@ package com.healthmarketscience.jackcess.impl;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import com.healthmarketscience.jackcess.ColumnBuilder;
import com.healthmarketscience.jackcess.DataType;
import com.healthmarketscience.jackcess.IndexBuilder;

/**
* Helper class used to maintain state during database mutation.
* Common helper class used to maintain state during database mutation.
*
* @author James Ahlborn
*/
abstract class DBMutator
{
private final DatabaseImpl _database;
private ColumnOffsets _colOffsets;

protected DBMutator(DatabaseImpl database) {
_database = database;
@@ -60,75 +53,11 @@ abstract class DBMutator
return getPageChannel().allocateNewPage();
}

public void setColumnOffsets(
int fixedOffset, int varOffset, int longVarOffset) {
if(_colOffsets == null) {
_colOffsets = new ColumnOffsets();
}
_colOffsets.set(fixedOffset, varOffset, longVarOffset);
}

public ColumnOffsets getColumnOffsets() {
return _colOffsets;
}

public static int calculateNameLength(String name) {
return (name.length() * JetFormat.TEXT_FIELD_UNIT_SIZE) + 2;
}

protected void validateColumn(Set<String> colNames, ColumnBuilder column) {

// FIXME for now, we can't create complex columns
if(column.getType() == DataType.COMPLEX_TYPE) {
throw new UnsupportedOperationException(
"Complex column creation is not yet implemented");
}
column.validate(getFormat());
if(!colNames.add(column.getName().toUpperCase())) {
throw new IllegalArgumentException("duplicate column name: " +
column.getName());
}

setColumnSortOrder(column);
}

protected void validateIndex(Set<String> colNames, Set<String> idxNames,
boolean[] foundPk, IndexBuilder index) {
index.validate(colNames, getFormat());
if(!idxNames.add(index.getName().toUpperCase())) {
throw new IllegalArgumentException("duplicate index name: " +
index.getName());
}
if(index.isPrimaryKey()) {
if(foundPk[0]) {
throw new IllegalArgumentException(
"found second primary key index: " + index.getName());
}
foundPk[0] = true;
}
}

protected static void validateAutoNumberColumn(Set<DataType> autoTypes,
ColumnBuilder column)
{
if(!column.getType().isMultipleAutoNumberAllowed() &&
!autoTypes.add(column.getType())) {
throw new IllegalArgumentException(
"Can have at most one AutoNumber column of type " + column.getType() +
" per table");
}
}

private void setColumnSortOrder(ColumnBuilder column) {
// set the sort order to the db default (if unspecified)
if(column.getType().isTextual() && (column.getTextSortOrder() == null)) {
column.setTextSortOrder(getDbSortOrder());
}
}

private ColumnImpl.SortOrder getDbSortOrder() {
protected ColumnImpl.SortOrder getDbSortOrder() {
try {
return _database.getDefaultSortOrder();
} catch(IOException e) {
@@ -136,140 +65,4 @@ abstract class DBMutator
}
return null;
}

abstract String getTableName();

public abstract int getTdefPageNumber();

abstract short getColumnNumber(String colName);

public abstract ColumnState getColumnState(ColumnBuilder col);

public abstract IndexDataState getIndexDataState(IndexBuilder idx);

/**
* Maintains additional state used during column writing.
* @usage _advanced_class_
*/
static final class ColumnOffsets
{
private short _fixedOffset;
private short _varOffset;
private short _longVarOffset;

public void set(int fixedOffset, int varOffset, int longVarOffset) {
_fixedOffset = (short)fixedOffset;
_varOffset = (short)varOffset;
_longVarOffset = (short)longVarOffset;
}
public short getNextVariableOffset(ColumnBuilder col) {
if(!col.getType().isLongValue()) {
return _varOffset++;
}
return _longVarOffset++;
}

public short getNextFixedOffset(ColumnBuilder col) {
short offset = _fixedOffset;
_fixedOffset += col.getType().getFixedSize(col.getLength());
return offset;
}
}

/**
* Maintains additional state used during column creation.
* @usage _advanced_class_
*/
static final class ColumnState
{
private byte _umapOwnedRowNumber;
private byte _umapFreeRowNumber;
// we always put both usage maps on the same page
private int _umapPageNumber;

public byte getUmapOwnedRowNumber() {
return _umapOwnedRowNumber;
}

public void setUmapOwnedRowNumber(byte newUmapOwnedRowNumber) {
_umapOwnedRowNumber = newUmapOwnedRowNumber;
}

public byte getUmapFreeRowNumber() {
return _umapFreeRowNumber;
}

public void setUmapFreeRowNumber(byte newUmapFreeRowNumber) {
_umapFreeRowNumber = newUmapFreeRowNumber;
}

public int getUmapPageNumber() {
return _umapPageNumber;
}

public void setUmapPageNumber(int newUmapPageNumber) {
_umapPageNumber = newUmapPageNumber;
}
}

/**
* Maintains additional state used during index data creation.
* @usage _advanced_class_
*/
static final class IndexDataState
{
private final List<IndexBuilder> _indexes = new ArrayList<IndexBuilder>();
private int _indexDataNumber;
private byte _umapRowNumber;
private int _umapPageNumber;
private int _rootPageNumber;

public IndexBuilder getFirstIndex() {
// all indexes which have the same backing IndexDataState will have
// equivalent columns and flags.
return _indexes.get(0);
}

public List<IndexBuilder> getIndexes() {
return _indexes;
}

public void addIndex(IndexBuilder idx) {
_indexes.add(idx);
}

public int getIndexDataNumber() {
return _indexDataNumber;
}

public void setIndexDataNumber(int newIndexDataNumber) {
_indexDataNumber = newIndexDataNumber;
}

public byte getUmapRowNumber() {
return _umapRowNumber;
}

public void setUmapRowNumber(byte newUmapRowNumber) {
_umapRowNumber = newUmapRowNumber;
}

public int getUmapPageNumber() {
return _umapPageNumber;
}

public void setUmapPageNumber(int newUmapPageNumber) {
_umapPageNumber = newUmapPageNumber;
}

public int getRootPageNumber() {
return _rootPageNumber;
}

public void setRootPageNumber(int newRootPageNumber) {
_rootPageNumber = newRootPageNumber;
}
}
}

+ 14
- 6
src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java View File

@@ -60,6 +60,7 @@ import com.healthmarketscience.jackcess.Relationship;
import com.healthmarketscience.jackcess.Row;
import com.healthmarketscience.jackcess.RuntimeIOException;
import com.healthmarketscience.jackcess.Table;
import com.healthmarketscience.jackcess.TableBuilder;
import com.healthmarketscience.jackcess.TableMetaData;
import com.healthmarketscience.jackcess.impl.query.QueryImpl;
import com.healthmarketscience.jackcess.query.Query;
@@ -1035,12 +1036,10 @@ public class DatabaseImpl implements Database
List<IndexBuilder> indexes)
throws IOException
{
if(lookupTable(name) != null) {
throw new IllegalArgumentException(withErrorContext(
"Cannot create table with name of existing table '" + name + "'"));
}

new TableCreator(this, name, columns, indexes).createTable();
new TableBuilder(name)
.addColumns(columns)
.addIndexes(indexes)
.toTable(this);
}

public void createLinkedTable(String name, String linkedDbName,
@@ -1582,6 +1581,15 @@ public class DatabaseImpl implements Database
}
_pageChannel.close();
}

public void validateNewTableName(String name) throws IOException {
if(lookupTable(name) != null) {
throw new IllegalArgumentException(withErrorContext(
"Cannot create table with name of existing table '" + name + "'"));
}

validateIdentifierName(name, getFormat().MAX_TABLE_NAME_LENGTH, "table");
}
/**
* Validates an identifier name.

+ 5
- 5
src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java View File

@@ -492,7 +492,7 @@ public class IndexData {
* @param idxCount num indexes to write
*/
protected static void writeRowCountDefinitions(
DBMutator creator, ByteBuffer buffer, int idxCount)
TableMutator creator, ByteBuffer buffer, int idxCount)
{
// index row counts (empty data)
ByteUtil.forward(buffer, (idxCount *
@@ -510,7 +510,7 @@ public class IndexData {
{
ByteBuffer rootPageBuffer = createRootPageBuffer(creator);

for(DBMutator.IndexDataState idxDataState : creator.getIndexDataStates()) {
for(TableMutator.IndexDataState idxDataState : creator.getIndexDataStates()) {
writeDefinition(creator, buffer, idxDataState, rootPageBuffer);
}
}
@@ -521,8 +521,8 @@ public class IndexData {
* @param buffer Buffer to write to
*/
protected static void writeDefinition(
DBMutator creator, ByteBuffer buffer,
DBMutator.IndexDataState idxDataState, ByteBuffer rootPageBuffer)
TableMutator creator, ByteBuffer buffer,
TableMutator.IndexDataState idxDataState, ByteBuffer rootPageBuffer)
throws IOException
{
if(rootPageBuffer == null) {
@@ -573,7 +573,7 @@ public class IndexData {
ByteUtil.forward(buffer, 5); // unknown
}

private static ByteBuffer createRootPageBuffer(DBMutator creator)
private static ByteBuffer createRootPageBuffer(TableMutator creator)
throws IOException
{
ByteBuffer rootPageBuffer = creator.getPageChannel().createPageBuffer();

+ 2
- 2
src/main/java/com/healthmarketscience/jackcess/impl/IndexImpl.java View File

@@ -351,10 +351,10 @@ public class IndexImpl implements Index, Comparable<IndexImpl>
}

protected static void writeDefinition(
DBMutator mutator, IndexBuilder idx, ByteBuffer buffer)
TableMutator mutator, IndexBuilder idx, ByteBuffer buffer)
throws IOException
{
DBMutator.IndexDataState idxDataState = mutator.getIndexDataState(idx);
TableMutator.IndexDataState idxDataState = mutator.getIndexDataState(idx);

// write logical index information
buffer.putInt(TableImpl.MAGIC_TABLE_NUMBER); // seemingly constant magic value which matches the table def

+ 16
- 7
src/main/java/com/healthmarketscience/jackcess/impl/RelationshipImpl.java View File

@@ -33,20 +33,20 @@ public class RelationshipImpl implements Relationship
{

/** flag indicating one-to-one relationship */
private static final int ONE_TO_ONE_FLAG = 0x00000001;
public static final int ONE_TO_ONE_FLAG = 0x00000001;
/** flag indicating no referential integrity */
private static final int NO_REFERENTIAL_INTEGRITY_FLAG = 0x00000002;
public static final int NO_REFERENTIAL_INTEGRITY_FLAG = 0x00000002;
/** flag indicating cascading updates (requires referential integrity) */
private static final int CASCADE_UPDATES_FLAG = 0x00000100;
public static final int CASCADE_UPDATES_FLAG = 0x00000100;
/** flag indicating cascading deletes (requires referential integrity) */
private static final int CASCADE_DELETES_FLAG = 0x00001000;
public static final int CASCADE_DELETES_FLAG = 0x00001000;
/** flag indicating cascading null on delete (requires referential
integrity) */
private static final int CASCADE_NULL_FLAG = 0x00002000;
public static final int CASCADE_NULL_FLAG = 0x00002000;
/** flag indicating left outer join */
private static final int LEFT_OUTER_JOIN_FLAG = 0x01000000;
public static final int LEFT_OUTER_JOIN_FLAG = 0x01000000;
/** flag indicating right outer join */
private static final int RIGHT_OUTER_JOIN_FLAG = 0x02000000;
public static final int RIGHT_OUTER_JOIN_FLAG = 0x02000000;

/** the name of this relationship */
private final String _name;
@@ -127,6 +127,15 @@ public class RelationshipImpl implements Relationship
public boolean isRightOuterJoin() {
return hasFlag(RIGHT_OUTER_JOIN_FLAG);
}

public JoinType getJoinType() {
if(isLeftOuterJoin()) {
return JoinType.LEFT_OUTER;
} else if(isRightOuterJoin()) {
return JoinType.RIGHT_OUTER;
}
return JoinType.INNER;
}
private boolean hasFlag(int flagMask) {
return((getFlags() & flagMask) != 0);

+ 42
- 14
src/main/java/com/healthmarketscience/jackcess/impl/TableCreator.java View File

@@ -29,6 +29,8 @@ import java.util.Set;
import com.healthmarketscience.jackcess.ColumnBuilder;
import com.healthmarketscience.jackcess.DataType;
import com.healthmarketscience.jackcess.IndexBuilder;
import com.healthmarketscience.jackcess.PropertyMap;
import com.healthmarketscience.jackcess.TableBuilder;

/**
* Helper class used to maintain state during table creation.
@@ -36,11 +38,11 @@ import com.healthmarketscience.jackcess.IndexBuilder;
* @author James Ahlborn
* @usage _advanced_class_
*/
class TableCreator extends DBMutator
public class TableCreator extends TableMutator
{
private final String _name;
private final List<ColumnBuilder> _columns;
private final List<IndexBuilder> _indexes;
private String _name;
private List<ColumnBuilder> _columns;
private List<IndexBuilder> _indexes;
private final List<IndexDataState> _indexDataStates =
new ArrayList<IndexDataState>();
private final Map<ColumnBuilder,ColumnState> _columnStates =
@@ -51,13 +53,8 @@ class TableCreator extends DBMutator
private int _indexCount;
private int _logicalIndexCount;

public TableCreator(DatabaseImpl database, String name,
List<ColumnBuilder> columns, List<IndexBuilder> indexes) {
public TableCreator(DatabaseImpl database) {
super(database);
_name = name;
_columns = columns;
_indexes = ((indexes != null) ? indexes :
Collections.<IndexBuilder>emptyList());
}

public String getName() {
@@ -153,7 +150,14 @@ class TableCreator extends DBMutator
* Creates the table in the database.
* @usage _advanced_method_
*/
public void createTable() throws IOException {
public TableImpl createTable(TableBuilder table) throws IOException {

_name = table.getName();
_columns = table.getColumns();
_indexes = table.getIndexes();
if(_indexes == null) {
_indexes = Collections.<IndexBuilder>emptyList();
}

validate();

@@ -190,6 +194,31 @@ class TableCreator extends DBMutator
getDatabase().addNewTable(_name, _tdefPageNumber, DatabaseImpl.TYPE_TABLE,
null, null);

TableImpl newTable = getDatabase().getTable(_name);

// add any table properties
boolean addedProps = false;
Map<String,PropertyMap.Property> props = table.getProperties();
if(props != null) {
newTable.getProperties().putAll(props.values());
addedProps = true;
}
for(ColumnBuilder cb : _columns) {
Map<String,PropertyMap.Property> colProps = cb.getProperties();
if(colProps != null) {
newTable.getColumn(cb.getName()).getProperties()
.putAll(colProps.values());
addedProps = true;
}
}

// all table and column props are saved together
if(addedProps) {
newTable.getProperties().save();
}

return newTable;

} finally {
getPageChannel().finishWrite();
}
@@ -217,10 +246,9 @@ class TableCreator extends DBMutator
/**
* Validates the new table information before attempting creation.
*/
private void validate() {
private void validate() throws IOException {

DatabaseImpl.validateIdentifierName(
_name, getFormat().MAX_TABLE_NAME_LENGTH, "table");
getDatabase().validateNewTableName(_name);
if((_columns == null) || _columns.isEmpty()) {
throw new IllegalArgumentException(

+ 13
- 13
src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java View File

@@ -1017,7 +1017,7 @@ public class TableImpl implements Table

private static void writeTableDefinitionBuffer(
ByteBuffer buffer, int tdefPageNumber,
DBMutator mutator, List<Integer> reservedPages)
TableMutator mutator, List<Integer> reservedPages)
throws IOException
{
buffer.rewind();
@@ -1099,10 +1099,10 @@ public class TableImpl implements Table
}

/**
* Writes a column defined by the given TableMutator to this table.
* Writes a column defined by the given TableUpdater to this table.
* @usage _advanced_method_
*/
protected ColumnImpl mutateAddColumn(TableMutator mutator) throws IOException
protected ColumnImpl mutateAddColumn(TableUpdater mutator) throws IOException
{
ColumnBuilder column = mutator.getColumn();
JetFormat format = mutator.getFormat();
@@ -1183,7 +1183,7 @@ public class TableImpl implements Table
// allocate usage maps for the long value col
Map.Entry<Integer,Integer> umapInfo = addUsageMaps(2, null);
System.out.println("FOO created umap " + umapInfo);
DBMutator.ColumnState colState = mutator.getColumnState(column);
TableMutator.ColumnState colState = mutator.getColumnState(column);
colState.setUmapPageNumber(umapInfo.getKey());
byte rowNum = umapInfo.getValue().byteValue();
colState.setUmapOwnedRowNumber(rowNum);
@@ -1281,10 +1281,10 @@ public class TableImpl implements Table
}

/**
* Writes a index defined by the given TableMutator to this table.
* Writes a index defined by the given TableUpdater to this table.
* @usage _advanced_method_
*/
protected IndexData mutateAddIndexData(TableMutator mutator) throws IOException
protected IndexData mutateAddIndexData(TableUpdater mutator) throws IOException
{
IndexBuilder index = mutator.getIndex();
JetFormat format = mutator.getFormat();
@@ -1326,7 +1326,7 @@ public class TableImpl implements Table
format.SIZE_INDEX_COLUMN_BLOCK));

// allocate usage maps and root page
DBMutator.IndexDataState idxDataState = mutator.getIndexDataState(index);
TableMutator.IndexDataState idxDataState = mutator.getIndexDataState(index);
int rootPageNumber = getPageChannel().allocateNewPage();
Map.Entry<Integer,Integer> umapInfo = addUsageMaps(1, rootPageNumber);
System.out.println("FOO created umap " + umapInfo);
@@ -1404,10 +1404,10 @@ public class TableImpl implements Table
}

/**
* Writes a index defined by the given TableMutator to this table.
* Writes a index defined by the given TableUpdater to this table.
* @usage _advanced_method_
*/
protected IndexImpl mutateAddIndex(TableMutator mutator) throws IOException
protected IndexImpl mutateAddIndex(TableUpdater mutator) throws IOException
{
IndexBuilder index = mutator.getIndex();
JetFormat format = mutator.getFormat();
@@ -1490,7 +1490,7 @@ public class TableImpl implements Table
return newIdx;
}

private void validateTableDefUpdate(TableMutator mutator, ByteBuffer tableBuffer)
private void validateTableDefUpdate(TableUpdater mutator, ByteBuffer tableBuffer)
throws IOException
{
if(!mutator.validateUpdatedTdef(tableBuffer)) {
@@ -1522,7 +1522,7 @@ public class TableImpl implements Table
}

private ByteBuffer loadCompleteTableDefinitionBufferForUpdate(
TableMutator mutator)
TableUpdater mutator)
throws IOException
{
// load complete table definition
@@ -1750,7 +1750,7 @@ public class TableImpl implements Table

// index umap
int indexIdx = i - 2;
DBMutator.IndexDataState idxDataState =
TableMutator.IndexDataState idxDataState =
creator.getIndexDataStates().get(indexIdx);
// allocate root page for the index
@@ -1774,7 +1774,7 @@ public class TableImpl implements Table
lvalColIdx /= 2;

ColumnBuilder lvalCol = lvalCols.get(lvalColIdx);
DBMutator.ColumnState colState =
TableMutator.ColumnState colState =
creator.getColumnState(lvalCol);

umapBuf.put(rowStart, UsageMap.MAP_TYPE_INLINE);

+ 150
- 192
src/main/java/com/healthmarketscience/jackcess/impl/TableMutator.java View File

@@ -16,11 +16,7 @@ limitations under the License.

package com.healthmarketscience.jackcess.impl;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@@ -29,253 +25,215 @@ import com.healthmarketscience.jackcess.DataType;
import com.healthmarketscience.jackcess.IndexBuilder;

/**
* Helper class used to maintain state during table mutation.
* Common helper class used to maintain state during table mutation.
*
* @author James Ahlborn
* @usage _advanced_class_
*/
public class TableMutator extends DBMutator
public abstract class TableMutator extends DBMutator
{
private final TableImpl _table;

private ColumnBuilder _column;
private IndexBuilder _index;
private int _origTdefLen;
private int _addedTdefLen;
private List<Integer> _nextPages = new ArrayList<Integer>(1);
private ColumnState _colState;
private IndexDataState _idxDataState;

public TableMutator(TableImpl table) {
super(table.getDatabase());
_table = table;
}
private ColumnOffsets _colOffsets;

public ColumnBuilder getColumn() {
return _column;
protected TableMutator(DatabaseImpl database) {
super(database);
}

public IndexBuilder getIndex() {
return _index;
public void setColumnOffsets(
int fixedOffset, int varOffset, int longVarOffset) {
if(_colOffsets == null) {
_colOffsets = new ColumnOffsets();
}
_colOffsets.set(fixedOffset, varOffset, longVarOffset);
}

@Override
String getTableName() {
return _table.getName();
}
@Override
public int getTdefPageNumber() {
return _table.getTableDefPageNumber();
public ColumnOffsets getColumnOffsets() {
return _colOffsets;
}

@Override
short getColumnNumber(String colName) {
for(ColumnImpl col : _table.getColumns()) {
if(col.getName().equalsIgnoreCase(colName)) {
return col.getColumnNumber();
protected void validateColumn(Set<String> colNames, ColumnBuilder column) {

// FIXME for now, we can't create complex columns
if(column.getType() == DataType.COMPLEX_TYPE) {
throw new UnsupportedOperationException(
"Complex column creation is not yet implemented");
}
column.validate(getFormat());
if(!colNames.add(column.getName().toUpperCase())) {
throw new IllegalArgumentException("duplicate column name: " +
column.getName());
}
}
return IndexData.COLUMN_UNUSED;
}

@Override
public ColumnState getColumnState(ColumnBuilder col) {
return ((col == _column) ? _colState : null);
setColumnSortOrder(column);
}

@Override
public IndexDataState getIndexDataState(IndexBuilder idx) {
return ((idx == _index) ? _idxDataState : null);
protected void validateIndex(Set<String> colNames, Set<String> idxNames,
boolean[] foundPk, IndexBuilder index) {
index.validate(colNames, getFormat());
if(!idxNames.add(index.getName().toUpperCase())) {
throw new IllegalArgumentException("duplicate index name: " +
index.getName());
}
if(index.isPrimaryKey()) {
if(foundPk[0]) {
throw new IllegalArgumentException(
"found second primary key index: " + index.getName());
}
foundPk[0] = true;
}
}

int getAddedTdefLen() {
return _addedTdefLen;
protected static void validateAutoNumberColumn(Set<DataType> autoTypes,
ColumnBuilder column)
{
if(!column.getType().isMultipleAutoNumberAllowed() &&
!autoTypes.add(column.getType())) {
throw new IllegalArgumentException(
"Can have at most one AutoNumber column of type " + column.getType() +
" per table");
}
}

void addTdefLen(int add) {
_addedTdefLen += add;
private void setColumnSortOrder(ColumnBuilder column) {
// set the sort order to the db default (if unspecified)
if(column.getType().isTextual() && (column.getTextSortOrder() == null)) {
column.setTextSortOrder(getDbSortOrder());
}
}

void setOrigTdefLen(int len) {
_origTdefLen = len;
}
abstract String getTableName();

List<Integer> getNextPages() {
return _nextPages;
}
public abstract int getTdefPageNumber();

void resetTdefInfo() {
_addedTdefLen = 0;
_origTdefLen = 0;
_nextPages.clear();
}
abstract short getColumnNumber(String colName);

public abstract ColumnState getColumnState(ColumnBuilder col);

public ColumnImpl addColumn(ColumnBuilder column) throws IOException {
public abstract IndexDataState getIndexDataState(IndexBuilder idx);

_column = column;
/**
* Maintains additional state used during column writing.
* @usage _advanced_class_
*/
static final class ColumnOffsets
{
private short _fixedOffset;
private short _varOffset;
private short _longVarOffset;

validateAddColumn();
public void set(int fixedOffset, int varOffset, int longVarOffset) {
_fixedOffset = (short)fixedOffset;
_varOffset = (short)varOffset;
_longVarOffset = (short)longVarOffset;
}
// assign column number and do some assorted column bookkeeping
short columnNumber = (short)_table.getMaxColumnCount();
_column.setColumnNumber(columnNumber);
if(_column.getType().isLongValue()) {
_colState = new ColumnState();
public short getNextVariableOffset(ColumnBuilder col) {
if(!col.getType().isLongValue()) {
return _varOffset++;
}
return _longVarOffset++;
}

getPageChannel().startExclusiveWrite();
try {

return _table.mutateAddColumn(this);

} finally {
getPageChannel().finishWrite();
public short getNextFixedOffset(ColumnBuilder col) {
short offset = _fixedOffset;
_fixedOffset += col.getType().getFixedSize(col.getLength());
return offset;
}
}

public IndexImpl addIndex(IndexBuilder index) throws IOException {

_index = index;

validateAddIndex();

// assign index number and do some assorted index bookkeeping
int indexNumber = _table.getLogicalIndexCount();
_index.setIndexNumber(indexNumber);

// find backing index state
findIndexDataState();

getPageChannel().startExclusiveWrite();
try {

if(_idxDataState.getIndexDataNumber() == _table.getIndexCount()) {
// we need a new backing index data
_table.mutateAddIndexData(this);

// we need to modify the table def again when adding the Index, so reset
resetTdefInfo();
}

return _table.mutateAddIndex(this);
/**
* Maintains additional state used during column creation.
* @usage _advanced_class_
*/
static final class ColumnState
{
private byte _umapOwnedRowNumber;
private byte _umapFreeRowNumber;
// we always put both usage maps on the same page
private int _umapPageNumber;

} finally {
getPageChannel().finishWrite();
public byte getUmapOwnedRowNumber() {
return _umapOwnedRowNumber;
}
}

boolean validateUpdatedTdef(ByteBuffer tableBuffer) {
// sanity check the updates
return((_origTdefLen + _addedTdefLen) == tableBuffer.limit());
}
public void setUmapOwnedRowNumber(byte newUmapOwnedRowNumber) {
_umapOwnedRowNumber = newUmapOwnedRowNumber;
}

private void validateAddColumn() {
public byte getUmapFreeRowNumber() {
return _umapFreeRowNumber;
}

if(_column == null) {
throw new IllegalArgumentException("Cannot add column with no column");
public void setUmapFreeRowNumber(byte newUmapFreeRowNumber) {
_umapFreeRowNumber = newUmapFreeRowNumber;
}
if((_table.getColumnCount() + 1) > getFormat().MAX_COLUMNS_PER_TABLE) {
throw new IllegalArgumentException(
"Cannot add column to table with " +
getFormat().MAX_COLUMNS_PER_TABLE + " columns");

public int getUmapPageNumber() {
return _umapPageNumber;
}
Set<String> colNames = getColumnNames();
// next, validate the column definition
validateColumn(colNames, _column);
if(_column.isAutoNumber()) {
// for most autonumber types, we can only have one of each type
Set<DataType> autoTypes = EnumSet.noneOf(DataType.class);
for(ColumnImpl column : _table.getAutoNumberColumns()) {
autoTypes.add(column.getType());
}

validateAutoNumberColumn(autoTypes, _column);
public void setUmapPageNumber(int newUmapPageNumber) {
_umapPageNumber = newUmapPageNumber;
}
}

private void validateAddIndex() {
if(_index == null) {
throw new IllegalArgumentException("Cannot add index with no index");
/**
* Maintains additional state used during index data creation.
* @usage _advanced_class_
*/
static final class IndexDataState
{
private final List<IndexBuilder> _indexes = new ArrayList<IndexBuilder>();
private int _indexDataNumber;
private byte _umapRowNumber;
private int _umapPageNumber;
private int _rootPageNumber;

public IndexBuilder getFirstIndex() {
// all indexes which have the same backing IndexDataState will have
// equivalent columns and flags.
return _indexes.get(0);
}
if((_table.getLogicalIndexCount() + 1) > getFormat().MAX_INDEXES_PER_TABLE) {
throw new IllegalArgumentException(
"Cannot add index to table with " +
getFormat().MAX_INDEXES_PER_TABLE + " indexes");
}
boolean foundPk[] = new boolean[1];
Set<String> idxNames = getIndexNames(foundPk);
// next, validate the index definition
validateIndex(getColumnNames(), idxNames, foundPk, _index);
}

private Set<String> getColumnNames() {
Set<String> colNames = new HashSet<String>();
for(ColumnImpl column : _table.getColumns()) {
colNames.add(column.getName().toUpperCase());
public List<IndexBuilder> getIndexes() {
return _indexes;
}
return colNames;
}

private Set<String> getIndexNames(boolean[] foundPk) {
Set<String> idxNames = new HashSet<String>();
for(IndexImpl index : _table.getIndexes()) {
idxNames.add(index.getName().toUpperCase());
if(index.isPrimaryKey()) {
foundPk[0] = true;
}
public void addIndex(IndexBuilder idx) {
_indexes.add(idx);
}
return idxNames;
}

private void findIndexDataState() {
public int getIndexDataNumber() {
return _indexDataNumber;
}

_idxDataState = new IndexDataState();
_idxDataState.addIndex(_index);
// search for an existing index which matches the given index (in terms of
// the backing data)
for(IndexData idxData : _table.getIndexDatas()) {
if(sameIndexData(_index, idxData)) {
_idxDataState.setIndexDataNumber(idxData.getIndexDataNumber());
return;
}
public void setIndexDataNumber(int newIndexDataNumber) {
_indexDataNumber = newIndexDataNumber;
}

// no matches found, need new index data state
_idxDataState.setIndexDataNumber(_table.getIndexCount());
}
public byte getUmapRowNumber() {
return _umapRowNumber;
}

private static boolean sameIndexData(IndexBuilder idx1, IndexData idx2) {
// index data can be combined if flags match and columns (and col flags)
// match
if(idx1.getFlags() != idx2.getIndexFlags()) {
return false;
public void setUmapRowNumber(byte newUmapRowNumber) {
_umapRowNumber = newUmapRowNumber;
}

if(idx1.getColumns().size() != idx2.getColumns().size()) {
return false;
public int getUmapPageNumber() {
return _umapPageNumber;
}
for(int i = 0; i < idx1.getColumns().size(); ++i) {
IndexBuilder.Column col1 = idx1.getColumns().get(i);
IndexData.ColumnDescriptor col2 = idx2.getColumns().get(i);

if(!sameIndexData(col1, col2)) {
return false;
}
public void setUmapPageNumber(int newUmapPageNumber) {
_umapPageNumber = newUmapPageNumber;
}

return true;
}
public int getRootPageNumber() {
return _rootPageNumber;
}

private static boolean sameIndexData(
IndexBuilder.Column col1, IndexData.ColumnDescriptor col2) {
return (col1.getName().equals(col2.getName()) &&
(col1.getFlags() == col2.getFlags()));
}
public void setRootPageNumber(int newRootPageNumber) {
_rootPageNumber = newRootPageNumber;
}
}
}

+ 281
- 0
src/main/java/com/healthmarketscience/jackcess/impl/TableUpdater.java View File

@@ -0,0 +1,281 @@
/*
Copyright (c) 2016 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.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import com.healthmarketscience.jackcess.ColumnBuilder;
import com.healthmarketscience.jackcess.DataType;
import com.healthmarketscience.jackcess.IndexBuilder;

/**
* Helper class used to maintain state during table mutation.
*
* @author James Ahlborn
* @usage _advanced_class_
*/
public class TableUpdater extends TableMutator
{
private final TableImpl _table;

private ColumnBuilder _column;
private IndexBuilder _index;
private int _origTdefLen;
private int _addedTdefLen;
private List<Integer> _nextPages = new ArrayList<Integer>(1);
private ColumnState _colState;
private IndexDataState _idxDataState;

public TableUpdater(TableImpl table) {
super(table.getDatabase());
_table = table;
}

public ColumnBuilder getColumn() {
return _column;
}

public IndexBuilder getIndex() {
return _index;
}

@Override
String getTableName() {
return _table.getName();
}
@Override
public int getTdefPageNumber() {
return _table.getTableDefPageNumber();
}

@Override
short getColumnNumber(String colName) {
for(ColumnImpl col : _table.getColumns()) {
if(col.getName().equalsIgnoreCase(colName)) {
return col.getColumnNumber();
}
}
return IndexData.COLUMN_UNUSED;
}

@Override
public ColumnState getColumnState(ColumnBuilder col) {
return ((col == _column) ? _colState : null);
}

@Override
public IndexDataState getIndexDataState(IndexBuilder idx) {
return ((idx == _index) ? _idxDataState : null);
}

int getAddedTdefLen() {
return _addedTdefLen;
}

void addTdefLen(int add) {
_addedTdefLen += add;
}

void setOrigTdefLen(int len) {
_origTdefLen = len;
}

List<Integer> getNextPages() {
return _nextPages;
}

void resetTdefInfo() {
_addedTdefLen = 0;
_origTdefLen = 0;
_nextPages.clear();
}

public ColumnImpl addColumn(ColumnBuilder column) throws IOException {

_column = column;

validateAddColumn();
// assign column number and do some assorted column bookkeeping
short columnNumber = (short)_table.getMaxColumnCount();
_column.setColumnNumber(columnNumber);
if(_column.getType().isLongValue()) {
_colState = new ColumnState();
}

getPageChannel().startExclusiveWrite();
try {

return _table.mutateAddColumn(this);

} finally {
getPageChannel().finishWrite();
}
}

public IndexImpl addIndex(IndexBuilder index) throws IOException {

_index = index;

validateAddIndex();

// assign index number and do some assorted index bookkeeping
int indexNumber = _table.getLogicalIndexCount();
_index.setIndexNumber(indexNumber);

// find backing index state
findIndexDataState();

getPageChannel().startExclusiveWrite();
try {

if(_idxDataState.getIndexDataNumber() == _table.getIndexCount()) {
// we need a new backing index data
_table.mutateAddIndexData(this);

// we need to modify the table def again when adding the Index, so reset
resetTdefInfo();
}

return _table.mutateAddIndex(this);

} finally {
getPageChannel().finishWrite();
}
}

boolean validateUpdatedTdef(ByteBuffer tableBuffer) {
// sanity check the updates
return((_origTdefLen + _addedTdefLen) == tableBuffer.limit());
}

private void validateAddColumn() {

if(_column == null) {
throw new IllegalArgumentException("Cannot add column with no column");
}
if((_table.getColumnCount() + 1) > getFormat().MAX_COLUMNS_PER_TABLE) {
throw new IllegalArgumentException(
"Cannot add column to table with " +
getFormat().MAX_COLUMNS_PER_TABLE + " columns");
}
Set<String> colNames = getColumnNames();
// next, validate the column definition
validateColumn(colNames, _column);
if(_column.isAutoNumber()) {
// for most autonumber types, we can only have one of each type
Set<DataType> autoTypes = EnumSet.noneOf(DataType.class);
for(ColumnImpl column : _table.getAutoNumberColumns()) {
autoTypes.add(column.getType());
}

validateAutoNumberColumn(autoTypes, _column);
}
}

private void validateAddIndex() {
if(_index == null) {
throw new IllegalArgumentException("Cannot add index with no index");
}
if((_table.getLogicalIndexCount() + 1) > getFormat().MAX_INDEXES_PER_TABLE) {
throw new IllegalArgumentException(
"Cannot add index to table with " +
getFormat().MAX_INDEXES_PER_TABLE + " indexes");
}
boolean foundPk[] = new boolean[1];
Set<String> idxNames = getIndexNames(foundPk);
// next, validate the index definition
validateIndex(getColumnNames(), idxNames, foundPk, _index);
}

private Set<String> getColumnNames() {
Set<String> colNames = new HashSet<String>();
for(ColumnImpl column : _table.getColumns()) {
colNames.add(column.getName().toUpperCase());
}
return colNames;
}

private Set<String> getIndexNames(boolean[] foundPk) {
Set<String> idxNames = new HashSet<String>();
for(IndexImpl index : _table.getIndexes()) {
idxNames.add(index.getName().toUpperCase());
if(index.isPrimaryKey()) {
foundPk[0] = true;
}
}
return idxNames;
}

private void findIndexDataState() {

_idxDataState = new IndexDataState();
_idxDataState.addIndex(_index);
// search for an existing index which matches the given index (in terms of
// the backing data)
for(IndexData idxData : _table.getIndexDatas()) {
if(sameIndexData(_index, idxData)) {
_idxDataState.setIndexDataNumber(idxData.getIndexDataNumber());
return;
}
}

// no matches found, need new index data state
_idxDataState.setIndexDataNumber(_table.getIndexCount());
}

private static boolean sameIndexData(IndexBuilder idx1, IndexData idx2) {
// index data can be combined if flags match and columns (and col flags)
// match
if(idx1.getFlags() != idx2.getIndexFlags()) {
return false;
}

if(idx1.getColumns().size() != idx2.getColumns().size()) {
return false;
}
for(int i = 0; i < idx1.getColumns().size(); ++i) {
IndexBuilder.Column col1 = idx1.getColumns().get(i);
IndexData.ColumnDescriptor col2 = idx2.getColumns().get(i);

if(!sameIndexData(col1, col2)) {
return false;
}
}

return true;
}

private static boolean sameIndexData(
IndexBuilder.Column col1, IndexData.ColumnDescriptor col2) {
return (col1.getName().equals(col2.getName()) &&
(col1.getFlags() == col2.getFlags()));
}
}

Loading…
Cancel
Save