aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java5
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/ColDefaultValueEvalContext.java2
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java2
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java218
-rw-r--r--src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java29
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)));
}