From 0fbd0d45ecfcb310452b246824883e74241de39f Mon Sep 17 00:00:00 2001 From: James Ahlborn Date: Tue, 26 Sep 2006 13:39:56 +0000 Subject: [PATCH] support reading 'compressed' indexes (fix 1563654) git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@124 f203690c-595d-4dc9-a70b-905162fa7fd2 --- .../healthmarketscience/jackcess/Index.java | 260 +++++++++++++++--- .../jackcess/JetFormat.java | 6 +- test/data/compIndexTest.mdb | Bin 0 -> 143360 bytes .../jackcess/DatabaseTest.java | 52 ++-- .../jackcess/IndexTest.java | 28 ++ 5 files changed, 273 insertions(+), 73 deletions(-) create mode 100644 test/data/compIndexTest.mdb diff --git a/src/java/com/healthmarketscience/jackcess/Index.java b/src/java/com/healthmarketscience/jackcess/Index.java index 1b3febe..43d9888 100644 --- a/src/java/com/healthmarketscience/jackcess/Index.java +++ b/src/java/com/healthmarketscience/jackcess/Index.java @@ -42,9 +42,10 @@ import java.util.List; import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; - import org.apache.commons.lang.builder.CompareToBuilder; + + /** * Access table index * @author Tim McCune @@ -59,6 +60,9 @@ public class Index implements Comparable { private static final int NEW_ENTRY_COLUMN_INDEX = -1; private static final byte REVERSE_ORDER_FLAG = (byte)0x01; + + private static final byte INDEX_NODE_PAGE_TYPE = (byte)0x03; + private static final byte INDEX_LEAF_PAGE_TYPE = (byte)0x04; static final Comparator BYTE_CODE_COMPARATOR = new Comparator() { @@ -195,7 +199,8 @@ public class Index implements Comparable { private String _name; /** is this index a primary key */ private boolean _primaryKey; - + /** FIXME, for now, we can't write multi-page indexes or indexes using the funky primary key compression scheme */ + boolean _readOnly; public Index(int parentPageNumber, PageChannel channel, JetFormat format) { _parentPageNumber = parentPageNumber; @@ -232,15 +237,19 @@ public class Index implements Comparable { public Collection getColumns() { return Collections.unmodifiableCollection(_columns.keySet()); } - + public void update() throws IOException { + if(_readOnly) { + throw new UnsupportedOperationException( + "FIXME cannot write indexes of this type yet"); + } _pageChannel.writePage(write(), _pageNumber); } /** * Write this index out to a buffer */ - public ByteBuffer write() throws IOException { + private ByteBuffer write() throws IOException { ByteBuffer buffer = _pageChannel.createPageBuffer(); buffer.put((byte) 0x04); //Page type buffer.put((byte) 0x01); //Unknown @@ -274,42 +283,154 @@ public class Index implements Comparable { } /** - * Read this index in from a buffer - * @param buffer Buffer to read from + * Read this index in from a tableBuffer + * @param tableBuffer table definition buffer to read from initial info * @param availableColumns Columns that this index may use */ - public void read(ByteBuffer buffer, List availableColumns) + public void read(ByteBuffer tableBuffer, List availableColumns) throws IOException { for (int i = 0; i < MAX_COLUMNS; i++) { - short columnNumber = buffer.getShort(); - Byte flags = new Byte(buffer.get()); + short columnNumber = tableBuffer.getShort(); + Byte flags = new Byte(tableBuffer.get()); if (columnNumber != COLUMN_UNUSED) { _columns.put(availableColumns.get(columnNumber), flags); } } - buffer.getInt(); //Forward past Unknown - _pageNumber = buffer.getInt(); - buffer.position(buffer.position() + 10); //Forward past other stuff + tableBuffer.getInt(); //Forward past Unknown + _pageNumber = tableBuffer.getInt(); + tableBuffer.position(tableBuffer.position() + 10); //Forward past other stuff ByteBuffer indexPage = _pageChannel.createPageBuffer(); - _pageChannel.readPage(indexPage, _pageNumber); - indexPage.position(_format.OFFSET_INDEX_ENTRY_MASK); - byte[] entryMask = new byte[_format.SIZE_INDEX_ENTRY_MASK]; - indexPage.get(entryMask); + + // find first leaf page + int leafPageNumber = _pageNumber; + while(true) { + _pageChannel.readPage(indexPage, leafPageNumber); + + if(indexPage.get(0) == INDEX_NODE_PAGE_TYPE) { + // FIXME we can't modify this index at this point in time + _readOnly = true; + + // found another node page + leafPageNumber = readNodePage(indexPage); + } else { + // found first leaf + indexPage.rewind(); + break; + } + } + + // read all leaf pages + while(true) { + + leafPageNumber = readLeafPage(indexPage); + if(leafPageNumber != 0) { + // FIXME we can't modify this index at this point in time + _readOnly = true; + + // found another one + _pageChannel.readPage(indexPage, leafPageNumber); + + } else { + // all done + break; + } + } + + } + + /** + * Reads the first entry off of an index node page and returns the next page + * number. + */ + private int readNodePage(ByteBuffer nodePage) + throws IOException + { + if(nodePage.get(0) != INDEX_NODE_PAGE_TYPE) { + throw new IOException("expected index node page, found " + + nodePage.get(0)); + } + + List nodeEntries = new ArrayList(); + readIndexPage(nodePage, false, null, nodeEntries); + + // grab the first entry + // FIXME, need to parse all...? + return nodeEntries.get(0).getSubPageNumber(); + } + + /** + * Reads an index leaf page. + * @return the next leaf page number, 0 if none + */ + private int readLeafPage(ByteBuffer leafPage) + throws IOException + { + if(leafPage.get(0) != INDEX_LEAF_PAGE_TYPE) { + throw new IOException("expected index leaf page, found " + + leafPage.get(0)); + } + + // note, "header" data is in LITTLE_ENDIAN format, entry data is in + // BIG_ENDIAN format + + int nextLeafPage = leafPage.getInt(_format.OFFSET_NEXT_INDEX_LEAF_PAGE); + readIndexPage(leafPage, true, _entries, null); + + return nextLeafPage; + } + + /** + * Reads an index page, populating the correct collection based on the page + * type (node or leaf). + */ + private void readIndexPage(ByteBuffer indexPage, boolean isLeaf, + Collection entries, + Collection nodeEntries) + throws IOException + { + // note, "header" data is in LITTLE_ENDIAN format, entry data is in + // BIG_ENDIAN format + int numCompressedBytes = indexPage.get( + _format.OFFSET_INDEX_COMPRESSED_BYTE_COUNT); + int entryMaskLength = _format.SIZE_INDEX_ENTRY_MASK; + int entryMaskPos = _format.OFFSET_INDEX_ENTRY_MASK; + int entryPos = entryMaskPos + _format.SIZE_INDEX_ENTRY_MASK; int lastStart = 0; - int nextEntryIndex = 0; - for (int i = 0; i < entryMask.length; i++) { + byte[] valuePrefix = null; + boolean firstEntry = true; + for (int i = 0; i < entryMaskLength; i++) { + byte entryMask = indexPage.get(entryMaskPos + i); for (int j = 0; j < 8; j++) { - if ((entryMask[i] & (1 << j)) != 0) { + if ((entryMask & (1 << j)) != 0) { int length = i * 8 + j - lastStart; - Entry e = new Entry(indexPage, nextEntryIndex++); - _entries.add(e); - lastStart += length; + indexPage.position(entryPos + lastStart); + if(isLeaf) { + entries.add(new Entry(indexPage, length, valuePrefix)); + } else { + nodeEntries.add(new NodeEntry(indexPage, length, valuePrefix)); + } + + // read any shared "compressed" bytes + if(firstEntry) { + firstEntry = false; + if(numCompressedBytes > 0) { + // FIXME we can't modify this index at this point in time + _readOnly = true; + + valuePrefix = new byte[numCompressedBytes]; + indexPage.position(entryPos + lastStart); + indexPage.get(valuePrefix); + } + } + + lastStart += length; } } } } + /** * Add a row to this index * @param row Row to add @@ -321,7 +442,8 @@ public class Index implements Comparable { { _entries.add(new Entry(row, pageNumber, rowNumber)); } - + + @Override public String toString() { StringBuilder rtn = new StringBuilder(); rtn.append("\tName: " + _name); @@ -467,7 +589,7 @@ public class Index implements Comparable { /** - * A single entry in an index (points to a single row) + * A single leaf entry in an index (points to a single row) */ private class Entry implements Comparable { @@ -499,15 +621,15 @@ public class Index implements Comparable { /** * Read an existing entry in from a buffer */ - public Entry(ByteBuffer buffer, int nextEntryIndex) throws IOException { + public Entry(ByteBuffer buffer, int length, byte[] valuePrefix) + throws IOException + { for(Map.Entry entry : _columns.entrySet()) { Column col = entry.getKey(); Byte flags = entry.getValue(); _entryColumns.add(newEntryColumn(col) - .initFromBuffer(buffer, nextEntryIndex, flags)); + .initFromBuffer(buffer, flags, valuePrefix)); } - // 3-byte int in big endian order! Gotta love those kooky MS - // programmers. :) _page = ByteUtil.get3ByteInt(buffer, ByteOrder.BIG_ENDIAN); _row = buffer.get(); } @@ -558,6 +680,7 @@ public class Index implements Comparable { buffer.put(_row); } + @Override public String toString() { return ("Page = " + _page + ", Row = " + _row + ", Columns = " + _entryColumns + "\n"); } @@ -618,8 +741,8 @@ public class Index implements Comparable { * Initialize from a buffer */ protected abstract EntryColumn initFromBuffer(ByteBuffer buffer, - int entryIndex, - byte flags) + byte flags, + byte[] valuePrefix) throws IOException; protected abstract boolean isNullValue(); @@ -680,15 +803,25 @@ public class Index implements Comparable { */ @Override protected EntryColumn initFromBuffer(ByteBuffer buffer, - int entryIndex, - byte flags) + byte flags, + byte[] valuePrefix) throws IOException { - byte flag = buffer.get(); + + + byte flag = ((valuePrefix == null) ? buffer.get() : valuePrefix[0]); // FIXME, reverse is 0x80, reverse null is 0xFF if (flag != (byte) 0) { - byte[] data = new byte[_column.getType().getFixedSize()]; - buffer.get(data); + byte[] data = new byte[_column.getType().getFixedSize()]; + int numPrefixBytes = ((valuePrefix == null) ? 0 : + (valuePrefix.length - 1)); + int dataOffset = 0; + if((valuePrefix != null) && (valuePrefix.length > 1)) { + System.arraycopy(valuePrefix, 1, data, 0, + (valuePrefix.length - 1)); + dataOffset += (valuePrefix.length - 1); + } + buffer.get(data, dataOffset, (data.length - dataOffset)); _value = (Comparable) _column.read(data, ByteOrder.BIG_ENDIAN); //ints and shorts are stored in index as value + 2147483648 @@ -700,7 +833,7 @@ public class Index implements Comparable { (long) Integer.MAX_VALUE + 1L)); } } - + return this; } @@ -782,11 +915,11 @@ public class Index implements Comparable { */ @Override protected EntryColumn initFromBuffer(ByteBuffer buffer, - int entryIndex, - byte flags) + byte flags, + byte[] valuePrefix) throws IOException { - byte flag = buffer.get(); + byte flag = ((valuePrefix == null) ? buffer.get() : valuePrefix[0]); // FIXME, reverse is 0x80, reverse null is 0xFF // end flag is FE, post extra bytes is FF 00 // extra bytes are inverted, so are normal bytes @@ -797,9 +930,20 @@ public class Index implements Comparable { ++endPos; } + // FIXME, prefix could probably include extraBytes... + // read index bytes - _valueBytes = new byte[endPos - buffer.position()]; - buffer.get(_valueBytes); + int numPrefixBytes = ((valuePrefix == null) ? 0 : + (valuePrefix.length - 1)); + int dataOffset = 0; + _valueBytes = new byte[(endPos - buffer.position()) + + numPrefixBytes]; + if(numPrefixBytes > 0) { + System.arraycopy(valuePrefix, 1, _valueBytes, 0, numPrefixBytes); + dataOffset += numPrefixBytes; + } + buffer.get(_valueBytes, dataOffset, + (_valueBytes.length - dataOffset)); // read end codes byte buffer.get(); @@ -884,5 +1028,37 @@ public class Index implements Comparable { } } - + + /** + * A single node entry in an index (points to a sub-page in the index) + */ + private class NodeEntry extends Entry { + + /** index page number of the page to which this node entry refers */ + private int _subPageNumber; + + + /** + * Read an existing node entry in from a buffer + */ + public NodeEntry(ByteBuffer buffer, int length, byte[] valuePrefix) + throws IOException + { + super(buffer, length, valuePrefix); + + _subPageNumber = ByteUtil.getInt(buffer, ByteOrder.BIG_ENDIAN); + } + + public int getSubPageNumber() { + return _subPageNumber; + } + + public String toString() { + return ("Page = " + getPage() + ", Row = " + getRow() + + ", SubPage = " + _subPageNumber + + ", Columns = " + getEntryColumns() + "\n"); + } + + } + } diff --git a/src/java/com/healthmarketscience/jackcess/JetFormat.java b/src/java/com/healthmarketscience/jackcess/JetFormat.java index d4ee261..327e96a 100644 --- a/src/java/com/healthmarketscience/jackcess/JetFormat.java +++ b/src/java/com/healthmarketscience/jackcess/JetFormat.java @@ -105,6 +105,7 @@ public abstract class JetFormat { public final int OFFSET_USED_PAGES_USAGE_MAP_DEF; public final int OFFSET_FREE_PAGES_USAGE_MAP_DEF; + public final int OFFSET_INDEX_COMPRESSED_BYTE_COUNT; public final int OFFSET_INDEX_ENTRY_MASK; public final int OFFSET_NEXT_INDEX_LEAF_PAGE; @@ -189,6 +190,7 @@ public abstract class JetFormat { OFFSET_USED_PAGES_USAGE_MAP_DEF = defineOffsetUsedPagesUsageMapDef(); OFFSET_FREE_PAGES_USAGE_MAP_DEF = defineOffsetFreePagesUsageMapDef(); + OFFSET_INDEX_COMPRESSED_BYTE_COUNT = defineOffsetIndexCompressedByteCount(); OFFSET_INDEX_ENTRY_MASK = defineOffsetIndexEntryMask(); OFFSET_NEXT_INDEX_LEAF_PAGE = defineOffsetNextIndexLeafPage(); @@ -252,6 +254,7 @@ public abstract class JetFormat { protected abstract int defineOffsetUsedPagesUsageMapDef(); protected abstract int defineOffsetFreePagesUsageMapDef(); + protected abstract int defineOffsetIndexCompressedByteCount(); protected abstract int defineOffsetIndexEntryMask(); protected abstract int defineOffsetNextIndexLeafPage(); @@ -316,8 +319,9 @@ public abstract class JetFormat { protected int defineOffsetUsedPagesUsageMapDef() { return 4027; } protected int defineOffsetFreePagesUsageMapDef() { return 3958; } + protected int defineOffsetIndexCompressedByteCount() { return 24; } protected int defineOffsetIndexEntryMask() { return 27; } - protected int defineOffsetNextIndexLeafPage() { return 12; } + protected int defineOffsetNextIndexLeafPage() { return 16; } protected int defineSizeIndexDefinition() { return 12; } protected int defineSizeColumnHeader() { return 25; } diff --git a/test/data/compIndexTest.mdb b/test/data/compIndexTest.mdb new file mode 100644 index 0000000000000000000000000000000000000000..b93db5ba9b8b69d965d8c9f3df00e7af1649704d GIT binary patch literal 143360 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
S1RUd^3{3W)3QY2+0u%jdzyyB=(C5e57jv?I z25_`L8yN4;0gm$L0Z01tfg}6{z~TNv;7R`Fz!UwefhYJk0f+ff&tr!AZwC(XQ#~2% zr?NZVPx^cPq}!lc%IUybyn-1spte6Su678pf9*(MzgkN3akZ4HzO~bVeQM7H_O7i4 z_NvAC7!zB&6&O=Xad~QQ0Y=x}1&peF1n91%dh4pCIvH8}cc8PD>VTt`Vz<|k{c7*RJ67+!Y*FszPLwbW7U<~p=9F{ZjjK&5UOP}FU}DseZ|Z2?|icNuVd-F3k0 z>h1$xTlXmNnmU{>o~!F#2VPb8A@IsN)JD%0b>9ImuS4DNT(&a;xNT=2;H5iJnx0E` zjsaf0GZ%Q#&XvFmccMk|Y~8sIxMe5BwRz{2zzcTX0Ib=Gv);35C(din`8%HgZru4i z@VuQkJ3Je9QjF*Bq!`!l+yh*<3-!=*&Mwq6&)Quc;F?{7fz`WED?C-ZvVfJlP}ZKa zcR_7W#V(Yxr+gR1ShkB|EZub*uw>V7fva~>epl^!30S;~a=LQYC%_fEptEQBE}TuC zqFv3v!d;Z#g8FD+e*FMoUj50yW%c8LOY2bsJ!jR=0xqdP9k{rDHSo;(D&QIQr1qlv zOM$1?UjtlNe=~4F{hh%1^-lrk)l=+q>(N$t=G2qgv+KVF&Z@`x>&dOhx#^kN&}hUb8j8r}h((m?u8Z1@~Fq2a&4@eTFBagC^Tp0SNCU`ivYF{Tl9 z-jm!o3YgT0GuM;YcnUC~F%#%(q?DZ8NGTcJSPqPD+z1@icm;4|F-;5d_y?wU=yl!`Y;I+Gl0_&ZyzIZoOjlO92W55e{KMUNt`+eY+-Tww|-V+79 zV2>AAvxmapw1>h!e@_N*4Ln-oC)Hy%b~V-gsci-pRn#dy9dq_TC6A-g`T6 z<=!WNEB3wsT)vk=F53G6uy8M>ykKt~Fn^x_=Iuibj9#{HByj0I%Eejx&<;c|**6oo zcwYhV%zY)mGxk*j7wtO_c>2Drz=iuLt_Az<0nXp|25{cKzXRv)qg>3{R|}lIZ#Qt( zeu^u1zZE!hKWbof&i)a=?EQ(r)Ar8*&e*>MIDLN+aN7P-;MD!7712}n{|cD3|50G( z{%3$0`(Fm8??)YoPTT(-Fm-Cz1Wsrf4;EPCK;PI_hKyT{?;Gotk zfCF1^01jxq4H(yomNvS7>wUm}t*-!&Yo*xxw!RDO)A}D^@788uFZ$t|=-9U7fH7?p zqo<7&j&37`quNq|?zU+_R~uT|=*YIyfzCF{yQ7WLU~i)|*xI%Lt!NNILVj_ z!$C$a3_BSp8jp<()B%r`jEyiN$hZndI2kv=2qWVT7#1?LhJuX8;bI~~Yc9w@ZSe>) z{()rQK!(vzC0u;$`uI)# zZ-PVTB}xUP0#X5~fK)&#aHJH-nUg3^GT}i2mcydO&CbSxO0>WeEsfh3%RCifQRX~t z!L@2EKut?euYub@ESXEo5m%v>>dx*R;rH!y{50)&irI*vDriwQeaUS~D=lfOFU{OfOGg3x(f1N85nh5% zeIY>)mVqwA5>~X7a}Hdq#A@U%0~RguriT^>|FrZsE$y9$yi)4vN2Eyg66AcY7H%b6 z=7C>EGvSsU`)>wD=8vsF)8IT1=dl*DKOuC`Ei!4`?B6JslXI z;i)n@cZf3sXr{Ze!njGplih{T$RaZKZIR@>x6zCS=%XvXK0O)-X!5pmYx26 zVjPxOAEP(u_+br;NI{PQ4YQ593!z$K;LY9 zQ16Pa!1NVQrZB+Pw^st6+-Rz>% literal 0 HcmV?d00001 diff --git a/test/src/java/com/healthmarketscience/jackcess/DatabaseTest.java b/test/src/java/com/healthmarketscience/jackcess/DatabaseTest.java index 541f321..4fc8722 100644 --- a/test/src/java/com/healthmarketscience/jackcess/DatabaseTest.java +++ b/test/src/java/com/healthmarketscience/jackcess/DatabaseTest.java @@ -3,8 +3,12 @@ package com.healthmarketscience.jackcess; import java.io.File; +import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.io.PrintWriter; import java.math.BigDecimal; import java.nio.ByteBuffer; @@ -323,19 +327,6 @@ public class DatabaseTest extends TestCase { assertTrue(!bogusFile.exists()); } - public void testPrimaryKey() throws Exception { - Table table = open().getTable("Table1"); - Map foundPKs = new HashMap(); - for(Index index : table.getIndexes()) { - foundPKs.put(index.getColumns().iterator().next().getName(), - index.isPrimaryKey()); - } - Map expectedPKs = new HashMap(); - expectedPKs.put("A", Boolean.TRUE); - expectedPKs.put("B", Boolean.FALSE); - assertEquals(expectedPKs, foundPKs); - } - public void testReadWithDeletedCols() throws Exception { Table table = Database.open(new File("test/data/delColTest.mdb")).getTable("Table1"); @@ -498,23 +489,6 @@ public class DatabaseTest extends TestCase { } } - public void testIndexSlots() throws Exception - { - Database mdb = Database.open(new File("test/data/indexTest.mdb")); - - Table table = mdb.getTable("Table1"); - assertEquals(4, table.getIndexes().size()); - assertEquals(4, table.getIndexSlotCount()); - - table = mdb.getTable("Table2"); - assertEquals(2, table.getIndexes().size()); - assertEquals(3, table.getIndexSlotCount()); - - table = mdb.getTable("Table3"); - assertEquals(2, table.getIndexes().size()); - assertEquals(3, table.getIndexSlotCount()); - } - public void testMultiPageTableDef() throws Exception { List columns = open().getTable("Table2").getColumns(); @@ -643,5 +617,23 @@ public class DatabaseTest extends TestCase { writer.println(row); } } + + static void copyFile(File srcFile, File dstFile) + throws IOException + { + // FIXME should really be using commons io FileUtils here, but don't want + // to add dep for one simple test method + byte[] buf = new byte[1024]; + OutputStream ostream = new FileOutputStream(dstFile); + InputStream istream = new FileInputStream(srcFile); + try { + int numBytes = 0; + while((numBytes = istream.read(buf)) >= 0) { + ostream.write(buf, 0, numBytes); + } + } finally { + ostream.close(); + } + } } diff --git a/test/src/java/com/healthmarketscience/jackcess/IndexTest.java b/test/src/java/com/healthmarketscience/jackcess/IndexTest.java index 0adeb10..33967bb 100644 --- a/test/src/java/com/healthmarketscience/jackcess/IndexTest.java +++ b/test/src/java/com/healthmarketscience/jackcess/IndexTest.java @@ -3,6 +3,7 @@ package com.healthmarketscience.jackcess; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -92,5 +93,32 @@ public class IndexTest extends TestCase { assertEquals(3, table.getIndexSlotCount()); } + public void testComplexIndex() throws Exception + { + // this file has an index with "compressed" entries and node pages + File origFile = new File("test/data/compIndexTest.mdb"); + Database db = Database.open(origFile); + Table t = db.getTable("Table1"); + assertEquals(512, countRows(t)); + db.close(); + + // copy to temp file and attemp to edit + File testFile = File.createTempFile("databaseTest", ".mdb"); + testFile.deleteOnExit(); + + copyFile(origFile, testFile); + + db = Database.open(testFile); + t = db.getTable("Table1"); + + try { + // we don't support writing these indexes + t.addRow(99, "abc", "def"); + fail("Should have thrown IOException"); + } catch(UnsupportedOperationException e) { + // success + } + } + } -- 2.39.5