diff options
8 files changed, 349 insertions, 242 deletions
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java index 596bc35..8950248 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java @@ -1660,6 +1660,32 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { } } + protected static void writeColUsageMapDefinitions( + TableCreator creator, ByteBuffer buffer) + throws IOException + { + // write long value column usage map references + for(ColumnBuilder lvalCol : creator.getLongValueColumns()) { + writeColUsageMapDefinition(creator, lvalCol, buffer); + } + } + + protected static void writeColUsageMapDefinition( + DBMutator creator, ColumnBuilder lvalCol, ByteBuffer buffer) + throws IOException + { + DBMutator.ColumnState colState = creator.getColumnState(lvalCol); + + buffer.putShort(lvalCol.getColumnNumber()); + + // owned pages umap (both are on same page) + buffer.put(colState.getUmapOwnedRowNumber()); + ByteUtil.put3ByteInt(buffer, colState.getUmapPageNumber()); + // free space pages umap + buffer.put(colState.getUmapFreeRowNumber()); + ByteUtil.put3ByteInt(buffer, colState.getUmapPageNumber()); + } + /** * Reads the sort order info from the given buffer from the given position. */ diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/DBMutator.java b/src/main/java/com/healthmarketscience/jackcess/impl/DBMutator.java index 8abc390..e599191 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/DBMutator.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/DBMutator.java @@ -18,6 +18,8 @@ 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; @@ -139,6 +141,10 @@ abstract class DBMutator 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_ @@ -168,4 +174,100 @@ abstract class DBMutator 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/FKEnforcer.java b/src/main/java/com/healthmarketscience/jackcess/impl/FKEnforcer.java index d2d4c50..d29836a 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/FKEnforcer.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/FKEnforcer.java @@ -50,7 +50,7 @@ final class FKEnforcer CaseInsensitiveColumnMatcher.INSTANCE; private final TableImpl _table; - private final List<ColumnImpl> _cols; + private List<ColumnImpl> _cols; private List<Joiner> _primaryJoinersChkUp; private List<Joiner> _primaryJoinersChkDel; private List<Joiner> _primaryJoinersDoUp; @@ -62,6 +62,10 @@ final class FKEnforcer _table = table; // at this point, only init the index columns + initColumns(); + } + + private void initColumns() { Set<ColumnImpl> cols = new TreeSet<ColumnImpl>(); for(IndexImpl idx : _table.getIndexes()) { IndexImpl.ForeignKeyReference ref = idx.getReference(); @@ -79,6 +83,22 @@ final class FKEnforcer } /** + * Resets the internals of this FKEnforcer (for post-table modification) + */ + void reset() { + // columns to enforce may have changed + initColumns(); + + // clear any existing joiners (will be re-created on next use) + _primaryJoinersChkUp = null; + _primaryJoinersChkDel = null; + _primaryJoinersDoUp = null; + _primaryJoinersDoDel = null; + _primaryJoinersDoNull = null; + _secondaryJoiners = null; + } + + /** * Does secondary initialization, if necessary. */ private void initialize() throws IOException { diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java b/src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java index 10deea6..3961a9b 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java @@ -27,7 +27,6 @@ import java.util.Comparator; import java.util.List; import java.util.Map; -import com.healthmarketscience.jackcess.ColumnBuilder; import com.healthmarketscience.jackcess.ConstraintViolationException; import com.healthmarketscience.jackcess.Index; import com.healthmarketscience.jackcess.IndexBuilder; @@ -511,54 +510,8 @@ public class IndexData { { ByteBuffer rootPageBuffer = createRootPageBuffer(creator); - for(TableCreator.IndexDataState idxDataState : creator.getIndexDataStates()) { - buffer.putInt(MAGIC_INDEX_NUMBER); // seemingly constant magic value - - // write column information (always MAX_COLUMNS entries) - IndexBuilder idx = idxDataState.getFirstIndex(); - List<IndexBuilder.Column> idxColumns = idx.getColumns(); - for(int i = 0; i < MAX_COLUMNS; ++i) { - - short columnNumber = COLUMN_UNUSED; - byte flags = 0; - - if(i < idxColumns.size()) { - - // determine column info - IndexBuilder.Column idxCol = idxColumns.get(i); - flags = idxCol.getFlags(); - - // find actual table column number - for(ColumnBuilder col : creator.getColumns()) { - if(col.getName().equalsIgnoreCase(idxCol.getName())) { - columnNumber = col.getColumnNumber(); - break; - } - } - if(columnNumber == COLUMN_UNUSED) { - // should never happen as this is validated before - throw new IllegalArgumentException( - withErrorContext( - "Column with name " + idxCol.getName() + " not found", - creator.getDatabase(), creator.getName(), idx.getName())); - } - } - - buffer.putShort(columnNumber); // table column number - buffer.put(flags); // column flags (e.g. ordering) - } - - buffer.put(idxDataState.getUmapRowNumber()); // umap row - ByteUtil.put3ByteInt(buffer, creator.getUmapPageNumber()); // umap page - - // write empty root index page - creator.getPageChannel().writePage(rootPageBuffer, - idxDataState.getRootPageNumber()); - - buffer.putInt(idxDataState.getRootPageNumber()); - buffer.putInt(0); // unknown - buffer.put(idx.getFlags()); // index flags (unique, etc.) - ByteUtil.forward(buffer, 5); // unknown + for(DBMutator.IndexDataState idxDataState : creator.getIndexDataStates()) { + writeDefinition(creator, buffer, idxDataState, rootPageBuffer); } } @@ -569,14 +522,13 @@ public class IndexData { */ protected static void writeDefinition( DBMutator creator, ByteBuffer buffer, - TableCreator.IndexDataState idxDataState, ByteBuffer rootPageBuffer) + DBMutator.IndexDataState idxDataState, ByteBuffer rootPageBuffer) throws IOException { if(rootPageBuffer == null) { rootPageBuffer = createRootPageBuffer(creator); } - // FIXME buffer.putInt(MAGIC_INDEX_NUMBER); // seemingly constant magic value // write column information (always MAX_COLUMNS entries) @@ -609,18 +561,17 @@ public class IndexData { buffer.put(flags); // column flags (e.g. ordering) } - // FIXME - // buffer.put(idxDataState.getUmapRowNumber()); // umap row - // ByteUtil.put3ByteInt(buffer, creator.getUmapPageNumber()); // umap page + buffer.put(idxDataState.getUmapRowNumber()); // umap row + ByteUtil.put3ByteInt(buffer, idxDataState.getUmapPageNumber()); // umap page - // // write empty root index page - // creator.getPageChannel().writePage(rootPageBuffer, - // idxDataState.getRootPageNumber()); + // write empty root index page + creator.getPageChannel().writePage(rootPageBuffer, + idxDataState.getRootPageNumber()); - // buffer.putInt(idxDataState.getRootPageNumber()); - // buffer.putInt(0); // unknown - // buffer.put(idx.getFlags()); // index flags (unique, etc.) - // ByteUtil.forward(buffer, 5); // unknown + buffer.putInt(idxDataState.getRootPageNumber()); + buffer.putInt(0); // unknown + buffer.put(idx.getFlags()); // index flags (unique, etc.) + ByteUtil.forward(buffer, 5); // unknown } private static ByteBuffer createRootPageBuffer(DBMutator creator) diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/IndexImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/IndexImpl.java index a927071..43b6c44 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/IndexImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/IndexImpl.java @@ -75,7 +75,6 @@ public class IndexImpl implements Index, Comparable<IndexImpl> JetFormat format) throws IOException { - ByteUtil.forward(tableBuffer, format.SKIP_BEFORE_INDEX_SLOT); //Forward past Unknown _indexNumber = tableBuffer.getInt(); int indexDataNumber = tableBuffer.getInt(); @@ -342,17 +341,7 @@ public class IndexImpl implements Index, Comparable<IndexImpl> { // write logical index information for(IndexBuilder idx : creator.getIndexes()) { - TableCreator.IndexDataState idxDataState = creator.getIndexDataState(idx); - buffer.putInt(TableImpl.MAGIC_TABLE_NUMBER); // seemingly constant magic value which matches the table def - buffer.putInt(idx.getIndexNumber()); // index num - buffer.putInt(idxDataState.getIndexDataNumber()); // index data num - buffer.put((byte)0); // related table type - buffer.putInt(INVALID_INDEX_NUMBER); // related index num - buffer.putInt(0); // related table definition page number - buffer.put((byte)0); // cascade updates flag - buffer.put((byte)0); // cascade deletes flag - buffer.put(idx.getType()); // index type flags - buffer.putInt(0); // unknown + writeDefinition(creator, idx, buffer); } // write index names @@ -361,6 +350,25 @@ public class IndexImpl implements Index, Comparable<IndexImpl> } } + protected static void writeDefinition( + DBMutator mutator, IndexBuilder idx, ByteBuffer buffer) + throws IOException + { + DBMutator.IndexDataState idxDataState = mutator.getIndexDataState(idx); + + // write logical index information + buffer.putInt(TableImpl.MAGIC_TABLE_NUMBER); // seemingly constant magic value which matches the table def + buffer.putInt(idx.getIndexNumber()); // index num + buffer.putInt(idxDataState.getIndexDataNumber()); // index data num + buffer.put((byte)0); // related table type + buffer.putInt(INVALID_INDEX_NUMBER); // related index num + buffer.putInt(0); // related table definition page number + buffer.put((byte)0); // cascade updates flag + buffer.put((byte)0); // cascade deletes flag + buffer.put(idx.getType()); // index type flags + buffer.putInt(0); // unknown + } + private String withErrorContext(String msg) { return withErrorContext(msg, getTable().getDatabase(), getName()); } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/TableCreator.java b/src/main/java/com/healthmarketscience/jackcess/impl/TableCreator.java index 2039d63..9234700 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/TableCreator.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/TableCreator.java @@ -93,6 +93,7 @@ class TableCreator extends DBMutator return _logicalIndexCount; } + @Override public IndexDataState getIndexDataState(IndexBuilder idx) { for(IndexDataState idxDataState : _indexDataStates) { for(IndexBuilder curIdx : idxDataState.getIndexes()) { @@ -108,6 +109,7 @@ class TableCreator extends DBMutator return _indexDataStates; } + @Override public ColumnState getColumnState(ColumnBuilder col) { return _columnStates.get(col); } @@ -297,99 +299,4 @@ class TableCreator extends DBMutator (col1.getFlags() == col2.getFlags())); } - /** - * 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/TableImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java index 5c704b6..97e073c 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java @@ -113,9 +113,9 @@ public class TableImpl implements Table /** Type of the table (either TYPE_SYSTEM or TYPE_USER) */ private final byte _tableType; /** Number of actual indexes on the table */ - private final int _indexCount; + private int _indexCount; /** Number of logical indexes for the table */ - private final int _logicalIndexCount; + private int _logicalIndexCount; /** page number of the definition of this table */ private final int _tableDefPageNumber; /** max Number of columns in the table (includes previous deletions) */ @@ -998,19 +998,8 @@ public class TableImpl implements Table IndexImpl.writeDefinitions(creator, buffer); } - // write long value column usage map references - for(ColumnBuilder lvalCol : creator.getLongValueColumns()) { - buffer.putShort(lvalCol.getColumnNumber()); - TableCreator.ColumnState colState = - creator.getColumnState(lvalCol); - - // owned pages umap (both are on same page) - buffer.put(colState.getUmapOwnedRowNumber()); - ByteUtil.put3ByteInt(buffer, colState.getUmapPageNumber()); - // free space pages umap - buffer.put(colState.getUmapFreeRowNumber()); - ByteUtil.put3ByteInt(buffer, colState.getUmapPageNumber()); - } + // column usage map references + ColumnImpl.writeColUsageMapDefinitions(creator, buffer); //End of tabledef buffer.put((byte) 0xff); @@ -1116,6 +1105,7 @@ public class TableImpl implements Table boolean isVarCol = column.isVariableLength(); boolean isLongVal = column.getType().isLongValue(); + //// // calculate how much more space we need in the table def if(isLongVal) { mutator.addTdefLen(10); @@ -1126,10 +1116,12 @@ public class TableImpl implements Table int nameByteLen = DBMutator.calculateNameLength(column.getName()); mutator.addTdefLen(nameByteLen); + //// // load current table definition and add space for new info ByteBuffer tableBuffer = loadCompleteTableDefinitionBufferForUpdate( mutator); + //// // update various bits of the table def ByteUtil.forward(tableBuffer, 29); tableBuffer.putShort((short)(_maxColumnCount + 1)); @@ -1171,9 +1163,7 @@ public class TableImpl implements Table ColumnImpl.writeDefinition(mutator, column, tableBuffer); // skip existing column names and write new name - for(int i = 0; i < _columns.size(); ++i) { - ByteUtil.forward(tableBuffer, tableBuffer.getShort()); - } + skipNames(tableBuffer, _columns.size()); ByteUtil.insertEmptyData(tableBuffer, nameByteLen); System.out.println("FOO pre name " + tableBuffer.position()); writeName(tableBuffer, column.getName(), mutator.getCharset()); @@ -1185,9 +1175,11 @@ public class TableImpl implements Table // allocate usage maps for the long value col Map.Entry<Integer,Integer> umapInfo = addUsageMaps(2); System.out.println("FOO created umap " + umapInfo); - int umapPageNum = umapInfo.getKey(); - int umapRow1 = umapInfo.getValue(); - int umapRow2 = umapRow1 + 1; + DBMutator.ColumnState colState = mutator.getColumnState(column); + colState.setUmapPageNumber(umapInfo.getKey()); + byte rowNum = umapInfo.getValue().byteValue(); + colState.setUmapOwnedRowNumber(rowNum); + colState.setUmapFreeRowNumber((byte)(rowNum + 1)); // skip past index defs System.out.println("FOO pre move " + tableBuffer.position()); @@ -1197,11 +1189,7 @@ public class TableImpl implements Table ByteUtil.forward(tableBuffer, (_logicalIndexCount * format.SIZE_INDEX_INFO_BLOCK)); System.out.println("FOO moved to " + tableBuffer.position()); - for(int i = 0; i < _logicalIndexCount; ++i) { - short len = tableBuffer.getShort(); - System.out.println("FOO skipping " + len); - ByteUtil.forward(tableBuffer, len); - } + skipNames(tableBuffer, _logicalIndexCount); // skip existing usage maps while(tableBuffer.remaining() >= 2) { @@ -1220,32 +1208,24 @@ public class TableImpl implements Table System.out.println("FOO about to write " + tableBuffer.position()); umapPos = tableBuffer.position(); ByteUtil.insertEmptyData(tableBuffer, 10); - tableBuffer.putShort(column.getColumnNumber()); - - // owned pages umap (both are on same page) - tableBuffer.put((byte)umapRow1); - ByteUtil.put3ByteInt(tableBuffer, umapPageNum); - // free space pages umap - tableBuffer.put((byte)umapRow2); - ByteUtil.put3ByteInt(tableBuffer, umapPageNum); + ColumnImpl.writeColUsageMapDefinition( + mutator, column, tableBuffer); } // sanity check the updates - if(!mutator.validateUpdatedTdef(tableBuffer)) { - throw new IllegalStateException( - withErrorContext("Failed update table definition (unexpected length)")); - } + validateTableDefUpdate(mutator, tableBuffer); // before writing the new table def, create the column ColumnImpl newCol = ColumnImpl.create(this, tableBuffer, colDefPos, column.getName(), _columns.size()); newCol.setColumnIndex(_columns.size()); + //// // write updated table def back to the database writeTableDefinitionBuffer(tableBuffer, _tableDefPageNumber, mutator, mutator.getNextPages()); - + //// // now, update current TableImpl _columns.add(newCol); @@ -1288,89 +1268,184 @@ public class TableImpl implements Table * Writes a index defined by the given TableMutator to this table. * @usage _advanced_method_ */ - protected IndexImpl mutateAddIndex(TableMutator mutator) throws IOException + protected IndexData mutateAddIndexData(TableMutator mutator) throws IOException { IndexBuilder index = mutator.getIndex(); JetFormat format = mutator.getFormat(); + //// // calculate how much more space we need in the table def - mutator.addTdefLen(format.SIZE_INDEX_INFO_BLOCK); + mutator.addTdefLen(format.SIZE_INDEX_DEFINITION + + format.SIZE_INDEX_COLUMN_BLOCK); + + //// + // load current table definition and add space for new info + ByteBuffer tableBuffer = loadCompleteTableDefinitionBufferForUpdate( + mutator); + + //// + // update various bits of the table def + ByteUtil.forward(tableBuffer, 39); + tableBuffer.putInt(_indexCount + 1); + + // move to end of index data def blocks + tableBuffer.position(format.SIZE_TDEF_HEADER + + (_indexCount * format.SIZE_INDEX_DEFINITION)); + + // write index row count definition (empty initially) + ByteUtil.insertEmptyData(tableBuffer, format.SIZE_INDEX_DEFINITION); + IndexData.writeRowCountDefinitions(mutator, tableBuffer, 1); - // FIXME - int indexDataNumber = 0; - boolean addingIndexData = (indexDataNumber >= _indexCount); - if(addingIndexData) { + // skip columns and column names + ByteUtil.forward(tableBuffer, + (_columns.size() * format.SIZE_COLUMN_DEF_BLOCK)); + skipNames(tableBuffer, _columns.size()); - // we are adding an index data as well - mutator.addTdefLen(format.SIZE_INDEX_DEFINITION + - format.SIZE_INDEX_COLUMN_BLOCK); + // move to end of current index datas + ByteUtil.forward(tableBuffer, (_indexCount * + format.SIZE_INDEX_COLUMN_BLOCK)); + + // write index data def + DBMutator.IndexDataState idxDataState = mutator.getIndexDataState(index); + int idxDataDefPos = tableBuffer.position(); + ByteUtil.insertEmptyData(tableBuffer, format.SIZE_INDEX_COLUMN_BLOCK); + IndexData.writeDefinition(mutator, tableBuffer, idxDataState, null); + + // sanity check the updates + validateTableDefUpdate(mutator, tableBuffer); + + // before writing the new table def, create the index data + tableBuffer.position(0); + IndexData newIdxData = IndexData.create( + this, tableBuffer, idxDataState.getIndexDataNumber(), format); + tableBuffer.position(idxDataDefPos); + newIdxData.read(tableBuffer, _columns); + + //// + // write updated table def back to the database + writeTableDefinitionBuffer(tableBuffer, _tableDefPageNumber, mutator, + mutator.getNextPages()); + + //// + // now, update current TableImpl + + for(IndexData.ColumnDescriptor iCol : newIdxData.getColumns()) { + _indexColumns.add(iCol.getColumn()); } + + ++_indexCount; + _indexDatas.add(newIdxData); + + completeTableMutation(tableBuffer); + + return newIdxData; + } + + /** + * Writes a index defined by the given TableMutator to this table. + * @usage _advanced_method_ + */ + protected IndexImpl mutateAddIndex(TableMutator mutator) throws IOException + { + IndexBuilder index = mutator.getIndex(); + JetFormat format = mutator.getFormat(); + + //// + // calculate how much more space we need in the table def + mutator.addTdefLen(format.SIZE_INDEX_INFO_BLOCK); int nameByteLen = DBMutator.calculateNameLength(index.getName()); mutator.addTdefLen(nameByteLen); + //// // load current table definition and add space for new info ByteBuffer tableBuffer = loadCompleteTableDefinitionBufferForUpdate( mutator); + //// // update various bits of the table def ByteUtil.forward(tableBuffer, 35); tableBuffer.putInt(_logicalIndexCount + 1); - int numIdxData = _indexCount + (addingIndexData ? 1 : 0); - tableBuffer.putInt(numIdxData); // move to end of index data def blocks tableBuffer.position(format.SIZE_TDEF_HEADER + (_indexCount * format.SIZE_INDEX_DEFINITION)); - if(addingIndexData) { - // write index row count definition (empty initially) - ByteUtil.insertEmptyData(tableBuffer, format.SIZE_INDEX_DEFINITION); - IndexData.writeRowCountDefinitions(mutator, tableBuffer, 1); - } - // skip columns and column names ByteUtil.forward(tableBuffer, (_columns.size() * format.SIZE_COLUMN_DEF_BLOCK)); - for(int i = 0; i < _columns.size(); ++i) { - ByteUtil.forward(tableBuffer, tableBuffer.getShort()); - } + skipNames(tableBuffer, _columns.size()); // move to end of current index datas ByteUtil.forward(tableBuffer, (_indexCount * format.SIZE_INDEX_COLUMN_BLOCK)); + // move to end of current indexes + ByteUtil.forward(tableBuffer, (_logicalIndexCount * + format.SIZE_INDEX_INFO_BLOCK)); - if(addingIndexData) { - // write index data def - ByteUtil.insertEmptyData(tableBuffer, format.SIZE_INDEX_COLUMN_BLOCK); - - // FIXME - } + int idxDefPos = tableBuffer.position(); + IndexImpl.writeDefinition(mutator, index, tableBuffer); - // FIXME + // skip existing index names and write new name + skipNames(tableBuffer, _logicalIndexCount); + ByteUtil.insertEmptyData(tableBuffer, nameByteLen); + writeName(tableBuffer, index.getName(), mutator.getCharset()); - IndexImpl newIdx = null; + // sanity check the updates + validateTableDefUpdate(mutator, tableBuffer); - // FIXME + // before writing the new table def, create the index + tableBuffer.position(idxDefPos); + IndexImpl newIdx = new IndexImpl(tableBuffer, _indexDatas, format); + newIdx.setName(index.getName()); + + //// + // write updated table def back to the database + writeTableDefinitionBuffer(tableBuffer, _tableDefPageNumber, mutator, + mutator.getNextPages()); + //// + // now, update current TableImpl - // FIXME, need to reset fkenforcer? + ++_logicalIndexCount; + _indexes.add(newIdx); completeTableMutation(tableBuffer); return newIdx; } + private void validateTableDefUpdate(TableMutator mutator, ByteBuffer tableBuffer) + throws IOException + { + if(!mutator.validateUpdatedTdef(tableBuffer)) { + throw new IllegalStateException( + withErrorContext("Failed updating table definition (unexpected length)")); + } + } + private void completeTableMutation(ByteBuffer tableBuffer) throws IOException { // lastly, may need to clear table def buffer _tableDefBufferH.possiblyInvalidate(_tableDefPageNumber, tableBuffer); + // update any foreign key enforcing + _fkEnforcer.reset(); + // update modification count so any active RowStates can keep themselves // up-to-date ++_modCount; } + /** + * Skips the given number of names in the table buffer. + */ + private static void skipNames(ByteBuffer tableBuffer, int count) { + for(int i = 0; i < count; ++i) { + ByteUtil.forward(tableBuffer, tableBuffer.getShort()); + } + } + private ByteBuffer loadCompleteTableDefinitionBufferForUpdate( TableMutator mutator) throws IOException @@ -1591,7 +1666,7 @@ public class TableImpl implements Table // index umap int indexIdx = i - 2; - TableCreator.IndexDataState idxDataState = + DBMutator.IndexDataState idxDataState = creator.getIndexDataStates().get(indexIdx); // allocate root page for the index @@ -1615,7 +1690,7 @@ public class TableImpl implements Table lvalColIdx /= 2; ColumnBuilder lvalCol = lvalCols.get(lvalColIdx); - TableCreator.ColumnState colState = + DBMutator.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 72a1481..b9e508f 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/TableMutator.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/TableMutator.java @@ -43,6 +43,8 @@ public class TableMutator extends DBMutator 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()); @@ -72,6 +74,16 @@ public class TableMutator extends DBMutator 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; } @@ -97,6 +109,9 @@ public class TableMutator extends DBMutator // 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 { @@ -115,7 +130,7 @@ public class TableMutator extends DBMutator validateAddIndex(); // assign index number and do some assorted index bookkeeping - int indexNumber = _table.getIndexes().size(); + int indexNumber = _table.getLogicalIndexCount(); _index.setIndexNumber(indexNumber); // find backing index state @@ -126,6 +141,9 @@ public class TableMutator extends DBMutator getPageChannel().startExclusiveWrite(); try { + // FIXME, maybe add index data + // _table.mutateAddIndexData(this); + return _table.mutateAddIndex(this); } finally { @@ -169,7 +187,7 @@ public class TableMutator extends DBMutator if(_index == null) { throw new IllegalArgumentException("Cannot add index with no index"); } - if((_table.getIndexes().size() + 1) > getFormat().MAX_INDEXES_PER_TABLE) { + if((_table.getLogicalIndexCount() + 1) > getFormat().MAX_INDEXES_PER_TABLE) { throw new IllegalArgumentException( "Cannot add index to table with " + getFormat().MAX_INDEXES_PER_TABLE + " indexes"); |