From 0dc74ed6799971c4cdcb8470bf94c59f4259a9ed Mon Sep 17 00:00:00 2001 From: James Ahlborn Date: Sat, 14 Jul 2018 03:05:57 +0000 Subject: [PATCH] fix handling of certain field validator expressions; add some tests for various expressions git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@1180 f203690c-595d-4dc9-a70b-905162fa7fd2 --- .../jackcess/impl/BaseEvalContext.java | 2 +- .../jackcess/impl/expr/Expressionator.java | 27 +++--- .../impl/expr/ExpressionatorTest.java | 96 +++++++++++++------ src/test/resources/test_exprs.txt | 91 ++++++++++++++++++ 4 files changed, 176 insertions(+), 40 deletions(-) create mode 100644 src/test/resources/test_exprs.txt diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java b/src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java index 9d72413..640be96 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java @@ -162,7 +162,7 @@ public abstract class BaseEvalContext implements EvalContext } } - protected static Value.Type toValueType(DataType dType) { + public static Value.Type toValueType(DataType dType) { Value.Type type = TYPE_MAP.get(dType); return ((type == null) ? Value.Type.STRING : type); } 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 4988f3e..c9af948 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java @@ -434,12 +434,12 @@ public class Expressionator // 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); - } + if((exprType == Type.FIELD_VALIDATOR) && !expr.isValidationExpr()) { + // a non-validation 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: @@ -1387,7 +1387,7 @@ public class Expressionator return sb.toString(); } - protected boolean isConditionalExpr() { + protected boolean isValidationExpr() { return false; } @@ -1596,8 +1596,8 @@ public class Expressionator } @Override - protected boolean isConditionalExpr() { - return _expr.isConditionalExpr(); + protected boolean isValidationExpr() { + return _expr.isValidationExpr(); } @Override @@ -1776,7 +1776,7 @@ public class Expressionator } @Override - protected boolean isConditionalExpr() { + protected boolean isValidationExpr() { return true; } @@ -1810,6 +1810,11 @@ public class Expressionator super(op, left, right); } + @Override + protected boolean isValidationExpr() { + return true; + } + @Override public Value eval(final EvalContext ctx) { @@ -1854,7 +1859,7 @@ public class Expressionator } @Override - protected boolean isConditionalExpr() { + protected boolean isValidationExpr() { return true; } } 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 ef5cef3..f491526 100644 --- a/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java @@ -16,21 +16,24 @@ limitations under the License. package com.healthmarketscience.jackcess.impl.expr; +import java.io.BufferedReader; +import java.io.FileReader; import java.math.BigDecimal; import java.text.SimpleDateFormat; import java.util.Date; import javax.script.Bindings; import javax.script.SimpleBindings; +import com.healthmarketscience.jackcess.DataType; import com.healthmarketscience.jackcess.DatabaseBuilder; import com.healthmarketscience.jackcess.TestUtil; import com.healthmarketscience.jackcess.expr.EvalContext; import com.healthmarketscience.jackcess.expr.Expression; -import com.healthmarketscience.jackcess.expr.Function; import com.healthmarketscience.jackcess.expr.FunctionLookup; import com.healthmarketscience.jackcess.expr.Identifier; import com.healthmarketscience.jackcess.expr.TemporalConfig; import com.healthmarketscience.jackcess.expr.Value; +import com.healthmarketscience.jackcess.impl.BaseEvalContext; import com.healthmarketscience.jackcess.impl.NumberFormatter; import junit.framework.TestCase; @@ -93,6 +96,8 @@ public class ExpressionatorTest extends TestCase validateExpr("' \"A\" '", "{\" \"\"A\"\" \"}", "\" \"\"A\"\" \""); + + validateExpr("<=1 And >=0", "{{{} <= {1}} And {{} >= {0}}}", " <= 1 And >= 0"); } private static void doTestSimpleBinOp(String opName, String... ops) throws Exception @@ -416,6 +421,47 @@ public class ExpressionatorTest extends TestCase assertEquals(-28d, eval("CDbl(9)-37", Value.Type.DOUBLE)); } + public void testParseSomeExprs() throws Exception + { + BufferedReader br = new BufferedReader(new FileReader("src/test/resources/test_exprs.txt")); + + TestContext tc = new TestContext() { + @Override + public Value getThisColumnValue() { + return BuiltinOperators.toValue(23.0); + } + + @Override + public Value getIdentifierValue(Identifier identifier) { + return BuiltinOperators.toValue(23.0); + } + }; + + String line = null; + while((line = br.readLine()) != null) { + line = line.trim(); + if(line.isEmpty()) { + continue; + } + + String[] parts = line.split(";", 3); + Expressionator.Type type = Expressionator.Type.valueOf(parts[0]); + DataType dType = + (("null".equals(parts[1])) ? null : DataType.valueOf(parts[1])); + String exprStr = parts[2]; + + Value.Type resultType = ((dType != null) ? + BaseEvalContext.toValueType(dType) : null); + + Expression expr = Expressionator.parse( + type, exprStr, resultType, tc); + + expr.eval(tc); + } + + br.close(); + } + private static void validateExpr(String exprStr, String debugStr) { validateExpr(exprStr, debugStr, exprStr); } @@ -424,7 +470,7 @@ public class ExpressionatorTest extends TestCase String cleanStr) { Expression expr = Expressionator.parse( Expressionator.Type.FIELD_VALIDATOR, exprStr, null, - new TestParseContext()); + new TestContext()); String foundDebugStr = expr.toDebugString(); if(foundDebugStr.startsWith("")) { assertEquals("{{} = " + @@ -440,20 +486,20 @@ public class ExpressionatorTest extends TestCase } static Object eval(String exprStr, Value.Type resultType) { + TestContext tc = new TestContext(); Expression expr = Expressionator.parse( - Expressionator.Type.DEFAULT_VALUE, exprStr, resultType, - new TestParseContext()); - return expr.eval(new TestEvalContext(null)); + Expressionator.Type.DEFAULT_VALUE, exprStr, resultType, tc); + return expr.eval(tc); } private static void evalFail( String exprStr, Class failure) { + TestContext tc = new TestContext(); Expression expr = Expressionator.parse( - Expressionator.Type.DEFAULT_VALUE, exprStr, null, - new TestParseContext()); + Expressionator.Type.DEFAULT_VALUE, exprStr, null, tc); try { - expr.eval(new TestEvalContext(null)); + expr.eval(tc); fail(failure + " should have been thrown"); } catch(Exception e) { assertTrue(failure.isInstance(e)); @@ -461,10 +507,10 @@ public class ExpressionatorTest extends TestCase } private static Boolean evalCondition(String exprStr, String thisVal) { + TestContext tc = new TestContext(BuiltinOperators.toValue(thisVal)); Expression expr = Expressionator.parse( - Expressionator.Type.FIELD_VALIDATOR, exprStr, null, - new TestParseContext()); - return (Boolean)expr.eval(new TestEvalContext(BuiltinOperators.toValue(thisVal))); + Expressionator.Type.FIELD_VALIDATOR, exprStr, null, tc); + return (Boolean)expr.eval(tc); } static int roundToLongInt(double d) { @@ -480,28 +526,18 @@ public class ExpressionatorTest extends TestCase return BuiltinOperators.normalize(bd); } - private static final class TestParseContext implements Expressionator.ParseContext - { - public TemporalConfig getTemporalConfig() { - return TemporalConfig.US_TEMPORAL_CONFIG; - } - public SimpleDateFormat createDateFormat(String formatStr) { - SimpleDateFormat sdf = DatabaseBuilder.createDateFormat(formatStr); - sdf.setTimeZone(TestUtil.TEST_TZ); - return sdf; - } - public FunctionLookup getFunctionLookup() { - return DefaultFunctions.LOOKUP; - } - } - - private static final class TestEvalContext implements EvalContext + private static class TestContext + implements Expressionator.ParseContext, EvalContext { private final Value _thisVal; private final RandomContext _rndCtx = new RandomContext(); private final Bindings _bindings = new SimpleBindings(); - private TestEvalContext(Value thisVal) { + private TestContext() { + this(null); + } + + private TestContext(Value thisVal) { _thisVal = thisVal; } @@ -519,6 +555,10 @@ public class ExpressionatorTest extends TestCase return sdf; } + public FunctionLookup getFunctionLookup() { + return DefaultFunctions.LOOKUP; + } + public Value getThisColumnValue() { if(_thisVal == null) { throw new UnsupportedOperationException(); diff --git a/src/test/resources/test_exprs.txt b/src/test/resources/test_exprs.txt new file mode 100644 index 0000000..eb76049 --- /dev/null +++ b/src/test/resources/test_exprs.txt @@ -0,0 +1,91 @@ +DEFAULT_VALUE;SHORT_DATE_TIME;Now() +DEFAULT_VALUE;NUMERIC;0 +FIELD_VALIDATOR;DOUBLE;<=1 And >=0 +FIELD_VALIDATOR;SHORT_DATE_TIME;>=#1/1/1900# +DEFAULT_VALUE;BOOLEAN;No +DEFAULT_VALUE;SHORT_DATE_TIME;Null +DEFAULT_VALUE;TEXT;NEW +DEFAULT_VALUE;LONG;0.0 +DEFAULT_VALUE;LONG;1 +DEFAULT_VALUE;DOUBLE;1.0 +DEFAULT_VALUE;LONG;3 +DEFAULT_VALUE;LONG;-1 +DEFAULT_VALUE;GUID;GenGUID() +FIELD_VALIDATOR;TEXT;Like "http://*" +DEFAULT_VALUE;BOOLEAN;True +DEFAULT_VALUE;SHORT_DATE_TIME;Date() +DEFAULT_VALUE;TEXT;"" +DEFAULT_VALUE;BOOLEAN;False +DEFAULT_VALUE;LONG;524287 +DEFAULT_VALUE;LONG;5 +DEFAULT_VALUE;LONG;10 +DEFAULT_VALUE;TEXT;' ' +EXPRESSION;MONEY;[UnitPrice]*[Quantity] +DEFAULT_VALUE;TEXT;"UTC" +EXPRESSION;BIG_INT;[BigNum]+10 +EXPRESSION;TEXT;IIf(IsNull([LastName]),IIf(IsNull([FirstName]),[Company],[FirstName]),IIf(IsNull([FirstName]),[LastName],[FirstName] & " " & [LastName])) +EXPRESSION;TEXT;IIf(IsNull([LastName]),IIf(IsNull([FirstName]),[Company],[FirstName]),IIf(IsNull([FirstName]),[LastName],[LastName] & ", " & [FirstName])) +RECORD_VALIDATOR;null;Not IsNull([Email] & [FullName] & [Login]) +DEFAULT_VALUE;DOUBLE;=0 +EXPRESSION;DOUBLE;[InitialLevel]+[Received]-[Shipped]-[Waste] +EXPRESSION;DOUBLE;[OnHand]-[Allocated] +EXPRESSION;DOUBLE;[Available]+[OnOrder]-[BackOrdered] +EXPRESSION;DOUBLE;IIf([TargetLevel]-[CurrentLevel]>0,[TargetLevel]-[CurrentLevel],0) +EXPRESSION;DOUBLE;IIf([BelowTargetLevel]>0,IIf([BelowTargetLevel]<[MinimumReorderQuantity],[MinimumReorderQuantity],[BelowTargetLevel]),0) +EXPRESSION;MONEY;[SubTotal]+[Tax]+[Shipping] +EXPRESSION;MONEY;[UnitPrice]*[Quantity]*(1-[Discount]) +DEFAULT_VALUE;TEXT;="None" +EXPRESSION;BOOLEAN;([StatusID]=10) +EXPRESSION;BOOLEAN;([StatusID]>=30) +EXPRESSION;BOOLEAN;[StatusID]>=40 +EXPRESSION;BOOLEAN;[StatusID]>50 +EXPRESSION;BOOLEAN;([StatusID]=20) +DEFAULT_VALUE;SHORT_DATE_TIME;=Date() +EXPRESSION;DOUBLE;Month([OrderDate]) +EXPRESSION;DOUBLE;Year([OrderDate]) +EXPRESSION;MONEY;[OrderSubTotal]+[Tax]+[ShippingFee] +EXPRESSION;DOUBLE;IIf([OrderMonth]<=3,1,IIf([OrderMonth]<=6,2,IIf([OrderMonth]<=9,3,4))) +EXPRESSION;BOOLEAN;([StatusID]=0) +EXPRESSION;BOOLEAN;([StatusID]=30) And (Not IsNull([ClosedDate])) +EXPRESSION;BOOLEAN;([StatusID]>=20) And (Not IsNull([ShippedDate])) +EXPRESSION;BOOLEAN;([StatusID]>=10) +EXPRESSION;BOOLEAN;Not [IsCompleted] +EXPRESSION;DOUBLE;[Quantity]*[UnitCost] +DEFAULT_VALUE;BOOLEAN;=False +EXPRESSION;MONEY;[OrderSubTotal]+[ShippingFee]+[Taxes] +EXPRESSION;BOOLEAN;Not IsNull([ClosedDate]) +EXPRESSION;BOOLEAN;Not IsNull([SubmittedDate]) +EXPRESSION;BOOLEAN;Not [IsSubmitted] +DEFAULT_VALUE;SHORT_DATE_TIME;=Now() +DEFAULT_VALUE;LONG;=Int(13) +FIELD_VALIDATOR;SHORT_DATE_TIME;=0 +FIELD_VALIDATOR;INT;>0 +FIELD_VALIDATOR;FLOAT;Between 0 And 1 +DEFAULT_VALUE;BOOLEAN;=No +EXPRESSION;TEXT;[LastName] & ", " & [FirstName] +EXPRESSION;LONG;Len([LastFirst]) +EXPRESSION;MONEY;[Salary]/12 +EXPRESSION;BOOLEAN;[Salary]>100000 +EXPRESSION;MEMO;[LastName] & ", " & [FirstName] & "=" & [LastFirst] +EXPRESSION;NUMERIC;[Salary]/52 +EXPRESSION;MONEY;[Salary] +EXPRESSION;BOOLEAN;True +EXPRESSION;NUMERIC;[Popularity] +EXPRESSION;FLOAT;[Salary]*0.13/[DecimalTest] +EXPRESSION;NUMERIC;([Salary]*[MonthlySalary]/[DecimalTest])*34.12342134 +DEFAULT_VALUE;COMPLEX_TYPE;"data4" +EXPRESSION;DOUBLE;NPer(1,2,3,4,5) +EXPRESSION;DOUBLE;[Time Field] +DEFAULT_VALUE;TEXT;=Rnd(25) +DEFAULT_VALUE;TEXT;=Rnd(13) +DEFAULT_VALUE;TEXT;=Rnd(-1) +DEFAULT_VALUE;TEXT;=Rnd(-2) +DEFAULT_VALUE;TEXT;=Rnd() +RECORD_VALIDATOR;null;[Field4]>10 +EXPRESSION;DOUBLE;[Field6]+3 +EXPRESSION;DOUBLE;[Table4].[Field1]+4 +EXPRESSION;LONG;[Field5]+5 +DEFAULT_VALUE;LONG;134217727 +DEFAULT_VALUE;TEXT;'+P-E' +FIELD_VALIDATOR;TEXT;Is Not Null -- 2.39.5