diff options
author | James Ahlborn <jtahlborn@yahoo.com> | 2016-07-19 03:45:53 +0000 |
---|---|---|
committer | James Ahlborn <jtahlborn@yahoo.com> | 2016-07-19 03:45:53 +0000 |
commit | 060172ce30489655622702db9f3c6ce119636735 (patch) | |
tree | 638c511e967525b90b39e7dad4d2c130f5a50c8e /src/main | |
parent | f2578a5a48195e58c2ce37b274c05f4514276bcc (diff) | |
download | jackcess-060172ce30489655622702db9f3c6ce119636735.tar.gz jackcess-060172ce30489655622702db9f3c6ce119636735.zip |
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
Diffstat (limited to 'src/main')
14 files changed, 571 insertions, 485 deletions
diff --git a/src/main/java/com/healthmarketscience/jackcess/ColumnBuilder.java b/src/main/java/com/healthmarketscience/jackcess/ColumnBuilder.java index 69b4b33..9be67d2 100644 --- a/src/main/java/com/healthmarketscience/jackcess/ColumnBuilder.java +++ b/src/main/java/com/healthmarketscience/jackcess/ColumnBuilder.java @@ -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) { diff --git a/src/main/java/com/healthmarketscience/jackcess/IndexBuilder.java b/src/main/java/com/healthmarketscience/jackcess/IndexBuilder.java index 36a64dc..7822b2d 100644 --- a/src/main/java/com/healthmarketscience/jackcess/IndexBuilder.java +++ b/src/main/java/com/healthmarketscience/jackcess/IndexBuilder.java @@ -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) { diff --git a/src/main/java/com/healthmarketscience/jackcess/Relationship.java b/src/main/java/com/healthmarketscience/jackcess/Relationship.java index 2474b52..4200e57 100644 --- a/src/main/java/com/healthmarketscience/jackcess/Relationship.java +++ b/src/main/java/com/healthmarketscience/jackcess/Relationship.java @@ -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(); } diff --git a/src/main/java/com/healthmarketscience/jackcess/TableBuilder.java b/src/main/java/com/healthmarketscience/jackcess/TableBuilder.java index 8e5272e..8f0b233 100644 --- a/src/main/java/com/healthmarketscience/jackcess/TableBuilder.java +++ b/src/main/java/com/healthmarketscience/jackcess/TableBuilder.java @@ -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. */ @@ -155,6 +163,22 @@ public class TableBuilder { } /** + * 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); } /** diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java index 8950248..998e80a 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java @@ -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()); diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/DBMutator.java b/src/main/java/com/healthmarketscience/jackcess/impl/DBMutator.java index 374af2b..b16a058 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/DBMutator.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/DBMutator.java @@ -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; - } - } - } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java index 99500dd..26cdc59 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java @@ -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. diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java b/src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java index ea6708d..a55259e 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java @@ -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(); diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/IndexImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/IndexImpl.java index 43b6c44..833ee98 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/IndexImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/IndexImpl.java @@ -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 diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/RelationshipImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/RelationshipImpl.java index 8775448..4563992 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/RelationshipImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/RelationshipImpl.java @@ -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); diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/TableCreator.java b/src/main/java/com/healthmarketscience/jackcess/impl/TableCreator.java index 34765e8..5fb3337 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/TableCreator.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/TableCreator.java @@ -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( diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java index cdd1a93..a4dfd6d 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java @@ -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); diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/TableMutator.java b/src/main/java/com/healthmarketscience/jackcess/impl/TableMutator.java index 8c9ba28..494bfb7 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/TableMutator.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/TableMutator.java @@ -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; + } + } } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/TableUpdater.java b/src/main/java/com/healthmarketscience/jackcess/impl/TableUpdater.java new file mode 100644 index 0000000..7ed52f5 --- /dev/null +++ b/src/main/java/com/healthmarketscience/jackcess/impl/TableUpdater.java @@ -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())); + } +} |