package com.healthmarketscience.jackcess.expr;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Date;
+
/**
*
* @author James Ahlborn
{
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()));
}
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();
}
* @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)
* @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);
}
* 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));
}
* 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));
* <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;
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());
}
* <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());
}
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());
}
}
/**
- * 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;
}
* 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) {
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)));
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;
--- /dev/null
+/*
+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());
+ }
+}
--- /dev/null
+// 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();
+}
--- /dev/null
+// 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();
+}
--- /dev/null
+/*
+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() + "'";
+ }
+}
--- /dev/null
+/*
+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;
+ }
+}
--- /dev/null
+/*
+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);
+ }
+}
--- /dev/null
+/*
+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);
+ }
+}
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;
/**
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() {}
// 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) {
return NULL_VAL;
}
- return toValue(!nonNullValueToBoolean(param1));
+ return toValue(!param1.getAsBoolean());
}
public static Value lessThan(Value param1, Value param2) {
return NULL_VAL;
}
- boolean b1 = nonNullValueToBoolean(param1);
+ boolean b1 = param1.getAsBoolean();
if(!b1) {
return FALSE_VAL;
}
return NULL_VAL;
}
- return toValue(nonNullValueToBoolean(param2));
+ return toValue(param2.getAsBoolean());
}
public static Value or(Value param1, Value param2) {
return NULL_VAL;
}
- boolean b1 = nonNullValueToBoolean(param1);
+ boolean b1 = param1.getAsBoolean();
if(b1) {
return TRUE_VAL;
}
return NULL_VAL;
}
- return toValue(nonNullValueToBoolean(param2));
+ return toValue(param2.getAsBoolean());
}
public static Value eqv(Value param1, Value param2) {
return NULL_VAL;
}
- boolean b1 = nonNullValueToBoolean(param1);
- boolean b2 = nonNullValueToBoolean(param2);
+ boolean b1 = param1.getAsBoolean();
+ boolean b2 = param2.getAsBoolean();
return toValue(b1 == b2);
}
return NULL_VAL;
}
- boolean b1 = nonNullValueToBoolean(param1);
- boolean b2 = nonNullValueToBoolean(param2);
+ boolean b1 = param1.getAsBoolean();
+ boolean b2 = param2.getAsBoolean();
return toValue(b1 ^ b2);
}
// "imp" uses short-circuit logic
if(paramIsNull(param1)) {
- if(paramIsNull(param2) || !nonNullValueToBoolean(param2)) {
+ if(paramIsNull(param2) || !param2.getAsBoolean()) {
// null propagation
return NULL_VAL;
}
return TRUE_VAL;
}
- boolean b1 = nonNullValueToBoolean(param1);
+ boolean b1 = param1.getAsBoolean();
if(!b1) {
return TRUE_VAL;
}
return NULL_VAL;
}
- return toValue(nonNullValueToBoolean(param2));
+ return toValue(param2.getAsBoolean());
}
public static Value isNull(Value param1) {
}
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) {
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));
}
}
--- /dev/null
+/*
+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;
+ }
+}
--- /dev/null
+/*
+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;
+ }
+}
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;
/**
*
}
}
- 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() + "()";
--- /dev/null
+/*
+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);
+ }
+}
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;
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;
List<Token> tokens = new ArrayList<Token>();
- ExprBuf buf = new ExprBuf(exprStr);
+ ExprBuf buf = new ExprBuf(exprStr, context);
while(buf.hasNext()) {
char c = buf.next();
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)));
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 +
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() {
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
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() {
return _valType;
}
+ public DateFormat getDateFormat() {
+ return _sdf;
+ }
+
@Override
public String toString() {
if(_type == TokenType.SPACE) {
}
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();
+ }
+ }
}
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;
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;
+
/**
*
case LITERAL:
- buf.setPendingExpr(new ELiteralValue(t.getValueType(), t.getValue()));
+ buf.setPendingExpr(new ELiteralValue(t.getValueType(), t.getValue(),
+ t.getDateFormat()));
break;
case OP:
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();
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;
_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);
}
}
{
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
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());
}
--- /dev/null
+/*
+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);
+ }
+}
--- /dev/null
+/*
+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;
+ }
+}
--- /dev/null
+/*
+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;
+ }
+}
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;
/**
// 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) {