diff options
5 files changed, 153 insertions, 103 deletions
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java b/src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java index 230afe2..9d72413 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java @@ -87,7 +87,7 @@ public abstract class BaseEvalContext implements EvalContext } public Value.Type getResultType() { - throw new UnsupportedOperationException(); + return null; } public Value getThisColumnValue() { @@ -181,7 +181,8 @@ public abstract class BaseEvalContext implements EvalContext private Expression getExpr() { // when the expression is parsed we replace the raw version - Expression expr = Expressionator.parse(_exprType, _exprStr, _dbCtx); + Expression expr = Expressionator.parse( + _exprType, _exprStr, getResultType(), _dbCtx); _expr = expr; return expr; } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/ColDefaultValueEvalContext.java b/src/main/java/com/healthmarketscience/jackcess/impl/ColDefaultValueEvalContext.java index 61a7c71..734c908 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/ColDefaultValueEvalContext.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/ColDefaultValueEvalContext.java @@ -23,7 +23,7 @@ import com.healthmarketscience.jackcess.impl.expr.Expressionator; * * @author James Ahlborn */ -public class ColDefaultValueEvalContext extends ColEvalContext +public class ColDefaultValueEvalContext extends ColEvalContext { public ColDefaultValueEvalContext(ColumnImpl col) { super(col); diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java index 2b64da5..c2eb177 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java @@ -44,7 +44,7 @@ import com.healthmarketscience.jackcess.expr.ParseException; class ExpressionTokenizer { private static final int EOF = -1; - private static final char QUOTED_STR_CHAR = '"'; + static final char QUOTED_STR_CHAR = '"'; private static final char SINGLE_QUOTED_STR_CHAR = '\''; private static final char OBJ_NAME_START_CHAR = '['; private static final char OBJ_NAME_END_CHAR = ']'; 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 1935149..f92fc5d 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java @@ -407,40 +407,17 @@ public class Expressionator private static final Expr FALSE_VALUE = new EConstValue( BuiltinOperators.FALSE_VAL, "False"); - private Expressionator() - { - } - - static String testTokenize(Type exprType, String exprStr, - ParseContext context) { - - if(context == null) { - context = DEFAULT_PARSE_CONTEXT; - } - List<Token> tokens = trimSpaces( - ExpressionTokenizer.tokenize(exprType, exprStr, context)); - - if(tokens == null) { - // FIXME, NULL_EXPR? - return null; - } - return tokens.toString(); - } + private Expressionator() {} public static Expression parse(Type exprType, String exprStr, + Value.Type resultType, ParseContext context) { if(context == null) { context = DEFAULT_PARSE_CONTEXT; } - // FIXME,restrictions: - // - default value only accepts simple exprs, otherwise becomes literal text - // - def val cannot refer to any columns - // - field validation cannot refer to other columns - // - record validation cannot refer to outside columns - List<Token> tokens = trimSpaces( ExpressionTokenizer.tokenize(exprType, exprStr, context)); @@ -449,20 +426,44 @@ public class Expressionator return null; } - Expr expr = parseExpression(new TokBuf(exprType, tokens, context), false); + TokBuf buf = new TokBuf(exprType, tokens, context); + + if(isLiteralDefaultValue(buf, resultType, exprStr)) { - if((exprType == Type.FIELD_VALIDATOR) && !expr.isConditionalExpr()) { - // a non-conditional expression for a FIELD_VALIDATOR treats the result - // as an equality comparison with the field in question. so, transform - // the expression accordingly - expr = new EImplicitCompOp(expr); + // this is handled as a literal string value, not an expression. no + // need to memo-ize cause it's a simple literal value + return new ExprWrapper( + new ELiteralValue(Value.Type.STRING, exprStr, null), resultType); } - return (expr.isConstant() ? - // for now, just cache at top-level for speed (could in theory cache - // intermediate values?) - new MemoizedExprWrapper(exprType, expr) : - new ExprWrapper(exprType, expr)); + // normal expression handling + Expr expr = parseExpression(buf, false); + + if((exprType == Type.FIELD_VALIDATOR) && !expr.isConditionalExpr()) { + // a non-conditional expression for a FIELD_VALIDATOR treats the result + // as an equality comparison with the field in question. so, transform + // the expression accordingly + expr = new EImplicitCompOp(expr); + } + + switch(exprType) { + case DEFAULT_VALUE: + case EXPRESSION: + return (expr.isConstant() ? + // for now, just cache at top-level for speed (could in theory + // cache intermediate values?) + new MemoizedExprWrapper(expr, resultType) : + new ExprWrapper(expr, resultType)); + case FIELD_VALIDATOR: + case RECORD_VALIDATOR: + return (expr.isConstant() ? + // for now, just cache at top-level for speed (could in theory + // cache intermediate values?) + new MemoizedCondExprWrapper(expr) : + new CondExprWrapper(expr)); + default: + throw new ParseException("unexpected expression type " + exprType); + } } private static List<Token> trimSpaces(List<Token> tokens) { @@ -1033,59 +1034,28 @@ public class Expressionator private final ParseContext _context; private int _pos; private Expr _pendingExpr; - private final boolean _simpleExpr; private TokBuf(Type exprType, List<Token> tokens, ParseContext context) { - this(exprType, false, tokens, null, 0, context); + this(exprType, tokens, null, 0, context); } private TokBuf(List<Token> tokens, TokBuf parent, int parentOff) { - this(parent._exprType, parent._simpleExpr, tokens, parent, parentOff, - parent._context); + this(parent._exprType, tokens, parent, parentOff, parent._context); } - private TokBuf(Type exprType, boolean simpleExpr, List<Token> tokens, - TokBuf parent, int parentOff, ParseContext context) { + private TokBuf(Type exprType, List<Token> tokens, TokBuf parent, + int parentOff, ParseContext context) { _exprType = exprType; _tokens = tokens; _parent = parent; _parentOff = parentOff; _context = context; - if(parent == null) { - // "top-level" expression, determine if it is a simple expression or not - simpleExpr = isSimpleExpression(); - } - _simpleExpr = simpleExpr; - } - - private boolean isSimpleExpression() { - if(_exprType != Type.DEFAULT_VALUE) { - return false; - } - - // a leading "=" indicates "full" expression handling for a DEFAULT_VALUE - Token t = peekNext(); - if(isOp(t, "=")) { - next(); - return false; - } - - // this is a "simple" DEFAULT_VALUE - return true; } public Type getExprType() { return _exprType; } - public boolean isSimpleExpr() { - return _simpleExpr; - } - - public boolean isTopLevel() { - return (_parent == null); - } - public int curPos() { return _pos; } @@ -1349,6 +1319,29 @@ public class Expressionator } } + private static boolean isLiteralDefaultValue( + TokBuf buf, Value.Type resultType, String exprStr) { + + // if a default value expression does not start with an '=' and is used in + // a string context, then it is taken as a literal value unless it starts + // with a " char + + if(buf.getExprType() != Type.DEFAULT_VALUE) { + return false; + } + + // a leading "=" indicates "full" expression handling for a DEFAULT_VALUE + // (consume this value once we detect it) + if(isOp(buf.peekNext(), "=")) { + buf.next(); + return false; + } + + return((resultType == Value.Type.STRING) && + ((exprStr.length() == 0) || + (exprStr.charAt(0) != ExpressionTokenizer.QUOTED_STR_CHAR))); + } + private interface LeftAssocExpr { public OpType getOp(); public Expr getLeft(); @@ -2004,31 +1997,16 @@ public class Expressionator } /** - * Expression wrapper for an Expr which caches the result of evaluation. + * Base Expression wrapper for an Expr. */ - private static class ExprWrapper implements Expression + private static abstract class BaseExprWrapper implements Expression { - private final Type _type; private final Expr _expr; - private ExprWrapper(Type type, Expr expr) { - _type = type; + private BaseExprWrapper(Expr expr) { _expr = expr; } - public Object eval(EvalContext ctx) { - switch(_type) { - case DEFAULT_VALUE: - case EXPRESSION: - return evalValue(ctx); - case FIELD_VALIDATOR: - case RECORD_VALIDATOR: - return evalCondition(ctx); - default: - throw new ParseException("unexpected expression type " + _type); - } - } - public String toDebugString() { return _expr.toDebugString(); } @@ -2046,14 +2024,13 @@ public class Expressionator return _expr.toString(); } - private Object evalValue(EvalContext ctx) { + protected Object evalValue(Value.Type resultType, EvalContext ctx) { Value val = _expr.eval(ctx); if(val.isNull()) { return null; } - Value.Type resultType = ctx.getResultType(); if(resultType == null) { // return as "native" type return val.get(); @@ -2078,7 +2055,7 @@ public class Expressionator } } - private Boolean evalCondition(EvalContext ctx) { + protected Boolean evalCondition(EvalContext ctx) { Value val = _expr.eval(ctx); if(val.isNull()) { @@ -2093,6 +2070,38 @@ public class Expressionator } /** + * Expression wrapper for an Expr which returns a value. + */ + private static class ExprWrapper extends BaseExprWrapper + { + private final Value.Type _resultType; + + private ExprWrapper(Expr expr, Value.Type resultType) { + super(expr); + _resultType = resultType; + } + + public Object eval(EvalContext ctx) { + return evalValue(_resultType, ctx); + } + } + + /** + * Expression wrapper for an Expr which returns a Boolean from a conditional + * expression. + */ + private static class CondExprWrapper extends BaseExprWrapper + { + private CondExprWrapper(Expr expr) { + super(expr); + } + + public Object eval(EvalContext ctx) { + return evalCondition(ctx); + } + } + + /** * Expression wrapper for a <i>pure</i> Expr which caches the result of * evaluation. */ @@ -2100,8 +2109,29 @@ public class Expressionator { private Object _val; - private MemoizedExprWrapper(Type type, Expr expr) { - super(type, expr); + private MemoizedExprWrapper(Expr expr, Value.Type resultType) { + super(expr, resultType); + } + + @Override + public Object eval(EvalContext ctx) { + if(_val == null) { + _val = super.eval(ctx); + } + return _val; + } + } + + /** + * Expression wrapper for a <i>pure</i> conditional Expr which caches the + * result of evaluation. + */ + private static final class MemoizedCondExprWrapper extends CondExprWrapper + { + private Object _val; + + private MemoizedCondExprWrapper(Expr expr) { + super(expr); } @Override 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 714421e..49db5bd 100644 --- a/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java @@ -324,6 +324,17 @@ public class ExpressionatorTest extends TestCase assertFalse(evalCondition("Like \"[abc*\"", "")); } + public void testLiteralDefaultValue() throws Exception + { + assertEquals("-28.0 blah ", eval("=CDbl(9)-37 & \" blah \"", + Value.Type.STRING)); + assertEquals("CDbl(9)-37 & \" blah \"", + eval("CDbl(9)-37 & \" blah \"", Value.Type.STRING)); + + assertEquals(-28d, eval("=CDbl(9)-37", Value.Type.DOUBLE)); + assertEquals(-28d, eval("CDbl(9)-37", Value.Type.DOUBLE)); + } + private static void validateExpr(String exprStr, String debugStr) { validateExpr(exprStr, debugStr, exprStr); } @@ -331,7 +342,7 @@ public class ExpressionatorTest extends TestCase private static void validateExpr(String exprStr, String debugStr, String cleanStr) { Expression expr = Expressionator.parse( - Expressionator.Type.FIELD_VALIDATOR, exprStr, null); + Expressionator.Type.FIELD_VALIDATOR, exprStr, null, null); String foundDebugStr = expr.toDebugString(); if(foundDebugStr.startsWith("<EImplicitCompOp>")) { assertEquals("<EImplicitCompOp>{<EThisValue>{<THIS_COL>} = " + @@ -343,14 +354,22 @@ public class ExpressionatorTest extends TestCase } static Object eval(String exprStr) { + return eval(exprStr, null); + } + + static Object eval(String exprStr, Value.Type resultType) { Expression expr = Expressionator.parse( - Expressionator.Type.DEFAULT_VALUE, exprStr, new TestParseContext()); + Expressionator.Type.DEFAULT_VALUE, exprStr, resultType, + new TestParseContext()); return expr.eval(new TestEvalContext(null)); } - private static void evalFail(String exprStr, Class<? extends Exception> failure) { + private static void evalFail( + String exprStr, Class<? extends Exception> failure) + { Expression expr = Expressionator.parse( - Expressionator.Type.DEFAULT_VALUE, exprStr, new TestParseContext()); + Expressionator.Type.DEFAULT_VALUE, exprStr, null, + new TestParseContext()); try { expr.eval(new TestEvalContext(null)); fail(failure + " should have been thrown"); @@ -361,7 +380,7 @@ public class ExpressionatorTest extends TestCase private static Boolean evalCondition(String exprStr, String thisVal) { Expression expr = Expressionator.parse( - Expressionator.Type.FIELD_VALIDATOR, exprStr, new TestParseContext()); + Expressionator.Type.FIELD_VALIDATOR, exprStr, null, new TestParseContext()); return (Boolean)expr.eval(new TestEvalContext(BuiltinOperators.toValue(thisVal))); } |