]> source.dussan.org Git - jackcess.git/commitdiff
handle literal string default values
authorJames Ahlborn <jtahlborn@yahoo.com>
Wed, 30 May 2018 04:12:46 +0000 (04:12 +0000)
committerJames Ahlborn <jtahlborn@yahoo.com>
Wed, 30 May 2018 04:12:46 +0000 (04:12 +0000)
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/branches/exprs@1157 f203690c-595d-4dc9-a70b-905162fa7fd2

src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java
src/main/java/com/healthmarketscience/jackcess/impl/ColDefaultValueEvalContext.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java
src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java

index 230afe22ebabfdba3ff01f67cddbc8a24f6ea3cd..9d724138c130cbbbd7ff76eec91df00fd32a6f7f 100644 (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;
     }
index 61a7c7184cd4830ec96b5076b64bc7b9ffdf11ac..734c9089e163a7482e4597edaa8849871e48f562 100644 (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);
index 2b64da5d8dca7eced2a5b5a79df2cea7ccb6613b..c2eb17743b8b686b47303850e6b75493eca3baa1 100644 (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 = ']';
index 1935149c4449d28188151e24624f07be4d0c2bb0..f92fc5d3045667a66ade3a4e6f32859f211b2486 100644 (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
index 714421e5d6720d96a77aeb7cbf83be2e398563a7..49db5bdb10aed0a2b8674ae3aa85ce03c913042c 100644 (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)));
   }