]> source.dussan.org Git - jackcess.git/commitdiff
Implement support for indexes on BINARY fields
authorJames Ahlborn <jtahlborn@yahoo.com>
Mon, 16 Mar 2015 20:16:51 +0000 (20:16 +0000)
committerJames Ahlborn <jtahlborn@yahoo.com>
Mon, 16 Mar 2015 20:16:51 +0000 (20:16 +0000)
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@921 f203690c-595d-4dc9-a70b-905162fa7fd2

src/changes/changes.xml
src/main/java/com/healthmarketscience/jackcess/impl/IndexCodes.java
src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java
src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnMatcher.java
src/test/data/V2010/binIdxTestV2010.accdb [new file with mode: 0644]
src/test/java/com/healthmarketscience/jackcess/IndexTest.java
src/test/java/com/healthmarketscience/jackcess/impl/JetFormatTest.java

index 0c866c00d3d94d6e8ff882524c8233d31ccbb91e..dcd9f90251b0afd6751ffe95a7a3710c24c664e8 100644 (file)
@@ -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">
index a605883012b9ec9d660f031b6b3c89946909abe3..6fbbc654d6fb63be61c651e42a81d5b474a2cbc0 100644 (file)
@@ -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;
   
index a6970a9009de34b7d206c698f47f20c3bd6b702f..f951dc6db934239394ad751b2bc2914a6e58d8ce 100644 (file)
@@ -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.
    */
index 35ecfbd36da5dd280587dcfffa3359a3c550feb7..2fac7bf80e01ac73abe6582b32ef102e04196102 100644 (file)
@@ -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 (file)
index 0000000..be8c61f
Binary files /dev/null and b/src/test/data/V2010/binIdxTestV2010.accdb differ
index 95cb2bea1cf1fb67ec47b56f4aca44ffc6b6a1ea..20828d110235ab06dcc42504af1d08dd085f7192 100644 (file)
@@ -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
   {
index 4f868966daa373cc88c94dc6946c7780fea64fb8..c73e777f592a0f6bc26cc39699441135435c3819 100644 (file)
@@ -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;