Browse Source

knock out some fixmes; add support for custom function lookup and custom bindings passed into expression evaluation

git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/branches/exprs@1151 f203690c-595d-4dc9-a70b-905162fa7fd2
tags/jackcess-2.2.0
James Ahlborn 6 years ago
parent
commit
a0e9ce63ee

+ 10
- 1
src/main/java/com/healthmarketscience/jackcess/Database.java View File

@@ -451,9 +451,18 @@ public interface Database extends Iterable<Table>, Closeable, Flushable
*/
public void setAllowAutoNumberInsert(Boolean allowAutoNumInsert);

// FIXME, docme
/**
* Gets the current expression evaluation policy. Expression evaluation is
* currently an experimental feature, and is therefore disabled by default.
*/
public boolean isEvaluateExpressions();

/**
* Sets the current expression evaluation policy. Expression evaluation is
* currently an experimental feature, and is therefore disabled by default.
* If {@code null}, resets to the default value.
* @usage _intermediate_method_
*/
public void setEvaluateExpressions(Boolean evaluateExpressions);

/**

+ 9
- 3
src/main/java/com/healthmarketscience/jackcess/expr/EvalConfig.java View File

@@ -16,17 +16,23 @@ limitations under the License.

package com.healthmarketscience.jackcess.expr;

import javax.script.Bindings;

/**
*
* @author James Ahlborn
*/
public interface EvalConfig
public interface EvalConfig
{
public TemporalConfig getTemporalConfig();

public void setTemporalConfig(TemporalConfig temporal);

public void putCustomExpressionFunction(Function func);
public FunctionLookup getFunctionLookup();

public void setFunctionLookup(FunctionLookup lookup);

public Bindings getBindings();

public Function getCustomExpressionFunction(String name);
public void setBindings(Bindings bindings);
}

+ 8
- 1
src/main/java/com/healthmarketscience/jackcess/expr/EvalContext.java View File

@@ -17,12 +17,13 @@ limitations under the License.
package com.healthmarketscience.jackcess.expr;

import java.text.SimpleDateFormat;
import javax.script.Bindings;

/**
*
* @author James Ahlborn
*/
public interface EvalContext
public interface EvalContext
{
public TemporalConfig getTemporalConfig();

@@ -35,4 +36,10 @@ public interface EvalContext
public Value getThisColumnValue();

public Value getIdentifierValue(Identifier identifier);

public Bindings getBindings();

public Object get(String key);

public void put(String key, Object value);
}

+ 26
- 0
src/main/java/com/healthmarketscience/jackcess/expr/FunctionLookup.java View File

@@ -0,0 +1,26 @@
/*
Copyright (c) 2018 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.expr;

/**
*
* @author James Ahlborn
*/
public interface FunctionLookup
{
public Function getFunction(String name);
}

+ 13
- 0
src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java View File

@@ -23,6 +23,7 @@ import java.util.Collection;
import java.util.Date;
import java.util.EnumMap;
import java.util.Map;
import javax.script.Bindings;

import com.healthmarketscience.jackcess.DataType;
import com.healthmarketscience.jackcess.JackcessException;
@@ -97,6 +98,18 @@ public abstract class BaseEvalContext implements EvalContext
throw new UnsupportedOperationException();
}

public Bindings getBindings() {
return _dbCtx.getBindings();
}

public Object get(String key) {
return _dbCtx.getBindings().get(key);
}

public void put(String key, Object value) {
_dbCtx.getBindings().put(key, value);
}

public Object eval() throws IOException {
try {
return _expr.eval(this);

+ 19
- 8
src/main/java/com/healthmarketscience/jackcess/impl/DBEvalContext.java View File

@@ -18,9 +18,12 @@ package com.healthmarketscience.jackcess.impl;

import java.text.SimpleDateFormat;
import java.util.Map;
import javax.script.Bindings;
import javax.script.SimpleBindings;

import com.healthmarketscience.jackcess.expr.EvalConfig;
import com.healthmarketscience.jackcess.expr.Function;
import com.healthmarketscience.jackcess.expr.FunctionLookup;
import com.healthmarketscience.jackcess.expr.TemporalConfig;
import com.healthmarketscience.jackcess.impl.expr.DefaultFunctions;
import com.healthmarketscience.jackcess.impl.expr.Expressionator;
@@ -35,11 +38,13 @@ public class DBEvalContext implements Expressionator.ParseContext, EvalConfig
private static final int MAX_CACHE_SIZE = 10;

private final DatabaseImpl _db;
private FunctionLookup _funcs = DefaultFunctions.LOOKUP;
private Map<String,SimpleDateFormat> _sdfs;
private TemporalConfig _temporal;
private final RandomContext _rndCtx = new RandomContext();
private Bindings _bindings = new SimpleBindings();

public DBEvalContext(DatabaseImpl db)
public DBEvalContext(DatabaseImpl db)
{
_db = db;
}
@@ -56,13 +61,20 @@ public class DBEvalContext implements Expressionator.ParseContext, EvalConfig
_temporal = temporal;
}

public void putCustomExpressionFunction(Function func) {
// FIXME writeme
public FunctionLookup getFunctionLookup() {
return _funcs;
}

public Function getCustomExpressionFunction(String name) {
// FIXME writeme
return null;
public void setFunctionLookup(FunctionLookup lookup) {
_funcs = lookup;
}

public Bindings getBindings() {
return _bindings;
}

public void setBindings(Bindings bindings) {
_bindings = bindings;
}

public SimpleDateFormat createDateFormat(String formatStr) {
@@ -82,7 +94,6 @@ public class DBEvalContext implements Expressionator.ParseContext, EvalConfig
}

public Function getExpressionFunction(String name) {
// FIXME, support custom function context?
return DefaultFunctions.getFunction(name);
return _funcs.getFunction(name);
}
}

+ 21
- 14
src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java View File

@@ -24,6 +24,7 @@ import java.util.Map;
import com.healthmarketscience.jackcess.expr.EvalContext;
import com.healthmarketscience.jackcess.expr.EvalException;
import com.healthmarketscience.jackcess.expr.Function;
import com.healthmarketscience.jackcess.expr.FunctionLookup;
import com.healthmarketscience.jackcess.expr.Value;
import com.healthmarketscience.jackcess.impl.DatabaseImpl;

@@ -31,13 +32,13 @@ import com.healthmarketscience.jackcess.impl.DatabaseImpl;
*
* @author James Ahlborn
*/
public class DefaultFunctions
public class DefaultFunctions
{
private static final Map<String,Function> FUNCS =
private static final Map<String,Function> FUNCS =
new HashMap<String,Function>();

private static final char NON_VAR_SUFFIX = '$';
static {
// load all default functions
DefaultTextFunctions.init();
@@ -45,7 +46,13 @@ public class DefaultFunctions
DefaultDateFunctions.init();
DefaultFinancialFunctions.init();
}

public static final FunctionLookup LOOKUP = new FunctionLookup() {
public Function getFunction(String name) {
return DefaultFunctions.getFunction(name);
}
};

private DefaultFunctions() {}

public static Function getFunction(String name) {
@@ -93,7 +100,7 @@ public class DefaultFunctions
paramStr.substring(1, paramStr.length() - 1) + ")}";
return new IllegalStateException(msg, t);
}
@Override
public String toString() {
return getName() + "()";
@@ -197,7 +204,7 @@ public class DefaultFunctions
}
}

protected abstract Value eval3(EvalContext ctx,
protected abstract Value eval3(EvalContext ctx,
Value param1, Value param2, Value param3);
}

@@ -257,10 +264,10 @@ public class DefaultFunctions
}
}

public static final Function IIF = registerFunc(new Func3("IIf") {
@Override
protected Value eval3(EvalContext ctx,
protected Value eval3(EvalContext ctx,
Value param1, Value param2, Value param3) {
// null is false
return ((!param1.isNull() && param1.getAsBoolean()) ? param2 : param3);
@@ -307,7 +314,7 @@ public class DefaultFunctions
return params[idx];
}
});
public static final Function SWITCH = registerFunc(new FuncVar("Switch") {
@Override
protected Value evalVar(EvalContext ctx, Value[] params) {
@@ -322,7 +329,7 @@ public class DefaultFunctions
return BuiltinOperators.NULL_VAL;
}
});
public static final Function OCT = registerStringFunc(new Func1NullIsNull("Oct") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
@@ -334,7 +341,7 @@ public class DefaultFunctions
return BuiltinOperators.toValue(Integer.toOctalString(lv));
}
});
public static final Function CBOOL = registerFunc(new Func1("CBool") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
@@ -480,7 +487,7 @@ public class DefaultFunctions
case BIG_DEC:
// vbDecimal
vType = 14;
break;
break;
default:
throw new EvalException("Unknown type " + type);
}
@@ -513,7 +520,7 @@ public class DefaultFunctions
break;
case BIG_DEC:
tName = "Decimal";
break;
break;
default:
throw new EvalException("Unknown type " + type);
}
@@ -521,7 +528,7 @@ public class DefaultFunctions
}
});


// https://www.techonthenet.com/access/functions/
// https://support.office.com/en-us/article/Access-Functions-by-category-b8b136c3-2716-4d39-94a2-658ce330ed83

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

@@ -26,7 +26,7 @@ import java.util.Random;
*
* @author James Ahlborn
*/
public class RandomContext
public class RandomContext
{
private Source _defRnd;
private Map<Integer,Source> _rnds;
@@ -34,7 +34,7 @@ public class RandomContext
// returned yet
private float _lastVal = 1.953125E-02f;

public RandomContext()
public RandomContext()
{
}

@@ -85,7 +85,7 @@ public class RandomContext
}

private static Random createRandom(long seed) {
// FIXME, support SecureRandom?
// TODO, support SecureRandom?
return new Random(seed);
}


+ 33
- 18
src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java View File

@@ -19,6 +19,8 @@ package com.healthmarketscience.jackcess.impl.expr;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.script.Bindings;
import javax.script.SimpleBindings;

import com.healthmarketscience.jackcess.DatabaseBuilder;
import com.healthmarketscience.jackcess.TestUtil;
@@ -34,7 +36,7 @@ import junit.framework.TestCase;
*
* @author James Ahlborn
*/
public class ExpressionatorTest extends TestCase
public class ExpressionatorTest extends TestCase
{
private static final double[] DBLS = {
-10.3d,-9.0d,-8.234d,-7.11111d,-6.99999d,-5.5d,-4.0d,-3.4159265d,-2.84d,
@@ -49,7 +51,7 @@ public class ExpressionatorTest extends TestCase
public void testParseSimpleExprs() throws Exception
{
validateExpr("\"A\"", "<ELiteralValue>{\"A\"}");
validateExpr("13", "<ELiteralValue>{13}");

validateExpr("-42", "<EUnaryOp>{- <ELiteralValue>{42}}");
@@ -91,7 +93,7 @@ public class ExpressionatorTest extends TestCase
private static void doTestSimpleBinOp(String opName, String... ops) throws Exception
{
for(String op : ops) {
validateExpr("\"A\" " + op + " \"B\"",
validateExpr("\"A\" " + op + " \"B\"",
"<" + opName + ">{<ELiteralValue>{\"A\"} " + op +
" <ELiteralValue>{\"B\"}}");
}
@@ -99,37 +101,37 @@ public class ExpressionatorTest extends TestCase

public void testOrderOfOperations() throws Exception
{
validateExpr("\"A\" Eqv \"B\"",
validateExpr("\"A\" Eqv \"B\"",
"<ELogicalOp>{<ELiteralValue>{\"A\"} Eqv <ELiteralValue>{\"B\"}}");

validateExpr("\"A\" Eqv \"B\" Xor \"C\"",
validateExpr("\"A\" Eqv \"B\" Xor \"C\"",
"<ELogicalOp>{<ELiteralValue>{\"A\"} Eqv <ELogicalOp>{<ELiteralValue>{\"B\"} Xor <ELiteralValue>{\"C\"}}}");

validateExpr("\"A\" Eqv \"B\" Xor \"C\" Or \"D\"",
validateExpr("\"A\" Eqv \"B\" Xor \"C\" Or \"D\"",
"<ELogicalOp>{<ELiteralValue>{\"A\"} Eqv <ELogicalOp>{<ELiteralValue>{\"B\"} Xor <ELogicalOp>{<ELiteralValue>{\"C\"} Or <ELiteralValue>{\"D\"}}}}");

validateExpr("\"A\" Eqv \"B\" Xor \"C\" Or \"D\" And \"E\"",
validateExpr("\"A\" Eqv \"B\" Xor \"C\" Or \"D\" And \"E\"",
"<ELogicalOp>{<ELiteralValue>{\"A\"} Eqv <ELogicalOp>{<ELiteralValue>{\"B\"} Xor <ELogicalOp>{<ELiteralValue>{\"C\"} Or <ELogicalOp>{<ELiteralValue>{\"D\"} And <ELiteralValue>{\"E\"}}}}}");

validateExpr("\"A\" Or \"B\" Or \"C\"",
validateExpr("\"A\" Or \"B\" Or \"C\"",
"<ELogicalOp>{<ELogicalOp>{<ELiteralValue>{\"A\"} Or <ELiteralValue>{\"B\"}} Or <ELiteralValue>{\"C\"}}");

validateExpr("\"A\" & \"B\" Is Null",
validateExpr("\"A\" & \"B\" Is Null",
"<ENullOp>{<EBinaryOp>{<ELiteralValue>{\"A\"} & <ELiteralValue>{\"B\"}} Is Null}");

validateExpr("\"A\" Or \"B\" Is Null",
validateExpr("\"A\" Or \"B\" Is Null",
"<ELogicalOp>{<ELiteralValue>{\"A\"} Or <ENullOp>{<ELiteralValue>{\"B\"} Is Null}}");

validateExpr("Not \"A\" & \"B\"",
validateExpr("Not \"A\" & \"B\"",
"<EUnaryOp>{Not <EBinaryOp>{<ELiteralValue>{\"A\"} & <ELiteralValue>{\"B\"}}}");

validateExpr("Not \"A\" Or \"B\"",
validateExpr("Not \"A\" Or \"B\"",
"<ELogicalOp>{<EUnaryOp>{Not <ELiteralValue>{\"A\"}} Or <ELiteralValue>{\"B\"}}");

validateExpr("\"A\" + \"B\" Not Between 37 - 15 And 52 / 4",
validateExpr("\"A\" + \"B\" Not Between 37 - 15 And 52 / 4",
"<EBetweenOp>{<EBinaryOp>{<ELiteralValue>{\"A\"} + <ELiteralValue>{\"B\"}} Not Between <EBinaryOp>{<ELiteralValue>{37} - <ELiteralValue>{15}} And <EBinaryOp>{<ELiteralValue>{52} / <ELiteralValue>{4}}}");

validateExpr("\"A\" + (\"B\" Not Between 37 - 15 And 52) / 4",
validateExpr("\"A\" + (\"B\" Not Between 37 - 15 And 52) / 4",
"<EBinaryOp>{<ELiteralValue>{\"A\"} + <EBinaryOp>{<EParen>{(<EBetweenOp>{<ELiteralValue>{\"B\"} Not Between <EBinaryOp>{<ELiteralValue>{37} - <ELiteralValue>{15}} And <ELiteralValue>{52}})} / <ELiteralValue>{4}}}");


@@ -324,13 +326,13 @@ public class ExpressionatorTest extends TestCase
validateExpr(exprStr, debugStr, exprStr);
}

private static void validateExpr(String exprStr, String debugStr,
private static void validateExpr(String exprStr, String debugStr,
String cleanStr) {
Expression expr = Expressionator.parse(
Expressionator.Type.FIELD_VALIDATOR, exprStr, null);
String foundDebugStr = expr.toDebugString();
if(foundDebugStr.startsWith("<EImplicitCompOp>")) {
assertEquals("<EImplicitCompOp>{<EThisValue>{<THIS_COL>} = " +
assertEquals("<EImplicitCompOp>{<EThisValue>{<THIS_COL>} = " +
debugStr + "}", foundDebugStr);
} else {
assertEquals(debugStr, foundDebugStr);
@@ -365,7 +367,7 @@ public class ExpressionatorTest extends TestCase
return new BigDecimal(d).setScale(0, BuiltinOperators.ROUND_MODE)
.intValueExact();
}
private static final class TestParseContext implements Expressionator.ParseContext
{
public TemporalConfig getTemporalConfig() {
@@ -386,6 +388,7 @@ public class ExpressionatorTest extends TestCase
{
private final Value _thisVal;
private final RandomContext _rndCtx = new RandomContext();
private final Bindings _bindings = new SimpleBindings();

private TestEvalContext(Value thisVal) {
_thisVal = thisVal;
@@ -418,6 +421,18 @@ public class ExpressionatorTest extends TestCase

public float getRandom(Integer seed) {
return _rndCtx.getRandom(seed);
}
}

public Bindings getBindings() {
return _bindings;
}

public Object get(String key) {
return _bindings.get(key);
}

public void put(String key, Object value) {
_bindings.put(key, value);
}
}
}

Loading…
Cancel
Save