From aa9555c6679d41a7c6f9d05c7fa2178d2882dd6e Mon Sep 17 00:00:00 2001 From: James Ahlborn Date: Thu, 19 May 2016 02:43:54 +0000 Subject: [PATCH] add support for sharing backing index data across indexes during table creation git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/branches/mutateops@994 f203690c-595d-4dc9-a70b-905162fa7fd2 --- .../jackcess/impl/IndexData.java | 11 +-- .../jackcess/impl/IndexImpl.java | 2 +- .../jackcess/impl/TableCreator.java | 92 ++++++++++++++++++- .../jackcess/impl/TableImpl.java | 10 +- .../jackcess/IndexTest.java | 51 ++++++++++ 5 files changed, 149 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java b/src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java index e5e6dc7..090baf0 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java @@ -501,10 +501,11 @@ public class IndexData { writeDataPage(rootPageBuffer, NEW_ROOT_DATA_PAGE, creator.getTdefPageNumber(), creator.getFormat()); - for(IndexBuilder idx : creator.getIndexes()) { + 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.getIndex(); List idxColumns = idx.getColumns(); for(int i = 0; i < MAX_COLUMNS; ++i) { @@ -537,16 +538,14 @@ public class IndexData { buffer.put(flags); // column flags (e.g. ordering) } - TableCreator.IndexState idxState = creator.getIndexState(idx); - - buffer.put(idxState.getUmapRowNumber()); // umap row + buffer.put(idxDataState.getUmapRowNumber()); // umap row ByteUtil.put3ByteInt(buffer, creator.getUmapPageNumber()); // umap page // write empty root index page creator.getPageChannel().writePage(rootPageBuffer, - idxState.getRootPageNumber()); + idxDataState.getRootPageNumber()); - buffer.putInt(idxState.getRootPageNumber()); + buffer.putInt(idxDataState.getRootPageNumber()); buffer.putInt(0); // unknown buffer.put(idx.getFlags()); // index flags (unique, etc.) ByteUtil.forward(buffer, 5); // unknown diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/IndexImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/IndexImpl.java index f3fe868..41eb669 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/IndexImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/IndexImpl.java @@ -345,7 +345,7 @@ public class IndexImpl implements Index, Comparable TableCreator.IndexState idxState = creator.getIndexState(idx); buffer.putInt(TableImpl.MAGIC_TABLE_NUMBER); // seemingly constant magic value which matches the table def buffer.putInt(idxState.getIndexNumber()); // index num - buffer.putInt(idxState.getIndexDataNumber()); // index data num + buffer.putInt(idxState.getIndexDataState().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 diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/TableCreator.java b/src/main/java/com/healthmarketscience/jackcess/impl/TableCreator.java index 89da310..a58622f 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/TableCreator.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/TableCreator.java @@ -43,6 +43,8 @@ class TableCreator extends DBMutator private final List _indexes; private final Map _indexStates = new IdentityHashMap(); + private final List _indexDataStates = + new ArrayList(); private final Map _columnStates = new IdentityHashMap(); private final List _lvalCols = new ArrayList(); @@ -96,6 +98,10 @@ class TableCreator extends DBMutator return _indexStates.get(idx); } + public List getIndexDataStates() { + return _indexDataStates; + } + public ColumnState getColumnState(ColumnBuilder col) { return _columnStates.get(col); } @@ -145,7 +151,7 @@ class TableCreator extends DBMutator for(IndexBuilder idx : _indexes) { IndexState idxState = new IndexState(); idxState.setIndexNumber(_logicalIndexCount++); - idxState.setIndexDataNumber(_indexCount++); + idxState.setIndexDataState(findIndexDataState(idx)); _indexStates.put(idx, idxState); } } @@ -169,6 +175,24 @@ class TableCreator extends DBMutator } } + private IndexDataState findIndexDataState(IndexBuilder idx) { + + // search for an index which matches the given index (in terms of the + // backing data) + for(IndexDataState idxDataState : _indexDataStates) { + if(sameIndexData(idxDataState.getIndex(), idx)) { + return idxDataState; + } + } + + // no matches found, need new index data state + IndexDataState idxDataState = new IndexDataState(); + idxDataState.setIndex(idx); + idxDataState.setIndexDataNumber(_indexCount++); + _indexDataStates.add(idxDataState); + return idxDataState; + } + /** * Validates the new table information before attempting creation. */ @@ -241,6 +265,35 @@ class TableCreator extends DBMutator return autoCols; } + private static boolean sameIndexData(IndexBuilder idx1, IndexBuilder idx2) { + // index data can be combined if flags match and columns (and col flags) + // match + if(idx1.getFlags() != idx2.getFlags()) { + 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); + IndexBuilder.Column col2 = idx2.getColumns().get(i); + + if(!sameIndexData(col1, col2)) { + return false; + } + } + + return true; + } + + private static boolean sameIndexData( + IndexBuilder.Column col1, IndexBuilder.Column col2) { + return (col1.getName().equals(col2.getName()) && + (col1.getFlags() == col2.getFlags())); + } + /** * Maintains additional state used during index creation. * @usage _advanced_class_ @@ -248,10 +301,7 @@ class TableCreator extends DBMutator static final class IndexState { private int _indexNumber; - private int _indexDataNumber; - private byte _umapRowNumber; - private int _umapPageNumber; - private int _rootPageNumber; + private IndexDataState _dataState; public int getIndexNumber() { return _indexNumber; @@ -261,6 +311,38 @@ class TableCreator extends DBMutator _indexNumber = newIndexNumber; } + public IndexDataState getIndexDataState() { + return _dataState; + } + + public void setIndexDataState(IndexDataState dataState) { + _dataState = dataState; + } + } + + /** + * Maintains additional state used during index data creation. + * @usage _advanced_class_ + */ + static final class IndexDataState + { + // all indexes which have the same backing IndexDataState will have + // equivalent columns and flags. we keep a reference to the first index + // which uses this backing index data. + private IndexBuilder _idx; + private int _indexDataNumber; + private byte _umapRowNumber; + private int _umapPageNumber; + private int _rootPageNumber; + + public IndexBuilder getIndex() { + return _idx; + } + + public void setIndex(IndexBuilder idx) { + _idx = idx; + } + public int getIndexDataNumber() { return _indexDataNumber; } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java index 18fee24..1a3d19f 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java @@ -1503,16 +1503,16 @@ public class TableImpl implements Table // index umap int indexIdx = i - 2; - IndexBuilder idx = creator.getIndexes().get(indexIdx); + TableCreator.IndexDataState idxDataState = + creator.getIndexDataStates().get(indexIdx); // allocate root page for the index int rootPageNumber = pageChannel.allocateNewPage(); // stash info for later use - TableCreator.IndexState idxState = creator.getIndexState(idx); - idxState.setRootPageNumber(rootPageNumber); - idxState.setUmapRowNumber((byte)umapRowNum); - idxState.setUmapPageNumber(umapPageNumber); + idxDataState.setRootPageNumber(rootPageNumber); + idxDataState.setUmapRowNumber((byte)umapRowNum); + idxDataState.setUmapPageNumber(umapPageNumber); // index map definition, including initial root page umapBuf.put(rowStart, UsageMap.MAP_TYPE_INLINE); diff --git a/src/test/java/com/healthmarketscience/jackcess/IndexTest.java b/src/test/java/com/healthmarketscience/jackcess/IndexTest.java index 28e2ff9..47f7b89 100644 --- a/src/test/java/com/healthmarketscience/jackcess/IndexTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/IndexTest.java @@ -462,6 +462,57 @@ public class IndexTest extends TestCase { } } + public void testIndexCreationSharedData() throws Exception + { + for (final FileFormat fileFormat : SUPPORTED_FILEFORMATS) { + Database db = create(fileFormat); + + Table t = new TableBuilder("TestTable") + .addColumn(new ColumnBuilder("id", DataType.LONG)) + .addColumn(new ColumnBuilder("data", DataType.TEXT)) + .addIndex(new IndexBuilder(IndexBuilder.PRIMARY_KEY_NAME) + .addColumns("id").setPrimaryKey()) + .addIndex(new IndexBuilder("Index1").addColumns("id")) + .addIndex(new IndexBuilder("Index2").addColumns("id")) + .addIndex(new IndexBuilder("Index3").addColumns(false, "id")) + .toTable(db); + + assertEquals(4, t.getIndexes().size()); + IndexImpl idx = (IndexImpl)t.getIndexes().get(0); + + assertEquals(IndexBuilder.PRIMARY_KEY_NAME, idx.getName()); + assertEquals(1, idx.getColumns().size()); + assertEquals("id", idx.getColumns().get(0).getName()); + assertTrue(idx.getColumns().get(0).isAscending()); + assertTrue(idx.isPrimaryKey()); + assertTrue(idx.isUnique()); + assertFalse(idx.shouldIgnoreNulls()); + assertNull(idx.getReference()); + + IndexImpl idx1 = (IndexImpl)t.getIndexes().get(1); + IndexImpl idx2 = (IndexImpl)t.getIndexes().get(2); + IndexImpl idx3 = (IndexImpl)t.getIndexes().get(3); + + assertNotSame(idx.getIndexData(), idx1.getIndexData()); + assertSame(idx1.getIndexData(), idx2.getIndexData()); + assertNotSame(idx2.getIndexData(), idx3.getIndexData()); + + t.addRow(2, "row2"); + t.addRow(1, "row1"); + t.addRow(3, "row3"); + + Cursor c = t.newCursor() + .setIndexByName(IndexBuilder.PRIMARY_KEY_NAME).toCursor(); + + for(int i = 1; i <= 3; ++i) { + Map row = c.getNextRow(); + assertEquals(i, row.get("id")); + assertEquals("row" + i, row.get("data")); + } + assertFalse(c.moveToNextRow()); + } + } + public void testGetForeignKeyIndex() throws Exception { for (final TestDB testDB : TestDB.getSupportedForBasename(Basename.INDEX, true)) { -- 2.39.5