Value.Type mathType = param1.getType();
switch(mathType) {
- // case STRING: break; unsupported
case DATE:
case TIME:
case DATE_TIME:
return toValue(-param1.getAsLong());
case DOUBLE:
return toValue(-param1.getAsDouble());
+ case STRING:
case BIG_DEC:
return toValue(param1.getAsBigDecimal().negate());
default:
return new LongValue((long)i);
}
+ public static Value toValue(long s) {
+ return new LongValue(s);
+ }
+
public static Value toValue(Long s) {
return new LongValue(s);
}
return new DoubleValue((double)f);
}
+ public static Value toValue(double s) {
+ return new DoubleValue(s);
+ }
+
public static Value toValue(Double s) {
return new DoubleValue(s);
}
return new BigDecimalValue(s);
}
- private static Value toDateValue(EvalContext ctx, Value.Type type, double v,
- Value param1, Value param2)
+ public static Value toValue(Value.Type type, Date d, DateFormat fmt) {
+ 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 date/time type " + type);
+ }
+ }
+
+ static Value toDateValue(EvalContext ctx, Value.Type type, double v,
+ Value param1, Value param2)
{
DateFormat fmt = null;
if((param1 instanceof BaseDateValue) && (param1.getType() == type)) {
} else if((param2 instanceof BaseDateValue) && (param2.getType() == type)) {
fmt = ((BaseDateValue)param2).getFormat();
} else {
+ fmt = getDateFormatForType(ctx, type);
+ }
+
+ Date d = new Date(ColumnImpl.fromDateDouble(v, fmt.getCalendar()));
+
+ return toValue(type, d, fmt);
+ }
+
+ static DateFormat getDateFormatForType(EvalContext ctx, Value.Type type) {
String fmtStr = null;
switch(type) {
case DATE:
fmtStr = ctx.getTemporalConfig().getDefaultDateTimeFormat();
break;
default:
- throw new RuntimeException("Unexpected type " + type);
+ throw new RuntimeException("Unexpected date/time type " + type);
}
- fmt = ctx.createDateFormat(fmtStr);
- }
-
- 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);
- }
+ return ctx.createDateFormat(fmtStr);
}
private static Value.Type getMathTypePrecedence(
--- /dev/null
+/*
+Copyright (c) 2017 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.text.DateFormat;
+import java.util.Calendar;
+import java.util.Date;
+
+import com.healthmarketscience.jackcess.expr.EvalContext;
+import com.healthmarketscience.jackcess.expr.Function;
+import com.healthmarketscience.jackcess.expr.Value;
+import com.healthmarketscience.jackcess.impl.ColumnImpl;
+import static com.healthmarketscience.jackcess.impl.expr.DefaultFunctions.*;
+
+/**
+ *
+ * @author James Ahlborn
+ */
+public class DefaultDateFunctions
+{
+ // min, valid, recognizable date: January 1, 100 A.D. 00:00:00
+ private static final double MIN_DATE = -657434.0d;
+ // max, valid, recognizable date: December 31, 9999 A.D. 23:59:59
+ private static final double MAX_DATE = 2958465.999988426d;
+
+ private DefaultDateFunctions() {}
+
+ static void init() {
+ // dummy method to ensure this class is loaded
+ }
+
+ public static final Function DATE = registerFunc(new Func0("Date") {
+ @Override
+ public boolean isPure() {
+ return false;
+ }
+ @Override
+ protected Value eval0(EvalContext ctx) {
+ DateFormat df = BuiltinOperators.getDateFormatForType(ctx, Value.Type.DATE);
+ double dd = ColumnImpl.toDateDouble(System.currentTimeMillis(), df.getCalendar());
+ // the integral part of the date/time double is the date value. discard
+ // the fractional portion
+ dd = ((long)dd);
+ return BuiltinOperators.toValue(Value.Type.DATE, new Date(), df);
+ }
+ });
+
+ public static final Function NOW = registerFunc(new Func0("Now") {
+ @Override
+ public boolean isPure() {
+ return false;
+ }
+ @Override
+ protected Value eval0(EvalContext ctx) {
+ DateFormat df = BuiltinOperators.getDateFormatForType(ctx, Value.Type.DATE_TIME);
+ return BuiltinOperators.toValue(Value.Type.DATE_TIME, new Date(), df);
+ }
+ });
+
+ public static final Function TIME = registerFunc(new Func0("Time") {
+ @Override
+ public boolean isPure() {
+ return false;
+ }
+ @Override
+ protected Value eval0(EvalContext ctx) {
+ DateFormat df = BuiltinOperators.getDateFormatForType(ctx, Value.Type.TIME);
+ double dd = ColumnImpl.toDateDouble(System.currentTimeMillis(), df.getCalendar());
+ // the fractional part of the date/time double is the time value. discard
+ // the integral portion
+ dd = Math.IEEEremainder(dd, 1.0d);
+ return BuiltinOperators.toValue(Value.Type.TIME, new Date(), df);
+ }
+ });
+
+ public static final Function HOUR = registerFunc(new Func1NullIsNull("Hour") {
+ @Override
+ protected Value eval1(EvalContext ctx, Value param1) {
+ return BuiltinOperators.toValue(
+ nonNullToCalendarField(ctx, param1, Calendar.HOUR_OF_DAY));
+ }
+ });
+
+ public static final Function MINUTE = registerFunc(new Func1NullIsNull("Minute") {
+ @Override
+ protected Value eval1(EvalContext ctx, Value param1) {
+ return BuiltinOperators.toValue(
+ nonNullToCalendarField(ctx, param1, Calendar.MINUTE));
+ }
+ });
+
+ public static final Function SECOND = registerFunc(new Func1NullIsNull("Second") {
+ @Override
+ protected Value eval1(EvalContext ctx, Value param1) {
+ return BuiltinOperators.toValue(
+ nonNullToCalendarField(ctx, param1, Calendar.SECOND));
+ }
+ });
+
+ public static final Function YEAR = registerFunc(new Func1NullIsNull("Year") {
+ @Override
+ protected Value eval1(EvalContext ctx, Value param1) {
+ // convert from 0 based to 1 based value
+ return BuiltinOperators.toValue(
+ nonNullToCalendarField(ctx, param1, Calendar.YEAR) + 1);
+ }
+ });
+
+ public static final Function MONTH = registerFunc(new Func1NullIsNull("Month") {
+ @Override
+ protected Value eval1(EvalContext ctx, Value param1) {
+ // convert from 0 based to 1 based value
+ return BuiltinOperators.toValue(
+ nonNullToCalendarField(ctx, param1, Calendar.MONTH) + 1);
+ }
+ });
+
+ public static final Function DAY = registerFunc(new Func1NullIsNull("Day") {
+ @Override
+ protected Value eval1(EvalContext ctx, Value param1) {
+ return BuiltinOperators.toValue(
+ nonNullToCalendarField(ctx, param1, Calendar.DAY_OF_MONTH));
+ }
+ });
+
+ public static final Function WEEKDAY = registerFunc(new FuncVar("Weekday", 1, 2) {
+ @Override
+ protected Value evalVar(EvalContext ctx, Value[] params) {
+ Value param1 = params[0];
+ if(param1 == null) {
+ return null;
+ }
+ int day = nonNullToCalendarField(ctx, param1, Calendar.DAY_OF_WEEK);
+ // FIXME handle first day of week
+ // if(params.length > 1) {
+ // int firstDay = params[1].getAsLong();
+ // }
+ return BuiltinOperators.toValue(day);
+ }
+ });
+
+
+ private static int nonNullToCalendarField(EvalContext ctx, Value param,
+ int field) {
+ return nonNullToCalendar(ctx, param).get(field);
+ }
+
+ private static Calendar nonNullToCalendar(EvalContext ctx, Value param) {
+ param = nonNullToDateValue(ctx, param);
+ if(param == null) {
+ // not a date/time
+ throw new IllegalStateException("Invalid date/time expression '" + param + "'");
+ }
+
+ Calendar cal =
+ ((param instanceof BaseDateValue) ?
+ ((BaseDateValue)param).getFormat().getCalendar() :
+ BuiltinOperators.getDateFormatForType(ctx, param.getType()).getCalendar());
+
+ cal.setTime(param.getAsDateTime(ctx));
+ return cal;
+ }
+
+ static Value nonNullToDateValue(EvalContext ctx, Value param) {
+ Value.Type type = param.getType();
+ if(type.isTemporal()) {
+ return param;
+ }
+
+ if(type == Value.Type.STRING) {
+ // see if we can coerce to date/time
+
+ // FIXME use ExpressionatorTokenizer to detect explicit date/time format
+
+ try {
+ return numberToDateValue(ctx, param.getAsDouble());
+ } catch(NumberFormatException ignored) {
+ // not a number
+ return null;
+ }
+ }
+
+ // must be a number
+ return numberToDateValue(ctx, param.getAsDouble());
+ }
+
+ private static Value numberToDateValue(EvalContext ctx, double dd) {
+ if((dd < MIN_DATE) || (dd > MAX_DATE)) {
+ // outside valid date range
+ return null;
+ }
+
+ boolean hasDate = (((long)dd) != 0L);
+ boolean hasTime = (Math.IEEEremainder(dd, 1.0d) != 0.0d);
+
+ Value.Type type = (hasDate ? (hasTime ? Value.Type.DATE_TIME : Value.Type.DATE) :
+ Value.Type.TIME);
+ DateFormat df = BuiltinOperators.getDateFormatForType(ctx, type);
+ Date d = new Date(ColumnImpl.fromDateDouble(dd, df.getCalendar()));
+ return BuiltinOperators.toValue(type, d, df);
+ }
+}
import java.math.BigDecimal;
import java.math.RoundingMode;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import com.healthmarketscience.jackcess.expr.EvalContext;
import com.healthmarketscience.jackcess.expr.Function;
import com.healthmarketscience.jackcess.expr.Value;
+import com.healthmarketscience.jackcess.impl.DatabaseImpl;
/**
*
// load all default functions
DefaultTextFunctions.init();
DefaultNumberFunctions.init();
+ DefaultDateFunctions.init();
}
private DefaultFunctions() {}
public static Function getFunction(String name) {
- return FUNCS.get(name.toLowerCase());
+ return FUNCS.get(DatabaseImpl.toLookupName(name));
}
public static abstract class BaseFunction implements Function
String range = ((_minParams == _maxParams) ? "" + _minParams :
_minParams + " to " + _maxParams);
throw new IllegalArgumentException(
- this + ": invalid number of parameters " +
+ "Invalid number of parameters " +
num + " passed, expected " + range);
}
}
- // FIXME, provide context for exceptions thrown
+ protected IllegalStateException invalidFunctionCall(
+ Throwable t, Value[] params)
+ {
+ String paramStr = Arrays.toString(params);
+ String msg = "Invalid function call {" + _name + "(" +
+ paramStr.substring(1, paramStr.length() - 1) + ")}";
+ return new IllegalStateException(msg, t);
+ }
@Override
public String toString() {
}
public final Value eval(EvalContext ctx, Value... params) {
- validateNumParams(params);
- return eval0(ctx);
+ try {
+ validateNumParams(params);
+ return eval0(ctx);
+ } catch(Exception e) {
+ throw invalidFunctionCall(e, params);
+ }
}
protected abstract Value eval0(EvalContext ctx);
}
public final Value eval(EvalContext ctx, Value... params) {
- validateNumParams(params);
- return eval1(ctx, params[0]);
+ try {
+ validateNumParams(params);
+ return eval1(ctx, params[0]);
+ } catch(Exception e) {
+ throw invalidFunctionCall(e, params);
+ }
}
protected abstract Value eval1(EvalContext ctx, Value param);
}
public final Value eval(EvalContext ctx, Value... params) {
- validateNumParams(params);
- Value param1 = params[0];
- if(param1.isNull()) {
- return param1;
+ try {
+ validateNumParams(params);
+ Value param1 = params[0];
+ if(param1.isNull()) {
+ return param1;
+ }
+ return eval1(ctx, param1);
+ } catch(Exception e) {
+ throw invalidFunctionCall(e, params);
}
- return eval1(ctx, param1);
}
protected abstract Value eval1(EvalContext ctx, Value param);
}
public final Value eval(EvalContext ctx, Value... params) {
- validateNumParams(params);
- return eval2(ctx, params[0], params[1]);
+ try {
+ validateNumParams(params);
+ return eval2(ctx, params[0], params[1]);
+ } catch(Exception e) {
+ throw invalidFunctionCall(e, params);
+ }
}
protected abstract Value eval2(EvalContext ctx, Value param1, Value param2);
}
public final Value eval(EvalContext ctx, Value... params) {
- validateNumParams(params);
- return eval3(ctx, params[0], params[1], params[2]);
+ try {
+ validateNumParams(params);
+ return eval3(ctx, params[0], params[1], params[2]);
+ } catch(Exception e) {
+ throw invalidFunctionCall(e, params);
+ }
}
protected abstract Value eval3(EvalContext ctx,
}
public final Value eval(EvalContext ctx, Value... params) {
- validateNumParams(params);
- return evalVar(ctx, params);
+ try {
+ validateNumParams(params);
+ return evalVar(ctx, params);
+ } catch(Exception e) {
+ throw invalidFunctionCall(e, params);
+ }
}
protected abstract Value evalVar(EvalContext ctx, Value[] params);
}
+
public static final Function IIF = registerFunc(new Func3("IIf") {
@Override
protected Value eval3(EvalContext ctx,
}
});
- // public static final Function CDATE = registerFunc(new Func1("CDate") {
- // @Override
- // protected Value eval1(EvalContext ctx, Value param1) {
- // FIXME
- // BigDecimal bd = param1.getAsBigDecimal();
- // bd.setScale(4, DEFAULT_ROUND_MODE);
- // return BuiltinOperators.toValue(bd);
- // }
- // });
+ public static final Function CDATE = registerFunc(new Func1("CDate") {
+ @Override
+ protected Value eval1(EvalContext ctx, Value param1) {
+ return DefaultDateFunctions.nonNullToDateValue(ctx, param1);
+ }
+ });
+ static {
+ registerFunc("CVDate", CDATE);
+ }
public static final Function CDBL = registerFunc(new Func1("CDbl") {
@Override
}
});
- // FIXME, CVAR
+ public static final Function CVAR = registerFunc(new Func1("CVar") {
+ @Override
+ protected Value eval1(EvalContext ctx, Value param1) {
+ return param1;
+ }
+ });
+
+ public static final Function ISNULL = registerFunc(new Func1("IsNull") {
+ @Override
+ protected Value eval1(EvalContext ctx, Value param1) {
+ return BuiltinOperators.toValue(param1.isNull());
+ }
+ });
+
+ public static final Function ISDATE = registerFunc(new Func1("IsDate") {
+ @Override
+ protected Value eval1(EvalContext ctx, Value param1) {
+ return BuiltinOperators.toValue(
+ !param1.isNull() &&
+ (DefaultDateFunctions.nonNullToDateValue(ctx, param1) != null));
+ }
+ });
private static long roundToLong(Value param) {
// https://support.office.com/en-us/article/Access-Functions-by-category-b8b136c3-2716-4d39-94a2-658ce330ed83
static Function registerFunc(Function func) {
- return registerFunc(false, func);
+ registerFunc(func.getName(), func);
+ return func;
}
static Function registerStringFunc(Function func) {
- return registerFunc(true, func);
+ // for our purposes the non-variant versions are the same function
+ // (e.g. "Foo" and "Foo$")
+ registerFunc(func.getName(), func);
+ registerFunc(func.getName() + NON_VAR_SUFFIX, func);
+ return func;
}
- private static Function registerFunc(boolean includeNonVar, Function func) {
- String fname = func.getName().toLowerCase();
- if(FUNCS.put(fname, func) != null) {
- throw new IllegalStateException("Duplicate function " + func);
- }
- if(includeNonVar) {
- // for our purposes the non-variant versions are the same function
- fname += NON_VAR_SUFFIX;
- if(FUNCS.put(fname, func) != null) {
- throw new IllegalStateException("Duplicate function " + func);
- }
+ private static void registerFunc(String fname, Function func) {
+ String lookupFname = DatabaseImpl.toLookupName(fname);
+ if(FUNCS.put(lookupFname, func) != null) {
+ throw new IllegalStateException("Duplicate function " + fname);
}
- return func;
}
}
public static final Function ABS = registerFunc(new Func1NullIsNull("Abs") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
- Value.Type type = param1.getType();
- if(!type.isNumeric()) {
- // FIXME how to handle text/date?
- // FIXME, cast to number, date as date?
- }
- if(type.isIntegral()) {
+ Value.Type mathType = param1.getType();
+
+ switch(mathType) {
+ case DATE:
+ case TIME:
+ case DATE_TIME:
+ // dates/times get converted to date doubles for arithmetic
+ double result = Math.abs(param1.getAsDouble());
+ return BuiltinOperators.toDateValue(ctx, mathType, result, param1, null);
+ case LONG:
return BuiltinOperators.toValue(Math.abs(param1.getAsLong()));
- }
- if(type.getPreferredFPType() == Value.Type.DOUBLE) {
+ case DOUBLE:
return BuiltinOperators.toValue(Math.abs(param1.getAsDouble()));
+ case STRING:
+ case BIG_DEC:
+ return BuiltinOperators.toValue(param1.getAsBigDecimal().abs());
+ default:
+ throw new RuntimeException("Unexpected type " + mathType);
}
- return BuiltinOperators.toValue(param1.getAsBigDecimal().abs());
}
});
if(hasTime) {
int strLen = dateStr.length();
- hasTime = ((strLen >= AMPM_SUFFIX_LEN) &&
+ hasAmPm = ((strLen >= AMPM_SUFFIX_LEN) &&
(dateStr.regionMatches(true, strLen - AMPM_SUFFIX_LEN,
AM_SUFFIX, 0, AMPM_SUFFIX_LEN) ||
dateStr.regionMatches(true, strLen - AMPM_SUFFIX_LEN,