Browse Source

Implement support for partial index lookups. Efficient IndexCursor lookups can now be done with multi-column indexes using only some of the columns in the index.

git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@1087 f203690c-595d-4dc9-a70b-905162fa7fd2
tags/jackcess-2.1.7
James Ahlborn 7 years ago
parent
commit
4feb8fbfd1

+ 7
- 0
src/changes/changes.xml View File

@@ -4,6 +4,13 @@
<author email="javajedi@users.sf.net">Tim McCune</author>
</properties>
<body>
<release version="2.1.7" date="TBD">
<action dev="jahlborn" type="update">
Implement support for partial index lookups. Efficient IndexCursor
lookups can now be done with multi-column indexes using only some of
the columns in the index.
</action>
</release>
<release version="2.1.6" date="2016-11-29">
<action dev="jahlborn" type="update" system="SourceForge2Features"
issue="34">

+ 9
- 5
src/main/java/com/healthmarketscience/jackcess/CursorBuilder.java View File

@@ -22,10 +22,11 @@ import java.util.Arrays;
import java.util.List;
import java.util.Map;

import com.healthmarketscience.jackcess.impl.TableImpl;
import com.healthmarketscience.jackcess.impl.IndexImpl;
import com.healthmarketscience.jackcess.impl.CursorImpl;
import com.healthmarketscience.jackcess.impl.IndexCursorImpl;
import com.healthmarketscience.jackcess.impl.IndexData;
import com.healthmarketscience.jackcess.impl.IndexImpl;
import com.healthmarketscience.jackcess.impl.TableImpl;
import com.healthmarketscience.jackcess.util.ColumnMatcher;


@@ -145,7 +146,8 @@ public class CursorBuilder {
* Searches for an index with the given column names.
*/
private CursorBuilder setIndexByColumns(List<String> searchColumns) {
IndexImpl index = _table.findIndexForColumns(searchColumns, false);
IndexImpl index = _table.findIndexForColumns(
searchColumns, TableImpl.IndexFeature.ANY_MATCH);
if(index == null) {
throw new IllegalArgumentException("Index with columns " +
searchColumns +
@@ -198,7 +200,8 @@ public class CursorBuilder {
*/
public CursorBuilder setStartEntry(Object... startEntry) {
if(startEntry != null) {
setStartRow(_index.constructIndexRowFromEntry(startEntry));
setStartRow(_index.constructPartialIndexRowFromEntry(
IndexData.MIN_VALUE, startEntry));
}
return this;
}
@@ -230,7 +233,8 @@ public class CursorBuilder {
*/
public CursorBuilder setEndEntry(Object... endEntry) {
if(endEntry != null) {
setEndRow(_index.constructIndexRowFromEntry(endEntry));
setEndRow(_index.constructPartialIndexRowFromEntry(
IndexData.MAX_VALUE, endEntry));
}
return this;
}

+ 5
- 0
src/main/java/com/healthmarketscience/jackcess/Index.java View File

@@ -39,6 +39,11 @@ public interface Index

public boolean isForeignKey();

/**
* @usage _general_method_
*/
public int getColumnCount();

/**
* @return the Columns for this index (unmodifiable)
*/

+ 26
- 32
src/main/java/com/healthmarketscience/jackcess/impl/IndexCursorImpl.java View File

@@ -20,7 +20,6 @@ import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

@@ -260,8 +259,7 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor
return false;
}

// either we found a row with the given value, or none exist in the
// table
// either we found a row with the given value, or none exist in the table
return currentRowMatchesImpl(columnPattern, valuePattern, columnMatcher);
}

@@ -310,37 +308,24 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor
return false;
}

// find actual matching row
IndexData indexData = _entryCursor.getIndexData();
Map<String,?> indexRowPattern = null;
if(rowPattern.size() == indexData.getColumns().size()) {
// the rowPattern matches our index columns exactly, so we can
// streamline our testing below
indexRowPattern = rowPattern;
} else {
// the rowPattern has more columns than just the index, so we need to
// do more work when testing below
Map<String,Object> tmpRowPattern = new LinkedHashMap<String,Object>();
indexRowPattern = tmpRowPattern;
for(IndexData.ColumnDescriptor idxCol : indexData.getColumns()) {
tmpRowPattern.put(idxCol.getName(), rowValues[idxCol.getColumnIndex()]);
}
}
// there may be multiple columns which fit the pattern subset used by
// 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
do {

if(!currentRowMatchesImpl(indexRowPattern, columnMatcher)) {
if(!currentRowMatchesEntryImpl(rowValues, columnMatcher)) {
// there are no more rows which could possibly match
break;
}

// note, if rowPattern == indexRowPattern, no need to do an extra
// comparison with the current row
if((rowPattern == indexRowPattern) ||
currentRowMatchesImpl(rowPattern, columnMatcher)) {
// note, if exactColumnMatch, no need to do an extra comparison with the
// current row (since the entry match check above is equivalent to this
// check)
if(exactColumnMatch || currentRowMatchesImpl(rowPattern, columnMatcher)) {
// found it!
return true;
}
@@ -359,8 +344,16 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor
Row row = getCurrentRow(getIndexEntryPattern());

for(IndexData.ColumnDescriptor col : getIndex().getColumns()) {
String columnName = col.getName();
Object patValue = rowValues[col.getColumnIndex()];

if((patValue == IndexData.MIN_VALUE) ||
(patValue == IndexData.MAX_VALUE)) {
// all remaining entry values are "special" (used for partial lookups)
return true;
}

String columnName = col.getName();
Object rowValue = row.get(columnName);
if(!columnMatcher.matches(getTable(), columnName, patValue, rowValue)) {
return false;
@@ -388,15 +381,16 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor
protected Object prepareSearchInfo(ColumnImpl columnPattern, Object valuePattern)
{
// attempt to generate a lookup row for this index
return _entryCursor.getIndexData().constructIndexRow(
columnPattern.getName(), valuePattern);
return _entryCursor.getIndexData().constructPartialIndexRow(
IndexData.MIN_VALUE, columnPattern.getName(), valuePattern);
}

@Override
protected Object prepareSearchInfo(Map<String,?> rowPattern)
{
// attempt to generate a lookup row for this index
return _entryCursor.getIndexData().constructIndexRow(rowPattern);
return _entryCursor.getIndexData().constructPartialIndexRow(
IndexData.MIN_VALUE, rowPattern);
}

@Override
@@ -417,7 +411,8 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor

private Object[] toRowValues(Object[] entryValues)
{
return _entryCursor.getIndexData().constructIndexRowFromEntry(entryValues);
return _entryCursor.getIndexData().constructPartialIndexRowFromEntry(
IndexData.MIN_VALUE, entryValues);
}
@Override
@@ -545,5 +540,4 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor
}
}

}

+ 93
- 5
src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java View File

@@ -330,6 +330,10 @@ public class IndexData {
return Collections.unmodifiableList(_columns);
}

public int getColumnCount() {
return _columns.size();
}

/**
* Whether or not the complete index state has been read.
*/
@@ -784,7 +788,7 @@ public class IndexData {
if(idx < 0) {
// the caller may have only read some of the row data, if this is the
// case, just search for the page/row numbers
// FIXME, we could force caller to get relevant values?
// TODO, we could force caller to get relevant values?
EntryCursor cursor = cursor();
Position tmpPos = null;
Position endPos = cursor._lastPos;
@@ -979,6 +983,37 @@ public class IndexData {
}
return idxRow;
}

/**
* Constructs an array of values appropriate for this index from the given
* column values, possibly only providing a prefix subset of the index
* columns (at least one value must be provided). If a prefix entry is
* provided, any missing, trailing index entry values will use the given
* filler value.
* @return the appropriate sparse array of data
* @throws IllegalArgumentException if at least one value is not provided
*/
public Object[] constructPartialIndexRowFromEntry(
Object filler, Object... values)
{
if(values.length == 0) {
throw new IllegalArgumentException(withErrorContext(
"At least one column value must be provided"));
}
if(values.length > _columns.size()) {
throw new IllegalArgumentException(withErrorContext(
"Too many column values given " + values.length +
", expected at most " + _columns.size()));
}
int valIdx = 0;
Object[] idxRow = new Object[getTable().getColumnCount()];
for(ColumnDescriptor col : _columns) {
idxRow[col.getColumnIndex()] =
((valIdx < values.length) ? values[valIdx] : filler);
++valIdx;
}
return idxRow;
}
/**
* Constructs an array of values appropriate for this index from the given
@@ -991,6 +1026,18 @@ public class IndexData {
return constructIndexRow(Collections.singletonMap(colName, value));
}

/**
* Constructs an array of values appropriate for this index from the given
* column value, which must be the first column of the index. Any missing,
* trailing index entry values will use the given filler value.
* @return the appropriate sparse array of data or {@code null} if no prefix
* list of columns for this index were provided
*/
public Object[] constructPartialIndexRow(Object filler, String colName, Object value)
{
return constructPartialIndexRow(filler, Collections.singletonMap(colName, value));
}

/**
* Constructs an array of values appropriate for this index from the given
* column values.
@@ -1012,6 +1059,42 @@ public class IndexData {
return idxRow;
}

/**
* Constructs an array of values appropriate for this index from the given
* column values, possibly only using a subset of the given values. A
* partial row can be created if one or more prefix column values are
* provided. If a prefix can be found, any missing, trailing index entry
* values will use the given filler value.
* @return the appropriate sparse array of data or {@code null} if no prefix
* list of columns for this index were provided
*/
public Object[] constructPartialIndexRow(Object filler, Map<String,?> row)
{
// see if we have at least one prefix column
int numCols = 0;
for(ColumnDescriptor col : _columns) {
if(!row.containsKey(col.getName())) {
if(numCols == 0) {
// can't do it, need at least first column
return null;
}
break;
}
++numCols;
}

// fill in the row with either the prefix values or the filler value, as
// appropriate
Object[] idxRow = new Object[getTable().getColumnCount()];
int valIdx = 0;
for(ColumnDescriptor col : _columns) {
idxRow[col.getColumnIndex()] =
((valIdx < numCols) ? row.get(col.getName()) : filler);
++valIdx;
}
return idxRow;
}

@Override
public String toString() {
ToStringBuilder sb = CustomToStringStyle.builder(this)
@@ -1266,6 +1349,7 @@ public class IndexData {
_entryBuffer.reset();
for(ColumnDescriptor col : _columns) {

Object value = values[col.getColumnIndex()];
if(ColumnImpl.isRawData(value)) {
// ignore it, we could not parse it
@@ -1273,13 +1357,17 @@ public class IndexData {
}

if(value == MIN_VALUE) {
// null is the "least" value
_entryBuffer.write(getNullEntryFlag(col.isAscending()));
// null is the "least" value (note the column "ascending" flag is
// irrelevant here because the entry bytes are _always_ interpreted
// least to greatest)
_entryBuffer.write(getNullEntryFlag(true));
continue;
}
if(value == MAX_VALUE) {
// the opposite null is the "greatest" value
_entryBuffer.write(getNullEntryFlag(!col.isAscending()));
// the opposite null is the "greatest" value (note the column
// "ascending" flag is irrelevant here because the entry bytes are
// _always_ interpreted least to greatest)
_entryBuffer.write(getNullEntryFlag(false));
continue;
}


+ 45
- 0
src/main/java/com/healthmarketscience/jackcess/impl/IndexImpl.java View File

@@ -220,6 +220,10 @@ public class IndexImpl implements Index, Comparable<IndexImpl>
return getIndexData().getColumns();
}

public int getColumnCount() {
return getIndexData().getColumnCount();
}

public CursorBuilder newCursor() {
return getTable().newCursor().setIndex(this);
}
@@ -286,6 +290,21 @@ public class IndexImpl implements Index, Comparable<IndexImpl>
return getIndexData().constructIndexRowFromEntry(values);
}
/**
* Constructs an array of values appropriate for this index from the given
* column values, possibly only providing a prefix subset of the index
* columns (at least one value must be provided). If a prefix entry is
* provided, any missing, trailing index entry values will use the given
* filler value.
* @return the appropriate sparse array of data
* @throws IllegalArgumentException if at least one value is not provided
*/
public Object[] constructPartialIndexRowFromEntry(
Object filler, Object... values)
{
return getIndexData().constructPartialIndexRowFromEntry(filler, values);
}

/**
* Constructs an array of values appropriate for this index from the given
* column value.
@@ -297,6 +316,18 @@ public class IndexImpl implements Index, Comparable<IndexImpl>
return constructIndexRow(Collections.singletonMap(colName, value));
}
/**
* Constructs an array of values appropriate for this index from the given
* column value, which must be the first column of the index. Any missing,
* trailing index entry values will use the given filler value.
* @return the appropriate sparse array of data or {@code null} if no prefix
* list of columns for this index were provided
*/
public Object[] constructPartialIndexRow(Object filler, String colName, Object value)
{
return constructPartialIndexRow(filler, Collections.singletonMap(colName, value));
}
/**
* Constructs an array of values appropriate for this index from the given
* column values.
@@ -308,6 +339,20 @@ public class IndexImpl implements Index, Comparable<IndexImpl>
return getIndexData().constructIndexRow(row);
}

/**
* Constructs an array of values appropriate for this index from the given
* column values, possibly only using a subset of the given values. A
* partial row can be created if one or more prefix column values are
* provided. If a prefix can be found, any missing, trailing index entry
* values will use the given filler value.
* @return the appropriate sparse array of data or {@code null} if no prefix
* list of columns for this index were provided
*/
public Object[] constructPartialIndexRow(Object filler, Map<String,?> row)
{
return getIndexData().constructPartialIndexRow(filler, row);
}

@Override
public String toString() {
ToStringBuilder sb = CustomToStringStyle.builder(this)

+ 7
- 6
src/main/java/com/healthmarketscience/jackcess/impl/RelationshipCreator.java View File

@@ -201,7 +201,7 @@ public class RelationshipCreator extends DBMutator

// for now, we will require the unique index on the primary table (just
// like access does). we could just create it auto-magically...
IndexImpl primaryIdx = getPrimaryUniqueIndex();
IndexImpl primaryIdx = getUniqueIndex(_primaryTable, _primaryCols);
if(primaryIdx == null) {
throw new IllegalArgumentException(withErrorContext(
"Missing unique index on primary table required to enforce integrity"));
@@ -307,16 +307,17 @@ public class RelationshipCreator extends DBMutator
private boolean isOneToOne() {
// a relationship is one to one if the two sides of the relationship have
// unique indexes on the relevant columns
if(getPrimaryUniqueIndex() == null) {
if(getUniqueIndex(_primaryTable, _primaryCols) == null) {
return false;
}
IndexImpl idx = _secondaryTable.findIndexForColumns(
getColumnNames(_secondaryCols), true);
IndexImpl idx = getUniqueIndex(_secondaryTable, _secondaryCols);
return (idx != null);
}

private IndexImpl getPrimaryUniqueIndex() {
return _primaryTable.findIndexForColumns(getColumnNames(_primaryCols), true);
private static IndexImpl getUniqueIndex(
TableImpl table, List<ColumnImpl> cols) {
return table.findIndexForColumns(getColumnNames(cols),
TableImpl.IndexFeature.EXACT_UNIQUE_ONLY);
}

private static String getTableErrorContext(

+ 26
- 7
src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java View File

@@ -86,6 +86,10 @@ public class TableImpl implements Table
*/
public static final byte TYPE_USER = 0x4e;

public enum IndexFeature {
EXACT_MATCH, EXACT_UNIQUE_ONLY, ANY_MATCH;
}

/** comparator which sorts variable length columns based on their index into
the variable length offset table */
private static final Comparator<ColumnImpl> VAR_LEN_COLUMN_COMPARATOR =
@@ -496,32 +500,47 @@ public class TableImpl implements Table
}

public IndexImpl findIndexForColumns(Collection<String> searchColumns,
boolean uniqueOnly) {
IndexFeature feature) {

IndexImpl partialIndex = null;
for(IndexImpl index : _indexes) {
Collection<? extends Index.Column> indexColumns = index.getColumns();
if(indexColumns.size() != searchColumns.size()) {
if(indexColumns.size() < searchColumns.size()) {
continue;
}
boolean exactMatch = (indexColumns.size() == searchColumns.size());

Iterator<String> sIter = searchColumns.iterator();
Iterator<? extends Index.Column> iIter = indexColumns.iterator();
boolean matches = true;
boolean searchMatches = true;
while(sIter.hasNext()) {
String sColName = sIter.next();
String iColName = iIter.next().getName();
if((sColName != iColName) &&
((sColName == null) || !sColName.equalsIgnoreCase(iColName))) {
matches = false;
searchMatches = false;
break;
}
}

if(matches && (!uniqueOnly || index.isUnique())) {
return index;
if(searchMatches) {
if(exactMatch && ((feature != IndexFeature.EXACT_UNIQUE_ONLY) ||
index.isUnique())) {
return index;
}

if(!exactMatch && (feature == IndexFeature.ANY_MATCH) &&
((partialIndex == null) ||
(indexColumns.size() < partialIndex.getColumnCount()))) {
// this is a better partial index match
partialIndex = index;
}
}
}

return null;
return partialIndex;
}
List<ColumnImpl> getAutoNumberColumns() {

+ 1
- 1
src/main/java/com/healthmarketscience/jackcess/impl/TableUpdater.java View File

@@ -295,7 +295,7 @@ public class TableUpdater extends TableMutator
return false;
}

if(idx1.getColumns().size() != idx2.getColumns().size()) {
if(idx1.getColumns().size() != idx2.getColumnCount()) {
return false;
}

+ 267
- 0
src/test/java/com/healthmarketscience/jackcess/CursorTest.java View File

@@ -30,6 +30,7 @@ import com.healthmarketscience.jackcess.impl.ColumnImpl;
import com.healthmarketscience.jackcess.impl.JetFormatTest;
import static com.healthmarketscience.jackcess.impl.JetFormatTest.*;
import com.healthmarketscience.jackcess.impl.RowIdImpl;
import com.healthmarketscience.jackcess.impl.TableImpl;
import com.healthmarketscience.jackcess.util.CaseInsensitiveColumnMatcher;
import com.healthmarketscience.jackcess.util.ColumnMatcher;
import com.healthmarketscience.jackcess.util.RowFilterTest;
@@ -1332,5 +1333,271 @@ public class CursorTest extends TestCase {
}
}

public void testPartialIndexFind() throws Exception
{
for (final FileFormat fileFormat : JetFormatTest.SUPPORTED_FILEFORMATS) {
Database db = createMem(fileFormat);

TableImpl t = (TableImpl)new TableBuilder("Test")
.addColumn(new ColumnBuilder("id", DataType.LONG))
.addColumn(new ColumnBuilder("data1", DataType.TEXT))
.addColumn(new ColumnBuilder("num2", DataType.LONG))
.addColumn(new ColumnBuilder("key3", DataType.TEXT))
.addColumn(new ColumnBuilder("value", DataType.TEXT))
.addIndex(new IndexBuilder("idx3").addColumns("data1", "num2", "key3"))
.toTable(db);

Index idx = t.findIndexForColumns(Arrays.asList("data1"),
TableImpl.IndexFeature.ANY_MATCH);
assertEquals("idx3", idx.getName());

idx = t.findIndexForColumns(Arrays.asList("data1", "num2"),
TableImpl.IndexFeature.ANY_MATCH);
assertEquals("idx3", idx.getName());

idx = t.findIndexForColumns(Arrays.asList("data1", "num2", "key3"),
TableImpl.IndexFeature.ANY_MATCH);
assertEquals("idx3", idx.getName());

assertNull(t.findIndexForColumns(Arrays.asList("num2"),
TableImpl.IndexFeature.ANY_MATCH));
assertNull(t.findIndexForColumns(Arrays.asList("data1", "key3"),
TableImpl.IndexFeature.ANY_MATCH));
assertNull(t.findIndexForColumns(Arrays.asList("data1"),
TableImpl.IndexFeature.EXACT_MATCH));


new IndexBuilder("idx2")
.addColumns("data1", "num2")
.addToTable(t);

idx = t.findIndexForColumns(Arrays.asList("data1"),
TableImpl.IndexFeature.ANY_MATCH);
assertEquals("idx2", idx.getName());

idx = t.findIndexForColumns(Arrays.asList("data1", "num2"),
TableImpl.IndexFeature.ANY_MATCH);
assertEquals("idx2", idx.getName());

idx = t.findIndexForColumns(Arrays.asList("data1", "num2", "key3"),
TableImpl.IndexFeature.ANY_MATCH);
assertEquals("idx3", idx.getName());

assertNull(t.findIndexForColumns(Arrays.asList("num2"),
TableImpl.IndexFeature.ANY_MATCH));
assertNull(t.findIndexForColumns(Arrays.asList("data1", "key3"),
TableImpl.IndexFeature.ANY_MATCH));
assertNull(t.findIndexForColumns(Arrays.asList("data1"),
TableImpl.IndexFeature.EXACT_MATCH));


new IndexBuilder("idx1")
.addColumns("data1")
.addToTable(t);

idx = t.findIndexForColumns(Arrays.asList("data1"),
TableImpl.IndexFeature.ANY_MATCH);
assertEquals("idx1", idx.getName());

idx = t.findIndexForColumns(Arrays.asList("data1", "num2"),
TableImpl.IndexFeature.ANY_MATCH);
assertEquals("idx2", idx.getName());

idx = t.findIndexForColumns(Arrays.asList("data1", "num2", "key3"),
TableImpl.IndexFeature.ANY_MATCH);
assertEquals("idx3", idx.getName());

assertNull(t.findIndexForColumns(Arrays.asList("num2"),
TableImpl.IndexFeature.ANY_MATCH));
assertNull(t.findIndexForColumns(Arrays.asList("data1", "key3"),
TableImpl.IndexFeature.ANY_MATCH));

db.close();
}
}

public void testPartialIndexLookup() throws Exception
{
for (final FileFormat fileFormat : JetFormatTest.SUPPORTED_FILEFORMATS) {
Database db = createMem(fileFormat);

TableImpl t = (TableImpl)new TableBuilder("Test")
.addColumn(new ColumnBuilder("id", DataType.LONG))
.addColumn(new ColumnBuilder("data1", DataType.TEXT))
.addColumn(new ColumnBuilder("num2", DataType.LONG))
.addColumn(new ColumnBuilder("key3", DataType.TEXT))
.addColumn(new ColumnBuilder("value", DataType.TEXT))
.addIndex(new IndexBuilder("idx3")
.addColumns(true, "data1")
.addColumns(false, "num2")
.addColumns(true, "key3")
)
.toTable(db);

int id = 1;
for(String str : Arrays.asList("A", "B", "C", "D")) {
for(int i = 4; i >= 0; --i) {
// for(int i = 0; i < 5; ++i) {
for(int j = 1; j < 3; ++j) {
t.addRow(id, str, i, "K" + j, "value" + id);
++id;
}
}
}

Index idx = t.getIndex("idx3");
doPartialIndexLookup(idx);

idx = new IndexBuilder("idx2")
.addColumns(true, "data1")
.addColumns(false, "num2")
.addToTable(t);
doPartialIndexLookup(idx);

idx = new IndexBuilder("idx1")
.addColumns(true, "data1")
.addToTable(t);
doPartialIndexLookup(idx);

db.close();
}
}

private static void doPartialIndexLookup(Index idx) throws Exception
{
int colCount = idx.getColumnCount();
IndexCursor c = new CursorBuilder(idx.getTable()).setIndex(idx).toIndexCursor();

doFindFirstByEntry(c, 21, "C");
doFindFirstByEntry(c, null, "Z");

if(colCount > 1) {
doFindFirstByEntry(c, 23, "C", 3);
doFindFirstByEntry(c, null, "C", 20);
}

if(colCount > 2) {
doFindFirstByEntry(c, 27, "C", 1, "K1");
doFindFirstByEntry(c, null, "C", 4, "K3");
}
try {
if(colCount > 2) {
c.findFirstRowByEntry("C", 4, "K1", 14);
} else if(colCount > 1) {
c.findFirstRowByEntry("C", 4, "K1");
} else {
c.findFirstRowByEntry("C", 4);
}
fail("IllegalArgumentException should have been thrown");
} catch(IllegalArgumentException expected) {
// scucess
}

doFindByEntryRange(c, 11, 20, "B");
doFindByEntry(c, new int[]{}, "Z");

if(colCount > 1) {
doFindByEntryRange(c, 13, 14, "B", 3);
doFindByEntry(c, new int[]{}, "B", 20);
}

if(colCount > 2) {
doFindByEntryRange(c, 14, 14, "B", 3, "K2");
doFindByEntry(c, new int[]{}, "B", 3, "K3");
}

doFindByRow(idx, 13,
"data1", "B", "value", "value13");
doFindByRow(idx, 13,
"data1", "B", "key3", "K1", "value", "value13");
doFindByRow(idx, 13,
"data1", "B", "num2", 3, "key3", "K1", "value", "value13");
doFindByRow(idx, 13,
"num2", 3, "value", "value13");
doFindByRow(idx, 13,
"value", "value13");
doFindByRow(idx, null,
"data1", "B", "num2", 5, "key3", "K1", "value", "value13");
doFindByRow(idx, null,
"data1", "B", "value", "value4");

Column col = idx.getTable().getColumn("data1");
doFindValue(idx, 21, col, "C");
doFindValue(idx, null, col, "Z");
col = idx.getTable().getColumn("value");
doFindValue(idx, 21, col, "value21");
doFindValue(idx, null, col, "valueZ");
}

private static void doFindFirstByEntry(IndexCursor c, Integer expectedId,
Object... entry)
throws Exception
{
if(expectedId != null) {
assertTrue(c.findFirstRowByEntry(entry));
assertEquals(expectedId, c.getCurrentRow().get("id"));
} else {
assertFalse(c.findFirstRowByEntry(entry));
}
}
private static void doFindByEntryRange(IndexCursor c, int start, int end,
Object... entry)
{
List<Integer> expectedIds = new ArrayList<Integer>();
for(int i = start; i <= end; ++i) {
expectedIds.add(i);
}
doFindByEntry(c, expectedIds, entry);
}
private static void doFindByEntry(IndexCursor c, int[] ids,
Object... entry)
{
List<Integer> expectedIds = new ArrayList<Integer>();
for(int id : ids) {
expectedIds.add(id);
}
doFindByEntry(c, expectedIds, entry);
}
private static void doFindByEntry(IndexCursor c, List<Integer> expectedIds,
Object... entry)
{
List<Integer> foundIds = new ArrayList<Integer>();
for(Row row : c.newEntryIterable(entry)) {
foundIds.add((Integer)row.get("id"));
}
assertEquals(expectedIds, foundIds);
}

private static void doFindByRow(Index idx, Integer id, Object... rowPairs)
throws Exception
{
Map<String,Object> map = createExpectedRow(
rowPairs);
Row r = CursorBuilder.findRow(idx, map);
if(id != null) {
assertEquals(id, r.get("id"));
} else {
assertNull(r);
}
}

private static void doFindValue(Index idx, Integer id,
Column columnPattern, Object valuePattern)
throws Exception
{
Object value = CursorBuilder.findValue(
idx, idx.getTable().getColumn("id"), columnPattern, valuePattern);
if(id != null) {
assertEquals(id, value);
} else {
assertNull(value);
}
}
}

Loading…
Cancel
Save