Browse Source

handle literal string default values

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

+ 3
- 2
src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java View File

@@ -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;
}

+ 1
- 1
src/main/java/com/healthmarketscience/jackcess/impl/ColDefaultValueEvalContext.java View File

@@ -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);

+ 1
- 1
src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java View File

@@ -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 = ']';

+ 124
- 94
src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java View File

@@ -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()) {
@@ -2092,6 +2069,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

+ 24
- 5
src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java View File

@@ -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)));
}


Loading…
Cancel
Save