]> source.dussan.org Git - jackcess.git/commitdiff
checkpoint reworking expression classes and implementing many basic operations
authorJames Ahlborn <jtahlborn@yahoo.com>
Fri, 9 Dec 2016 14:48:33 +0000 (14:48 +0000)
committerJames Ahlborn <jtahlborn@yahoo.com>
Fri, 9 Dec 2016 14:48:33 +0000 (14:48 +0000)
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/branches/exprs@1067 f203690c-595d-4dc9-a70b-905162fa7fd2

20 files changed:
src/main/java/com/healthmarketscience/jackcess/expr/Value.java
src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDateValue.java [new file with mode: 0644]
src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDelayedValue.java [new file with mode: 0644]
src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseNumericValue.java [new file with mode: 0644]
src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseValue.java [new file with mode: 0644]
src/main/java/com/healthmarketscience/jackcess/impl/expr/BigDecimalValue.java [new file with mode: 0644]
src/main/java/com/healthmarketscience/jackcess/impl/expr/BigIntegerValue.java [new file with mode: 0644]
src/main/java/com/healthmarketscience/jackcess/impl/expr/BooleanValue.java [new file with mode: 0644]
src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/DateTimeValue.java [new file with mode: 0644]
src/main/java/com/healthmarketscience/jackcess/impl/expr/DateValue.java [new file with mode: 0644]
src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/DoubleValue.java [new file with mode: 0644]
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 [new file with mode: 0644]
src/main/java/com/healthmarketscience/jackcess/impl/expr/StringValue.java [new file with mode: 0644]
src/main/java/com/healthmarketscience/jackcess/impl/expr/TimeValue.java [new file with mode: 0644]
src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnMatcher.java

index 5c1d13f358c474fa20ca61a8a46fad20bb5c150c..c9a2a221f9adf85e10c46352380d8b105f87d668 100644 (file)
@@ -16,6 +16,10 @@ limitations under the License.
 
 package com.healthmarketscience.jackcess.expr;
 
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Date;
+
 /**
  *
  * @author James Ahlborn
@@ -24,16 +28,26 @@ public interface Value
 {
   public enum Type
   {
+    // FIXME, ditch boolean type -> -1,0
     NULL, BOOLEAN, STRING, DATE, TIME, DATE_TIME, LONG, DOUBLE, BIG_INT, BIG_DEC;
 
     public boolean isNumeric() {
       return inRange(LONG, BIG_DEC);
     }
 
+    public boolean isIntegral() {
+      // note when BOOLEAN is converted to number, it is integral
+      return ((this == LONG) || (this == BIG_INT) || (this == BOOLEAN));
+    }
+
     public boolean isTemporal() {
       return inRange(DATE, DATE_TIME);
     }
 
+    public Type getPreferredFPType() {
+      return((ordinal() <= DOUBLE.ordinal()) ? DOUBLE : BIG_DEC);
+    }
+
     private boolean inRange(Type start, Type end) {
       return ((start.ordinal() <= ordinal()) && (ordinal() <= end.ordinal()));
     }
@@ -41,5 +55,22 @@ public interface Value
 
 
   public Type getType();
+
   public Object get();
+
+  public Boolean getAsBoolean();
+
+  public String getAsString();
+
+  public Date getAsDateTime();
+
+  public Long getAsLong();
+
+  public Double getAsDouble();
+
+  public BigInteger getAsBigInteger();
+
+  public BigDecimal getAsBigDecimal();
+
+  public Value toNumericValue();
 }
index d61bab87a1a386301ab4b9b6c87e0d71f223a688..203ad82a46334286e3acffee369702e29419295a 100644 (file)
@@ -795,9 +795,27 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
    * @usage _advanced_method_
    */
   public long fromDateDouble(double value)
+  {
+    return fromDateDouble(value, getCalendar());
+  }
+
+  /**
+   * Returns a java long time value converted from an access date double.
+   * @usage _advanced_method_
+   */
+  public static long fromDateDouble(double value, DatabaseImpl db)
+  {
+    return fromDateDouble(value, db.getCalendar());
+  }
+
+  /**
+   * Returns a java long time value converted from an access date double.
+   * @usage _advanced_method_
+   */
+  public static long fromDateDouble(double value, Calendar c)
   {
     long localTime = fromLocalDateDouble(value);
-    return localTime - getFromLocalTimeZoneOffset(localTime);
+    return localTime - getFromLocalTimeZoneOffset(localTime, c);
   }
 
   static long fromLocalDateDouble(double value)
@@ -842,11 +860,31 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
    * @usage _advanced_method_
    */
   public double toDateDouble(Object value)
+  {
+    return toDateDouble(value, getCalendar());
+  }
+
+  /**
+   * Returns an access date double converted from a java Date/Calendar/Number
+   * time value.
+   * @usage _advanced_method_
+   */
+  public static double toDateDouble(Object value, DatabaseImpl db)
+  {
+    return toDateDouble(value, db.getCalendar());
+  }
+
+  /**
+   * Returns an access date double converted from a java Date/Calendar/Number
+   * time value.
+   * @usage _advanced_method_
+   */
+  public static double toDateDouble(Object value, Calendar c)
   {
     // seems access stores dates in the local timezone.  guess you just
     // hope you read it in the same timezone in which it was written!
     long time = toDateLong(value);
-    time += getToLocalTimeZoneOffset(time);
+    time += getToLocalTimeZoneOffset(time, c);
     return toLocalDateDouble(time);
   }
 
@@ -881,9 +919,8 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
    * Gets the timezone offset from UTC to local time for the given time
    * (including DST).
    */
-  private long getToLocalTimeZoneOffset(long time)
+  private static long getToLocalTimeZoneOffset(long time, Calendar c)
   {
-    Calendar c = getCalendar();
     c.setTimeInMillis(time);
     return ((long)c.get(Calendar.ZONE_OFFSET) + c.get(Calendar.DST_OFFSET));
   }  
@@ -892,11 +929,10 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
    * Gets the timezone offset from local time to UTC for the given time
    * (including DST).
    */
-  private long getFromLocalTimeZoneOffset(long time)
+  private static long getFromLocalTimeZoneOffset(long time, Calendar c)
   {
     // getting from local time back to UTC is a little wonky (and not
     // guaranteed to get you back to where you started)
-    Calendar c = getCalendar();
     c.setTimeInMillis(time);
     // apply the zone offset first to get us closer to the original time
     c.setTimeInMillis(time - c.get(Calendar.ZONE_OFFSET));
@@ -1414,7 +1450,17 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
    *         <code>null</code> is returned as 0 and Numbers are converted
    *         using their double representation.
    */
-  static BigDecimal toBigDecimal(Object value)
+  BigDecimal toBigDecimal(Object value)
+  {
+    return toBigDecimal(value, getDatabase());
+  }
+
+  /**
+   * @return an appropriate BigDecimal representation of the given object.
+   *         <code>null</code> is returned as 0 and Numbers are converted
+   *         using their double representation.
+   */
+  static BigDecimal toBigDecimal(Object value, DatabaseImpl db)
   {
     if(value == null) {
       return BigDecimal.ZERO;
@@ -1424,6 +1470,11 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
       return new BigDecimal((BigInteger)value);
     } else if(value instanceof Number) {
       return new BigDecimal(((Number)value).doubleValue());
+    } else if(value instanceof Boolean) {
+      // access seems to like -1 for true and 0 for false
+      return ((Boolean)value) ? BigDecimal.valueOf(-1) : BigDecimal.ZERO;
+    } else if(value instanceof Date) {
+      return new BigDecimal(toDateDouble(value, db));
     }
     return new BigDecimal(value.toString());
   }
@@ -1433,12 +1484,27 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
    *         <code>null</code> is returned as 0 and Strings are parsed as
    *         Doubles.
    */
-  private static Number toNumber(Object value)
+  private Number toNumber(Object value)
+  {
+    return toNumber(value, getDatabase());
+  }
+
+  /**
+   * @return an appropriate Number representation of the given object.
+   *         <code>null</code> is returned as 0 and Strings are parsed as
+   *         Doubles.
+   */
+  private static Number toNumber(Object value, DatabaseImpl db)
   {
     if(value == null) {
       return BigDecimal.ZERO;
     } else if(value instanceof Number) {
       return (Number)value;
+    } else if(value instanceof Boolean) {
+      // access seems to like -1 for true and 0 for false
+      return ((Boolean)value) ? -1 : 0;
+    } else if(value instanceof Date) {
+      return toDateDouble(value, db);
     }
     return Double.valueOf(value.toString());
   }
@@ -1532,7 +1598,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
       if(obj instanceof BigInteger) {
         return (((BigInteger)obj).compareTo(BigInteger.ZERO) != 0);
       }
-      return (((Number)obj).doubleValue() != 0.0);
+      return (((Number)obj).doubleValue() != 0.0d);
     }
     return Boolean.parseBoolean(obj.toString());
   }
@@ -1549,11 +1615,11 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
   }
 
   /**
-   * Treat booleans as integers (C-style).
+   * Treat booleans as integers (access-style).
    */
   protected static Object booleanToInteger(Object obj) {
     if (obj instanceof Boolean) {
-      obj = ((Boolean) obj) ? 1 : 0;
+      obj = ((Boolean) obj) ? -1 : 0;
     }
     return obj;
   }
@@ -1770,7 +1836,8 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
    * Converts the given value to the "internal" representation for the given
    * data type.
    */
-  public static Object toInternalValue(DataType dataType, Object value)
+  public static Object toInternalValue(DataType dataType, Object value,
+                                       DatabaseImpl db)
     throws IOException
   {
     if(value == null) {
@@ -1781,21 +1848,21 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
     case BOOLEAN:
       return ((value instanceof Boolean) ? value : toBooleanValue(value));
     case BYTE:
-      return ((value instanceof Byte) ? value : toNumber(value).byteValue());
+      return ((value instanceof Byte) ? value : toNumber(value, db).byteValue());
     case INT:
       return ((value instanceof Short) ? value : 
-              toNumber(value).shortValue());
+              toNumber(value, db).shortValue());
     case LONG:
       return ((value instanceof Integer) ? value : 
-              toNumber(value).intValue());
+              toNumber(value, db).intValue());
     case MONEY:
-      return toBigDecimal(value);
+      return toBigDecimal(value, db);
     case FLOAT:
       return ((value instanceof Float) ? value : 
-              toNumber(value).floatValue());
+              toNumber(value, db).floatValue());
     case DOUBLE:
       return ((value instanceof Double) ? value : 
-              toNumber(value).doubleValue());
+              toNumber(value, db).doubleValue());
     case SHORT_DATE_TIME:
       return ((value instanceof DateExt) ? value :
               new Date(toDateLong(value)));
@@ -1805,7 +1872,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
       return ((value instanceof String) ? value : 
               toCharSequence(value).toString());
     case NUMERIC:
-      return toBigDecimal(value);
+      return toBigDecimal(value, db);
     case COMPLEX_TYPE:
       // leave alone for now?
       return value;
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDateValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDateValue.java
new file mode 100644 (file)
index 0000000..8a3ff8f
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+Copyright (c) 2016 James Ahlborn
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package com.healthmarketscience.jackcess.impl.expr;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.text.DateFormat;
+import java.util.Date;
+
+import com.healthmarketscience.jackcess.expr.Value;
+import com.healthmarketscience.jackcess.impl.ColumnImpl;
+
+/**
+ *
+ * @author James Ahlborn
+ */
+public abstract class BaseDateValue extends BaseValue
+{
+  private final Date _val;
+  private final DateFormat _fmt;
+
+  public BaseDateValue(Date val, DateFormat fmt) 
+  {
+    _val = val;
+    _fmt = fmt;
+  }
+
+  public Object get() {
+    return _val;
+  }
+
+  protected Double getNumber() {
+    return ColumnImpl.toDateDouble(_val, _fmt.getCalendar());
+  }
+
+  @Override
+  public Boolean getAsBoolean() {
+    // ms access seems to treat dates/times as "true"
+    return Boolean.TRUE;
+  }
+
+  @Override
+  public String getAsString() {
+    return _fmt.format(_val);
+  }
+
+  @Override
+  public Long getAsLong() {
+    return getNumber().longValue();
+  }
+
+  @Override
+  public Double getAsDouble() {
+    return getNumber();
+  }
+
+  @Override
+  public BigInteger getAsBigInteger() {
+    return getAsBigDecimal().toBigInteger();
+  }
+
+  @Override
+  public BigDecimal getAsBigDecimal() {
+    return BigDecimal.valueOf(getNumber());
+  }
+
+  @Override
+  public Value toNumericValue() {
+    return new DoubleValue(getNumber());
+  }
+}
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDelayedValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDelayedValue.java
new file mode 100644 (file)
index 0000000..8849390
--- /dev/null
@@ -0,0 +1,70 @@
+// Copyright (c) 2016 Dell Boomi, Inc.
+
+package com.healthmarketscience.jackcess.impl.expr;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Date;
+
+import com.healthmarketscience.jackcess.expr.Value;
+
+/**
+ *
+ * @author James Ahlborn
+ */
+public abstract class BaseDelayedValue implements Value
+{
+  private Value _val;
+
+  protected BaseDelayedValue() {
+  }
+
+  private Value getDelegate() {
+    if(_val == null) {
+      _val = eval();
+    }
+    return _val;
+  }
+
+  public Value.Type getType() {
+    return getDelegate().getType();
+  }
+
+  public Object get() {
+    return getDelegate().get();
+  }
+
+  public Boolean getAsBoolean() {
+    return getDelegate().getAsBoolean();
+  }
+
+  public String getAsString() {
+    return getDelegate().getAsString();
+  }
+
+  public Date getAsDateTime() {
+    return getDelegate().getAsDateTime();
+  }
+
+  public Long getAsLong() {
+    return getDelegate().getAsLong();
+  }
+
+  public Double getAsDouble() {
+    return getDelegate().getAsDouble();
+  }
+
+  public BigInteger getAsBigInteger() {
+    return getDelegate().getAsBigInteger();
+  }
+
+  public BigDecimal getAsBigDecimal() {
+    return getDelegate().getAsBigDecimal();
+  }
+
+  public Value toNumericValue() {
+    return getDelegate().toNumericValue();
+  }
+
+  protected abstract Value eval();
+}
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseNumericValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseNumericValue.java
new file mode 100644 (file)
index 0000000..ab6b24b
--- /dev/null
@@ -0,0 +1,39 @@
+// Copyright (c) 2016 Dell Boomi, Inc.
+
+package com.healthmarketscience.jackcess.impl.expr;
+
+import com.healthmarketscience.jackcess.expr.Value;
+
+/**
+ *
+ * @author James Ahlborn
+ */
+public abstract class BaseNumericValue extends BaseValue 
+{
+
+  protected BaseNumericValue() 
+  {
+  }
+
+  @Override
+  public String getAsString() {
+    return getNumber().toString();
+  }
+
+  @Override
+  public Long getAsLong() {
+    return getNumber().longValue();
+  }
+
+  @Override
+  public Double getAsDouble() {
+    return getNumber().doubleValue();
+  }
+
+  @Override
+  public Value toNumericValue() {
+    return this;
+  }
+
+  protected abstract Number getNumber();
+}
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseValue.java
new file mode 100644 (file)
index 0000000..d75e671
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+Copyright (c) 2016 James Ahlborn
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package com.healthmarketscience.jackcess.impl.expr;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Date;
+
+import com.healthmarketscience.jackcess.expr.Value;
+
+/**
+ *
+ * @author James Ahlborn
+ */
+public abstract class BaseValue implements Value
+{
+  public Boolean getAsBoolean() {
+    throw invalidConversion(Value.Type.BOOLEAN);
+  }
+
+  public String getAsString() {
+    throw invalidConversion(Value.Type.STRING);
+  }
+
+  public Date getAsDateTime() {
+    throw invalidConversion(Value.Type.DATE_TIME);
+  }
+
+  public Long getAsLong() {
+    throw invalidConversion(Value.Type.LONG);
+  }
+
+  public Double getAsDouble() {
+    throw invalidConversion(Value.Type.DOUBLE);
+  }
+
+  public BigInteger getAsBigInteger() {
+    throw invalidConversion(Value.Type.BIG_INT);
+  }
+
+  public BigDecimal getAsBigDecimal() {
+    throw invalidConversion(Value.Type.BIG_DEC);
+  }
+
+  public Value toNumericValue() {
+    throw invalidConversion(Value.Type.LONG);
+  }
+
+  private UnsupportedOperationException invalidConversion(Value.Type newType) {
+    return new UnsupportedOperationException(
+        getType() + " value cannot be converted to " + newType);
+  }
+
+  @Override
+  public String toString() {
+    return "Value[" + getType() + "] '" + get() + "'";
+  }
+}
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BigDecimalValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BigDecimalValue.java
new file mode 100644 (file)
index 0000000..386eb79
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+Copyright (c) 2016 James Ahlborn
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package com.healthmarketscience.jackcess.impl.expr;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+/**
+ *
+ * @author James Ahlborn
+ */
+public class BigDecimalValue extends BaseNumericValue
+{
+  private final BigDecimal _val;
+
+  public BigDecimalValue(BigDecimal val) 
+  {
+    _val = val;
+  }
+
+  public Type getType() {
+    return Type.BIG_DEC;
+  }
+
+  public Object get() {
+    return _val;
+  }
+
+  @Override
+  protected Number getNumber() {
+    return _val;
+  }
+
+  @Override
+  public Boolean getAsBoolean() {
+    return (_val.compareTo(BigDecimal.ZERO) != 0L);
+  }
+
+  @Override
+  public String getAsString() {
+    return _val.toPlainString();
+  }
+
+  @Override
+  public BigInteger getAsBigInteger() {
+    return _val.toBigInteger();
+  }
+
+  @Override
+  public BigDecimal getAsBigDecimal() {
+    return _val;
+  }
+}
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BigIntegerValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BigIntegerValue.java
new file mode 100644 (file)
index 0000000..9aa787f
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+Copyright (c) 2016 James Ahlborn
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package com.healthmarketscience.jackcess.impl.expr;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+/**
+ *
+ * @author James Ahlborn
+ */
+public class BigIntegerValue extends BaseNumericValue
+{
+  private final BigInteger _val;
+
+  public BigIntegerValue(BigInteger val) 
+  {
+    _val = val;
+  }
+
+  public Type getType() {
+    return Type.BIG_INT;
+  }
+
+  public Object get() {
+    return _val;
+  }
+
+  @Override
+  protected Number getNumber() {
+    return _val;
+  }
+
+  @Override
+  public Boolean getAsBoolean() {
+    return (_val.compareTo(BigInteger.ZERO) != 0L);
+  }
+
+  @Override
+  public BigInteger getAsBigInteger() {
+    return _val;
+  }
+
+  @Override
+  public BigDecimal getAsBigDecimal() {
+    return new BigDecimal(_val);
+  }
+}
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BooleanValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BooleanValue.java
new file mode 100644 (file)
index 0000000..cc31617
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+Copyright (c) 2016 James Ahlborn
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package com.healthmarketscience.jackcess.impl.expr;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import com.healthmarketscience.jackcess.expr.Value;
+
+/**
+ *
+ * @author James Ahlborn
+ */
+public class BooleanValue extends BaseValue
+{
+  private final Boolean _val;
+
+  public BooleanValue(Boolean val) 
+  {
+    _val = val;
+  }
+
+  public Type getType() {
+    return Type.BOOLEAN;
+  }
+
+  public Object get() {
+    return _val;
+  }
+
+  @Override
+  public Boolean getAsBoolean() {
+    return _val;
+  }
+
+  @Override
+  public String getAsString() {
+    // access seems to like -1 for true and 0 for false
+    return (_val ? "-1" : "0");
+  }
+
+  @Override
+  public Long getAsLong() {
+    // access seems to like -1 for true and 0 for false
+    return numericBoolean(_val);
+  }
+
+  @Override
+  public Double getAsDouble() {
+    // access seems to like -1 for true and 0 for false
+    return (_val ? -1d : 0d);
+  }
+
+  @Override
+  public BigInteger getAsBigInteger() {
+    // access seems to like -1 for true and 0 for false
+    return (_val ? BigInteger.valueOf(-1) : BigInteger.ZERO);
+  }
+
+  @Override
+  public BigDecimal getAsBigDecimal() {
+    // access seems to like -1 for true and 0 for false
+    return (_val ? BigDecimal.valueOf(-1) : BigDecimal.ZERO);
+  }
+
+  @Override
+  public Value toNumericValue() {
+    return new LongValue(getAsLong());
+  }
+
+  protected static long numericBoolean(Boolean b) {
+    // access seems to like -1 for true and 0 for false
+    return (b ? -1L : 0L);
+  }
+}
index aa58b9715d43456da5ff16875ffe4f4bdbe10b07..2ef3c23df4544796eaa7dacc706798d03b3a1f10 100644 (file)
@@ -16,19 +16,15 @@ limitations under the License.
 
 package com.healthmarketscience.jackcess.impl.expr;
 
-import java.io.IOException;
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.text.DateFormat;
+import java.text.Format;
 import java.util.Date;
-import java.util.Map;
 import java.util.regex.Pattern;
 
-import com.healthmarketscience.jackcess.RuntimeIOException;
-import com.healthmarketscience.jackcess.impl.ColumnImpl;
-import com.healthmarketscience.jackcess.expr.Expression;
 import com.healthmarketscience.jackcess.expr.Value;
-import com.healthmarketscience.jackcess.expr.Function;
-import com.healthmarketscience.jackcess.expr.RowContext;
+import com.healthmarketscience.jackcess.impl.ColumnImpl;
 
 
 /**
@@ -38,31 +34,17 @@ import com.healthmarketscience.jackcess.expr.RowContext;
 public class BuiltinOperators 
 {
 
-  public static final Value NULL_VAL = 
-    new SimpleValue(Value.Type.NULL, null);
-  public static final Value TRUE_VAL = 
-    new SimpleValue(Value.Type.BOOLEAN, Boolean.TRUE);
-  public static final Value FALSE_VAL = 
-    new SimpleValue(Value.Type.BOOLEAN, Boolean.FALSE);
-
-  public static class SimpleValue implements Value
-  {
-    private final Value.Type _type;
-    private final Object _val;
-
-    public SimpleValue(Value.Type type, Object val) {
-      _type = type;
-      _val = val;
-    }
-
-    public Value.Type getType() {
-      return _type;
+  public static final Value NULL_VAL = new BaseValue() {
+    public Type getType() {
+      return Type.NULL;
     }
-
     public Object get() {
-      return _val;
+      return null;
     }
-  }
+  };
+  public static final Value TRUE_VAL = new BooleanValue(Boolean.TRUE);
+  public static final Value FALSE_VAL = new BooleanValue(Boolean.FALSE);
+  public static final Value EMPTY_STR_VAL = new StringValue("");
 
   private BuiltinOperators() {}
 
@@ -79,51 +61,275 @@ public class BuiltinOperators
   // FIXME, Imp operator?
 
   public static Value negate(Value param1) {
-    // FIXME
-    return null;
+    if(paramIsNull(param1)) {
+      // null propagation
+      return NULL_VAL;
+    }
+
+    Value.Type mathType = param1.getType();
+
+    switch(mathType) {
+    case BOOLEAN:
+      return toValue(-getAsNumericBoolean(param1));
+    // case STRING: break; unsupported
+    case DATE:
+    case TIME:
+    case DATE_TIME:
+      // dates/times get converted to date doubles for arithmetic
+      double result = -param1.getAsDouble();
+      return toDateValue(mathType, result, param1, null);
+    case LONG:
+      return toValue(-param1.getAsLong());
+    case DOUBLE:
+      return toValue(-param1.getAsDouble());
+    case BIG_INT:
+      return toValue(param1.getAsBigInteger().negate());
+    case BIG_DEC:
+      return toValue(param1.getAsBigDecimal().negate());
+    default:
+      throw new RuntimeException("Unexpected type " + mathType);
+    }
   }
 
   public static Value add(Value param1, Value param2) {
-    // FIXME
-    return null;
+    if(anyParamIsNull(param1, param2)) {
+      // null propagation
+      return NULL_VAL;
+    }
+
+    Value.Type mathType = getSimpleMathTypePrecedence(param1, param2);
+
+    switch(mathType) {
+    case BOOLEAN:
+      return toValue(getAsNumericBoolean(param1) + getAsNumericBoolean(param2));
+    case STRING: 
+      // string '+' is a null-propagation (handled above) concat
+      return nonNullConcat(param1, param2);
+    case DATE:
+    case TIME:
+    case DATE_TIME:
+      // dates/times get converted to date doubles for arithmetic
+      double result = param1.getAsDouble() + param2.getAsDouble();
+      return toDateValue(mathType, result, param1, param2);
+    case LONG:
+      return toValue(param1.getAsLong() + param2.getAsLong());
+    case DOUBLE:
+      return toValue(param1.getAsDouble() + param2.getAsDouble());
+    case BIG_INT:
+      return toValue(param1.getAsBigInteger().add(param2.getAsBigInteger()));
+    case BIG_DEC:
+      return toValue(param1.getAsBigDecimal().add(param2.getAsBigDecimal()));
+    default:
+      throw new RuntimeException("Unexpected type " + mathType);
+    }
   }
 
   public static Value subtract(Value param1, Value param2) {
-    // FIXME
-    return null;
+    if(anyParamIsNull(param1, param2)) {
+      // null propagation
+      return NULL_VAL;
+    }
+
+    Value.Type mathType = getSimpleMathTypePrecedence(param1, param2);
+
+    switch(mathType) {
+    case BOOLEAN:
+      return toValue(getAsNumericBoolean(param1) - getAsNumericBoolean(param2));
+    // case STRING: break; unsupported
+    case DATE:
+    case TIME:
+    case DATE_TIME:
+      // dates/times get converted to date doubles for arithmetic
+      double result = param1.getAsDouble() - param2.getAsDouble();
+      return toDateValue(mathType, result, param1, param2);
+    case LONG:
+      return toValue(param1.getAsLong() - param2.getAsLong());
+    case DOUBLE:
+      return toValue(param1.getAsDouble() - param2.getAsDouble());
+    case BIG_INT:
+      return toValue(param1.getAsBigInteger().subtract(param2.getAsBigInteger()));
+    case BIG_DEC:
+      return toValue(param1.getAsBigDecimal().subtract(param2.getAsBigDecimal()));
+    default:
+      throw new RuntimeException("Unexpected type " + mathType);
+    }
   }
 
   public static Value multiply(Value param1, Value param2) {
-    // FIXME
-    return null;
+    if(anyParamIsNull(param1, param2)) {
+      // null propagation
+      return NULL_VAL;
+    }
+
+    Value.Type mathType = getGeneralMathTypePrecedence(param1, param2);
+
+    switch(mathType) {
+    case BOOLEAN:
+      return toValue(getAsNumericBoolean(param1) * getAsNumericBoolean(param2));
+    // 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:
+      return toValue(param1.getAsDouble() * param2.getAsDouble());
+    case BIG_INT:
+      return toValue(param1.getAsBigInteger().multiply(param2.getAsBigInteger()));
+    case BIG_DEC:
+      return toValue(param1.getAsBigDecimal().multiply(param2.getAsBigDecimal()));
+    default:
+      throw new RuntimeException("Unexpected type " + mathType);
+    }
   }
 
   public static Value divide(Value param1, Value param2) {
-    // FIXME
-    return null;
+    if(anyParamIsNull(param1, param2)) {
+      // null propagation
+      return NULL_VAL;
+    }
+
+    Value.Type mathType = getGeneralMathTypePrecedence(param1, param2);
+
+    switch(mathType) {
+    case BOOLEAN:
+      return toValue(getAsNumericBoolean(param1) / getAsNumericBoolean(param2));
+    // 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:
+      long lp1 = param1.getAsLong();
+      long lp2 = param2.getAsLong();
+      if((lp1 % lp2) == 0) {
+        return toValue(lp1 / lp2);
+      }
+      return toValue((double)lp1 / (double)lp2);
+    case DOUBLE:
+      return toValue(param1.getAsDouble() / param2.getAsDouble());
+    case BIG_INT:
+      BigInteger bip1 = param1.getAsBigInteger();
+      BigInteger bip2 = param2.getAsBigInteger();
+      BigInteger[] res = bip1.divideAndRemainder(bip2);
+      if(res[1].compareTo(BigInteger.ZERO) == 0) {
+        return toValue(res[0]);
+      }
+      return toValue(new BigDecimal(bip1).divide(new BigDecimal(bip2)));
+    case BIG_DEC:
+      return toValue(param1.getAsBigDecimal().divide(param2.getAsBigDecimal()));
+    default:
+      throw new RuntimeException("Unexpected type " + mathType);
+    }
   }
 
+  @SuppressWarnings("fallthrough")
   public static Value intDivide(Value param1, Value param2) {
-    // FIXME
-    return null;
+    if(anyParamIsNull(param1, param2)) {
+      // null propagation
+      return NULL_VAL;
+    }
+
+    Value.Type mathType = getGeneralMathTypePrecedence(param1, param2);
+
+    boolean wasDouble = false;
+    switch(mathType) {
+    case BOOLEAN:
+      return toValue(getAsNumericBoolean(param1) / getAsNumericBoolean(param2));
+    // 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_INT:
+    case BIG_DEC:
+      BigInteger result = param1.getAsBigInteger().divide(
+          param2.getAsBigInteger());
+      return (wasDouble ? toValue(result.longValue()) : toValue(result));
+    default:
+      throw new RuntimeException("Unexpected type " + mathType);
+    }
   }
 
   public static Value exp(Value param1, Value param2) {
-    // FIXME
-    return null;
+    if(anyParamIsNull(param1, param2)) {
+      // null propagation
+      return NULL_VAL;
+    }
+
+    Value.Type mathType = getGeneralMathTypePrecedence(param1, param2);
+
+    // jdk only supports general pow() as doubles, let's go with that
+    double result = Math.pow(param1.getAsDouble(), param2.getAsDouble());
+
+    // attempt to convert integral types back to integrals if possible
+    switch(mathType) {
+    case BOOLEAN:
+    case LONG:
+      if(isIntegral(result)) {
+        return toValue((long)result);
+      }
+      break;
+    case BIG_INT:
+      if(isIntegral(result)) {
+        return toValue(BigDecimal.valueOf(result).toBigInteger());
+      }
+      break;
+    }
+
+    return toValue(result);
+  }
+
+  @SuppressWarnings("fallthrough")
+  public static Value mod(Value param1, Value param2) {
+    if(anyParamIsNull(param1, param2)) {
+      // null propagation
+      return NULL_VAL;
+    }
+
+    Value.Type mathType = getGeneralMathTypePrecedence(param1, param2);
+
+    boolean wasDouble = false;
+    switch(mathType) {
+    case BOOLEAN:
+      return toValue(getAsNumericBoolean(param1) % getAsNumericBoolean(param2));
+    // 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.getAsDouble() % param2.getAsDouble());
+    case DOUBLE:
+      wasDouble = true;
+      // fallthrough
+    case BIG_INT:
+    case BIG_DEC:
+      BigInteger result = param1.getAsBigInteger().mod(param2.getAsBigInteger());
+      return (wasDouble ? toValue(result.longValue()) : toValue(result));
+    default:
+      throw new RuntimeException("Unexpected type " + mathType);
+    }
   }
 
   public static Value concat(Value param1, Value param2) {
+
     // note, this op converts null to empty string
-    
+    if(paramIsNull(param1)) {
+      param1 = EMPTY_STR_VAL;
+    }
 
-    // FIXME
-    return null;
+    if(paramIsNull(param2)) {
+      param2 = EMPTY_STR_VAL;
+    }
+
+    return nonNullConcat(param1, param2);
   }
 
-  public static Value mod(Value param1, Value param2) {
-    // FIXME
-    return null;
+  private static Value nonNullConcat(Value param1, Value param2) {
+    return toValue(param1.getAsString().concat(param2.getAsString()));
   }
 
   public static Value not(Value param1) {
@@ -132,7 +338,7 @@ public class BuiltinOperators
       return NULL_VAL;
     }
     
-    return toValue(!nonNullValueToBoolean(param1));
+    return toValue(!param1.getAsBoolean());
   }
 
   public static Value lessThan(Value param1, Value param2) {
@@ -197,7 +403,7 @@ public class BuiltinOperators
       return NULL_VAL;
     }
 
-    boolean b1 = nonNullValueToBoolean(param1);
+    boolean b1 = param1.getAsBoolean();
     if(!b1) {
       return FALSE_VAL;
     }
@@ -206,7 +412,7 @@ public class BuiltinOperators
       return NULL_VAL;
     }
 
-    return toValue(nonNullValueToBoolean(param2));
+    return toValue(param2.getAsBoolean());
   }
 
   public static Value or(Value param1, Value param2) {
@@ -217,7 +423,7 @@ public class BuiltinOperators
       return NULL_VAL;
     }
 
-    boolean b1 = nonNullValueToBoolean(param1);
+    boolean b1 = param1.getAsBoolean();
     if(b1) {
       return TRUE_VAL;
     }
@@ -226,7 +432,7 @@ public class BuiltinOperators
       return NULL_VAL;
     }
 
-    return toValue(nonNullValueToBoolean(param2));
+    return toValue(param2.getAsBoolean());
   }
 
   public static Value eqv(Value param1, Value param2) {
@@ -235,8 +441,8 @@ public class BuiltinOperators
       return NULL_VAL;
     }
 
-    boolean b1 = nonNullValueToBoolean(param1);
-    boolean b2 = nonNullValueToBoolean(param2);
+    boolean b1 = param1.getAsBoolean();
+    boolean b2 = param2.getAsBoolean();
 
     return toValue(b1 == b2);
   }
@@ -247,8 +453,8 @@ public class BuiltinOperators
       return NULL_VAL;
     }
 
-    boolean b1 = nonNullValueToBoolean(param1);
-    boolean b2 = nonNullValueToBoolean(param2);
+    boolean b1 = param1.getAsBoolean();
+    boolean b2 = param2.getAsBoolean();
 
     return toValue(b1 ^ b2);
   }
@@ -258,7 +464,7 @@ public class BuiltinOperators
     // "imp" uses short-circuit logic
 
     if(paramIsNull(param1)) {
-      if(paramIsNull(param2) || !nonNullValueToBoolean(param2)) {
+      if(paramIsNull(param2) || !param2.getAsBoolean()) {
         // null propagation
         return NULL_VAL;
       }
@@ -266,7 +472,7 @@ public class BuiltinOperators
       return TRUE_VAL;
     }
 
-    boolean b1 = nonNullValueToBoolean(param1);
+    boolean b1 = param1.getAsBoolean();
     if(!b1) {
       return TRUE_VAL;
     }
@@ -276,7 +482,7 @@ public class BuiltinOperators
       return NULL_VAL;
     }
 
-    return toValue(nonNullValueToBoolean(param2));
+    return toValue(param2.getAsBoolean());
   }
 
   public static Value isNull(Value param1) {
@@ -288,20 +494,23 @@ public class BuiltinOperators
   }
 
   public static Value like(Value param1, Pattern pattern) {
-    // FIXME
-    return null;
+    if(paramIsNull(param1)) {
+      // null propagation
+      return NULL_VAL;
+    }
+    
+    return toValue(pattern.matcher(param1.getAsString()).matches());
   }
 
   public static Value between(Value param1, Value param2, Value param3) {
-
-    // null propagate any field left to right.  uses short circuit eval
+    // FIXME, use delay for and() or check here?
+    // null propagate any param.  uses short circuit eval of params
     if(anyParamIsNull(param1, param2, param3)) {
       // null propagation
       return NULL_VAL;
     }
 
-    // FIXME
-    return null;
+    return and(greaterThanEq(param1, param2), lessThanEq(param1, param3));
   }
 
   public static Value notBetween(Value param1, Value param2, Value param3) {
@@ -346,95 +555,163 @@ public class BuiltinOperators
     return (param1.getType() == Value.Type.NULL);
   }
 
-  protected static CharSequence paramToString(Object param) {
-    try {
-      return ColumnImpl.toCharSequence(param);
-    } catch(IOException e) {
-      throw new RuntimeIOException(e);
-    }
-  }
-
-  protected static boolean paramToBoolean(Object param) {
-    // FIXME, null is false...?
-    return ColumnImpl.toBooleanValue(param);
-  }
-
-  protected static Number paramToNumber(Object param) {
-    // FIXME
-    return null;
-  }
+  protected static int nonNullCompareTo(
+      Value param1, Value param2)
+  {
+    Value.Type compareType = getGeneralMathTypePrecedence(param1, param2);
 
-  protected static boolean nonNullValueToBoolean(Value val) {
-    switch(val.getType()) {
+    switch(compareType) {
     case BOOLEAN:
-      return (Boolean)val.get();
+      return compare(getAsNumericBoolean(param1), getAsNumericBoolean(param2));
     case STRING:
-    case DATE:
-    case TIME:
-    case DATE_TIME:
-      // strings and dates are always true
-      return true;
+      // string comparison is only valid if _both_ params are strings
+      if(param1.getType() != param2.getType()) {
+        throw new RuntimeException("Unexpected type " + compareType);
+      }
+      return param1.getAsString().compareToIgnoreCase(param2.getAsString());
+    // case DATE: break; promoted to double
+    // case TIME: break; promoted to double
+    // case DATE_TIME: break; promoted to double
     case LONG:
-      return (((Number)val.get()).longValue() != 0L);
+      return param1.getAsLong().compareTo(param2.getAsLong());
     case DOUBLE:
-      return (((Number)val.get()).doubleValue() != 0.0d);
+      return param1.getAsDouble().compareTo(param2.getAsDouble());
     case BIG_INT:
-      return (((BigInteger)val.get()).compareTo(BigInteger.ZERO) != 0L);
+      return param1.getAsBigInteger().compareTo(param2.getAsBigInteger());
     case BIG_DEC:
-      return (((BigDecimal)val.get()).compareTo(BigDecimal.ZERO) != 0L);
+      return param1.getAsBigDecimal().compareTo(param2.getAsBigDecimal());
     default:
-      throw new RuntimeException("Unexpected type " + val.getType());
+      throw new RuntimeException("Unexpected type " + compareType);
     }
   }
 
-  protected static int nonNullCompareTo(
-      Value param1, Value param2)
-  {
-    // FIXME
-    return 0;
+  private static int compare(long l1, long l2) {
+    return ((l1 < l2) ? -1 : ((l1 > l2) ? 1 : 0));
+  }
+
+  private static long getAsNumericBoolean(Value v) {
+    return BooleanValue.numericBoolean(v.getAsBoolean());
   }
 
   public static Value toValue(boolean b) {
     return (b ? TRUE_VAL : FALSE_VAL);
   }
 
-  public static Value toValue(Object obj) {
-    if(obj == null) {
-      return NULL_VAL;
+  public static Value toValue(String s) {
+    return new StringValue(s);
+  }
+
+  public static Value toValue(Long s) {
+    return new LongValue(s);
+  }
+
+  public static Value toValue(Double s) {
+    return new DoubleValue(s);
+  }
+
+  public static Value toValue(BigInteger s) {
+    return new BigIntegerValue(s);
+  }
+
+  public static Value toValue(BigDecimal s) {
+    return new BigDecimalValue(s);
+  }
+
+  private static Value toDateValue(Value.Type type, double v, 
+                                   Value param1, Value param2)
+  {
+    // FIXME find format from first matching param
+    DateFormat fmt = null;
+    // if(param1.getType() == type) {
+    //   fmt = (DateFormat)param1.getFormat();
+    // } else if(param2 != null) {
+    //   fmt = (DateFormat)param2.getFormat();
+    // }
+
+    Date d = new Date(ColumnImpl.fromDateDouble(v, fmt.getCalendar()));
+
+    switch(type) {
+    case DATE:
+      return new DateValue(d, fmt);
+    case TIME:
+      return new TimeValue(d, fmt);
+    case DATE_TIME:
+      return new DateTimeValue(d, fmt);
+    default:
+      throw new RuntimeException("Unexpected type " + type);
+    }
+  }
+
+  private static Value.Type getSimpleMathTypePrecedence(
+      Value param1, Value param2)
+  {
+    Value.Type t1 = param1.getType();
+    Value.Type t2 = param2.getType();
+
+    if(t1 == t2) {
+      return t1;
     }
 
-    if(obj instanceof Value) {
-      return (Value)obj;
+    if((t1 == Value.Type.STRING) || (t2 == Value.Type.STRING)) {
+      // string always wins
+      return Value.Type.STRING;
     }
 
-    if(obj instanceof Boolean) {
-      return (((Boolean)obj) ? TRUE_VAL : FALSE_VAL);
+    // for "simple" math, keep as date/times
+    if(t1.isTemporal() || t2.isTemporal()) {
+      return (t1.isTemporal() ?
+              (t2.isTemporal() ? 
+               // for mixed temporal types, always go to date/time
+               Value.Type.DATE_TIME : t1) :
+              t2);
     }
 
-    if(obj instanceof Date) {
-      // any way to figure out whether it's a date/time/dateTime?
-      return new SimpleValue(Value.Type.DATE_TIME, obj);
+    // if both types are integral, choose "largest"
+    if(t1.isIntegral() && t2.isIntegral()) {
+      return max(t1, t2);
     }
 
-    if(obj instanceof Number) {
-      if((obj instanceof Double) || (obj instanceof Float)) {
-        return new SimpleValue(Value.Type.DOUBLE, obj);
-      }
-      if(obj instanceof BigDecimal) {
-        return new SimpleValue(Value.Type.BIG_DEC, obj);
-      }
-      if(obj instanceof BigInteger) {
-        return new SimpleValue(Value.Type.BIG_INT, obj);
+    // choose largest relevant floating-point type
+    return max(t1.getPreferredFPType(), t2.getPreferredFPType());
+  }
+
+  private static Value.Type getGeneralMathTypePrecedence(
+      Value param1, Value param2)
+  {
+    Value.Type t1 = param1.getType();
+    Value.Type t2 = param2.getType();
+
+    // note: for general math, date/time become double
+
+    if(t1 == t2) {
+
+      if(t1.isTemporal()) {
+        return Value.Type.DOUBLE;
       }
-      return new SimpleValue(Value.Type.LONG, obj);
+
+      return t1;
     }
 
-    try {
-      return new SimpleValue(Value.Type.STRING, 
-                             ColumnImpl.toCharSequence(obj).toString());
-    } catch(IOException e) {
-      throw new RuntimeIOException(e);
+    if((t1 == Value.Type.STRING) || (t2 == Value.Type.STRING)) {
+      // string always wins
+      return Value.Type.STRING;
     }
+
+    // if both types are integral, choose "largest"
+    if(t1.isIntegral() && t2.isIntegral()) {
+      return max(t1, t2);
+    }
+
+    // choose largest relevant floating-point type
+    return max(t1.getPreferredFPType(), t2.getPreferredFPType());
+  }
+
+  private static Value.Type max(Value.Type t1, Value.Type t2) {
+    return ((t1.compareTo(t2) > 0) ? t1 : t2);
+  }
+
+  private static boolean isIntegral(double d) {
+    return (d == Math.rint(d));
   }
 
 }
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DateTimeValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DateTimeValue.java
new file mode 100644 (file)
index 0000000..abc047f
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+Copyright (c) 2016 James Ahlborn
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package com.healthmarketscience.jackcess.impl.expr;
+
+import java.text.DateFormat;
+import java.util.Date;
+
+/**
+ *
+ * @author James Ahlborn
+ */
+public class DateTimeValue extends BaseDateValue
+{
+
+  public DateTimeValue(Date val, DateFormat fmt) 
+  {
+    super(val, fmt);
+  }
+
+  public Type getType() {
+    return Type.DATE_TIME;
+  }
+}
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DateValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DateValue.java
new file mode 100644 (file)
index 0000000..558e3ab
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+Copyright (c) 2016 James Ahlborn
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package com.healthmarketscience.jackcess.impl.expr;
+
+import java.text.DateFormat;
+import java.util.Date;
+
+/**
+ *
+ * @author James Ahlborn
+ */
+public class DateValue extends BaseDateValue
+{
+  public DateValue(Date val, DateFormat fmt) 
+  {
+    super(val, fmt);
+  }
+
+  public Type getType() {
+    return Type.DATE;
+  }
+}
index 7031f0e88f5339bb36327c6d0c91e4e96c7ab72a..70ff3ccb56ad8b2ff152b977822c9765e8f56e12 100644 (file)
@@ -20,10 +20,8 @@ import java.util.HashMap;
 import java.util.Map;
 
 
-import com.healthmarketscience.jackcess.expr.Expression;
 import com.healthmarketscience.jackcess.expr.Value;
 import com.healthmarketscience.jackcess.expr.Function;
-import com.healthmarketscience.jackcess.expr.RowContext;
 
 /**
  *
@@ -68,21 +66,6 @@ public class DefaultFunctions
       }
     }
 
-    protected static CharSequence paramToString(Object param)
-    {
-      return BuiltinOperators.paramToString(param);
-    }
-
-    protected static boolean paramToBoolean(Object param)
-    {
-      return BuiltinOperators.paramToBoolean(param);
-    }
-
-    protected static Number paramToNumber(Object param)
-    {
-      return BuiltinOperators.paramToNumber(param);
-    }
-
     @Override
     public String toString() {
       return getName() + "()";
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DoubleValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DoubleValue.java
new file mode 100644 (file)
index 0000000..4a77fb9
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+Copyright (c) 2016 James Ahlborn
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package com.healthmarketscience.jackcess.impl.expr;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+/**
+ *
+ * @author James Ahlborn
+ */
+public class DoubleValue extends BaseNumericValue
+{
+  private final Double _val;
+
+  public DoubleValue(Double val) 
+  {
+    _val = val;
+  }
+
+  public Type getType() {
+    return Type.DOUBLE;
+  }
+
+  public Object get() {
+    return _val;
+  }
+
+  @Override
+  protected Number getNumber() {
+    return _val;
+  }
+
+  @Override
+  public Boolean getAsBoolean() {
+    return (_val.doubleValue() != 0.0d);
+  }
+
+  @Override
+  public Double getAsDouble() {
+    return _val;
+  }
+
+  @Override
+  public BigInteger getAsBigInteger() {
+    return getAsBigDecimal().toBigInteger();
+  }
+
+  @Override
+  public BigDecimal getAsBigDecimal() {
+    return BigDecimal.valueOf(_val);
+  }
+}
index 40418f4ac5590b51b414ad28d36a48aca38e1e8c..570823add5cba2a6fd85d38024c5361d8776d5dd 100644 (file)
@@ -17,15 +17,20 @@ limitations under the License.
 package com.healthmarketscience.jackcess.impl.expr;
 
 import java.math.BigDecimal;
+import java.text.DateFormat;
+import java.text.FieldPosition;
 import java.text.ParseException;
-import java.text.SimpleDateFormat;
+import java.text.ParsePosition;
 import java.util.AbstractMap;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TimeZone;
 
 import static com.healthmarketscience.jackcess.impl.expr.Expressionator.*;
 import com.healthmarketscience.jackcess.expr.Value;
@@ -44,9 +49,16 @@ class ExpressionTokenizer
   private static final char DATE_LIT_QUOTE_CHAR = '#';
   private static final char EQUALS_CHAR = '=';
 
-  private static final String DATE_FORMAT = "M/d/yyyy";
-  private static final String TIME_FORMAT = "HH:mm:ss";
-  private static final String DATE_TIME_FORMAT = DATE_FORMAT + " " + TIME_FORMAT;
+  static final String DATE_FORMAT = "M/d/yyyy";
+  static final String TIME_FORMAT_24 = "HH:mm:ss";
+  static final String TIME_FORMAT_12 = "hh:mm:ss a";
+  static final String DATE_TIME_FORMAT_24 = DATE_FORMAT + " " + TIME_FORMAT_24;
+  static final String DATE_TIME_FORMAT_12 = DATE_FORMAT + " " + TIME_FORMAT_12;
+  private static final int AMPM_SUFFIX_LEN = 3;
+  private static final String AM_SUFFIX = " am";
+  private static final String PM_SUFFIX = " pm";
+  // access times are based on this date (not the UTC base)
+  private static final String BASE_DATE = "12/30/1899 ";
 
   private static final byte IS_OP_FLAG =     0x01;
   private static final byte IS_COMP_FLAG =   0x02;
@@ -87,7 +99,7 @@ class ExpressionTokenizer
 
     List<Token> tokens = new ArrayList<Token>();
 
-    ExprBuf buf = new ExprBuf(exprStr);
+    ExprBuf buf = new ExprBuf(exprStr, context);
 
     while(buf.hasNext()) {
       char c = buf.next();
@@ -156,7 +168,7 @@ class ExpressionTokenizer
                                  Value.Type.STRING));
             break;
           case DATE_LIT_QUOTE_CHAR:
-            tokens.add(parseDateLiteralString(buf, context));
+            tokens.add(parseDateLiteralString(buf));
             break;
           case OBJ_NAME_START_CHAR:
             tokens.add(new Token(TokenType.OBJ_NAME, parseObjNameString(buf)));
@@ -307,24 +319,33 @@ class ExpressionTokenizer
     return sb.toString();
   }
 
-  private static Token parseDateLiteralString(
-      ExprBuf buf, ParseContext context) 
+  private static Token parseDateLiteralString(ExprBuf buf) 
   {
     String dateStr = parseStringUntil(buf, DATE_LIT_QUOTE_CHAR, null);
     
     boolean hasDate = (dateStr.indexOf('/') >= 0);
     boolean hasTime = (dateStr.indexOf(':') >= 0);
+    boolean hasAmPm = false;
+    
+    if(hasTime) {
+      int strLen = dateStr.length();
+      hasTime = ((strLen >= AMPM_SUFFIX_LEN) &&
+                 (dateStr.regionMatches(true, strLen - AMPM_SUFFIX_LEN, 
+                                        AM_SUFFIX, 0, AMPM_SUFFIX_LEN) ||
+                  dateStr.regionMatches(true, strLen - AMPM_SUFFIX_LEN, 
+                                        PM_SUFFIX, 0, AMPM_SUFFIX_LEN)));
+    }
 
-    SimpleDateFormat sdf = null;
+    DateFormat sdf = null;
     Value.Type valType = null;
     if(hasDate && hasTime) {
-      sdf = buf.getDateTimeFormat(context);
+      sdf = (hasAmPm ? buf.getDateTimeFormat12() : buf.getDateTimeFormat24());
       valType = Value.Type.DATE_TIME;
     } else if(hasDate) {
-      sdf = buf.getDateFormat(context);
+      sdf = buf.getDateFormat();
       valType = Value.Type.DATE;
     } else if(hasTime) {
-      sdf = buf.getTimeFormat(context);
+      sdf = (hasAmPm ? buf.getTimeFormat12() : buf.getTimeFormat24());
       valType = Value.Type.TIME;
     } else {
       throw new IllegalArgumentException("Invalid date time literal " + dateStr +
@@ -409,14 +430,18 @@ class ExpressionTokenizer
   private static final class ExprBuf
   {
     private final String _str;
+    private final ParseContext _ctx;
     private int _pos;
-    private SimpleDateFormat _dateFmt;
-    private SimpleDateFormat _timeFmt;
-    private SimpleDateFormat _dateTimeFmt;
+    private DateFormat _dateFmt;
+    private DateFormat _timeFmt12;
+    private DateFormat _dateTimeFmt12;
+    private DateFormat _timeFmt24;
+    private DateFormat _dateTimeFmt24;
     private final StringBuilder _scratch = new StringBuilder();
     
-    private ExprBuf(String str) {
+    private ExprBuf(String str, ParseContext ctx) {
       _str = str;
+      _ctx = ctx;
     }
 
     private int len() {
@@ -459,25 +484,41 @@ class ExpressionTokenizer
       return _scratch;
     }
 
-    public SimpleDateFormat getDateFormat(ParseContext context) {
+    public DateFormat getDateFormat() {
       if(_dateFmt == null) {
-        _dateFmt = context.createDateFormat(DATE_FORMAT);
+        _dateFmt = _ctx.createDateFormat(DATE_FORMAT);
       }
       return _dateFmt;
     }
 
-    public SimpleDateFormat getTimeFormat(ParseContext context) {
-      if(_timeFmt == null) {
-        _timeFmt = context.createDateFormat(TIME_FORMAT);
+    public DateFormat getTimeFormat12() {
+      if(_timeFmt12 == null) {
+        _timeFmt12 = new TimeFormat(
+            getDateTimeFormat12(), _ctx.createDateFormat(TIME_FORMAT_12));
+      }
+      return _timeFmt12;
+    }
+
+    public DateFormat getDateTimeFormat12() {
+      if(_dateTimeFmt12 == null) {
+        _dateTimeFmt12 = _ctx.createDateFormat(DATE_TIME_FORMAT_12);
+      }
+      return _dateTimeFmt12;
+    }
+
+    public DateFormat getTimeFormat24() {
+      if(_timeFmt24 == null) {
+        _timeFmt24 = new TimeFormat(
+            getDateTimeFormat24(), _ctx.createDateFormat(TIME_FORMAT_24));
       }
-      return _timeFmt;
+      return _timeFmt24;
     }
 
-    public SimpleDateFormat getDateTimeFormat(ParseContext context) {
-      if(_dateTimeFmt == null) {
-        _dateTimeFmt = context.createDateFormat(DATE_TIME_FORMAT);
+    public DateFormat getDateTimeFormat24() {
+      if(_dateTimeFmt24 == null) {
+        _dateTimeFmt24 = _ctx.createDateFormat(DATE_TIME_FORMAT_24);
       }
-      return _dateTimeFmt;
+      return _dateTimeFmt24;
     }
 
     @Override
@@ -493,20 +534,27 @@ class ExpressionTokenizer
     private final Object _val;
     private final String _valStr;
     private final Value.Type _valType;
+    private final DateFormat _sdf;
 
     private Token(TokenType type, String val) {
       this(type, val, val);
     }
 
     private Token(TokenType type, Object val, String valStr) {
-      this(type, val, valStr, null);
+      this(type, val, valStr, null, null);
     }
 
     private Token(TokenType type, Object val, String valStr, Value.Type valType) {
+      this(type, val, valStr, valType, null);
+    }
+
+    private Token(TokenType type, Object val, String valStr, Value.Type valType,
+                  DateFormat sdf) {
       _type = type;
       _val = ((val != null) ? val : valStr);
       _valStr = valStr;
       _valType = valType;
+      _sdf = sdf;
     }
 
     public TokenType getType() {
@@ -525,6 +573,10 @@ class ExpressionTokenizer
       return _valType;
     }
 
+    public DateFormat getDateFormat() {
+      return _sdf;
+    }
+
     @Override
     public String toString() {
       if(_type == TokenType.SPACE) {
@@ -536,6 +588,41 @@ class ExpressionTokenizer
       }
       return str;
     }
-  } 
+  }
+
+  private static final class TimeFormat extends DateFormat
+  {
+    private static final long serialVersionUID = 0L;
+    private final DateFormat _parseDelegate;
+    private final DateFormat _fmtDelegate;
+
+    private TimeFormat(DateFormat parseDelegate, DateFormat fmtDelegate)
+    {
+      _parseDelegate = parseDelegate;
+      _fmtDelegate = fmtDelegate;
+    }
+
+    @Override
+    public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) {
+      return _fmtDelegate.format(date, toAppendTo, fieldPosition);
+    }
+
+    @Override
+    public Date parse(String source, ParsePosition pos) {
+      // we parse as a full date/time in order to get the correct "base date"
+      // used by access
+      return _parseDelegate.parse(BASE_DATE + source, pos);
+    }
+
+    @Override
+    public Calendar getCalendar() {
+      return _fmtDelegate.getCalendar();
+    }
+
+    @Override
+    public TimeZone getTimeZone() {
+      return _fmtDelegate.getTimeZone();
+    }
+  }
 
 }
index 625358ee993959afc3594489a1ad4e9035a76142..e61639389217112a4f74489e2dd078d0431f059b 100644 (file)
@@ -16,10 +16,13 @@ limitations under the License.
 
 package com.healthmarketscience.jackcess.impl.expr;
 
+import java.math.BigDecimal;
+import java.text.DateFormat;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Date;
 import java.util.Deque;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -32,11 +35,12 @@ import java.util.regex.Pattern;
 
 import com.healthmarketscience.jackcess.DatabaseBuilder;
 import com.healthmarketscience.jackcess.expr.Expression;
-import com.healthmarketscience.jackcess.expr.Value;
 import com.healthmarketscience.jackcess.expr.Function;
 import com.healthmarketscience.jackcess.expr.RowContext;
-import com.healthmarketscience.jackcess.impl.expr.ExpressionTokenizer.TokenType;
+import com.healthmarketscience.jackcess.expr.Value;
 import com.healthmarketscience.jackcess.impl.expr.ExpressionTokenizer.Token;
+import com.healthmarketscience.jackcess.impl.expr.ExpressionTokenizer.TokenType;
+
 
 /**
  *
@@ -452,7 +456,8 @@ public class Expressionator
 
       case LITERAL:
         
-        buf.setPendingExpr(new ELiteralValue(t.getValueType(), t.getValue()));
+        buf.setPendingExpr(new ELiteralValue(t.getValueType(), t.getValue(),
+                                             t.getDateFormat()));
         break;
         
       case OP:
@@ -1230,6 +1235,24 @@ public class Expressionator
                            Pattern.UNICODE_CASE);
   }
 
+  private static Value toLiteralValue(Value.Type valType, Object value, 
+                                      DateFormat sdf)
+  {
+    switch(valType) {
+    case STRING:
+      return new StringValue((String)value);
+    case DATE:
+      return new DateValue((Date)value, sdf);
+    case TIME:
+      return new TimeValue((Date)value, sdf);
+    case DATE_TIME:
+      return new DateTimeValue((Date)value, sdf);
+    case BIG_DEC:
+      return new BigDecimalValue((BigDecimal)value);
+    default:
+      throw new RuntimeException("unexpected literal type " + valType);
+    } 
+  }
 
   private interface LeftAssocExpr {
     public OpType getOp();
@@ -1243,9 +1266,8 @@ public class Expressionator
     public void setRight(Expr right);
   }
 
-  private static final class DelayedValue implements Value
+  private static final class DelayedValue extends BaseDelayedValue
   {
-    private Value _val;
     private final Expr _expr;
     private final RowContext _ctx;
 
@@ -1254,19 +1276,9 @@ public class Expressionator
       _ctx = ctx;
     }
 
-    private Value getDelegate() {
-      if(_val == null) {
-        _val = _expr.eval(_ctx);
-      }
-      return _val;
-    }
-
-    public Value.Type getType() {
-      return getDelegate().getType();
-    }
-
-    public Object get() {
-      return getDelegate().get();
+    @Override
+    protected Value eval() {
+      return _expr.eval(_ctx);
     }
   }
   
@@ -1411,8 +1423,9 @@ public class Expressionator
   {
     private final Value _val;
 
-    private ELiteralValue(Value.Type valType, Object value) {
-      _val = new BuiltinOperators.SimpleValue(valType, value);
+    private ELiteralValue(Value.Type valType, Object value,
+                          DateFormat sdf) {
+      _val = toLiteralValue(valType, value, sdf);
     }
 
     @Override
@@ -1425,9 +1438,7 @@ public class Expressionator
       if(_val.getType() == Value.Type.STRING) {
         literalStrToString((String)_val.get(), sb);
       } else if(_val.getType().isTemporal()) {
-        //   // FIXME Date,Time,DateTime formatting?
-        //   sb.append("#").append(_value).append("#");
-        throw new UnsupportedOperationException();
+        sb.append("#").append(_val.getAsString()).append("#");
       } else {
         sb.append(_val.get());
       }
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/LongValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/LongValue.java
new file mode 100644 (file)
index 0000000..a85b7ff
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+Copyright (c) 2016 James Ahlborn
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package com.healthmarketscience.jackcess.impl.expr;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+/**
+ *
+ * @author James Ahlborn
+ */
+public class LongValue extends BaseNumericValue
+{
+  private final Long _val;
+
+  public LongValue(Long val) 
+  {
+    _val = val;
+  }
+
+  public Type getType() {
+    return Type.LONG;
+  }
+
+  public Object get() {
+    return _val;
+  }
+
+  @Override
+  protected Number getNumber() {
+    return _val;
+  }
+
+  @Override
+  public Boolean getAsBoolean() {
+    return (_val.longValue() != 0L);
+  }
+
+  @Override
+  public Long getAsLong() {
+    return _val;
+  }
+
+  @Override
+  public BigInteger getAsBigInteger() {
+    return BigInteger.valueOf(_val);
+  }
+
+  @Override
+  public BigDecimal getAsBigDecimal() {
+    return BigDecimal.valueOf(_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
new file mode 100644 (file)
index 0000000..5bbaa9d
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+Copyright (c) 2016 James Ahlborn
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package com.healthmarketscience.jackcess.impl.expr;
+
+/**
+ *
+ * @author James Ahlborn
+ */
+public class StringValue extends BaseValue
+{
+  private final String _val;
+
+  public StringValue(String val) 
+  {
+    _val = val;
+  }
+
+  public Type getType() {
+    return Type.STRING;
+  }
+
+  public Object get() {
+    return _val;
+  }
+
+  @Override
+  public Boolean getAsBoolean() {
+    // ms access seems to treat strings as "true"
+    return Boolean.TRUE;
+  }
+
+  @Override
+  public String getAsString() {
+    return _val;
+  }
+}
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/TimeValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/TimeValue.java
new file mode 100644 (file)
index 0000000..cedb461
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+Copyright (c) 2016 James Ahlborn
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package com.healthmarketscience.jackcess.impl.expr;
+
+import java.text.DateFormat;
+import java.util.Date;
+
+/**
+ *
+ * @author James Ahlborn
+ */
+public class TimeValue extends BaseDateValue
+{
+
+  public TimeValue(Date val, DateFormat fmt) 
+  {
+    super(val, fmt);
+  }
+
+  public Type getType() {
+    return Type.TIME;
+  }
+}
index a2bf31a255275918df99b81d97828a7270d1fe11..a564834a76d97f7ddd0416767ed874a5750adcb7 100644 (file)
@@ -23,6 +23,7 @@ import java.util.Arrays;
 import com.healthmarketscience.jackcess.DataType;
 import com.healthmarketscience.jackcess.Table;
 import com.healthmarketscience.jackcess.impl.ColumnImpl;
+import com.healthmarketscience.jackcess.impl.DatabaseImpl;
 import org.apache.commons.lang.ObjectUtils;
 
 /**
@@ -54,8 +55,9 @@ public class SimpleColumnMatcher implements ColumnMatcher {
       // values and try again
       DataType dataType = table.getColumn(columnName).getType();
       try {
-        Object internalV1 = ColumnImpl.toInternalValue(dataType, value1);
-        Object internalV2 = ColumnImpl.toInternalValue(dataType, value2);
+        DatabaseImpl db = (DatabaseImpl)table.getDatabase();
+        Object internalV1 = ColumnImpl.toInternalValue(dataType, value1, db);
+        Object internalV2 = ColumnImpl.toInternalValue(dataType, value2, db);
         
         return equals(internalV1, internalV2);
       } catch(IOException e) {