diff options
author | James Ahlborn <jtahlborn@yahoo.com> | 2016-12-31 17:37:13 +0000 |
---|---|---|
committer | James Ahlborn <jtahlborn@yahoo.com> | 2016-12-31 17:37:13 +0000 |
commit | 68c982c48f75f121112b780c829e000acc036f0c (patch) | |
tree | 865f0e7b13d7f9511b4f0a9e777bfd621383e8b1 | |
parent | 1260b3bff1855161ec5f129695cb690356d72ca8 (diff) | |
download | jackcess-68c982c48f75f121112b780c829e000acc036f0c.tar.gz jackcess-68c982c48f75f121112b780c829e000acc036f0c.zip |
change evaluation context
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/branches/exprs@1076 f203690c-595d-4dc9-a70b-905162fa7fd2
-rw-r--r-- | src/main/java/com/healthmarketscience/jackcess/expr/EvalContext.java (renamed from src/main/java/com/healthmarketscience/jackcess/expr/RowContext.java) | 8 | ||||
-rw-r--r-- | src/main/java/com/healthmarketscience/jackcess/expr/Expression.java | 4 | ||||
-rw-r--r-- | src/main/java/com/healthmarketscience/jackcess/expr/Function.java | 2 | ||||
-rw-r--r-- | src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDateValue.java | 4 | ||||
-rw-r--r-- | src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java | 45 | ||||
-rw-r--r-- | src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java | 26 | ||||
-rw-r--r-- | src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java | 209 | ||||
-rw-r--r-- | src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java | 4 |
8 files changed, 175 insertions, 127 deletions
diff --git a/src/main/java/com/healthmarketscience/jackcess/expr/RowContext.java b/src/main/java/com/healthmarketscience/jackcess/expr/EvalContext.java index cc60f4d..3ec3a88 100644 --- a/src/main/java/com/healthmarketscience/jackcess/expr/RowContext.java +++ b/src/main/java/com/healthmarketscience/jackcess/expr/EvalContext.java @@ -16,12 +16,18 @@ limitations under the License. package com.healthmarketscience.jackcess.expr; +import java.text.SimpleDateFormat; + /** * * @author James Ahlborn */ -public interface RowContext +public interface EvalContext { + public Value.Type getResultType(); + + public SimpleDateFormat createDateFormat(String formatStr); + public Value getThisColumnValue(); public Value getRowValue(String collectionName, String objName, diff --git a/src/main/java/com/healthmarketscience/jackcess/expr/Expression.java b/src/main/java/com/healthmarketscience/jackcess/expr/Expression.java index 99d695f..9e9b836 100644 --- a/src/main/java/com/healthmarketscience/jackcess/expr/Expression.java +++ b/src/main/java/com/healthmarketscience/jackcess/expr/Expression.java @@ -22,9 +22,7 @@ package com.healthmarketscience.jackcess.expr; */ public interface Expression { - public Object evalDefault(); - - public Boolean evalCondition(RowContext ctx); + public Object eval(EvalContext ctx); public String toDebugString(); diff --git a/src/main/java/com/healthmarketscience/jackcess/expr/Function.java b/src/main/java/com/healthmarketscience/jackcess/expr/Function.java index 10ecc2b..0d94dde 100644 --- a/src/main/java/com/healthmarketscience/jackcess/expr/Function.java +++ b/src/main/java/com/healthmarketscience/jackcess/expr/Function.java @@ -23,6 +23,6 @@ package com.healthmarketscience.jackcess.expr; public interface Function { public String getName(); - public Value eval(Value... params); + public Value eval(EvalContext ctx, Value... params); public boolean isPure(); } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDateValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDateValue.java index 4cca8d2..68ad69f 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDateValue.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDateValue.java @@ -43,6 +43,10 @@ public abstract class BaseDateValue extends BaseValue return _val; } + protected DateFormat getFormat() { + return _fmt; + } + protected Double getNumber() { return ColumnImpl.toDateDouble(_val, _fmt.getCalendar()); } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java index c295208..b05bc16 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java @@ -23,6 +23,7 @@ import java.text.Format; import java.util.Date; import java.util.regex.Pattern; +import com.healthmarketscience.jackcess.expr.EvalContext; import com.healthmarketscience.jackcess.expr.Value; import com.healthmarketscience.jackcess.impl.ColumnImpl; @@ -54,7 +55,7 @@ public class BuiltinOperators private BuiltinOperators() {} - // FIXME, null propagation: + // null propagation rules: // http://www.utteraccess.com/wiki/index.php/Nulls_And_Their_Behavior // https://theaccessbuddy.wordpress.com/2012/10/24/6-logical-operators-in-ms-access-that-you-must-know-operator-types-3-of-5/ // - number ops @@ -64,9 +65,8 @@ public class BuiltinOperators // - Or - can be true if one arg is true // - between, not, like, in // - *NOT* concal op '&' - // FIXME, Imp operator? - public static Value negate(Value param1) { + public static Value negate(EvalContext ctx, Value param1) { if(param1.isNull()) { // null propagation return NULL_VAL; @@ -81,7 +81,7 @@ public class BuiltinOperators case DATE_TIME: // dates/times get converted to date doubles for arithmetic double result = -param1.getAsDouble(); - return toDateValue(mathType, result, param1, null); + return toDateValue(ctx, mathType, result, param1, null); case LONG: return toValue(-param1.getAsLong()); case DOUBLE: @@ -95,7 +95,7 @@ public class BuiltinOperators } } - public static Value add(Value param1, Value param2) { + public static Value add(EvalContext ctx, Value param1, Value param2) { if(anyParamIsNull(param1, param2)) { // null propagation return NULL_VAL; @@ -112,7 +112,7 @@ public class BuiltinOperators case DATE_TIME: // dates/times get converted to date doubles for arithmetic double result = param1.getAsDouble() + param2.getAsDouble(); - return toDateValue(mathType, result, param1, param2); + return toDateValue(ctx, mathType, result, param1, param2); case LONG: return toValue(param1.getAsLong() + param2.getAsLong()); case DOUBLE: @@ -126,7 +126,7 @@ public class BuiltinOperators } } - public static Value subtract(Value param1, Value param2) { + public static Value subtract(EvalContext ctx, Value param1, Value param2) { if(anyParamIsNull(param1, param2)) { // null propagation return NULL_VAL; @@ -141,7 +141,7 @@ public class BuiltinOperators case DATE_TIME: // dates/times get converted to date doubles for arithmetic double result = param1.getAsDouble() - param2.getAsDouble(); - return toDateValue(mathType, result, param1, param2); + return toDateValue(ctx, mathType, result, param1, param2); case LONG: return toValue(param1.getAsLong() - param2.getAsLong()); case DOUBLE: @@ -622,16 +622,31 @@ public class BuiltinOperators return new BigDecimalValue(s); } - private static Value toDateValue(Value.Type type, double v, + private static Value toDateValue(EvalContext ctx, Value.Type type, double v, Value param1, Value param2) { - // FIXME find format from first matching param DateFormat fmt = null; - // if(param1.getType() == type) { - // fmt = (DateFormat)param1.getFormat(); - // } else if(param2 != null) { - // fmt = (DateFormat)param2.getFormat(); - // } + if((param1 instanceof BaseDateValue) && (param1.getType() == type)) { + fmt = ((BaseDateValue)param1).getFormat(); + } else if((param2 instanceof BaseDateValue) && (param2.getType() == type)) { + fmt = ((BaseDateValue)param2).getFormat(); + } else { + String fmtStr = null; + switch(type) { + case DATE: + fmtStr = ExpressionTokenizer.DATE_FORMAT; + break; + case TIME: + fmtStr = ExpressionTokenizer.TIME_FORMAT_24; + break; + case DATE_TIME: + fmtStr = ExpressionTokenizer.DATE_TIME_FORMAT_24; + break; + default: + throw new RuntimeException("Unexpected type " + type); + } + fmt = ctx.createDateFormat(fmtStr); + } Date d = new Date(ColumnImpl.fromDateDouble(v, fmt.getCalendar())); diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java index 17e8d10..fbcd683 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java @@ -22,6 +22,7 @@ import java.util.Map; import com.healthmarketscience.jackcess.expr.Value; import com.healthmarketscience.jackcess.expr.Function; +import com.healthmarketscience.jackcess.expr.EvalContext; /** * @@ -83,12 +84,12 @@ public class DefaultFunctions super(name, 1, 1); } - public final Value eval(Value... params) { + public final Value eval(EvalContext ctx, Value... params) { validateNumParams(params); - return eval1(params[0]); + return eval1(ctx, params[0]); } - protected abstract Value eval1(Value param); + protected abstract Value eval1(EvalContext ctx, Value param); } public static abstract class Func2 extends BaseFunction @@ -97,12 +98,12 @@ public class DefaultFunctions super(name, 2, 2); } - public final Value eval(Value... params) { + public final Value eval(EvalContext ctx, Value... params) { validateNumParams(params); - return eval2(params[0], params[1]); + return eval2(ctx, params[0], params[1]); } - protected abstract Value eval2(Value param1, Value param2); + protected abstract Value eval2(EvalContext ctx, Value param1, Value param2); } public static abstract class Func3 extends BaseFunction @@ -111,18 +112,21 @@ public class DefaultFunctions super(name, 3, 3); } - public final Value eval(Value... params) { + public final Value eval(EvalContext ctx, Value... params) { validateNumParams(params); - return eval3(params[0], params[1], params[2]); + return eval3(ctx, params[0], params[1], params[2]); } - protected abstract Value eval3(Value param1, Value param2, Value param3); + protected abstract Value eval3(EvalContext ctx, + Value param1, Value param2, Value param3); } public static final Function IIF = registerFunc(new Func3("IIf") { @Override - protected Value eval3(Value param1, Value param2, Value param3) { - return (param1.getAsBoolean() ? param2 : param3); + protected Value eval3(EvalContext ctx, + Value param1, Value param2, Value param3) { + // null is false + return ((!param1.isNull() && param1.getAsBoolean()) ? param2 : param3); } }); diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java index 5a9686f..ab45eda 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java @@ -36,7 +36,7 @@ import java.util.regex.Pattern; import com.healthmarketscience.jackcess.DatabaseBuilder; import com.healthmarketscience.jackcess.expr.Expression; import com.healthmarketscience.jackcess.expr.Function; -import com.healthmarketscience.jackcess.expr.RowContext; +import com.healthmarketscience.jackcess.expr.EvalContext; import com.healthmarketscience.jackcess.expr.Value; import com.healthmarketscience.jackcess.impl.expr.ExpressionTokenizer.Token; import com.healthmarketscience.jackcess.impl.expr.ExpressionTokenizer.TokenType; @@ -104,12 +104,12 @@ public class Expressionator private enum UnaryOp implements OpType { NEG("-", false) { - @Override public Value eval(Value param1) { - return BuiltinOperators.negate(param1); + @Override public Value eval(EvalContext ctx, Value param1) { + return BuiltinOperators.negate(ctx, param1); } }, NOT("Not", true) { - @Override public Value eval(Value param1) { + @Override public Value eval(EvalContext ctx, Value param1) { return BuiltinOperators.not(param1); } }; @@ -131,47 +131,47 @@ public class Expressionator return _str; } - public abstract Value eval(Value param1); + public abstract Value eval(EvalContext ctx, Value param1); } private enum BinaryOp implements OpType { PLUS("+") { - @Override public Value eval(Value param1, Value param2) { - return BuiltinOperators.add(param1, param2); + @Override public Value eval(EvalContext ctx, Value param1, Value param2) { + return BuiltinOperators.add(ctx, param1, param2); } }, MINUS("-") { - @Override public Value eval(Value param1, Value param2) { - return BuiltinOperators.subtract(param1, param2); + @Override public Value eval(EvalContext ctx, Value param1, Value param2) { + return BuiltinOperators.subtract(ctx, param1, param2); } }, MULT("*") { - @Override public Value eval(Value param1, Value param2) { + @Override public Value eval(EvalContext ctx, Value param1, Value param2) { return BuiltinOperators.multiply(param1, param2); } }, DIV("/") { - @Override public Value eval(Value param1, Value param2) { + @Override public Value eval(EvalContext ctx, Value param1, Value param2) { return BuiltinOperators.divide(param1, param2); } }, INT_DIV("\\") { - @Override public Value eval(Value param1, Value param2) { + @Override public Value eval(EvalContext ctx, Value param1, Value param2) { return BuiltinOperators.intDivide(param1, param2); } }, EXP("^") { - @Override public Value eval(Value param1, Value param2) { + @Override public Value eval(EvalContext ctx, Value param1, Value param2) { return BuiltinOperators.exp(param1, param2); } }, CONCAT("&") { - @Override public Value eval(Value param1, Value param2) { + @Override public Value eval(EvalContext ctx, Value param1, Value param2) { return BuiltinOperators.concat(param1, param2); } }, MOD("Mod") { - @Override public Value eval(Value param1, Value param2) { + @Override public Value eval(EvalContext ctx, Value param1, Value param2) { return BuiltinOperators.mod(param1, param2); } }; @@ -187,7 +187,7 @@ public class Expressionator return _str; } - public abstract Value eval(Value param1, Value param2); + public abstract Value eval(EvalContext ctx, Value param1, Value param2); } private enum CompOp implements OpType { @@ -363,7 +363,7 @@ public class Expressionator @Override public boolean isPure() { return false; } - @Override protected Value eval(RowContext ctx) { + @Override public Value eval(EvalContext ctx) { return ctx.getThisColumnValue(); } @Override protected void toExprString(StringBuilder sb, boolean isDebug) { @@ -421,12 +421,11 @@ public class Expressionator } Expr expr = parseExpression(new TokBuf(exprType, tokens, context), false); - if(expr.isPure()) { - // for now, just cache at top-level for speed (could in theory cache - // intermediate values?) - expr = new MemoizedPureExpression(expr); - } - return expr; + return (expr.isPure() ? + // for now, just cache at top-level for speed (could in theory cache + // intermediate values?) + new MemoizedExprWrapper(exprType, expr) : + new ExprWrapper(exprType, expr)); } private static List<Token> trimSpaces(List<Token> tokens) { @@ -1162,7 +1161,7 @@ public class Expressionator } private static Value[] exprListToValues( - List<Expr> exprs, RowContext ctx) { + List<Expr> exprs, EvalContext ctx) { Value[] paramVals = new Value[exprs.size()]; for(int i = 0; i < exprs.size(); ++i) { paramVals[i] = exprs.get(i).eval(ctx); @@ -1171,7 +1170,7 @@ public class Expressionator } private static Value[] exprListToDelayedValues( - List<Expr> exprs, RowContext ctx) { + List<Expr> exprs, EvalContext ctx) { Value[] paramVals = new Value[exprs.size()]; for(int i = 0; i < exprs.size(); ++i) { paramVals[i] = new DelayedValue(exprs.get(i), ctx); @@ -1300,52 +1299,22 @@ public class Expressionator private static final class DelayedValue extends BaseDelayedValue { private final Expr _expr; - private final RowContext _ctx; + private final EvalContext _ctx; - private DelayedValue(Expr expr, RowContext ctx) { + private DelayedValue(Expr expr, EvalContext ctx) { _expr = expr; _ctx = ctx; } @Override - protected Value eval() { + public Value eval() { return _expr.eval(_ctx); } } - private static abstract class Expr implements Expression + private static abstract class Expr { - public Object evalDefault() { - Value val = eval(null); - - if(val.isNull()) { - return null; - } - - // FIXME, booleans seem to go to -1 (true),0 (false) ...? - - return val.get(); - } - - public Boolean evalCondition(RowContext ctx) { - Value val = eval(ctx); - - if(val.isNull()) { - return null; - } - - // FIXME, is this only true for non-numeric...? - // if(val.getType() != Value.Type.BOOLEAN) { - // // a single value as a conditional expression seems to act like an - // // implicit "=" - // // FIXME, what about row validators? - // val = BuiltinOperators.equals(val, ctx.getThisColumnValue()); - // } - - return val.getAsBoolean(); - } - @Override public String toString() { StringBuilder sb = new StringBuilder(); @@ -1427,7 +1396,7 @@ public class Expressionator public abstract boolean isPure(); - protected abstract Value eval(RowContext ctx); + public abstract Value eval(EvalContext ctx); protected abstract void toExprString(StringBuilder sb, boolean isDebug); } @@ -1448,7 +1417,7 @@ public class Expressionator } @Override - protected Value eval(RowContext ctx) { + public Value eval(EvalContext ctx) { return _val; } @@ -1473,7 +1442,7 @@ public class Expressionator } @Override - public Value eval(RowContext ctx) { + public Value eval(EvalContext ctx) { return _val; } @@ -1508,7 +1477,7 @@ public class Expressionator } @Override - public Value eval(RowContext ctx) { + public Value eval(EvalContext ctx) { return ctx.getRowValue(_collectionName, _objName, _fieldName); } @@ -1538,7 +1507,7 @@ public class Expressionator } @Override - protected Value eval(RowContext ctx) { + public Value eval(EvalContext ctx) { return _expr.eval(ctx); } @@ -1566,8 +1535,8 @@ public class Expressionator } @Override - protected Value eval(RowContext ctx) { - return _func.eval(exprListToValues(_params, ctx)); + public Value eval(EvalContext ctx) { + return _func.eval(ctx, exprListToValues(_params, ctx)); } @Override @@ -1635,8 +1604,8 @@ public class Expressionator } @Override - protected Value eval(RowContext ctx) { - return ((BinaryOp)_op).eval(_left.eval(ctx), _right.eval(ctx)); + public Value eval(EvalContext ctx) { + return ((BinaryOp)_op).eval(ctx, _left.eval(ctx), _right.eval(ctx)); } } @@ -1669,8 +1638,8 @@ public class Expressionator } @Override - protected Value eval(RowContext ctx) { - return ((UnaryOp)_op).eval(_expr.eval(ctx)); + public Value eval(EvalContext ctx) { + return ((UnaryOp)_op).eval(ctx, _expr.eval(ctx)); } @Override @@ -1690,7 +1659,7 @@ public class Expressionator } @Override - protected Value eval(RowContext ctx) { + public Value eval(EvalContext ctx) { return ((CompOp)_op).eval(_left.eval(ctx), _right.eval(ctx)); } } @@ -1702,7 +1671,7 @@ public class Expressionator } @Override - protected Value eval(final RowContext ctx) { + public Value eval(final EvalContext ctx) { // logical operations do short circuit evaluation, so we need to delay // computing results until necessary @@ -1747,7 +1716,7 @@ public class Expressionator } @Override - protected Value eval(RowContext ctx) { + public Value eval(EvalContext ctx) { return _op.eval(_expr.eval(ctx), null, null); } @@ -1771,7 +1740,7 @@ public class Expressionator } @Override - protected Value eval(RowContext ctx) { + public Value eval(EvalContext ctx) { return _op.eval(_expr.eval(ctx), _pattern, null); } @@ -1801,7 +1770,7 @@ public class Expressionator } @Override - protected Value eval(RowContext ctx) { + public Value eval(EvalContext ctx) { return _op.eval(_expr.eval(ctx), exprListToDelayedValues(_exprs, ctx), null); } @@ -1842,7 +1811,7 @@ public class Expressionator } @Override - protected Value eval(RowContext ctx) { + public Value eval(EvalContext ctx) { return _op.eval(_expr.eval(ctx), new DelayedValue(_startRangeExpr, ctx), new DelayedValue(_endRangeExpr, ctx)); @@ -1859,40 +1828,92 @@ public class Expressionator } /** - * Wrapper for a <i>pure</i> Expr which caches the result of evaluation. + * Expression wrapper for an Expr which caches the result of evaluation. */ - private static final class MemoizedPureExpression extends Expr + private static class ExprWrapper implements Expression { + private final Type _type; private final Expr _expr; - private Value _val; - private MemoizedPureExpression(Expr expr) { + private ExprWrapper(Type type, Expr expr) { + _type = type; _expr = expr; - } + } - @Override - protected Value eval(RowContext ctx) { - if(_val == null) { - // since expr is pure, row context should not be used - _val = _expr.eval(null); + public Object eval(EvalContext ctx) { + switch(_type) { + case DEFAULT_VALUE: + return evalDefault(ctx); + case FIELD_VALIDATOR: + case RECORD_VALIDATOR: + return evalCondition(ctx); + default: + throw new RuntimeException("unexpected expression type " + _type); } - return _val; } - @Override + public String toDebugString() { + return _expr.toDebugString(); + } + public boolean isPure() { - return true; + return _expr.isPure(); } @Override - protected void toString(StringBuilder sb, boolean isDebug) { - // don't display this class in debug string - _expr.toString(sb, isDebug); + public String toString() { + return _expr.toString(); + } + + private Object evalDefault(EvalContext ctx) { + Value val = _expr.eval(ctx); + + if(val.isNull()) { + return null; + } + + // FIXME, booleans seem to go to -1 (true),0 (false) ...? + + return val.get(); + } + + private Boolean evalCondition(EvalContext ctx) { + Value val = _expr.eval(ctx); + + if(val.isNull()) { + return null; + } + + // FIXME, is this only true for non-numeric...? + // if(val.getType() != Value.Type.BOOLEAN) { + // // a single value as a conditional expression seems to act like an + // // implicit "=" + // // FIXME, what about row validators? + // val = BuiltinOperators.equals(val, ctx.getThisColumnValue()); + // } + + return val.getAsBoolean(); } + } + + /** + * Expression wrapper for a <i>pure</i> Expr which caches the result of + * evaluation. + */ + private static final class MemoizedExprWrapper extends ExprWrapper + { + private Object _val; + + private MemoizedExprWrapper(Type type, Expr expr) { + super(type, expr); + } @Override - protected void toExprString(StringBuilder sb, boolean isDebug) { - throw new UnsupportedOperationException(); + public Object eval(EvalContext ctx) { + if(_val == null) { + _val = super.eval(ctx); + } + return _val; } } } diff --git a/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java b/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java index 3109ea4..ffcf901 100644 --- a/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java @@ -264,14 +264,14 @@ public class ExpressionatorTest extends TestCase private static Object eval(String exprStr) { Expression expr = Expressionator.parse( Expressionator.Type.DEFAULT_VALUE, exprStr, new TestContext()); - return expr.evalDefault(); + return expr.eval(null); } private static void evalFail(String exprStr, Class<? extends Exception> failure) { Expression expr = Expressionator.parse( Expressionator.Type.DEFAULT_VALUE, exprStr, new TestContext()); try { - expr.evalDefault(); + expr.eval(null); fail(failure + " should have been thrown"); } catch(Exception e) { assertTrue(failure.isInstance(e)); |