From f9202b4fabd510611c13a18c3e5e1542760447a2 Mon Sep 17 00:00:00 2001 From: James Ahlborn Date: Fri, 16 Aug 2019 01:23:38 +0000 Subject: [PATCH] use new general 97 index support git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/branches/a97_indexes@1312 f203690c-595d-4dc9-a70b-905162fa7fd2 --- .../jackcess/impl/IndexCursorImpl.java | 85 +++++++-------- .../jackcess/impl/IndexData.java | 28 ++++- .../jackcess/impl/IndexCodesTest.java | 101 +++++++++--------- 3 files changed, 117 insertions(+), 97 deletions(-) diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/IndexCursorImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/IndexCursorImpl.java index 6c58182..43046ba 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/IndexCursorImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/IndexCursorImpl.java @@ -42,7 +42,7 @@ import org.apache.commons.logging.LogFactory; */ public class IndexCursorImpl extends CursorImpl implements IndexCursor { - private static final Log LOG = LogFactory.getLog(IndexCursorImpl.class); + private static final Log LOG = LogFactory.getLog(IndexCursorImpl.class); /** IndexDirHandler for forward traversal */ private final IndexDirHandler _forwardDirHandler = @@ -68,7 +68,7 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor _index.initialize(); _entryCursor = entryCursor; } - + /** * Creates an indexed cursor for the given table, narrowed to the given * range. @@ -76,7 +76,7 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor * Note, index based table traversal may not include all rows, as certain * types of indexes do not include all entries (namely, some indexes ignore * null entries, see {@link Index#shouldIgnoreNulls}). - * + * * @param table the table over which this cursor will traverse * @param index index for the table which will define traversal order as * well as enhance certain lookups @@ -98,14 +98,9 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor throw new IllegalArgumentException( "Given index is not for given table: " + index + ", " + table); } - if(!table.getFormat().INDEXES_SUPPORTED) { - throw new IllegalArgumentException( - "JetFormat " + table.getFormat() + - " does not currently support index lookups"); - } if(index.getIndexData().getUnsupportedReason() != null) { throw new IllegalArgumentException( - "Given index " + index + + "Given index " + index + " is not usable for indexed lookups due to " + index.getIndexData().getUnsupportedReason()); } @@ -115,7 +110,7 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor // init the column matcher appropriately for the index type cursor.setColumnMatcher(null); return cursor; - } + } private Set getIndexEntryPattern() { @@ -135,7 +130,7 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor } @Override - public Row findRowByEntry(Object... entryValues) + public Row findRowByEntry(Object... entryValues) throws IOException { if(findFirstRowByEntry(entryValues)) { @@ -143,16 +138,16 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor } return null; } - + @Override - public boolean findFirstRowByEntry(Object... entryValues) - throws IOException + public boolean findFirstRowByEntry(Object... entryValues) + throws IOException { PositionImpl curPos = _curPos; PositionImpl prevPos = _prevPos; boolean found = false; try { - found = findFirstRowByEntryImpl(toRowValues(entryValues), true, + found = findFirstRowByEntryImpl(toRowValues(entryValues), true, _columnMatcher); return found; } finally { @@ -167,8 +162,8 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor } @Override - public void findClosestRowByEntry(Object... entryValues) - throws IOException + public void findClosestRowByEntry(Object... entryValues) + throws IOException { PositionImpl curPos = _curPos; PositionImpl prevPos = _prevPos; @@ -189,8 +184,8 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor } @Override - public boolean currentRowMatchesEntry(Object... entryValues) - throws IOException + public boolean currentRowMatchesEntry(Object... entryValues) + throws IOException { return currentRowMatchesEntryImpl(toRowValues(entryValues), _columnMatcher); } @@ -205,17 +200,17 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor toRowValues(iterBuilder.getEntryValues()), iterBuilder.getColumnMatcher()); } - + @Override protected IndexDirHandler getDirHandler(boolean moveForward) { return (moveForward ? _forwardDirHandler : _reverseDirHandler); } - + @Override protected boolean isUpToDate() { return(super.isUpToDate() && _entryCursor.isUpToDate()); } - + @Override protected void reset(boolean moveForward) { _entryCursor.reset(moveForward); @@ -259,7 +254,7 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor return super.findAnotherRowImpl(columnPattern, valuePattern, moveForward, columnMatcher, rowValues); } - + // sweet, we can use our index if(!findPotentialRow(rowValues, true)) { return false; @@ -274,14 +269,14 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor * match the given values. Caller manages save/restore on failure. * * @param rowValues the column values built from the index column values - * @param requireMatch whether or not an exact match is found + * @param requireMatch whether or not an exact match is desired * @return {@code true} if a valid row was found with the given values, * {@code false} if no row was found */ protected boolean findFirstRowByEntryImpl(Object[] rowValues, boolean requireMatch, - ColumnMatcher columnMatcher) - throws IOException + ColumnMatcher columnMatcher) + throws IOException { if(!findPotentialRow(rowValues, requireMatch)) { return false; @@ -317,7 +312,7 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor // determine if the pattern columns exactly match the index columns boolean exactColumnMatch = rowPattern.keySet().equals( getIndexEntryPattern()); - + // there may be multiple rows which fit the pattern subset used by // the index, so we need to keep checking until our index values no // longer match @@ -337,12 +332,12 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor } } while(moveToAnotherRow(moveForward)); - + // none of the potential rows matched return false; } - private boolean currentRowMatchesEntryImpl(Object[] rowValues, + private boolean currentRowMatchesEntryImpl(Object[] rowValues, ColumnMatcher columnMatcher) throws IOException { @@ -353,7 +348,7 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor Object patValue = rowValues[col.getColumnIndex()]; - if((patValue == IndexData.MIN_VALUE) || + if((patValue == IndexData.MIN_VALUE) || (patValue == IndexData.MAX_VALUE)) { // all remaining entry values are "special" (used for partial lookups) return true; @@ -366,9 +361,9 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor } } - return true; + return true; } - + private boolean findPotentialRow(Object[] rowValues, boolean requireMatch) throws IOException { @@ -400,8 +395,8 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor } @Override - protected boolean keepSearching(ColumnMatcher columnMatcher, - Object searchInfo) + protected boolean keepSearching(ColumnMatcher columnMatcher, + Object searchInfo) throws IOException { if(searchInfo instanceof Object[]) { @@ -420,7 +415,7 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor return _entryCursor.getIndexData().constructPartialIndexRowFromEntry( IndexData.MIN_VALUE, entryValues); } - + @Override protected PositionImpl findAnotherPosition( RowState rowState, PositionImpl curPos, boolean moveForward) @@ -451,7 +446,7 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor public abstract IndexData.Entry getAnotherEntry() throws IOException; } - + /** * Handles moving the table index cursor forward. */ @@ -469,7 +464,7 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor return _entryCursor.getNextEntry(); } } - + /** * Handles moving the table index cursor backward. */ @@ -486,15 +481,15 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor public IndexData.Entry getAnotherEntry() throws IOException { return _entryCursor.getPreviousEntry(); } - } - + } + /** * Value object which maintains the current position of an IndexCursor. */ private static final class IndexPosition extends PositionImpl { private final IndexData.Entry _entry; - + private IndexPosition(IndexData.Entry entry) { _entry = entry; } @@ -503,11 +498,11 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor public RowIdImpl getRowId() { return getEntry().getRowId(); } - + public IndexData.Entry getEntry() { return _entry; } - + @Override protected boolean equalsImpl(Object o) { return getEntry().equals(((IndexPosition)o).getEntry()); @@ -525,7 +520,7 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor private final class EntryIterator extends BaseIterator { private final Object[] _rowValues; - + private EntryIterator(Collection columnNames, Object[] rowValues, ColumnMatcher columnMatcher) { @@ -541,9 +536,9 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor @Override protected boolean findNext() throws IOException { - return (moveToNextRow() && + return (moveToNextRow() && currentRowMatchesEntryImpl(_rowValues, _colMatcher)); - } + } } } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java b/src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java index ca91fef..bc2d111 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java @@ -1515,11 +1515,14 @@ public class IndexData { case TEXT: case MEMO: ColumnImpl.SortOrder sortOrder = col.getTextSortOrder(); + if(ColumnImpl.GENERAL_SORT_ORDER.equals(sortOrder)) { + return new GenTextColumnDescriptor(col, flags); + } if(ColumnImpl.GENERAL_LEGACY_SORT_ORDER.equals(sortOrder)) { return new GenLegTextColumnDescriptor(col, flags); } - if(ColumnImpl.GENERAL_SORT_ORDER.equals(sortOrder)) { - return new GenTextColumnDescriptor(col, flags); + if(ColumnImpl.GENERAL_97_SORT_ORDER.equals(sortOrder)) { + return new Gen97TextColumnDescriptor(col, flags); } // unsupported sort order setUnsupportedReason("unsupported collating sort order " + sortOrder + @@ -1914,6 +1917,27 @@ public class IndexData { } } + /** + * ColumnDescriptor for "general 97" sort order text based columns. + */ + private static final class Gen97TextColumnDescriptor + extends ColumnDescriptor + { + private Gen97TextColumnDescriptor(ColumnImpl column, byte flags) + throws IOException + { + super(column, flags); + } + + @Override + protected void writeNonNullValue(Object value, ByteStream bout) + throws IOException + { + General97IndexCodes.GEN_97_INSTANCE.writeNonNullIndexTextValue( + value, bout, isAscending()); + } + } + /** * ColumnDescriptor for guid columns. */ diff --git a/src/test/java/com/healthmarketscience/jackcess/impl/IndexCodesTest.java b/src/test/java/com/healthmarketscience/jackcess/impl/IndexCodesTest.java index 5b7a440..878ffdc 100644 --- a/src/test/java/com/healthmarketscience/jackcess/impl/IndexCodesTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/impl/IndexCodesTest.java @@ -56,7 +56,7 @@ public class IndexCodesTest extends TestCase { SPECIAL_CHARS.put('\'', "\\'"); SPECIAL_CHARS.put('\\', "\\\\"); } - + public IndexCodesTest(String name) throws Exception { super(name); } @@ -77,7 +77,7 @@ public class IndexCodesTest extends TestCase { } } - private static void checkIndexEntries(final TestDB testDB, Table t, Index index) throws Exception + public static void checkIndexEntries(final TestDB testDB, Table t, Index index) throws Exception { // index.initialize(); // System.out.println("Ind " + index); @@ -94,14 +94,14 @@ public class IndexCodesTest extends TestCase { } finally { if(!success) { System.out.println("CurPos: " + curPos); - System.out.println("Value: " + row + ": " + + System.out.println("Value: " + row + ": " + toUnicodeStr(row.get("data"))); - } + } } } - + } - + private static void findRow(final TestDB testDB, Table t, Index index, Row expectedRow, Cursor.Position expectedPos) @@ -111,7 +111,7 @@ public class IndexCodesTest extends TestCase { Cursor cursor = CursorBuilder.createCursor(index, idxRow, idxRow); Cursor.Position startPos = cursor.getSavepoint().getCurrentPosition(); - + cursor.beforeFirst(); while(cursor.moveToNextRow()) { Row row = cursor.getCurrentRow(); @@ -125,11 +125,12 @@ public class IndexCodesTest extends TestCase { // TODO long rows not handled completely yet in V2010 // seems to truncate entry at 508 bytes with some trailing 2 byte seq - if(testDB.getExpectedFileFormat() == Database.FileFormat.V2010) { + if((testDB != null) && + (testDB.getExpectedFileFormat() == Database.FileFormat.V2010)) { String rowId = expectedRow.getString("name"); String tName = t.getName(); if(("Table11".equals(tName) || "Table11_desc".equals(tName)) && - ("row10".equals(rowId) || "row11".equals(rowId) || + ("row10".equals(rowId) || "row11".equals(rowId) || "row12".equals(rowId))) { System.out.println( "TODO long rows not handled completely yet in V2010: " + tName + @@ -142,13 +143,13 @@ public class IndexCodesTest extends TestCase { entryToString(startPos)); } - + ////// // // The code below is for use in reverse engineering index entries. // ////// - + public void testNothing() throws Exception { // keep this so build doesn't fail if other tests are disabled } @@ -161,7 +162,7 @@ public class IndexCodesTest extends TestCase { .addColumn(new ColumnBuilder("row", DataType.TEXT)) .addColumn(new ColumnBuilder("data", DataType.TEXT)) .toTable(db); - + for(int i = 0; i < 256; ++i) { String str = "AA" + ((char)i) + "AA"; t.addRow("row" + i, str); @@ -182,7 +183,7 @@ public class IndexCodesTest extends TestCase { (byte)42 + i, (short)53 + i, 13 * i, (6.7d / i), null, null, true); } - + db.close(); } @@ -212,10 +213,10 @@ public class IndexCodesTest extends TestCase { .toTable(db); char c = (char)0x3041; // crazy 7F 02 ... A0 - char c2 = (char)0x30A2; // crazy 7F 02 ... + char c2 = (char)0x30A2; // crazy 7F 02 ... char c3 = (char)0x2045; // inat 27 ... 1C char c4 = (char)0x3043; // crazy 7F 03 ... A0 - char c5 = (char)0x3046; // crazy 7F 04 ... + char c5 = (char)0x3046; // crazy 7F 04 ... char c6 = (char)0x30F6; // crazy 7F 0D ... A0 char c7 = (char)0x3099; // unprint 03 char c8 = (char)0x0041; // A @@ -232,13 +233,13 @@ public class IndexCodesTest extends TestCase { // t = new TableBuilder("Table2") // .addColumn(new ColumnBuilder("data", DataType.TEXT)) // .toTable(db); - + // writeChars(0x0000, t); // t = new TableBuilder("Table3") // .addColumn(new ColumnBuilder("data", DataType.TEXT)) // .toTable(db); - + // writeChars(0x0400, t); @@ -255,13 +256,13 @@ public class IndexCodesTest extends TestCase { Index ind = t.getIndexes().iterator().next(); ((IndexImpl)ind).initialize(); - + System.out.println("Ind " + ind); Cursor cursor = CursorBuilder.createCursor(ind); while(cursor.moveToNextRow()) { System.out.println("======="); - String entryStr = + String entryStr = entryToString(cursor.getSavepoint().getCurrentPosition()); System.out.println("Entry Bytes: " + entryStr); System.out.println("Value: " + cursor.getCurrentRow() + "; " + @@ -316,10 +317,10 @@ public class IndexCodesTest extends TestCase { System.out.println("Savepoint: " + cursor.getSavepoint()); System.out.println("Value: " + cursor.getCurrentRow()); } - + db.close(); } - + public void x_testReverseIsoMdb2010() throws Exception { Database db = open(Database.FileFormat.V2010, new File("/data2/jackcess_test/testAllIndexCodes3_2010.accdb")); @@ -343,8 +344,8 @@ public class IndexCodesTest extends TestCase { Map inat2Codes = new TreeMap(); Map inat2ExtraCodes = new TreeMap(); Map inat2CrazyCodes = new TreeMap(); - - + + Cursor cursor = CursorBuilder.createCursor(index); while(cursor.moveToNextRow()) { // System.out.println("======="); @@ -377,14 +378,14 @@ public class IndexCodesTest extends TestCase { handleInlineEntry(m.group(1), c, inlineCodes); } else if(entryStr.contains("01 01 01 80")) { - + // handle most unprintable codes type = "UNPRINTABLE"; Matcher m = unprintPat.matcher(entryStr); m.find(); handleUnprintableEntry(m.group(2), c, unprintCodes); - } else if(entryStr.contains("01 02 02") && + } else if(entryStr.contains("01 02 02") && !entryStr.contains("FF 02 80 FF 80")) { // handle chars w/ symbols @@ -393,7 +394,7 @@ public class IndexCodesTest extends TestCase { m.find(); handleInternationalEntry(m.group(1), m.group(2), c, inatInlineCodes, inatExtraCodes); - + } else if(entryStr.contains("0E 02 0E 02 0E 02 0E 02 01 02")) { // handle chars w/ symbols @@ -401,7 +402,7 @@ public class IndexCodesTest extends TestCase { Matcher m = unprint2Pat.matcher(entryStr); m.find(); handleUnprintable2Entry(m.group(1), c, unprint2Codes); - + } else if(entryStr.contains("FF 02 80 FF 80")) { type = "CRAZY_INAT"; @@ -415,8 +416,8 @@ public class IndexCodesTest extends TestCase { // throw new RuntimeException("unhandled " + entryStr); System.out.println("unhandled " + entryStr); - } - + } + System.out.println("Type: " + type); } @@ -456,7 +457,7 @@ public class IndexCodesTest extends TestCase { toByteString(extra)); continue; } - + chars = unprintCodes.get(cc); if(chars != null) { System.out.println("U" + toByteString(chars)); @@ -495,10 +496,10 @@ public class IndexCodesTest extends TestCase { throw new RuntimeException("Unhandled char " + toUnicodeStr(c)); } System.out.println("\n***END CODES"); - + db.close(); } - + public void x_testReverseIsoMdb() throws Exception { Database db = open(Database.FileFormat.V2000, new File("/data2/jackcess_test/testAllIndexCodes3.mdb")); @@ -522,8 +523,8 @@ public class IndexCodesTest extends TestCase { Map inat2Codes = new TreeMap(); Map inat2ExtraCodes = new TreeMap(); Map inat2CrazyCodes = new TreeMap(); - - + + Cursor cursor = CursorBuilder.createCursor(index); while(cursor.moveToNextRow()) { // System.out.println("======="); @@ -555,14 +556,14 @@ public class IndexCodesTest extends TestCase { handleInlineEntry(m.group(1), c, inlineCodes); } else if(entryStr.contains("01 01 01 80")) { - + // handle most unprintable codes type = "UNPRINTABLE"; Matcher m = unprintPat.matcher(entryStr); m.find(); handleUnprintableEntry(m.group(2), c, unprintCodes); - } else if(entryStr.contains("01 02 02") && + } else if(entryStr.contains("01 02 02") && !entryStr.contains("FF 02 80 FF 80")) { // handle chars w/ symbols @@ -571,7 +572,7 @@ public class IndexCodesTest extends TestCase { m.find(); handleInternationalEntry(m.group(1), m.group(2), c, inatInlineCodes, inatExtraCodes); - + } else if(entryStr.contains("4A 4A 4A 4A 01 02")) { // handle chars w/ symbols @@ -579,7 +580,7 @@ public class IndexCodesTest extends TestCase { Matcher m = unprint2Pat.matcher(entryStr); m.find(); handleUnprintable2Entry(m.group(1), c, unprint2Codes); - + } else if(entryStr.contains("FF 02 80 FF 80")) { type = "CRAZY_INAT"; @@ -592,8 +593,8 @@ public class IndexCodesTest extends TestCase { } else { throw new RuntimeException("unhandled " + entryStr); - } - + } + System.out.println("Type: " + type); } @@ -633,7 +634,7 @@ public class IndexCodesTest extends TestCase { toByteString(extra)); continue; } - + chars = unprintCodes.get(cc); if(chars != null) { System.out.println("U" + toByteString(chars)); @@ -672,7 +673,7 @@ public class IndexCodesTest extends TestCase { throw new RuntimeException("Unhandled char " + toUnicodeStr(c)); } System.out.println("\n***END CODES"); - + db.close(); } @@ -691,21 +692,21 @@ public class IndexCodesTest extends TestCase { { inlineCodes.put(c, entryCodes.trim().split(" ")); } - + private static void handleUnprintableEntry( String entryCodes, char c, Map unprintCodes) throws Exception { unprintCodes.put(c, entryCodes.trim().split(" ")); } - + private static void handleUnprintable2Entry( String entryCodes, char c, Map unprintCodes) throws Exception { unprintCodes.put(c, entryCodes.trim().split(" ")); } - + private static void handleInternationalEntry( String inlineCodes, String entryCodes, char c, Map inatInlineCodes, @@ -732,20 +733,20 @@ public class IndexCodesTest extends TestCase { } } - private static String toUnicodeStr(Object obj) throws Exception { + public static String toUnicodeStr(Object obj) throws Exception { StringBuilder sb = new StringBuilder(); for(char c : obj.toString().toCharArray()) { sb.append(toUnicodeStr(c)).append(" "); } return sb.toString(); } - + private static String toUnicodeStr(char c) throws Exception { String specialStr = SPECIAL_CHARS.get(c); if(specialStr != null) { return specialStr; } - + String digits = Integer.toHexString(c).toUpperCase(); while(digits.length() < 4) { digits = "0" + digits; @@ -769,7 +770,7 @@ public class IndexCodesTest extends TestCase { } return builder.toString(); } - + public static String entryToString(Cursor.Position curPos) throws Exception { @@ -783,5 +784,5 @@ public class IndexCodesTest extends TestCase { return ByteUtil.toHexString(ByteBuffer.wrap(entryBytes), 0, entryBytes.length, false); } - + } -- 2.39.5