]> source.dussan.org Git - jackcess.git/commitdiff
long type should be int values; implement more date/time and misc other functions
authorJames Ahlborn <jtahlborn@yahoo.com>
Sat, 23 Sep 2017 05:04:01 +0000 (05:04 +0000)
committerJames Ahlborn <jtahlborn@yahoo.com>
Sat, 23 Sep 2017 05:04:01 +0000 (05:04 +0000)
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/branches/exprs@1120 f203690c-595d-4dc9-a70b-905162fa7fd2

17 files changed:
src/main/java/com/healthmarketscience/jackcess/expr/EvalContext.java
src/main/java/com/healthmarketscience/jackcess/expr/Value.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDateValue.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDelayedValue.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseNumericValue.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseValue.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultNumberFunctions.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultTextFunctions.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/LongValue.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/StringValue.java
src/test/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctionsTest.java
src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java

index 9f442f196304bab960d196ab015e6b13b9b6f5e3..c140594055662596d55dd10660c299f2c0c457c3 100644 (file)
@@ -36,5 +36,5 @@ public interface EvalContext
   public Value getRowValue(String collectionName, String objName,
                            String colName);
 
-  public Random getRandom(Long seed);
+  public Random getRandom(Integer seed);
 }
index 1fd4390a190d727fc22ad3f0f7f840a27c569775..39008f2cd1ccce2bc7f254aa4cd59242550dbdd6 100644 (file)
@@ -73,7 +73,7 @@ public interface Value
 
   public Date getAsDateTime(EvalContext ctx);
 
-  public Long getAsLong();
+  public Integer getAsLongInt();
 
   public Double getAsDouble();
 
index 4bf03afe874491b975649f775daff024e4429b24..188416a537e5abcbfb697dcc0582b8958fb8a781 100644 (file)
@@ -67,8 +67,8 @@ public abstract class BaseDateValue extends BaseValue
   }
 
   @Override
-  public Long getAsLong() {
-    return getNumber().longValue();
+  public Integer getAsLongInt() {
+    return roundToLongInt();
   }
 
   @Override
index c34a914e08d3b99f39142516732258b93d127517..e8ae3397410bc24bcf460d7a50527fa77ddc150c 100644 (file)
@@ -64,8 +64,8 @@ public abstract class BaseDelayedValue implements Value
     return getDelegate().getAsDateTime(ctx);
   }
 
-  public Long getAsLong() {
-    return getDelegate().getAsLong();
+  public Integer getAsLongInt() {
+    return getDelegate().getAsLongInt();
   }
 
   public Double getAsDouble() {
index 585c71e47ddbc4fa5d6cdb988093f3e6a1e6e857..dd12d7cbfe7508abb93d57cf43d6dd69b8ea0302 100644 (file)
@@ -39,8 +39,8 @@ public abstract class BaseNumericValue extends BaseValue
   }
 
   @Override
-  public Long getAsLong() {
-    return getNumber().longValue();
+  public Integer getAsLongInt() {
+    return roundToLongInt();
   }
 
   @Override
index e30c303690b98a8e083c0ff642931187ed084592..6107fbc796ccc1df19a13769d1aa8b658682dc54 100644 (file)
@@ -44,7 +44,7 @@ public abstract class BaseValue implements Value
     throw invalidConversion(Value.Type.DATE_TIME);
   }
 
-  public Long getAsLong() {
+  public Integer getAsLongInt() {
     throw invalidConversion(Value.Type.LONG);
   }
 
@@ -61,6 +61,11 @@ public abstract class BaseValue implements Value
         getType() + " value cannot be converted to " + newType);
   }
 
+  protected Integer roundToLongInt() {
+    return getAsBigDecimal().setScale(0, BuiltinOperators.ROUND_MODE)
+      .intValueExact();
+  }
+  
   @Override
   public String toString() {
     return "Value[" + getType() + "] '" + get() + "'";
index 0cf71c4ad4cedd83ab9d8624808bfdd4fd455d05..2037f34a55efb2568cbc0e08f61bbc926fa4ef1f 100644 (file)
@@ -17,7 +17,7 @@ limitations under the License.
 package com.healthmarketscience.jackcess.impl.expr;
 
 import java.math.BigDecimal;
-import java.math.BigInteger;
+import java.math.RoundingMode;
 import java.text.DateFormat;
 import java.util.Date;
 import java.util.regex.Pattern;
@@ -35,6 +35,9 @@ public class BuiltinOperators
 {
   private static final String DIV_BY_ZERO = "/ by zero";
 
+  private static final double MIN_INT = Integer.MIN_VALUE;
+  private static final double MAX_INT = Integer.MAX_VALUE;
+  
   public static final Value NULL_VAL = new BaseValue() {
     @Override public boolean isNull() {
       return true;
@@ -48,11 +51,13 @@ public class BuiltinOperators
   };
   // access seems to like -1 for true and 0 for false (boolean values are
   // basically an illusion)
-  public static final Value TRUE_VAL = new LongValue(-1L);
-  public static final Value FALSE_VAL = new LongValue(0L);
+  public static final Value TRUE_VAL = new LongValue(-1);
+  public static final Value FALSE_VAL = new LongValue(0);
   public static final Value EMPTY_STR_VAL = new StringValue("");
   public static final Value ZERO_VAL = FALSE_VAL;
 
+  public static final RoundingMode ROUND_MODE = RoundingMode.HALF_EVEN;
+  
   private enum CoercionType {
     SIMPLE(true, true), GENERAL(false, true), COMPARE(false, false);
 
@@ -95,7 +100,7 @@ public class BuiltinOperators
       double result = -param1.getAsDouble();
       return toDateValue(ctx, mathType, result, param1, null);
     case LONG:
-      return toValue(-param1.getAsLong());
+      return toValue(-param1.getAsLongInt());
     case DOUBLE:
       return toValue(-param1.getAsDouble());
     case STRING:
@@ -126,7 +131,7 @@ public class BuiltinOperators
       double result = param1.getAsDouble() + param2.getAsDouble();
       return toDateValue(ctx, mathType, result, param1, param2);
     case LONG:
-      return toValue(param1.getAsLong() + param2.getAsLong());
+      return toValue(param1.getAsLongInt() + param2.getAsLongInt());
     case DOUBLE:
       return toValue(param1.getAsDouble() + param2.getAsDouble());
     case BIG_DEC:
@@ -154,7 +159,7 @@ public class BuiltinOperators
       double result = param1.getAsDouble() - param2.getAsDouble();
       return toDateValue(ctx, mathType, result, param1, param2);
     case LONG:
-      return toValue(param1.getAsLong() - param2.getAsLong());
+      return toValue(param1.getAsLongInt() - param2.getAsLongInt());
     case DOUBLE:
       return toValue(param1.getAsDouble() - param2.getAsDouble());
     case BIG_DEC:
@@ -179,7 +184,7 @@ public class BuiltinOperators
     // case TIME: break; promoted to double
     // case DATE_TIME: break; promoted to double
     case LONG:
-      return toValue(param1.getAsLong() * param2.getAsLong());
+      return toValue(param1.getAsLongInt() * param2.getAsLongInt());
     case DOUBLE:
       return toValue(param1.getAsDouble() * param2.getAsDouble());
     case BIG_DEC:
@@ -204,8 +209,8 @@ public class BuiltinOperators
     // case TIME: break; promoted to double
     // case DATE_TIME: break; promoted to double
     case LONG:
-      long lp1 = param1.getAsLong();
-      long lp2 = param2.getAsLong();
+      int lp1 = param1.getAsLongInt();
+      int lp2 = param2.getAsLongInt();
       if((lp1 % lp2) == 0) {
         return toValue(lp1 / lp2);
       }
@@ -223,7 +228,6 @@ public class BuiltinOperators
     }
   }
 
-  @SuppressWarnings("fallthrough")
   public static Value intDivide(Value param1, Value param2) {
     if(anyParamIsNull(param1, param2)) {
       // null propagation
@@ -232,25 +236,10 @@ public class BuiltinOperators
 
     Value.Type mathType = getMathTypePrecedence(param1, param2, 
                                                 CoercionType.GENERAL);
-
-    boolean wasDouble = false;
-    switch(mathType) {
-    // case STRING: break; unsupported
-    // case DATE: break; promoted to double
-    // case TIME: break; promoted to double
-    // case DATE_TIME: break; promoted to double
-    case LONG:
-      return toValue(param1.getAsLong() / param2.getAsLong());
-    case DOUBLE:
-      wasDouble = true;
-      // fallthrough
-    case BIG_DEC:
-      BigInteger result = getAsBigInteger(param1).divide(
-          getAsBigInteger(param2));
-      return (wasDouble ? toValue(result.longValue()) : toValue(result));
-    default:
+    if(mathType == Value.Type.STRING) {
       throw new RuntimeException("Unexpected type " + mathType);
     }
+    return toValue(param1.getAsLongInt() / param2.getAsLongInt());
   }
 
   public static Value exp(Value param1, Value param2) {
@@ -267,13 +256,12 @@ public class BuiltinOperators
 
     // attempt to convert integral types back to integrals if possible
     if((mathType == Value.Type.LONG) && isIntegral(result)) {
-      return toValue((long)result);
+      return toValue((int)result);
     }
 
     return toValue(result);
   }
 
-  @SuppressWarnings("fallthrough")
   public static Value mod(Value param1, Value param2) {
     if(anyParamIsNull(param1, param2)) {
       // null propagation
@@ -283,33 +271,10 @@ public class BuiltinOperators
     Value.Type mathType = getMathTypePrecedence(param1, param2, 
                                                 CoercionType.GENERAL);
 
-    boolean wasDouble = false;
-    switch(mathType) {
-    // case STRING: break; unsupported
-    // case DATE: break; promoted to double
-    // case TIME: break; promoted to double
-    // case DATE_TIME: break; promoted to double
-    case LONG:
-      return toValue(param1.getAsLong() % param2.getAsLong());
-    case DOUBLE:
-      wasDouble = true;
-      // fallthrough
-    case BIG_DEC:
-      BigInteger bi1 = getAsBigInteger(param1);
-      BigInteger bi2 = getAsBigInteger(param2).abs();
-      if(bi2.signum() == 0) {
-        throw new ArithmeticException(DIV_BY_ZERO);
-      }
-      BigInteger result = bi1.mod(bi2);
-      // BigInteger.mod differs from % when using negative values, need to
-      // make them consistent
-      if((bi1.signum() == -1) && (result.signum() == 1)) {
-        result = result.subtract(bi2);
-      }
-      return (wasDouble ? toValue(result.longValue()) : toValue(result));
-    default:
+    if(mathType == Value.Type.STRING) {
       throw new RuntimeException("Unexpected type " + mathType);
     }
+    return toValue(param1.getAsLongInt() % param2.getAsLongInt());
   }
 
   public static Value concat(Value param1, Value param2) {
@@ -577,7 +542,7 @@ public class BuiltinOperators
     // case TIME: break; promoted to double
     // case DATE_TIME: break; promoted to double
     case LONG:
-      return param1.getAsLong().compareTo(param2.getAsLong());
+      return param1.getAsLongInt().compareTo(param2.getAsLongInt());
     case DOUBLE:
       return param1.getAsDouble().compareTo(param2.getAsDouble());
     case BIG_DEC:
@@ -596,15 +561,11 @@ public class BuiltinOperators
   }
 
   public static Value toValue(int i) {
-    return new LongValue((long)i);
+    return new LongValue(i);
   }
 
-  public static Value toValue(long s) {
-    return new LongValue(s);
-  }
-
-  public static Value toValue(Long s) {
-    return new LongValue(s);
+  public static Value toValue(Integer i) {
+    return new LongValue(i);
   }
 
   public static Value toValue(float f) {
@@ -619,14 +580,15 @@ public class BuiltinOperators
     return new DoubleValue(s);
   }
 
-  public static Value toValue(BigInteger s) {
-    return toValue(new BigDecimal(s));
-  }
-
   public static Value toValue(BigDecimal s) {
     return new BigDecimalValue(s);
   }
 
+  public static Value toValue(Value.Type type, double dd, DateFormat fmt) {
+    return toValue(type, new Date(ColumnImpl.fromDateDouble(dd, fmt.getCalendar())),
+                   fmt);
+  }
+  
   public static Value toValue(Value.Type type, Date d, DateFormat fmt) {
     switch(type) {
     case DATE:
@@ -770,10 +732,9 @@ public class BuiltinOperators
   }
 
   static boolean isIntegral(double d) {
-    return ((d == Math.rint(d)) && !Double.isInfinite(d) && !Double.isNaN(d));
+    double id = Math.rint(d);
+    return ((d == id) && (d >= MIN_INT) && (d <= MAX_INT) &&
+            !Double.isInfinite(d) && !Double.isNaN(d));
   }
 
-  private static BigInteger getAsBigInteger(Value v) {
-    return v.getAsBigDecimal().toBigInteger();
-  }
 }
index 2d3a77700430543fcbd97c44dc9fcdc4802c8db7..1030772add44ef72e8ea12fbae1f8bf7cb7f79df 100644 (file)
@@ -38,6 +38,13 @@ public class DefaultDateFunctions
   private static final double MIN_DATE = -657434.0d;
   // max, valid, recognizable date: December 31, 9999 A.D. 23:59:59
   private static final double MAX_DATE = 2958465.999988426d;
+
+  private static final long SECONDS_PER_DAY = 24L * 60L * 60L;
+  private static final double DSECONDS_PER_DAY = SECONDS_PER_DAY;
+
+  private static final long SECONDS_PER_HOUR = 60L * 60L;
+  private static final long SECONDS_PER_MINUTE = 60L;
+  private static final long MILLIS_PER_SECOND = 1000L;
   
   private DefaultDateFunctions() {}
 
@@ -46,49 +53,81 @@ public class DefaultDateFunctions
   }
 
   public static final Function DATE = registerFunc(new Func0("Date") {
-    @Override
-    public boolean isPure() {
-      return false;
-    }
     @Override
     protected Value eval0(EvalContext ctx) {
-      DateFormat df = BuiltinOperators.getDateFormatForType(ctx, Value.Type.DATE);
-      double dd = ColumnImpl.toDateDouble(System.currentTimeMillis(), df.getCalendar());
-      // the integral part of the date/time double is the date value.  discard
-      // the fractional portion
-      dd = ((long)dd);
-      return BuiltinOperators.toValue(Value.Type.DATE, new Date(), df);
+      DateFormat fmt = BuiltinOperators.getDateFormatForType(ctx, Value.Type.DATE);
+      double dd = dateOnly(currentTimeDouble(fmt));
+      return BuiltinOperators.toValue(Value.Type.DATE, dd, fmt);
     }
   });
 
-  public static final Function NOW = registerFunc(new Func0("Now") {
+  public static final Function DATEVALUE = registerFunc(new Func1NullIsNull("DateValue") {
     @Override
-    public boolean isPure() {
-      return false;
+    protected Value eval1(EvalContext ctx, Value param1) {
+      Value dv = nonNullToDateValue(ctx, param1);
+      if(dv.getType() == Value.Type.DATE) {
+        return dv;
+      }
+      double dd = dateOnly(dv.getAsDouble());
+      DateFormat fmt = BuiltinOperators.getDateFormatForType(ctx, Value.Type.DATE);
+      return BuiltinOperators.toValue(Value.Type.DATE, dd, fmt);
     }
+  });
+    
+  public static final Function NOW = registerFunc(new Func0("Now") {
     @Override
     protected Value eval0(EvalContext ctx) {
-      DateFormat df = BuiltinOperators.getDateFormatForType(ctx, Value.Type.DATE_TIME);
-      return BuiltinOperators.toValue(Value.Type.DATE_TIME, new Date(), df);
+      DateFormat fmt = BuiltinOperators.getDateFormatForType(ctx, Value.Type.DATE_TIME);
+      return BuiltinOperators.toValue(Value.Type.DATE_TIME, new Date(), fmt);
     }
   });
 
   public static final Function TIME = registerFunc(new Func0("Time") {
     @Override
-    public boolean isPure() {
-      return false;
+    protected Value eval0(EvalContext ctx) {
+      DateFormat fmt = BuiltinOperators.getDateFormatForType(ctx, Value.Type.TIME);
+      double dd = timeOnly(currentTimeDouble(fmt));
+      return BuiltinOperators.toValue(Value.Type.TIME, dd, fmt);
+    }
+  });
+
+  public static final Function TIMEVALUE = registerFunc(new Func1NullIsNull("TimeValue") {
+    @Override
+    protected Value eval1(EvalContext ctx, Value param1) {
+      Value dv = nonNullToDateValue(ctx, param1);
+      if(dv.getType() == Value.Type.TIME) {
+        return dv;
+      }
+      double dd = timeOnly(dv.getAsDouble());
+      DateFormat fmt = BuiltinOperators.getDateFormatForType(ctx, Value.Type.TIME);
+      return BuiltinOperators.toValue(Value.Type.TIME, dd, fmt);
     }
+  });
+  
+  public static final Function TIMER = registerFunc(new Func0("Timer") {
     @Override
     protected Value eval0(EvalContext ctx) {
-      DateFormat df = BuiltinOperators.getDateFormatForType(ctx, Value.Type.TIME);
-      double dd = ColumnImpl.toDateDouble(System.currentTimeMillis(), df.getCalendar());
-      // the fractional part of the date/time double is the time value.  discard
-      // the integral portion
-      dd = Math.IEEEremainder(dd, 1.0d);
-      return BuiltinOperators.toValue(Value.Type.TIME, new Date(), df);
+      DateFormat fmt = BuiltinOperators.getDateFormatForType(ctx, Value.Type.TIME);
+      double dd = timeOnly(currentTimeDouble(fmt)) * DSECONDS_PER_DAY;
+      return BuiltinOperators.toValue(dd);
     }
   });
 
+  public static final Function TIMESERIAL = registerFunc(new Func3("TimeSerial") {
+    @Override
+    protected Value eval3(EvalContext ctx, Value param1, Value param2, Value param3) {
+      int hours = param1.getAsLongInt();
+      int minutes = param2.getAsLongInt();
+      int seconds = param3.getAsLongInt();
+
+      long totalSeconds = (hours * SECONDS_PER_HOUR) +
+        (minutes * SECONDS_PER_MINUTE) + seconds;
+      DateFormat fmt = BuiltinOperators.getDateFormatForType(ctx, Value.Type.TIME);
+      double dd = totalSeconds / DSECONDS_PER_DAY;
+      return BuiltinOperators.toValue(Value.Type.TIME, dd, fmt);
+    }
+  });
+  
   public static final Function HOUR = registerFunc(new Func1NullIsNull("Hour") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
@@ -168,11 +207,7 @@ public class DefaultDateFunctions
       throw new IllegalStateException("Invalid date/time expression '" + param + "'");
     }
 
-    Calendar cal = 
-      ((param instanceof BaseDateValue) ?
-       ((BaseDateValue)param).getFormat().getCalendar() :
-       BuiltinOperators.getDateFormatForType(ctx, param.getType()).getCalendar());
-
+    Calendar cal = getDateValueFormat(ctx, param).getCalendar();
     cal.setTime(param.getAsDateTime(ctx));
     return cal;
   }
@@ -206,13 +241,34 @@ public class DefaultDateFunctions
       return null;
     }
 
-    boolean hasDate = (((long)dd) != 0L);
-    boolean hasTime = (Math.IEEEremainder(dd, 1.0d) != 0.0d);
+    boolean hasDate = (dateOnly(dd) != 0.0d);
+    boolean hasTime = (timeOnly(dd) != 0.0d);
 
     Value.Type type = (hasDate ? (hasTime ? Value.Type.DATE_TIME : Value.Type.DATE) :
                        Value.Type.TIME);
-    DateFormat df = BuiltinOperators.getDateFormatForType(ctx, type);
-    Date d = new Date(ColumnImpl.fromDateDouble(dd, df.getCalendar()));
-    return BuiltinOperators.toValue(type, d, df);
+    DateFormat fmt = BuiltinOperators.getDateFormatForType(ctx, type);
+    return BuiltinOperators.toValue(type, dd, fmt);
+  }
+
+  private static DateFormat getDateValueFormat(EvalContext ctx, Value param) {
+    return ((param instanceof BaseDateValue) ?
+            ((BaseDateValue)param).getFormat() :
+       BuiltinOperators.getDateFormatForType(ctx, param.getType()));
+  }
+  
+  private static double dateOnly(double dd) {
+    // the integral part of the date/time double is the date value.  discard
+    // the fractional portion
+    return (long)dd;
+  }
+  
+  private static double timeOnly(double dd) {
+    // the fractional part of the date/time double is the time value.  discard
+    // the integral portion and convert to seconds
+    return new BigDecimal(dd).remainder(BigDecimal.ONE).doubleValue();
+  }
+
+  private static double currentTimeDouble(DateFormat fmt) {
+    return ColumnImpl.toDateDouble(System.currentTimeMillis(), fmt.getCalendar());
   }
 }
index 958442478562d4507e6811ed2aa62b57e069e316..34f0bf642e12c949c3a26d8d02f8f842b3e9e998 100644 (file)
@@ -17,7 +17,6 @@ limitations under the License.
 package com.healthmarketscience.jackcess.impl.expr;
 
 import java.math.BigDecimal;
-import java.math.RoundingMode;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
@@ -37,7 +36,6 @@ public class DefaultFunctions
     new HashMap<String,Function>();
 
   private static final char NON_VAR_SUFFIX = '$';
-  static final RoundingMode DEFAULT_ROUND_MODE = RoundingMode.HALF_EVEN;
   
   static {
     // load all default functions
@@ -58,11 +56,6 @@ public class DefaultFunctions
     private final int _minParams;
     private final int _maxParams;
 
-    protected BaseFunction(String name)
-    {
-      this(name, 0, Integer.MAX_VALUE);
-    }
-
     protected BaseFunction(String name, int minParams, int maxParams)
     {
       _name = name;
@@ -111,6 +104,12 @@ public class DefaultFunctions
       super(name, 0, 0);
     }
 
+    @Override
+    public boolean isPure() {
+      // 0-arg functions are usually not pure
+      return false;
+    }
+
     public final Value eval(EvalContext ctx, Value... params) {
       try {
         validateNumParams(params);
@@ -202,6 +201,10 @@ public class DefaultFunctions
 
   public static abstract class FuncVar extends BaseFunction
   {
+    protected FuncVar(String name) {
+      super(name, 0, Integer.MAX_VALUE);
+    }
+
     protected FuncVar(String name, int minParams, int maxParams) {
       super(name, minParams, maxParams);
     }
@@ -235,8 +238,8 @@ public class DefaultFunctions
          (param1.getAsString().length() == 0)) {
         return BuiltinOperators.ZERO_VAL;
       }
-      long lv = param1.getAsLong();
-      return BuiltinOperators.toValue(Long.toHexString(lv).toUpperCase());
+      int lv = param1.getAsLongInt();
+      return BuiltinOperators.toValue(Integer.toHexString(lv).toUpperCase());
     }
   });
 
@@ -257,6 +260,33 @@ public class DefaultFunctions
     }
   });
 
+  public static final Function CHOOSE = registerFunc(new FuncVar("Choose", 1, Integer.MAX_VALUE) {
+    @Override
+    protected Value evalVar(EvalContext ctx, Value[] params) {
+      Value param1 = params[0];
+      int idx = param1.getAsLongInt();
+      if((idx < 1) || (idx >= params.length)) {
+        return BuiltinOperators.NULL_VAL;
+      }
+      return params[idx];
+    }
+  });
+  
+  public static final Function SWITCH = registerFunc(new FuncVar("Switch") {
+    @Override
+    protected Value evalVar(EvalContext ctx, Value[] params) {
+      if((params.length % 2) != 0) {
+        throw new IllegalStateException("Odd number of parameters");
+      }
+      for(int i = 0; i < params.length; i+=2) {
+        if(params[i].getAsBoolean()) {
+          return params[i + 1];
+        }
+      }
+      return BuiltinOperators.NULL_VAL;
+    }
+  });
+  
   public static final Function OCT = registerStringFunc(new Func1NullIsNull("Oct") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
@@ -264,8 +294,8 @@ public class DefaultFunctions
          (param1.getAsString().length() == 0)) {
         return BuiltinOperators.ZERO_VAL;
       }
-      long lv = param1.getAsLong();
-      return BuiltinOperators.toValue(Long.toOctalString(lv));
+      int lv = param1.getAsLongInt();
+      return BuiltinOperators.toValue(Integer.toOctalString(lv));
     }
   });
   
@@ -280,7 +310,7 @@ public class DefaultFunctions
   public static final Function CBYTE = registerFunc(new Func1("CByte") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      long lv = roundToLong(param1);
+      int lv = param1.getAsLongInt();
       if((lv < 0) || (lv > 255)) {
         throw new IllegalStateException("Byte code '" + lv + "' out of range ");
       }
@@ -292,7 +322,7 @@ public class DefaultFunctions
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
       BigDecimal bd = param1.getAsBigDecimal();
-      bd = bd.setScale(4, DEFAULT_ROUND_MODE);
+      bd = bd.setScale(4, BuiltinOperators.ROUND_MODE);
       return BuiltinOperators.toValue(bd);
     }
   });
@@ -326,7 +356,7 @@ public class DefaultFunctions
   public static final Function CINT = registerFunc(new Func1("CInt") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      long lv = roundToLong(param1);
+      int lv = param1.getAsLongInt();
       if((lv < Short.MIN_VALUE) || (lv > Short.MAX_VALUE)) {
         throw new IllegalStateException("Int value '" + lv + "' out of range ");
       }
@@ -337,10 +367,7 @@ public class DefaultFunctions
   public static final Function CLNG = registerFunc(new Func1("CLng") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      long lv = roundToLong(param1);
-      if((lv < Integer.MIN_VALUE) || (lv > Integer.MAX_VALUE)) {
-        throw new IllegalStateException("Long value '" + lv + "' out of range ");
-      }
+      int lv = param1.getAsLongInt();
       return BuiltinOperators.toValue(lv);
     }
   });
@@ -386,14 +413,79 @@ public class DefaultFunctions
     }
   });
 
+  public static final Function VARTYPE = registerFunc(new Func1("VarType") {
+    @Override
+    protected Value eval1(EvalContext ctx, Value param1) {
+      Value.Type type = param1.getType();
+      int vType = 0;
+      switch(type) {
+      case NULL:
+        // vbNull
+        vType = 1;
+        break;
+      case STRING:
+        // vbString
+        vType = 8;
+        break;
+      case DATE:
+      case TIME:
+      case DATE_TIME:
+        // vbDate
+        vType = 7;
+        break;
+      case LONG:
+        // vbLong
+        vType = 3;
+        break;
+      case DOUBLE:
+        // vbDouble
+        vType = 5;
+        break;
+      case BIG_DEC:
+        // vbDecimal
+        vType = 14;
+        break;        
+      default:
+        throw new RuntimeException("Unknown type " + type);
+      }
+      return BuiltinOperators.toValue(vType);
+    }
+  });
 
-  private static long roundToLong(Value param) {
-    if(param.getType().isIntegral()) {
-      return param.getAsLong();
+  public static final Function TYPENAME = registerFunc(new Func1("TypeName") {
+    @Override
+    protected Value eval1(EvalContext ctx, Value param1) {
+      Value.Type type = param1.getType();
+      String tName = null;
+      switch(type) {
+      case NULL:
+        tName = "Null";
+        break;
+      case STRING:
+        tName = "String";
+        break;
+      case DATE:
+      case TIME:
+      case DATE_TIME:
+        tName = "Date";
+        break;
+      case LONG:
+        tName = "Long";
+        break;
+      case DOUBLE:
+        tName = "Double";
+        break;
+      case BIG_DEC:
+        tName = "Decimal";
+        break;        
+      default:
+        throw new RuntimeException("Unknown type " + type);
+      }
+      return BuiltinOperators.toValue(tName);
     }
-    return param.getAsBigDecimal().setScale(0, DEFAULT_ROUND_MODE)
-      .longValue();
-  }
+  });
+
+  
 
   // https://www.techonthenet.com/access/functions/
   // https://support.office.com/en-us/article/Access-Functions-by-category-b8b136c3-2716-4d39-94a2-658ce330ed83
index 2bd3651135f382d758035dd45d8346a7180a659e..6f717972d996bcded5bd85b8b2cb9430e074fccd 100644 (file)
@@ -49,7 +49,7 @@ public class DefaultNumberFunctions
         double result = Math.abs(param1.getAsDouble());
         return BuiltinOperators.toDateValue(ctx, mathType, result, param1, null);
       case LONG:
-        return BuiltinOperators.toValue(Math.abs(param1.getAsLong()));
+        return BuiltinOperators.toValue(Math.abs(param1.getAsLongInt()));
       case DOUBLE:
         return BuiltinOperators.toValue(Math.abs(param1.getAsDouble()));
       case STRING:
@@ -88,7 +88,7 @@ public class DefaultNumberFunctions
       if(param1.getType().isIntegral()) {
         return param1;
       }
-      return BuiltinOperators.toValue(param1.getAsDouble().longValue());
+      return BuiltinOperators.toValue(param1.getAsDouble().intValue());
     }
   });
 
@@ -98,7 +98,7 @@ public class DefaultNumberFunctions
       if(param1.getType().isIntegral()) {
         return param1;
       }
-      return BuiltinOperators.toValue((long)Math.floor(param1.getAsDouble()));
+      return BuiltinOperators.toValue((int)Math.floor(param1.getAsDouble()));
     }
   });
 
@@ -116,7 +116,7 @@ public class DefaultNumberFunctions
     }
     @Override
     protected Value evalVar(EvalContext ctx, Value[] params) {
-      Long seed = ((params.length > 0) ? params[0].getAsLong() : null);
+      Integer seed = ((params.length > 0) ? params[0].getAsLongInt() : null);
       return BuiltinOperators.toValue(ctx.getRandom(seed).nextFloat());
     }
   });
@@ -133,9 +133,10 @@ public class DefaultNumberFunctions
       }
       int scale = 0;
       if(params.length > 1) {
-        scale = params[1].getAsLong().intValue();
+        scale = params[1].getAsLongInt();
       }
-      BigDecimal bd = param1.getAsBigDecimal().setScale(scale, DEFAULT_ROUND_MODE);
+      BigDecimal bd = param1.getAsBigDecimal()
+        .setScale(scale, BuiltinOperators.ROUND_MODE);
       return BuiltinOperators.toValue(bd);
     }
   });
@@ -145,7 +146,7 @@ public class DefaultNumberFunctions
     protected Value eval1(EvalContext ctx, Value param1) {
       int signum = 0;
       if(param1.getType().isIntegral()) {
-        long lv = param1.getAsLong();
+        int lv = param1.getAsLongInt();
         signum = ((lv > 0) ? 1 : ((lv < 0) ? -1 : 0));
       } else {
         signum = param1.getAsBigDecimal().signum();
index 278ea4ed598f7d2063909c1c7dce4a47c42f4089..96c6115c856b481436726d36861243fe7f66204b 100644 (file)
@@ -44,7 +44,7 @@ public class DefaultTextFunctions
       if(len == 0) {
         throw new IllegalStateException("No characters in string");
       } 
-      long lv = str.charAt(0);
+      int lv = str.charAt(0);
       if((lv < 0) || (lv > 255)) {
         throw new IllegalStateException("Character code '" + lv +
                                         "' out of range ");
@@ -61,7 +61,7 @@ public class DefaultTextFunctions
       if(len == 0) {
         throw new IllegalStateException("No characters in string");
       } 
-      long lv = str.charAt(0);
+      int lv = str.charAt(0);
       return BuiltinOperators.toValue(lv);
     }
   });
@@ -69,12 +69,12 @@ public class DefaultTextFunctions
   public static final Function CHR = registerStringFunc(new Func1("Chr") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      long lv = param1.getAsLong();
+      int lv = param1.getAsLongInt();
       if((lv < 0) || (lv > 255)) {
         throw new IllegalStateException("Character code '" + lv +
                                         "' out of range ");
       }
-      char[] cs = Character.toChars((int)lv);
+      char[] cs = Character.toChars(lv);
       return BuiltinOperators.toValue(new String(cs));
     }
   });
@@ -82,8 +82,8 @@ public class DefaultTextFunctions
   public static final Function CHRW = registerStringFunc(new Func1("ChrW") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      long lv = param1.getAsLong();
-      char[] cs = Character.toChars((int)lv);
+      int lv = param1.getAsLongInt();
+      char[] cs = Character.toChars(lv);
       return BuiltinOperators.toValue(new String(cs));
     }
   });
@@ -107,7 +107,7 @@ public class DefaultTextFunctions
       int start = 0;
       if(params.length > 2) {
         // 1 based offsets
-        start = params[0].getAsLong().intValue() - 1;
+        start = params[0].getAsLongInt() - 1;
         ++idx;
       }
       Value param1 = params[idx++];
@@ -169,7 +169,7 @@ public class DefaultTextFunctions
         return BuiltinOperators.toValue(start + 1);
       }
       if(params.length > 2) {
-        start = params[2].getAsLong().intValue();
+        start = params[2].getAsLongInt();
         if(start == -1) {
           start = s1Len;
         } 
@@ -215,7 +215,7 @@ public class DefaultTextFunctions
         return param1;
       }
       String str = param1.getAsString();
-      int len = (int)Math.min(str.length(), param2.getAsLong());
+      int len = Math.min(str.length(), param2.getAsLongInt());
       return BuiltinOperators.toValue(str.substring(0, len));
     }
   });
@@ -228,7 +228,7 @@ public class DefaultTextFunctions
       }
       String str = param1.getAsString();
       int strLen = str.length();
-      int len = (int)Math.min(strLen, param2.getAsLong());
+      int len = Math.min(strLen, param2.getAsLongInt());
       return BuiltinOperators.toValue(str.substring(strLen - len, strLen));
     }
   });
@@ -243,9 +243,9 @@ public class DefaultTextFunctions
       String str = param1.getAsString();
       int strLen = str.length();
       // 1 based offsets
-      int start = (int)Math.max(strLen, params[1].getAsLong() - 1);
+      int start = Math.max(strLen, params[1].getAsLongInt() - 1);
       int len = Math.max(
-          ((params.length > 2) ? params[2].getAsLong().intValue() : strLen),
+          ((params.length > 2) ? params[2].getAsLongInt() : strLen),
           (strLen - start));
       return BuiltinOperators.toValue(str.substring(start, start + len));
     }
@@ -286,7 +286,7 @@ public class DefaultTextFunctions
   public static final Function SPACE = registerStringFunc(new Func1("Space") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      int lv = param1.getAsLong().intValue();
+      int lv = param1.getAsLongInt();
       return BuiltinOperators.toValue(nchars(lv, ' '));
     }
   });
@@ -317,7 +317,7 @@ public class DefaultTextFunctions
       if(param1.isNull() || param2.isNull()) {
         return BuiltinOperators.NULL_VAL;
       }
-      int lv = param1.getAsLong().intValue();
+      int lv = param1.getAsLongInt();
       char c = (char)(param2.getAsString().charAt(0) % 256);
       return BuiltinOperators.toValue(nchars(lv, c));
     }
@@ -359,7 +359,7 @@ public class DefaultTextFunctions
   }
 
   private static boolean doIgnoreCase(Value paramCmp) {
-    int cmpType = paramCmp.getAsLong().intValue();
+    int cmpType = paramCmp.getAsLongInt();
     switch(cmpType) {
     case -1:
       // vbUseCompareOption -> default is binary
index 563c4bf7a5a5d1ac3aeb515ef1a9a59f1c8e8d96..0efd21a2fb060623377ba4eb2ac7fbc6a4d6ddfc 100644 (file)
@@ -394,7 +394,7 @@ class ExpressionTokenizer
         // what number type to use here?
         Object num = (isFp ? 
                       (Number)Double.valueOf(numStr) : 
-                      (Number)Long.valueOf(numStr));
+                      (Number)Integer.valueOf(numStr));
         foundNum = true;
         return new Token(TokenType.LITERAL, num, numStr, 
                          (isFp ? Value.Type.DOUBLE : Value.Type.LONG));
index ec464ecf37e5953653245e209bdf50b294123b86..9e65c5ec6bb5011082f908efd94d7884c71f7ece 100644 (file)
@@ -1280,7 +1280,7 @@ public class Expressionator
     case DATE_TIME:
       return new DateTimeValue((Date)value, sdf);
     case LONG:
-      return new LongValue((Long)value);
+      return new LongValue((Integer)value);
     case DOUBLE:
       return new DoubleValue((Double)value);
     case BIG_DEC:
@@ -1953,7 +1953,7 @@ public class Expressionator
       case DATE_TIME:
         return val.getAsDateTime(ctx);
       case LONG:
-        return val.getAsLong();
+        return val.getAsLongInt();
       case DOUBLE:
         return val.getAsDouble();
       case BIG_DEC:
index 16ac83de1f14eced536d0918a9a1f2d41962c083..217a5ee003d54a6497a5e111952995ebc96a9af7 100644 (file)
@@ -24,9 +24,9 @@ import java.math.BigDecimal;
  */
 public class LongValue extends BaseNumericValue
 {
-  private final Long _val;
+  private final Integer _val;
 
-  public LongValue(Long val) 
+  public LongValue(Integer val) 
   {
     _val = val;
   }
@@ -50,7 +50,7 @@ public class LongValue extends BaseNumericValue
   }
 
   @Override
-  public Long getAsLong() {
+  public Integer getAsLongInt() {
     return _val;
   }
 
index 61331396ceef62bc30caefe38b704b6932c387b5..be3cfff50304e94a2b21a4303f0f485028f9b194 100644 (file)
@@ -54,8 +54,8 @@ public class StringValue extends BaseValue
   }
 
   @Override
-  public Long getAsLong() {
-    return getNumber().longValue();
+  public Integer getAsLongInt() {
+    return roundToLongInt();
   }
 
   @Override
index d422b72e6435dca6aeb5d13c8c511458aa959d37..ed1aa2335e93af6b53ae2696a6b8bb3ea2835e7a 100644 (file)
@@ -45,8 +45,8 @@ public class DefaultFunctionsTest extends TestCase
   {
     assertEquals("foo", eval("=IIf(10 > 1, \"foo\", \"bar\")"));
     assertEquals("bar", eval("=IIf(10 < 1, \"foo\", \"bar\")"));
-    assertEquals(102L, eval("=Asc(\"foo\")"));
-    assertEquals(9786L, eval("=AscW(\"\u263A\")"));
+    assertEquals(102, eval("=Asc(\"foo\")"));
+    assertEquals(9786, eval("=AscW(\"\u263A\")"));
     assertEquals("f", eval("=Chr(102)"));
     assertEquals("\u263A", eval("=ChrW(9786)"));
     assertEquals("263A", eval("=Hex(9786)"));
@@ -60,16 +60,16 @@ public class DefaultFunctionsTest extends TestCase
     assertEquals(" 9786", eval("=Str(9786)"));
     assertEquals("-42", eval("=Str(-42)"));
 
-    assertEquals(-1L, eval("=CBool(\"1\")"));
-    assertEquals(13L, eval("=CByte(\"13\")"));
-    assertEquals(14L, eval("=CByte(\"13.7\")"));
+    assertEquals(-1, eval("=CBool(\"1\")"));
+    assertEquals(13, eval("=CByte(\"13\")"));
+    assertEquals(14, eval("=CByte(\"13.7\")"));
     assertEquals(new BigDecimal("57.1235"), eval("=CCur(\"57.12346\")"));
     assertEquals(new Double("57.12345"), eval("=CDbl(\"57.12345\")"));
     assertEquals(new BigDecimal("57.123456789"), eval("=CDec(\"57.123456789\")"));
-    assertEquals(513L, eval("=CInt(\"513\")"));
-    assertEquals(514L, eval("=CInt(\"513.7\")"));
-    assertEquals(345513L, eval("=CLng(\"345513\")"));
-    assertEquals(345514L, eval("=CLng(\"345513.7\")"));
+    assertEquals(513, eval("=CInt(\"513\")"));
+    assertEquals(514, eval("=CInt(\"513.7\")"));
+    assertEquals(345513, eval("=CLng(\"345513\")"));
+    assertEquals(345514, eval("=CLng(\"345513.7\")"));
     assertEquals(new Float("57.12345").doubleValue(),
                  eval("=CSng(\"57.12345\")"));
     assertEquals("9786", eval("=CStr(9786)"));
index 6475c4e598f688670ca21cd0a5fb384cd360235c..f0e539798f8a435904e7ab2a3954c59c6ae839d7 100644 (file)
@@ -16,6 +16,7 @@ limitations under the License.
 
 package com.healthmarketscience.jackcess.impl.expr;
 
+import java.math.BigDecimal;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.Random;
@@ -131,7 +132,7 @@ public class ExpressionatorTest extends TestCase
 
   public void testSimpleMathExpressions() throws Exception
   {
-    for(long i = -10L; i <= 10L; ++i) {
+    for(int i = -10; i <= 10; ++i) {
       assertEquals(-i, eval("=-(" + i + ")"));
     }
 
@@ -139,8 +140,8 @@ public class ExpressionatorTest extends TestCase
       assertEquals(-i, eval("=-(" + i + ")"));
     }
 
-    for(long i = -10L; i <= 10L; ++i) {
-      for(long j = -10L; j <= 10L; ++j) {
+    for(int i = -10; i <= 10; ++i) {
+      for(int j = -10; j <= 10; ++j) {
         assertEquals((i + j), eval("=" + i + " + " + j));
       }
     }
@@ -151,8 +152,8 @@ public class ExpressionatorTest extends TestCase
       }
     }
 
-    for(long i = -10L; i <= 10L; ++i) {
-      for(long j = -10L; j <= 10L; ++j) {
+    for(int i = -10; i <= 10; ++i) {
+      for(int j = -10; j <= 10; ++j) {
         assertEquals((i - j), eval("=" + i + " - " + j));
       }
     }
@@ -163,8 +164,8 @@ public class ExpressionatorTest extends TestCase
       }
     }
 
-    for(long i = -10L; i <= 10L; ++i) {
-      for(long j = -10L; j <= 10L; ++j) {
+    for(int i = -10; i <= 10; ++i) {
+      for(int j = -10; j <= 10; ++j) {
         assertEquals((i * j), eval("=" + i + " * " + j));
       }
     }
@@ -175,8 +176,8 @@ public class ExpressionatorTest extends TestCase
       }
     }
 
-    for(long i = -10L; i <= 10L; ++i) {
-      for(long j = -10L; j <= 10L; ++j) {
+    for(int i = -10; i <= 10; ++i) {
+      for(int j = -10; j <= 10; ++j) {
         if(j == 0L) {
           evalFail("=" + i + " \\ " + j, ArithmeticException.class);
         } else {
@@ -187,17 +188,18 @@ public class ExpressionatorTest extends TestCase
 
     for(double i : DBLS) {
       for(double j : DBLS) {
-        if((long)j == 0L) {
+        if(roundToLongInt(j) == 0) {
           evalFail("=" + i + " \\ " + j, ArithmeticException.class);
         } else {
-          assertEquals(((long)i / (long)j), eval("=" + i + " \\ " + j));
+          assertEquals((roundToLongInt(i) / roundToLongInt(j)),
+                       eval("=" + i + " \\ " + j));
         }
       }
     }
 
-    for(long i = -10L; i <= 10L; ++i) {
-      for(long j = -10L; j <= 10L; ++j) {
-        if(j == 0L) {
+    for(int i = -10; i <= 10; ++i) {
+      for(int j = -10; j <= 10; ++j) {
+        if(j == 0) {
           evalFail("=" + i + " Mod " + j, ArithmeticException.class);
         } else {
           assertEquals((i % j), eval("=" + i + " Mod " + j));
@@ -207,22 +209,23 @@ public class ExpressionatorTest extends TestCase
 
     for(double i : DBLS) {
       for(double j : DBLS) {
-        if((long)j == 0L) {
+        if(roundToLongInt(j) == 0) {
           evalFail("=" + i + " Mod " + j, ArithmeticException.class);
         } else {
-          assertEquals(((long)i % (long)j), eval("=" + i + " Mod " + j));
+          assertEquals((roundToLongInt(i) % roundToLongInt(j)),
+                       eval("=" + i + " Mod " + j));
         }
       }
     }
 
-    for(long i = -10L; i <= 10L; ++i) {
-      for(long j = -10L; j <= 10L; ++j) {
-        if(j == 0L) {
+    for(int i = -10; i <= 10; ++i) {
+      for(int j = -10; j <= 10; ++j) {
+        if(j == 0) {
           evalFail("=" + i + " / " + j, ArithmeticException.class);
         } else {
           double result = (double)i / (double)j;
-          if((long)result == result) {
-            assertEquals((long)result, eval("=" + i + " / " + j));
+          if((int)result == result) {
+            assertEquals((int)result, eval("=" + i + " / " + j));
           } else {
             assertEquals(result, eval("=" + i + " / " + j));
           }
@@ -240,11 +243,11 @@ public class ExpressionatorTest extends TestCase
       }
     }
 
-    for(long i = -10L; i <= 10L; ++i) {
-      for(long j = -10L; j <= 10L; ++j) {
+    for(int i = -10; i <= 10; ++i) {
+      for(int j = -10; j <= 10; ++j) {
         double result = Math.pow(i, j);
-        if((long)result == result) {
-          assertEquals((long)result, eval("=" + i + " ^ " + j));
+        if((int)result == result) {
+          assertEquals((int)result, eval("=" + i + " ^ " + j));
         } else {
           assertEquals(result, eval("=" + i + " ^ " + j));
         }
@@ -261,8 +264,8 @@ public class ExpressionatorTest extends TestCase
     assertEquals("12foo", eval("=12 + \"foo\""));
     assertEquals("foo12", eval("=\"foo\" + 12"));
 
-    assertEquals(37L, eval("=\"25\" + 12"));
-    assertEquals(37L, eval("=12 + \"25\""));
+    assertEquals(37, eval("=\"25\" + 12"));
+    assertEquals(37, eval("=12 + \"25\""));
 
     evalFail(("=12 - \"foo\""), RuntimeException.class);
     evalFail(("=\"foo\" - 12"), RuntimeException.class);
@@ -273,7 +276,7 @@ public class ExpressionatorTest extends TestCase
     assertEquals("25foo12", eval("=\"25foo\" + 12"));
 
     assertEquals(new Date(1485579600000L), eval("=#1/1/2017# + 27"));
-    assertEquals(128208L, eval("=#1/1/2017# * 3"));
+    assertEquals(128208, eval("=#1/1/2017# * 3"));
   }
 
   public void testLikeExpression() throws Exception
@@ -331,6 +334,11 @@ public class ExpressionatorTest extends TestCase
     return (Boolean)expr.eval(new TestEvalContext(BuiltinOperators.toValue(thisVal)));
   }
 
+  static int roundToLongInt(double d) {
+    return new BigDecimal(d).setScale(0, BuiltinOperators.ROUND_MODE)
+      .intValueExact();
+  }
+  
   private static final class TestParseContext implements Expressionator.ParseContext
   {
     public TemporalConfig getTemporalConfig() {
@@ -384,7 +392,7 @@ public class ExpressionatorTest extends TestCase
       throw new UnsupportedOperationException();
     }
 
-    public Random getRandom(Long seed) {
+    public Random getRandom(Integer seed) {
       if(seed == null) {
         if(_defRnd == null) {
           _defRnd = new Random(System.currentTimeMillis());