aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJames Ahlborn <jtahlborn@yahoo.com>2015-03-16 20:16:51 +0000
committerJames Ahlborn <jtahlborn@yahoo.com>2015-03-16 20:16:51 +0000
commitfad035e0b93b55502f54474343c9ac618a62e66d (patch)
tree8eea55ea3bae05ef79cea1d0bcd33d1ad79841f1 /src
parentb60366623ebdd7ce72798cb7fc4e40fa8e0cacc8 (diff)
downloadjackcess-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.xml3
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/IndexCodes.java4
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java132
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnMatcher.java16
-rw-r--r--src/test/data/V2010/binIdxTestV2010.accdbbin0 -> 417792 bytes
-rw-r--r--src/test/java/com/healthmarketscience/jackcess/IndexTest.java39
-rw-r--r--src/test/java/com/healthmarketscience/jackcess/impl/JetFormatTest.java3
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
new file mode 100644
index 0000000..be8c61f
--- /dev/null
+++ b/src/test/data/V2010/binIdxTestV2010.accdb
Binary files 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;