diff options
17 files changed, 327 insertions, 204 deletions
diff --git a/src/main/java/com/healthmarketscience/jackcess/expr/EvalContext.java b/src/main/java/com/healthmarketscience/jackcess/expr/EvalContext.java index 9f442f1..c140594 100644 --- a/src/main/java/com/healthmarketscience/jackcess/expr/EvalContext.java +++ b/src/main/java/com/healthmarketscience/jackcess/expr/EvalContext.java @@ -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); } diff --git a/src/main/java/com/healthmarketscience/jackcess/expr/Value.java b/src/main/java/com/healthmarketscience/jackcess/expr/Value.java index 1fd4390..39008f2 100644 --- a/src/main/java/com/healthmarketscience/jackcess/expr/Value.java +++ b/src/main/java/com/healthmarketscience/jackcess/expr/Value.java @@ -73,7 +73,7 @@ public interface Value public Date getAsDateTime(EvalContext ctx); - public Long getAsLong(); + public Integer getAsLongInt(); public Double getAsDouble(); diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDateValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDateValue.java index 4bf03af..188416a 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDateValue.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDateValue.java @@ -67,8 +67,8 @@ public abstract class BaseDateValue extends BaseValue } @Override - public Long getAsLong() { - return getNumber().longValue(); + public Integer getAsLongInt() { + return roundToLongInt(); } @Override diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDelayedValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDelayedValue.java index c34a914..e8ae339 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDelayedValue.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDelayedValue.java @@ -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() { diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseNumericValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseNumericValue.java index 585c71e..dd12d7c 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseNumericValue.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseNumericValue.java @@ -39,8 +39,8 @@ public abstract class BaseNumericValue extends BaseValue } @Override - public Long getAsLong() { - return getNumber().longValue(); + public Integer getAsLongInt() { + return roundToLongInt(); } @Override diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseValue.java index e30c303..6107fbc 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseValue.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseValue.java @@ -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() + "'"; diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java index 0cf71c4..2037f34 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java @@ -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(); - } } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java index 2d3a777..1030772 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java @@ -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() {} @@ -47,48 +54,80 @@ 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()); } } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java index 9584424..34f0bf6 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java @@ -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 diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultNumberFunctions.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultNumberFunctions.java index 2bd3651..6f71797 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultNumberFunctions.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultNumberFunctions.java @@ -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(); diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultTextFunctions.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultTextFunctions.java index 278ea4e..96c6115 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultTextFunctions.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultTextFunctions.java @@ -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 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 563c4bf..0efd21a 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java @@ -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)); 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 ec464ec..9e65c5e 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java @@ -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: diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/LongValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/LongValue.java index 16ac83d..217a5ee 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/LongValue.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/LongValue.java @@ -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; } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/StringValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/StringValue.java index 6133139..be3cfff 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/StringValue.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/StringValue.java @@ -54,8 +54,8 @@ public class StringValue extends BaseValue } @Override - public Long getAsLong() { - return getNumber().longValue(); + public Integer getAsLongInt() { + return roundToLongInt(); } @Override diff --git a/src/test/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctionsTest.java b/src/test/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctionsTest.java index d422b72..ed1aa23 100644 --- a/src/test/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctionsTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctionsTest.java @@ -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)")); 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 6475c4e..f0e5397 100644 --- a/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java @@ -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()); |