Browse Source

Add support for using Predicates to match values in Cursors. Add PatternColumnPredicate for searching with various pattern syntaxes

git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@1328 f203690c-595d-4dc9-a70b-905162fa7fd2
tags/jackcess-3.5.0
James Ahlborn 3 years ago
parent
commit
9e0b807683

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

@@ -18,6 +18,11 @@
<action dev="jahlborn" type="update">
Change the default DateTimeType to LOCAL_DATE_TIME.
</action>
<action dev="jahlborn" type="add">
Add support for Predicate value patterns in cursor find methods. Add
PatternColumnPredicate for creating Predicate instances which can
match values using various pattern syntaxes.
</action>
</release>
<release version="3.0.1" date="2019-04-13">
<action dev="jahlborn" type="update">

+ 25
- 7
src/main/java/com/healthmarketscience/jackcess/Cursor.java View File

@@ -253,7 +253,10 @@ public interface Cursor extends Iterable<Row>
* @param columnPattern column from the table for this cursor which is being
* matched by the valuePattern
* @param valuePattern value which is equal to the corresponding value in
* the matched row
* the matched row. If this object is an instance of
* {@link java.util.function.Predicate}, it will be
* applied to the potential row value instead
* (overriding any configured ColumnMatcher)
* @return {@code true} if a valid row was found with the given value,
* {@code false} if no row was found
*/
@@ -269,7 +272,10 @@ public interface Cursor extends Iterable<Row>
* @param columnPattern column from the table for this cursor which is being
* matched by the valuePattern
* @param valuePattern value which is equal to the corresponding value in
* the matched row
* the matched row. If this object is an instance of
* {@link java.util.function.Predicate}, it will be
* applied to the potential row value instead
* (overriding any configured ColumnMatcher)
* @return {@code true} if a valid row was found with the given value,
* {@code false} if no row was found
*/
@@ -286,7 +292,10 @@ public interface Cursor extends Iterable<Row>
* the Table (you cannot use it to find successive matches).
*
* @param rowPattern column names and values which must be equal to the
* corresponding values in the matched row
* corresponding values in the matched row. If a value is
* an instance of {@link java.util.function.Predicate}, it
* will be applied to the potential row value instead
* (overriding any configured ColumnMatcher)
* @return {@code true} if a valid row was found with the given values,
* {@code false} if no row was found
*/
@@ -299,7 +308,10 @@ public interface Cursor extends Iterable<Row>
* is restored to its previous state.
*
* @param rowPattern column names and values which must be equal to the
* corresponding values in the matched row
* corresponding values in the matched row. If a value is
* an instance of {@link java.util.function.Predicate}, it
* will be applied to the potential row value instead
* (overriding any configured ColumnMatcher)
* @return {@code true} if a valid row was found with the given values,
* {@code false} if no row was found
*/
@@ -309,8 +321,11 @@ public interface Cursor extends Iterable<Row>
* Returns {@code true} if the current row matches the given pattern.
* @param columnPattern column from the table for this cursor which is being
* matched by the valuePattern
* @param valuePattern value which is tested for equality with the
* corresponding value in the current row
* @param valuePattern value which is equal to the corresponding value in
* the matched row. If this object is an instance of
* {@link java.util.function.Predicate}, it will be
* applied to the potential row value instead
* (overriding any configured ColumnMatcher)
*/
public boolean currentRowMatches(Column columnPattern, Object valuePattern)
throws IOException;
@@ -318,7 +333,10 @@ public interface Cursor extends Iterable<Row>
/**
* Returns {@code true} if the current row matches the given pattern.
* @param rowPattern column names and values which must be equal to the
* corresponding values in the current row
* corresponding values in the matched row. If a value is
* an instance of {@link java.util.function.Predicate}, it
* will be applied to the potential row value instead
* (overriding any configured ColumnMatcher)
*/
public boolean currentRowMatches(Map<String,?> rowPattern) throws IOException;


+ 79
- 65
src/main/java/com/healthmarketscience/jackcess/impl/CursorImpl.java View File

@@ -22,6 +22,7 @@ import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.function.Predicate;

import com.healthmarketscience.jackcess.Column;
import com.healthmarketscience.jackcess.Cursor;
@@ -54,14 +55,14 @@ import org.apache.commons.logging.LogFactory;
* @author James Ahlborn
*/
public abstract class CursorImpl implements Cursor
{
private static final Log LOG = LogFactory.getLog(CursorImpl.class);
{
private static final Log LOG = LogFactory.getLog(CursorImpl.class);

/** boolean value indicating forward movement */
public static final boolean MOVE_FORWARD = true;
/** boolean value indicating reverse movement */
public static final boolean MOVE_REVERSE = false;
/** identifier for this cursor */
private final IdImpl _id;
/** owning table */
@@ -101,7 +102,7 @@ public abstract class CursorImpl implements Cursor
public RowState getRowState() {
return _rowState;
}
@Override
public IdImpl getId() {
return _id;
@@ -128,7 +129,7 @@ public abstract class CursorImpl implements Cursor
@Override
public void setErrorHandler(ErrorHandler newErrorHandler) {
_rowState.setErrorHandler(newErrorHandler);
}
}

@Override
public ColumnMatcher getColumnMatcher() {
@@ -173,14 +174,14 @@ public abstract class CursorImpl implements Cursor
restorePosition(savepoint.getCurrentPosition(),
savepoint.getPreviousPosition());
}
/**
* Returns the first row id (exclusive) as defined by this cursor.
*/
protected PositionImpl getFirstPosition() {
return _firstPos;
}
/**
* Returns the last row id (exclusive) as defined by this cursor.
*/
@@ -191,13 +192,13 @@ public abstract class CursorImpl implements Cursor
@Override
public void reset() {
beforeFirst();
}
}

@Override
public void beforeFirst() {
reset(MOVE_FORWARD);
}
@Override
public void afterLast() {
reset(MOVE_REVERSE);
@@ -207,7 +208,7 @@ public abstract class CursorImpl implements Cursor
public boolean isBeforeFirst() throws IOException {
return isAtBeginning(MOVE_FORWARD);
}
@Override
public boolean isAfterLast() throws IOException {
return isAtBeginning(MOVE_REVERSE);
@@ -219,7 +220,7 @@ public abstract class CursorImpl implements Cursor
}
return false;
}
@Override
public boolean isCurrentRowDeleted() throws IOException
{
@@ -228,7 +229,7 @@ public abstract class CursorImpl implements Cursor
TableImpl.positionAtRowData(_rowState, _curPos.getRowId());
return _rowState.isDeleted();
}
/**
* Resets this cursor for traversing the given direction.
*/
@@ -236,8 +237,8 @@ public abstract class CursorImpl implements Cursor
_curPos = getDirHandler(moveForward).getBeginningPosition();
_prevPos = _curPos;
_rowState.reset();
}
}
@Override
public Iterator<Row> iterator() {
return new RowIterator(null, true, MOVE_FORWARD);
@@ -259,8 +260,8 @@ public abstract class CursorImpl implements Cursor
Map.Entry<Column,Object> matchPattern = (Map.Entry<Column,Object>)
iterBuilder.getMatchPattern();
return new ColumnMatchIterator(
iterBuilder.getColumnNames(), (ColumnImpl)matchPattern.getKey(),
matchPattern.getValue(), iterBuilder.isReset(),
iterBuilder.getColumnNames(), (ColumnImpl)matchPattern.getKey(),
matchPattern.getValue(), iterBuilder.isReset(),
iterBuilder.isForward(), iterBuilder.getColumnMatcher());
}
case ROW_MATCH: {
@@ -268,14 +269,14 @@ public abstract class CursorImpl implements Cursor
Map<String,?> matchPattern = (Map<String,?>)
iterBuilder.getMatchPattern();
return new RowMatchIterator(
iterBuilder.getColumnNames(), matchPattern,iterBuilder.isReset(),
iterBuilder.getColumnNames(), matchPattern,iterBuilder.isReset(),
iterBuilder.isForward(), iterBuilder.getColumnMatcher());
}
default:
throw new RuntimeException("unknown match type " + iterBuilder.getType());
}
}
@Override
public void deleteCurrentRow() throws IOException {
_table.deleteRow(_rowState, _curPos.getRowId());
@@ -287,8 +288,8 @@ public abstract class CursorImpl implements Cursor
}

@Override
public <M extends Map<String,Object>> M updateCurrentRowFromMap(M row)
throws IOException
public <M extends Map<String,Object>> M updateCurrentRowFromMap(M row)
throws IOException
{
return _table.updateRowFromMap(_rowState, _curPos.getRowId(), row);
}
@@ -299,7 +300,7 @@ public abstract class CursorImpl implements Cursor
}

@Override
public Row getNextRow(Collection<String> columnNames)
public Row getNextRow(Collection<String> columnNames)
throws IOException
{
return getAnotherRow(columnNames, MOVE_FORWARD);
@@ -311,7 +312,7 @@ public abstract class CursorImpl implements Cursor
}

@Override
public Row getPreviousRow(Collection<String> columnNames)
public Row getPreviousRow(Collection<String> columnNames)
throws IOException
{
return getAnotherRow(columnNames, MOVE_REVERSE);
@@ -327,14 +328,14 @@ public abstract class CursorImpl implements Cursor
* {@code null} if there is not another row in the given direction.
*/
private Row getAnotherRow(Collection<String> columnNames,
boolean moveForward)
boolean moveForward)
throws IOException
{
if(moveToAnotherRow(moveForward)) {
return getCurrentRow(columnNames);
}
return null;
}
}

@Override
public boolean moveToNextRow() throws IOException
@@ -373,12 +374,12 @@ public abstract class CursorImpl implements Cursor
{
restorePosition(curPos, _curPos);
}
/**
* Restores a current and previous position for the cursor if the given
* positions are different from the current positions.
*/
protected final void restorePosition(PositionImpl curPos,
protected final void restorePosition(PositionImpl curPos,
PositionImpl prevPos)
throws IOException
{
@@ -398,7 +399,7 @@ public abstract class CursorImpl implements Cursor
_curPos = curPos;
_rowState.reset();
}
/**
* Rechecks the current position if the underlying data structures have been
* modified.
@@ -466,13 +467,13 @@ public abstract class CursorImpl implements Cursor
throws IOException
{
return findFirstRow((ColumnImpl)columnPattern, valuePattern);
}
}
public boolean findFirstRow(ColumnImpl columnPattern, Object valuePattern)
throws IOException
{
return findAnotherRow(columnPattern, valuePattern, true, MOVE_FORWARD,
_columnMatcher,
_columnMatcher,
prepareSearchInfo(columnPattern, valuePattern));
}

@@ -481,16 +482,16 @@ public abstract class CursorImpl implements Cursor
throws IOException
{
return findNextRow((ColumnImpl)columnPattern, valuePattern);
}
}
public boolean findNextRow(ColumnImpl columnPattern, Object valuePattern)
throws IOException
{
return findAnotherRow(columnPattern, valuePattern, false, MOVE_FORWARD,
_columnMatcher,
_columnMatcher,
prepareSearchInfo(columnPattern, valuePattern));
}
protected boolean findAnotherRow(ColumnImpl columnPattern, Object valuePattern,
boolean reset, boolean moveForward,
ColumnMatcher columnMatcher, Object searchInfo)
@@ -516,7 +517,7 @@ public abstract class CursorImpl implements Cursor
}
}
}
@Override
public boolean findFirstRow(Map<String,?> rowPattern) throws IOException
{
@@ -533,7 +534,7 @@ public abstract class CursorImpl implements Cursor
}

protected boolean findAnotherRow(Map<String,?> rowPattern, boolean reset,
boolean moveForward,
boolean moveForward,
ColumnMatcher columnMatcher, Object searchInfo)
throws IOException
{
@@ -544,7 +545,7 @@ public abstract class CursorImpl implements Cursor
if(reset) {
reset(moveForward);
}
found = findAnotherRowImpl(rowPattern, moveForward, columnMatcher,
found = findAnotherRowImpl(rowPattern, moveForward, columnMatcher,
searchInfo);
return found;
} finally {
@@ -570,17 +571,17 @@ public abstract class CursorImpl implements Cursor
{
return currentRowMatchesImpl(columnPattern, valuePattern, _columnMatcher);
}
protected boolean currentRowMatchesImpl(ColumnImpl columnPattern,
protected boolean currentRowMatchesImpl(ColumnImpl columnPattern,
Object valuePattern,
ColumnMatcher columnMatcher)
throws IOException
{
return columnMatcher.matches(getTable(), columnPattern.getName(),
valuePattern,
getCurrentRowValue(columnPattern));
return currentRowMatchesPattern(
columnPattern.getName(), valuePattern, columnMatcher,
getCurrentRowValue(columnPattern));
}
@Override
public boolean currentRowMatches(Map<String,?> rowPattern)
throws IOException
@@ -600,15 +601,28 @@ public abstract class CursorImpl implements Cursor

for(Map.Entry<String,Object> e : row.entrySet()) {
String columnName = e.getKey();
if(!columnMatcher.matches(getTable(), columnName,
rowPattern.get(columnName), e.getValue())) {
if(!currentRowMatchesPattern(columnName, rowPattern.get(columnName),
columnMatcher, e.getValue())) {
return false;
}
}

return true;
}

@SuppressWarnings("unchecked")
protected final boolean currentRowMatchesPattern(
String columnPattern, Object valuePattern,
ColumnMatcher columnMatcher, Object rowValue) {
// if the value pattern is a Predicate use that to test the value
if(valuePattern instanceof Predicate<?>) {
return ((Predicate<Object>)valuePattern).test(rowValue);
}
// otherwise, use the configured ColumnMatcher
return columnMatcher.matches(getTable(), columnPattern, valuePattern,
rowValue);
}

/**
* Moves to the next row (as defined by the cursor) where the given column
* has the given value. Caller manages save/restore on failure.
@@ -649,8 +663,8 @@ public abstract class CursorImpl implements Cursor
* @return {@code true} if a valid row was found with the given values,
* {@code false} if no row was found
*/
protected boolean findAnotherRowImpl(Map<String,?> rowPattern,
boolean moveForward,
protected boolean findAnotherRowImpl(Map<String,?> rowPattern,
boolean moveForward,
ColumnMatcher columnMatcher,
Object searchInfo)
throws IOException
@@ -664,7 +678,7 @@ public abstract class CursorImpl implements Cursor
}
}
return false;
}
}

/**
* Called before a search commences to allow for search specific data to be
@@ -688,8 +702,8 @@ public abstract class CursorImpl implements Cursor
* Called by findAnotherRowImpl to determine if the search should continue
* after finding a row which does not match the current pattern.
*/
protected boolean keepSearching(ColumnMatcher columnMatcher,
Object searchInfo)
protected boolean keepSearching(ColumnMatcher columnMatcher,
Object searchInfo)
throws IOException
{
return true;
@@ -779,7 +793,7 @@ public abstract class CursorImpl implements Cursor
return(_curPos.getRowId().isValid() && !isCurrentRowDeleted() &&
!isBeforeFirst() && !isAfterLast());
}
@Override
public String toString() {
return getClass().getSimpleName() + " CurPosition " + _curPos +
@@ -790,9 +804,9 @@ public abstract class CursorImpl implements Cursor
* Returns the appropriate position information for the given row (which is
* the current row and is valid).
*/
protected abstract PositionImpl getRowPosition(RowIdImpl rowId)
protected abstract PositionImpl getRowPosition(RowIdImpl rowId)
throws IOException;
/**
* Finds the next non-deleted row after the given row (as defined by this
* cursor) and returns the id of the row, where "next" may be backwards if
@@ -821,7 +835,7 @@ public abstract class CursorImpl implements Cursor
protected final ColumnMatcher _colMatcher;
protected Boolean _hasNext;
protected boolean _validRow;
protected BaseIterator(Collection<String> columnNames,
boolean reset, boolean moveForward,
ColumnMatcher columnMatcher)
@@ -850,9 +864,9 @@ public abstract class CursorImpl implements Cursor
throw new RuntimeIOException(e);
}
}
return _hasNext;
return _hasNext;
}
@Override
public Row next() {
if(!hasNext()) {
@@ -884,7 +898,7 @@ public abstract class CursorImpl implements Cursor
protected abstract boolean findNext() throws IOException;
}

/**
* Row iterator for this cursor, modifiable.
*/
@@ -911,7 +925,7 @@ public abstract class CursorImpl implements Cursor
private final ColumnImpl _columnPattern;
private final Object _valuePattern;
private final Object _searchInfo;
private ColumnMatchIterator(Collection<String> columnNames,
ColumnImpl columnPattern, Object valuePattern,
boolean reset, boolean moveForward,
@@ -938,7 +952,7 @@ public abstract class CursorImpl implements Cursor
{
private final Map<String,?> _rowPattern;
private final Object _searchInfo;
private RowMatchIterator(Collection<String> columnNames,
Map<String,?> rowPattern,
boolean reset, boolean moveForward,
@@ -967,7 +981,7 @@ public abstract class CursorImpl implements Cursor
public abstract PositionImpl getEndPosition();
}

/**
* Identifier for a cursor. Will be equal to any other cursor of the same
* type for the same table. Primarily used to check the validity of a
@@ -1014,7 +1028,7 @@ public abstract class CursorImpl implements Cursor
public final int hashCode() {
return getRowId().hashCode();
}
@Override
public final boolean equals(Object o) {
return((this == o) ||
@@ -1045,7 +1059,7 @@ public abstract class CursorImpl implements Cursor
private final PositionImpl _curPos;
private final PositionImpl _prevPos;

private SavepointImpl(IdImpl cursorId, PositionImpl curPos,
private SavepointImpl(IdImpl cursorId, PositionImpl curPos,
PositionImpl prevPos) {
_cursorId = cursorId;
_curPos = curPos;
@@ -1068,9 +1082,9 @@ public abstract class CursorImpl implements Cursor

@Override
public String toString() {
return getClass().getSimpleName() + " " + _cursorId + " CurPosition " +
return getClass().getSimpleName() + " " + _cursorId + " CurPosition " +
_curPos + ", PrevPosition " + _prevPos;
}
}
}

+ 12
- 3
src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java View File

@@ -391,7 +391,8 @@ public class Expressionator
SpecOp.NOT_BETWEEN});

private static final Set<Character> REGEX_SPEC_CHARS = new HashSet<Character>(
Arrays.asList('\\','.','%','=','+', '$','^','|','(',')','{','}','&'));
Arrays.asList('\\','.','%','=','+', '$','^','|','(',')','{','}','&',
'[',']','*','?'));
// this is a regular expression which will never match any string
private static final Pattern UNMATCHABLE_REGEX = Pattern.compile("(?!)");

@@ -1243,7 +1244,11 @@ public class Expressionator
.append("\"");
}

private static Pattern likePatternToRegex(String pattern) {
/**
* Converts an ms access like pattern to a java regex, always matching case
* insensitively.
*/
public static Pattern likePatternToRegex(String pattern) {

StringBuilder sb = new StringBuilder(pattern.length());

@@ -1289,7 +1294,7 @@ public class Expressionator
sb.append('[').append(charClass).append(']');
i += (endPos - startPos) + 1;

} else if(REGEX_SPEC_CHARS.contains(c)) {
} else if(isRegexSpecialChar(c)) {
// this char is special in regexes, so escape it
sb.append('\\').append(c);
} else {
@@ -1306,6 +1311,10 @@ public class Expressionator
}
}

public static boolean isRegexSpecialChar(char c) {
return REGEX_SPEC_CHARS.contains(c);
}

private static Value toLiteralValue(Value.Type valType, Object value) {
switch(valType) {
case STRING:

+ 4
- 7
src/main/java/com/healthmarketscience/jackcess/util/CaseInsensitiveColumnMatcher.java View File

@@ -28,18 +28,15 @@ import com.healthmarketscience.jackcess.impl.ColumnImpl;
* Concrete implementation of ColumnMatcher which tests textual columns
* case-insensitively ({@link DataType#TEXT} and {@link DataType#MEMO}), and
* all other columns using simple equality.
*
*
* @author James Ahlborn
* @usage _general_class_
*/
public class CaseInsensitiveColumnMatcher implements ColumnMatcher {

public static final CaseInsensitiveColumnMatcher INSTANCE =
public static final CaseInsensitiveColumnMatcher INSTANCE =
new CaseInsensitiveColumnMatcher();

public CaseInsensitiveColumnMatcher() {
}

@Override
public boolean matches(Table table, String columnName, Object value1,
@@ -47,7 +44,7 @@ public class CaseInsensitiveColumnMatcher implements ColumnMatcher {
{
if(!table.getColumn(columnName).getType().isTextual()) {
// use simple equality
return SimpleColumnMatcher.INSTANCE.matches(table, columnName,
return SimpleColumnMatcher.INSTANCE.matches(table, columnName,
value1, value2);
}

@@ -60,7 +57,7 @@ public class CaseInsensitiveColumnMatcher implements ColumnMatcher {
((cs1 != null) && (cs2 != null) &&
cs1.toString().equalsIgnoreCase(cs2.toString())));
} catch(IOException e) {
throw new RuntimeIOException("Could not read column " + columnName
throw new RuntimeIOException("Could not read column " + columnName
+ " value", e);
}
}

+ 127
- 0
src/main/java/com/healthmarketscience/jackcess/util/PatternColumnPredicate.java View File

@@ -0,0 +1,127 @@
/*
Copyright (c) 2020 James Ahlborn

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package com.healthmarketscience.jackcess.util;

import java.io.IOException;
import java.util.function.Predicate;
import java.util.regex.Pattern;

import com.healthmarketscience.jackcess.RuntimeIOException;
import com.healthmarketscience.jackcess.impl.ColumnImpl;
import com.healthmarketscience.jackcess.impl.expr.Expressionator;

/**
* Predicate which tests a column value against a {@link Pattern}. The static
* factory methods can be used to construct the Pattern from various forms of
* wildcard pattern syntaxes.
*
* This class can be used as a value pattern in the various Cursor search
* methods, e.g. {@link com.healthmarketscience.jackcess.Cursor#findFirstRow(com.healthmarketscience.jackcess.Column,Object)}.
*
* @author James Ahlborn
*/
public class PatternColumnPredicate implements Predicate<Object>
{
private static final int LIKE_REGEX_FLAGS = Pattern.DOTALL;
private static final int CI_LIKE_REGEX_FLAGS =
LIKE_REGEX_FLAGS | Pattern.CASE_INSENSITIVE |
Pattern.UNICODE_CASE;

private final Pattern _pattern;

public PatternColumnPredicate(Pattern pattern) {
_pattern = pattern;
}

@Override
public boolean test(Object value) {
try {
// convert column value to string
CharSequence cs = ColumnImpl.toCharSequence(value);

return _pattern.matcher(cs).matches();
} catch(IOException e) {
throw new RuntimeIOException("Could not coerece column value to string", e);
}
}

private static Pattern sqlLikeToRegex(
String value, boolean caseInsensitive)
{
StringBuilder sb = new StringBuilder(value.length());

for(int i = 0; i < value.length(); ++i) {
char c = value.charAt(i);

if(c == '%') {
sb.append(".*");
} else if(c == '_') {
sb.append('.');
} else if(c == '\\') {
if(i + 1 < value.length()) {
appendLiteralChar(sb, value.charAt(++i));
}
} else {
appendLiteralChar(sb, c);
}
}

int flags = (caseInsensitive ? CI_LIKE_REGEX_FLAGS : LIKE_REGEX_FLAGS);
return Pattern.compile(sb.toString(), flags);
}

private static void appendLiteralChar(StringBuilder sb, char c) {
if(Expressionator.isRegexSpecialChar(c)) {
sb.append('\\');
}
sb.append(c);
}

/**
* @return a PatternColumnPredicate which tests values against the given ms
* access wildcard pattern (always case insensitive)
*/
public static PatternColumnPredicate forAccessLike(String pattern) {
return new PatternColumnPredicate(Expressionator.likePatternToRegex(pattern));
}

/**
* @return a PatternColumnPredicate which tests values against the given sql
* like pattern (supports escape char '\')
*/
public static PatternColumnPredicate forSqlLike(String pattern) {
return forSqlLike(pattern, false);
}

/**
* @return a PatternColumnPredicate which tests values against the given sql
* like pattern (supports escape char '\'), optionally case
* insensitive
*/
public static PatternColumnPredicate forSqlLike(
String pattern, boolean caseInsensitive) {
return new PatternColumnPredicate(sqlLikeToRegex(pattern, caseInsensitive));
}

/**
* @return a PatternColumnPredicate which tests values against the given
* java regex pattern
*/
public static PatternColumnPredicate forJavaRegex(String pattern) {
return new PatternColumnPredicate(Pattern.compile(pattern));
}
}

+ 0
- 3
src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnMatcher.java View File

@@ -38,9 +38,6 @@ public class SimpleColumnMatcher implements ColumnMatcher {

public static final SimpleColumnMatcher INSTANCE = new SimpleColumnMatcher();

public SimpleColumnMatcher() {
}

@Override
public boolean matches(Table table, String columnName, Object value1,
Object value2)

+ 138
- 0
src/test/java/com/healthmarketscience/jackcess/util/PatternColumnPredicateTest.java View File

@@ -0,0 +1,138 @@
/*
Copyright (c) 2020 James Ahlborn

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package com.healthmarketscience.jackcess.util;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import com.healthmarketscience.jackcess.Column;
import com.healthmarketscience.jackcess.ColumnBuilder;
import com.healthmarketscience.jackcess.CursorBuilder;
import com.healthmarketscience.jackcess.DataType;
import com.healthmarketscience.jackcess.Database;
import static com.healthmarketscience.jackcess.Database.*;
import com.healthmarketscience.jackcess.IndexCursor;
import com.healthmarketscience.jackcess.Row;
import com.healthmarketscience.jackcess.Table;
import com.healthmarketscience.jackcess.TableBuilder;
import static com.healthmarketscience.jackcess.TestUtil.*;
import static com.healthmarketscience.jackcess.impl.JetFormatTest.*;
import junit.framework.TestCase;

/**
*
* @author James Ahlborn
*/
public class PatternColumnPredicateTest extends TestCase
{

public PatternColumnPredicateTest(String name) {
super(name);
}

public void testRegexPredicate() throws Exception {
for (final FileFormat fileFormat : SUPPORTED_FILEFORMATS) {
Database db = createTestDb(fileFormat);

Table t = db.getTable("Test");

assertEquals(
Arrays.asList("Foo", "some row", "aNoThEr row", "nonsense"),
findRowsByPattern(
t, PatternColumnPredicate.forJavaRegex(".*o.*")));

assertEquals(
Arrays.asList("Bar", "0102", "FOO", "BAR", "67", "bunch_13_data", "42 is the ANSWER", "[try] matching t.h+i}s"),
findRowsByPattern(
t, PatternColumnPredicate.forJavaRegex(".*o.*").negate()));

assertEquals(
Arrays.asList("Foo", "some row", "FOO", "aNoThEr row", "nonsense"),
findRowsByPattern(
t, PatternColumnPredicate.forAccessLike("*o*")));

assertEquals(
Arrays.asList("0102", "67", "bunch_13_data", "42 is the ANSWER"),
findRowsByPattern(
t, PatternColumnPredicate.forAccessLike("*##*")));

assertEquals(
Arrays.asList("42 is the ANSWER"),
findRowsByPattern(
t, PatternColumnPredicate.forAccessLike("## *")));

assertEquals(
Arrays.asList("Foo"),
findRowsByPattern(
t, PatternColumnPredicate.forSqlLike("F_o")));

assertEquals(
Arrays.asList("Foo", "FOO"),
findRowsByPattern(
t, PatternColumnPredicate.forSqlLike("F_o", true)));

assertEquals(
Arrays.asList("[try] matching t.h+i}s"),
findRowsByPattern(
t, PatternColumnPredicate.forSqlLike("[try] % t.h+i}s")));

assertEquals(
Arrays.asList("bunch_13_data"),
findRowsByPattern(
t, PatternColumnPredicate.forSqlLike("bunch\\_%\\_data")));

db.close();
}
}

private static List<String> findRowsByPattern(
Table t, Predicate<Object> pred) {
return t.getDefaultCursor().newIterable()
.setMatchPattern("data", pred)
.stream()
.map(r -> r.getString("data"))
.collect(Collectors.toList());
}

private static Database createTestDb(FileFormat fileFormat) throws Exception {
Database db = create(fileFormat);

Table table = new TableBuilder("Test")
.addColumn(new ColumnBuilder("id", DataType.LONG).setAutoNumber(true))
.addColumn(new ColumnBuilder("data", DataType.TEXT))
.setPrimaryKey("id")
.toTable(db);

table.addRow(Column.AUTO_NUMBER, "Foo");
table.addRow(Column.AUTO_NUMBER, "some row");
table.addRow(Column.AUTO_NUMBER, "Bar");
table.addRow(Column.AUTO_NUMBER, "0102");
table.addRow(Column.AUTO_NUMBER, "FOO");
table.addRow(Column.AUTO_NUMBER, "BAR");
table.addRow(Column.AUTO_NUMBER, "67");
table.addRow(Column.AUTO_NUMBER, "aNoThEr row");
table.addRow(Column.AUTO_NUMBER, "bunch_13_data");
table.addRow(Column.AUTO_NUMBER, "42 is the ANSWER");
table.addRow(Column.AUTO_NUMBER, "[try] matching t.h+i}s");
table.addRow(Column.AUTO_NUMBER, "nonsense");

return db;
}
}

Loading…
Cancel
Save