From fad035e0b93b55502f54474343c9ac618a62e66d Mon Sep 17 00:00:00 2001 From: James Ahlborn Date: Mon, 16 Mar 2015 20:16:51 +0000 Subject: Implement support for indexes on BINARY fields git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@921 f203690c-595d-4dc9-a70b-905162fa7fd2 --- src/changes/changes.xml | 3 + .../jackcess/impl/IndexCodes.java | 4 - .../jackcess/impl/IndexData.java | 132 ++++++++++++++++----- .../jackcess/util/SimpleColumnMatcher.java | 16 ++- src/test/data/V2010/binIdxTestV2010.accdb | Bin 0 -> 417792 bytes .../healthmarketscience/jackcess/IndexTest.java | 39 ++++++ .../jackcess/impl/JetFormatTest.java | 3 +- 7 files changed, 158 insertions(+), 39 deletions(-) create mode 100644 src/test/data/V2010/binIdxTestV2010.accdb (limited to 'src') diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 0c866c0..dcd9f90 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -28,6 +28,9 @@ Added contextual information to many errors and warnings. + + Implement support for indexes on BINARY fields. + diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/IndexCodes.java b/src/main/java/com/healthmarketscience/jackcess/impl/IndexCodes.java index a605883..6fbbc65 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/IndexCodes.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/IndexCodes.java @@ -40,10 +40,6 @@ public class IndexCodes { static final byte DESC_START_FLAG = (byte)0x80; static final byte DESC_NULL_FLAG = (byte)0xFF; - static final byte MID_GUID = (byte)0x09; - static final byte ASC_END_GUID = (byte)0x08; - static final byte DESC_END_GUID = (byte)0xF7; - static final byte ASC_BOOLEAN_TRUE = (byte)0x00; static final byte ASC_BOOLEAN_FALSE = (byte)0xFF; diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java b/src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java index a6970a9..f951dc6 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java @@ -1313,6 +1313,71 @@ public class IndexData { return column.write(value, 0, ENTRY_BYTE_ORDER).array(); } + /** + * Writes a binary value using the general binary entry encoding rules. + */ + private static void writeGeneralBinaryEntry(byte[] valueBytes, boolean isAsc, + ByteStream bout) + { + int dataLen = valueBytes.length; + int extraLen = (dataLen + 7) / 8; + int entryLen = ((dataLen + extraLen + 8) / 9) * 9; + + // reserve space for the full entry + bout.ensureNewCapacity(entryLen); + + // binary data is written in 8 byte segments with a trailing length byte. + // The length byte is the amount of valid bytes in the segment (where 9 + // indicates that there is more data _after_ this segment). + byte[] partialEntryBytes = new byte[9]; + + // bit twiddling rules: + // - isAsc => nothing + // - !isAsc => flipBytes, _but keep intermediate 09 unflipped_! + + // first, write any intermediate segements + int segmentLen = dataLen; + int pos = 0; + while(segmentLen > 8) { + + System.arraycopy(valueBytes, pos, partialEntryBytes, 0, 8); + if(!isAsc) { + // note, we do _not_ flip the length byte for intermediate segments + flipBytes(partialEntryBytes, 0, 8); + } + + // we are writing intermediate segments (there is more data after this + // segment), so the length is always 9. + partialEntryBytes[8] = (byte)9; + + pos += 8; + segmentLen -= 8; + + bout.write(partialEntryBytes); + } + + // write the last segment (with slightly different rules) + if(segmentLen > 0) { + + System.arraycopy(valueBytes, pos, partialEntryBytes, 0, segmentLen); + + // clear out an intermediate bytes between the real data and the final + // length byte + for(int i = segmentLen; i < 8; ++i) { + partialEntryBytes[i] = 0; + } + + partialEntryBytes[8] = (byte)segmentLen; + + if(!isAsc) { + // note, we _do_ flip the last length byte + flipBytes(partialEntryBytes, 0, 9); + } + + bout.write(partialEntryBytes); + } + } + /** * Creates one of the special index entries. */ @@ -1359,6 +1424,8 @@ public class IndexData { return new BooleanColumnDescriptor(col, flags); case GUID: return new GuidColumnDescriptor(col, flags); + case BINARY: + return new BinaryColumnDescriptor(col, flags); default: // we can't modify this index at this point in time @@ -1467,15 +1534,14 @@ public class IndexData { writeNonNullValue(value, bout); } - protected abstract void writeNonNullValue( - Object value, ByteStream bout) + protected abstract void writeNonNullValue(Object value, ByteStream bout) throws IOException; @Override public String toString() { return CustomToStringStyle.builder(this) .append("column", getColumn()) - .append("flags", getFlags()) + .append("flags", getFlags() + " " + (isAscending() ? "(ASC)" : "(DSC)")) .toString(); } } @@ -1492,8 +1558,7 @@ public class IndexData { } @Override - protected void writeNonNullValue( - Object value, ByteStream bout) + protected void writeNonNullValue(Object value, ByteStream bout) throws IOException { byte[] valueBytes = encodeNumberColumnValue(value, getColumn()); @@ -1524,8 +1589,7 @@ public class IndexData { } @Override - protected void writeNonNullValue( - Object value, ByteStream bout) + protected void writeNonNullValue(Object value, ByteStream bout) throws IOException { byte[] valueBytes = encodeNumberColumnValue(value, getColumn()); @@ -1575,8 +1639,7 @@ public class IndexData { } @Override - protected void writeNonNullValue( - Object value, ByteStream bout) + protected void writeNonNullValue(Object value, ByteStream bout) throws IOException { byte[] valueBytes = encodeNumberColumnValue(value, getColumn()); @@ -1641,8 +1704,7 @@ public class IndexData { } @Override - protected void writeNonNullValue( - Object value, ByteStream bout) + protected void writeNonNullValue(Object value, ByteStream bout) throws IOException { byte[] valueBytes = encodeNumberColumnValue(value, getColumn()); @@ -1699,8 +1761,7 @@ public class IndexData { } @Override - protected void writeNonNullValue( - Object value, ByteStream bout) + protected void writeNonNullValue(Object value, ByteStream bout) throws IOException { GeneralLegacyIndexCodes.GEN_LEG_INSTANCE.writeNonNullIndexTextValue( @@ -1720,8 +1781,7 @@ public class IndexData { } @Override - protected void writeNonNullValue( - Object value, ByteStream bout) + protected void writeNonNullValue(Object value, ByteStream bout) throws IOException { GeneralIndexCodes.GEN_INSTANCE.writeNonNullIndexTextValue( @@ -1741,29 +1801,37 @@ public class IndexData { } @Override - protected void writeNonNullValue( - Object value, ByteStream bout) + protected void writeNonNullValue(Object value, ByteStream bout) throws IOException { - byte[] valueBytes = encodeNumberColumnValue(value, getColumn()); - - // index format <8-bytes> 0x09 <8-bytes> 0x08 - - // bit twiddling rules: - // - isAsc => nothing - // - !isAsc => flipBytes, _but keep 09 unflipped_! - if(!isAscending()) { - flipBytes(valueBytes); - } - - bout.write(valueBytes, 0, 8); - bout.write(MID_GUID); - bout.write(valueBytes, 8, 8); - bout.write(isAscending() ? ASC_END_GUID : DESC_END_GUID); + writeGeneralBinaryEntry( + encodeNumberColumnValue(value, getColumn()), isAscending(), + bout); } } + /** + * ColumnDescriptor for BINARY columns. + */ + private static final class BinaryColumnDescriptor extends ColumnDescriptor + { + private BinaryColumnDescriptor(ColumnImpl column, byte flags) + throws IOException + { + super(column, flags); + } + + @Override + protected void writeNonNullValue(Object value, ByteStream bout) + throws IOException + { + writeGeneralBinaryEntry( + ColumnImpl.toByteArray(value), isAscending(), bout); + } + } + + /** * ColumnDescriptor for columns which we cannot currently write. */ diff --git a/src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnMatcher.java b/src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnMatcher.java index 35ecfbd..2fac7bf 100644 --- a/src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnMatcher.java +++ b/src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnMatcher.java @@ -21,6 +21,7 @@ USA package com.healthmarketscience.jackcess.util; import java.io.IOException; +import java.util.Arrays; import com.healthmarketscience.jackcess.DataType; import com.healthmarketscience.jackcess.Table; @@ -45,7 +46,7 @@ public class SimpleColumnMatcher implements ColumnMatcher { public boolean matches(Table table, String columnName, Object value1, Object value2) { - if(ObjectUtils.equals(value1, value2)) { + if(equals(value1, value2)) { return true; } @@ -59,7 +60,7 @@ public class SimpleColumnMatcher implements ColumnMatcher { Object internalV1 = ColumnImpl.toInternalValue(dataType, value1); Object internalV2 = ColumnImpl.toInternalValue(dataType, value2); - return ObjectUtils.equals(internalV1, internalV2); + return equals(internalV1, internalV2); } catch(IOException e) { // ignored, just go with the original result } @@ -67,4 +68,15 @@ public class SimpleColumnMatcher implements ColumnMatcher { return false; } + /** + * Returns {@code true} if the two objects are equal, handling {@code null} + * and objects of type {@code byte[]}. + */ + private static boolean equals(Object o1, Object o2) + { + return (ObjectUtils.equals(o1, o2) || + ((o1 instanceof byte[]) && (o2 instanceof byte[]) && + Arrays.equals((byte[])o1, (byte[])o2))); + } + } diff --git a/src/test/data/V2010/binIdxTestV2010.accdb b/src/test/data/V2010/binIdxTestV2010.accdb new file mode 100644 index 0000000..be8c61f Binary files /dev/null and b/src/test/data/V2010/binIdxTestV2010.accdb differ diff --git a/src/test/java/com/healthmarketscience/jackcess/IndexTest.java b/src/test/java/com/healthmarketscience/jackcess/IndexTest.java index 95cb2be..20828d1 100644 --- a/src/test/java/com/healthmarketscience/jackcess/IndexTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/IndexTest.java @@ -678,6 +678,45 @@ public class IndexTest extends TestCase { } } + public void testBinaryIndex() throws Exception + { + for (final TestDB testDB : TestDB.getSupportedForBasename(Basename.BINARY_INDEX)) { + Database db = open(testDB); + + Table table = db.getTable("Test"); + + Index idx = table.getIndex("BinAscIdx"); + doTestBinaryIndex(idx, "BinAsc", false); + + idx = table.getIndex("BinDscIdx"); + doTestBinaryIndex(idx, "BinDsc", true); + + db.close(); + } + } + + private static void doTestBinaryIndex(Index idx, String colName, boolean forward) + throws Exception + { + IndexCursor ic = CursorBuilder.createCursor(idx); + + for(Row row : idx.getTable().getDefaultCursor().newIterable().setForward(forward)) { + int id = row.getInt("ID"); + byte[] data = row.getBytes(colName); + + boolean found = false; + for(Row idxRow : ic.newEntryIterable(data)) { + + assertTrue(Arrays.equals(data, idxRow.getBytes(colName))); + if(id == idxRow.getInt("ID")) { + found = true; + } + } + + assertTrue(found); + } + } + private void doCheckForeignKeyIndex(Table ta, Index ia, Table tb) throws Exception { diff --git a/src/test/java/com/healthmarketscience/jackcess/impl/JetFormatTest.java b/src/test/java/com/healthmarketscience/jackcess/impl/JetFormatTest.java index 4f86896..c73e777 100644 --- a/src/test/java/com/healthmarketscience/jackcess/impl/JetFormatTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/impl/JetFormatTest.java @@ -48,7 +48,8 @@ public class JetFormatTest extends TestCase { UNSUPPORTED("unsupportedFieldsTest"), LINKED("linkerTest"), BLOB("testOle"), - CALC_FIELD("calcFieldTest"); + CALC_FIELD("calcFieldTest"), + BINARY_INDEX("binIdxTest"); private final String _basename; -- cgit v1.2.3