git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/branches/exprs@1151 f203690c-595d-4dc9-a70b-905162fa7fd2tags/jackcess-2.2.0
@@ -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); | |||
/** |
@@ -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); | |||
} |
@@ -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); | |||
} |
@@ -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); | |||
} |
@@ -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); |
@@ -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); | |||
} | |||
} |
@@ -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 |
@@ -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); | |||
} | |||
@@ -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); | |||
} | |||
} | |||
} |