*/
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 =
_index.initialize();
_entryCursor = entryCursor;
}
-
+
/**
* Creates an indexed cursor for the given table, narrowed to the given
* range.
* 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
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());
}
// init the column matcher appropriately for the index type
cursor.setColumnMatcher(null);
return cursor;
- }
+ }
private Set<String> getIndexEntryPattern()
{
}
@Override
- public Row findRowByEntry(Object... entryValues)
+ public Row findRowByEntry(Object... entryValues)
throws IOException
{
if(findFirstRowByEntry(entryValues)) {
}
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 {
}
@Override
- public void findClosestRowByEntry(Object... entryValues)
- throws IOException
+ public void findClosestRowByEntry(Object... entryValues)
+ throws IOException
{
PositionImpl curPos = _curPos;
PositionImpl prevPos = _prevPos;
}
@Override
- public boolean currentRowMatchesEntry(Object... entryValues)
- throws IOException
+ public boolean currentRowMatchesEntry(Object... entryValues)
+ throws IOException
{
return currentRowMatchesEntryImpl(toRowValues(entryValues), _columnMatcher);
}
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);
return super.findAnotherRowImpl(columnPattern, valuePattern, moveForward,
columnMatcher, rowValues);
}
-
+
// sweet, we can use our index
if(!findPotentialRow(rowValues, true)) {
return false;
* 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;
// 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
}
} 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
{
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;
}
}
- return true;
+ return true;
}
-
+
private boolean findPotentialRow(Object[] rowValues, boolean requireMatch)
throws IOException
{
}
@Override
- protected boolean keepSearching(ColumnMatcher columnMatcher,
- Object searchInfo)
+ protected boolean keepSearching(ColumnMatcher columnMatcher,
+ Object searchInfo)
throws IOException
{
if(searchInfo instanceof Object[]) {
return _entryCursor.getIndexData().constructPartialIndexRowFromEntry(
IndexData.MIN_VALUE, entryValues);
}
-
+
@Override
protected PositionImpl findAnotherPosition(
RowState rowState, PositionImpl curPos, boolean moveForward)
public abstract IndexData.Entry getAnotherEntry()
throws IOException;
}
-
+
/**
* Handles moving the table index cursor forward.
*/
return _entryCursor.getNextEntry();
}
}
-
+
/**
* Handles moving the table index cursor backward.
*/
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;
}
public RowIdImpl getRowId() {
return getEntry().getRowId();
}
-
+
public IndexData.Entry getEntry() {
return _entry;
}
-
+
@Override
protected boolean equalsImpl(Object o) {
return getEntry().equals(((IndexPosition)o).getEntry());
private final class EntryIterator extends BaseIterator
{
private final Object[] _rowValues;
-
+
private EntryIterator(Collection<String> columnNames, Object[] rowValues,
ColumnMatcher columnMatcher)
{
@Override
protected boolean findNext() throws IOException {
- return (moveToNextRow() &&
+ return (moveToNextRow() &&
currentRowMatchesEntryImpl(_rowValues, _colMatcher));
- }
+ }
}
}
SPECIAL_CHARS.put('\'', "\\'");
SPECIAL_CHARS.put('\\', "\\\\");
}
-
+
public IndexCodesTest(String name) throws Exception {
super(name);
}
}
}
- 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);
} 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)
Cursor cursor = CursorBuilder.createCursor(index, idxRow, idxRow);
Cursor.Position startPos = cursor.getSavepoint().getCurrentPosition();
-
+
cursor.beforeFirst();
while(cursor.moveToNextRow()) {
Row row = cursor.getCurrentRow();
// 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 +
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
}
.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);
(byte)42 + i, (short)53 + i, 13 * i,
(6.7d / i), null, null, true);
}
-
+
db.close();
}
.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
// 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);
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() + "; " +
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"));
Map<Character,String[]> inat2Codes = new TreeMap<Character,String[]>();
Map<Character,String[]> inat2ExtraCodes = new TreeMap<Character,String[]>();
Map<Character,String[]> inat2CrazyCodes = new TreeMap<Character,String[]>();
-
-
+
+
Cursor cursor = CursorBuilder.createCursor(index);
while(cursor.moveToNextRow()) {
// System.out.println("=======");
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
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
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";
// throw new RuntimeException("unhandled " + entryStr);
System.out.println("unhandled " + entryStr);
- }
-
+ }
+
System.out.println("Type: " + type);
}
toByteString(extra));
continue;
}
-
+
chars = unprintCodes.get(cc);
if(chars != null) {
System.out.println("U" + toByteString(chars));
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"));
Map<Character,String[]> inat2Codes = new TreeMap<Character,String[]>();
Map<Character,String[]> inat2ExtraCodes = new TreeMap<Character,String[]>();
Map<Character,String[]> inat2CrazyCodes = new TreeMap<Character,String[]>();
-
-
+
+
Cursor cursor = CursorBuilder.createCursor(index);
while(cursor.moveToNextRow()) {
// System.out.println("=======");
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
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
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";
} else {
throw new RuntimeException("unhandled " + entryStr);
- }
-
+ }
+
System.out.println("Type: " + type);
}
toByteString(extra));
continue;
}
-
+
chars = unprintCodes.get(cc);
if(chars != null) {
System.out.println("U" + toByteString(chars));
throw new RuntimeException("Unhandled char " + toUnicodeStr(c));
}
System.out.println("\n***END CODES");
-
+
db.close();
}
{
inlineCodes.put(c, entryCodes.trim().split(" "));
}
-
+
private static void handleUnprintableEntry(
String entryCodes, char c, Map<Character,String[]> unprintCodes)
throws Exception
{
unprintCodes.put(c, entryCodes.trim().split(" "));
}
-
+
private static void handleUnprintable2Entry(
String entryCodes, char c, Map<Character,String[]> unprintCodes)
throws Exception
{
unprintCodes.put(c, entryCodes.trim().split(" "));
}
-
+
private static void handleInternationalEntry(
String inlineCodes, String entryCodes, char c,
Map<Character,String[]> inatInlineCodes,
}
}
- 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;
}
return builder.toString();
}
-
+
public static String entryToString(Cursor.Position curPos)
throws Exception
{
return ByteUtil.toHexString(ByteBuffer.wrap(entryBytes),
0, entryBytes.length, false);
}
-
+
}