diff options
author | James Ahlborn <jtahlborn@yahoo.com> | 2015-03-16 20:16:51 +0000 |
---|---|---|
committer | James Ahlborn <jtahlborn@yahoo.com> | 2015-03-16 20:16:51 +0000 |
commit | fad035e0b93b55502f54474343c9ac618a62e66d (patch) | |
tree | 8eea55ea3bae05ef79cea1d0bcd33d1ad79841f1 /src | |
parent | b60366623ebdd7ce72798cb7fc4e40fa8e0cacc8 (diff) | |
download | jackcess-fad035e0b93b55502f54474343c9ac618a62e66d.tar.gz jackcess-fad035e0b93b55502f54474343c9ac618a62e66d.zip |
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
Diffstat (limited to 'src')
-rw-r--r-- | src/changes/changes.xml | 3 | ||||
-rw-r--r-- | src/main/java/com/healthmarketscience/jackcess/impl/IndexCodes.java | 4 | ||||
-rw-r--r-- | src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java | 132 | ||||
-rw-r--r-- | src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnMatcher.java | 16 | ||||
-rw-r--r-- | src/test/data/V2010/binIdxTestV2010.accdb | bin | 0 -> 417792 bytes | |||
-rw-r--r-- | src/test/java/com/healthmarketscience/jackcess/IndexTest.java | 39 | ||||
-rw-r--r-- | src/test/java/com/healthmarketscience/jackcess/impl/JetFormatTest.java | 3 |
7 files changed, 158 insertions, 39 deletions
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 @@ <action dev="jahlborn" type="add" system="SourceForge2Features" issue="29"> Added contextual information to many errors and warnings. </action> + <action dev="jahlborn" type="add" system="SourceForge2Features" issue="29"> + Implement support for indexes on BINARY fields. + </action> </release> <release version="2.0.8" date="2014-12-26"> <action dev="jahlborn" type="fix" system="SourceForge2" issue="113"> 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 @@ -1314,6 +1314,71 @@ public class IndexData { } /** + * 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. */ private static Entry createSpecialEntry(RowIdImpl rowId) { @@ -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,30 +1801,38 @@ 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. */ private final class ReadOnlyColumnDescriptor extends ColumnDescriptor 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 Binary files differnew file mode 100644 index 0000000..be8c61f --- /dev/null +++ b/src/test/data/V2010/binIdxTestV2010.accdb 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; |