--- /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.util;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+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.util.Expression.*;
+
+
+/**
+ *
+ * @author James Ahlborn
+ */
+public class BuiltinOperators
+{
+
+ public static final Value NULL_VAL =
+ new SimpleValue(ValueType.NULL, null);
+ public static final Value TRUE_VAL =
+ new SimpleValue(ValueType.BOOLEAN, Boolean.TRUE);
+ public static final Value FALSE_VAL =
+ new SimpleValue(ValueType.BOOLEAN, Boolean.FALSE);
+
+ public static class SimpleValue implements Value
+ {
+ private final ValueType _type;
+ private final Object _val;
+
+ public SimpleValue(ValueType type, Object val) {
+ _type = type;
+ _val = val;
+ }
+
+ public ValueType getType() {
+ return _type;
+ }
+
+ public Object get() {
+ return _val;
+ }
+ }
+
+ private BuiltinOperators() {}
+
+ // FIXME, null propagation:
+ // http://www.utteraccess.com/wiki/index.php/Nulls_And_Their_Behavior
+ // https://theaccessbuddy.wordpress.com/2012/10/24/6-logical-operators-in-ms-access-that-you-must-know-operator-types-3-of-5/
+ // - number ops
+ // - comparison ops
+ // - logical ops (some "special")
+ // - And - can be false if one arg is false
+ // - Or - can be true if one arg is true
+ // - between, not, like, in
+ // - *NOT* concal op '&'
+ // FIXME, Imp operator?
+
+ public static Value negate(Value param1) {
+ // FIXME
+ return null;
+ }
+
+ public static Value add(Value param1, Value param2) {
+ // FIXME
+ return null;
+ }
+
+ public static Value subtract(Value param1, Value param2) {
+ // FIXME
+ return null;
+ }
+
+ public static Value multiply(Value param1, Value param2) {
+ // FIXME
+ return null;
+ }
+
+ public static Value divide(Value param1, Value param2) {
+ // FIXME
+ return null;
+ }
+
+ public static Value intDivide(Value param1, Value param2) {
+ // FIXME
+ return null;
+ }
+
+ public static Value exp(Value param1, Value param2) {
+ // FIXME
+ return null;
+ }
+
+ public static Value concat(Value param1, Value param2) {
+ // note, this op converts null to empty string
+
+
+ // FIXME
+ return null;
+ }
+
+ public static Value mod(Value param1, Value param2) {
+ // FIXME
+ return null;
+ }
+
+ public static Value not(Value param1) {
+ if(paramIsNull(param1)) {
+ // null propagation
+ return NULL_VAL;
+ }
+
+ return toValue(!nonNullValueToBoolean(param1));
+ }
+
+ public static Value lessThan(Value param1, Value param2) {
+ if(anyParamIsNull(param1, param2)) {
+ // null propagation
+ return NULL_VAL;
+ }
+
+ return toValue(nonNullCompareTo(param1, param2) < 0);
+ }
+
+ public static Value greaterThan(Value param1, Value param2) {
+ if(anyParamIsNull(param1, param2)) {
+ // null propagation
+ return NULL_VAL;
+ }
+
+ return toValue(nonNullCompareTo(param1, param2) > 0);
+ }
+
+ public static Value lessThanEq(Value param1, Value param2) {
+ if(anyParamIsNull(param1, param2)) {
+ // null propagation
+ return NULL_VAL;
+ }
+
+ return toValue(nonNullCompareTo(param1, param2) <= 0);
+ }
+
+ public static Value greaterThanEq(Value param1, Value param2) {
+ if(anyParamIsNull(param1, param2)) {
+ // null propagation
+ return NULL_VAL;
+ }
+
+ return toValue(nonNullCompareTo(param1, param2) >= 0);
+ }
+
+ public static Value equals(Value param1, Value param2) {
+ if(anyParamIsNull(param1, param2)) {
+ // null propagation
+ return NULL_VAL;
+ }
+
+ return toValue(nonNullCompareTo(param1, param2) == 0);
+ }
+
+ public static Value notEquals(Value param1, Value param2) {
+ if(anyParamIsNull(param1, param2)) {
+ // null propagation
+ return NULL_VAL;
+ }
+
+ return toValue(nonNullCompareTo(param1, param2) != 0);
+ }
+
+ public static Value and(Value param1, Value param2) {
+
+ // "and" uses short-circuit logic
+
+ if(paramIsNull(param1)) {
+ return NULL_VAL;
+ }
+
+ boolean b1 = nonNullValueToBoolean(param1);
+ if(!b1) {
+ return FALSE_VAL;
+ }
+
+ if(paramIsNull(param2)) {
+ return NULL_VAL;
+ }
+
+ return toValue(nonNullValueToBoolean(param2));
+ }
+
+ public static Value or(Value param1, Value param2) {
+
+ // "or" uses short-circuit logic
+
+ if(paramIsNull(param1)) {
+ return NULL_VAL;
+ }
+
+ boolean b1 = nonNullValueToBoolean(param1);
+ if(b1) {
+ return TRUE_VAL;
+ }
+
+ if(paramIsNull(param2)) {
+ return NULL_VAL;
+ }
+
+ return toValue(nonNullValueToBoolean(param2));
+ }
+
+ public static Value eqv(Value param1, Value param2) {
+ if(anyParamIsNull(param1, param2)) {
+ // null propagation
+ return NULL_VAL;
+ }
+
+ boolean b1 = nonNullValueToBoolean(param1);
+ boolean b2 = nonNullValueToBoolean(param2);
+
+ return toValue(b1 == b2);
+ }
+
+ public static Value xor(Value param1, Value param2) {
+ if(anyParamIsNull(param1, param2)) {
+ // null propagation
+ return NULL_VAL;
+ }
+
+ boolean b1 = nonNullValueToBoolean(param1);
+ boolean b2 = nonNullValueToBoolean(param2);
+
+ return toValue(b1 ^ b2);
+ }
+
+ public static Value imp(Value param1, Value param2) {
+
+ // "imp" uses short-circuit logic
+
+ if(paramIsNull(param1)) {
+ if(paramIsNull(param2) || !nonNullValueToBoolean(param2)) {
+ // null propagation
+ return NULL_VAL;
+ }
+
+ return TRUE_VAL;
+ }
+
+ boolean b1 = nonNullValueToBoolean(param1);
+ if(!b1) {
+ return TRUE_VAL;
+ }
+
+ if(paramIsNull(param2)) {
+ // null propagation
+ return NULL_VAL;
+ }
+
+ return toValue(nonNullValueToBoolean(param2));
+ }
+
+ public static Value isNull(Value param1) {
+ return toValue(param1.getType() == ValueType.NULL);
+ }
+
+ public static Value isNotNull(Value param1) {
+ return toValue(param1.getType() == ValueType.NULL);
+ }
+
+ public static Value like(Value param1, Pattern pattern) {
+ // FIXME
+ return null;
+ }
+
+ public static Value between(Value param1, Value param2, Value param3) {
+
+ // null propagate any field left to right. uses short circuit eval
+ if(anyParamIsNull(param1, param2, param3)) {
+ // null propagation
+ return NULL_VAL;
+ }
+
+ // FIXME
+ return null;
+ }
+
+ public static Value notBetween(Value param1, Value param2, Value param3) {
+ return not(between(param1, param2, param3));
+ }
+
+ public static Value in(Value param1, Value[] params) {
+
+ // null propagate any param. uses short circuit eval of params
+ if(paramIsNull(param1)) {
+ // null propagation
+ return NULL_VAL;
+ }
+
+ for(Value val : params) {
+ if(paramIsNull(val)) {
+ continue;
+ }
+
+ // FIXME test
+ }
+
+ // FIXME
+ return null;
+ }
+
+ public static Value notIn(Value param1, Value[] params) {
+ return not(in(param1, params));
+ }
+
+
+ private static boolean anyParamIsNull(Value param1, Value param2) {
+ return (paramIsNull(param1) || paramIsNull(param2));
+ }
+
+ private static boolean anyParamIsNull(Value param1, Value param2,
+ Value param3) {
+ return (paramIsNull(param1) || paramIsNull(param2) || paramIsNull(param3));
+ }
+
+ private static boolean paramIsNull(Value param1) {
+ return (param1.getType() == ValueType.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 boolean nonNullValueToBoolean(Value val) {
+ switch(val.getType()) {
+ case BOOLEAN:
+ return (Boolean)val.get();
+ case STRING:
+ case DATE:
+ case TIME:
+ case DATE_TIME:
+ // strings and dates are always true
+ return true;
+ case LONG:
+ return (((Number)val.get()).longValue() != 0L);
+ case DOUBLE:
+ return (((Number)val.get()).doubleValue() != 0.0d);
+ case BIG_INT:
+ return (((BigInteger)val.get()).compareTo(BigInteger.ZERO) != 0L);
+ case BIG_DEC:
+ return (((BigDecimal)val.get()).compareTo(BigDecimal.ZERO) != 0L);
+ default:
+ throw new RuntimeException("Unexpected type " + val.getType());
+ }
+ }
+
+ protected static int nonNullCompareTo(
+ Value param1, Value param2)
+ {
+ // FIXME
+ return 0;
+ }
+
+ public static Value toValue(boolean b) {
+ return (b ? TRUE_VAL : FALSE_VAL);
+ }
+
+ public static Value toValue(Object obj) {
+ if(obj == null) {
+ return NULL_VAL;
+ }
+
+ if(obj instanceof Value) {
+ return (Value)obj;
+ }
+
+ if(obj instanceof Boolean) {
+ return (((Boolean)obj) ? TRUE_VAL : FALSE_VAL);
+ }
+
+ if(obj instanceof Date) {
+ // any way to figure out whether it's a date/time/dateTime?
+ return new SimpleValue(ValueType.DATE_TIME, obj);
+ }
+
+ if(obj instanceof Number) {
+ if((obj instanceof Double) || (obj instanceof Float)) {
+ return new SimpleValue(ValueType.DOUBLE, obj);
+ }
+ if(obj instanceof BigDecimal) {
+ return new SimpleValue(ValueType.BIG_DEC, obj);
+ }
+ if(obj instanceof BigInteger) {
+ return new SimpleValue(ValueType.BIG_INT, obj);
+ }
+ return new SimpleValue(ValueType.LONG, obj);
+ }
+
+ try {
+ return new SimpleValue(ValueType.STRING,
+ ColumnImpl.toCharSequence(obj).toString());
+ } catch(IOException e) {
+ throw new RuntimeIOException(e);
+ }
+ }
+
+}
import java.util.Map;
import java.util.Set;
-import com.healthmarketscience.jackcess.DatabaseBuilder;
-import com.healthmarketscience.jackcess.impl.DatabaseImpl;
import static com.healthmarketscience.jackcess.util.Expressionator.*;
+import com.healthmarketscience.jackcess.util.Expression.ValueType;
+
/**
*
* Tokenizes an expression string of the given type and (optionally) in the
* context of the relevant database.
*/
- static List<Token> tokenize(Type exprType, String exprStr, DatabaseImpl db) {
+ static List<Token> tokenize(Type exprType, String exprStr,
+ ParseContext context) {
if(exprStr != null) {
exprStr = exprStr.trim();
case IS_OP_FLAG:
// special case '-' for negative number
- Map.Entry<?,String> numLit = maybeParseNumberLiteral(c, buf);
+ Token numLit = maybeParseNumberLiteral(c, buf);
if(numLit != null) {
- tokens.add(new Token(TokenType.LITERAL, numLit.getKey(),
- numLit.getValue()));
+ tokens.add(numLit);
continue;
}
switch(c) {
case QUOTED_STR_CHAR:
- tokens.add(new Token(TokenType.LITERAL, parseQuotedString(buf)));
+ tokens.add(new Token(TokenType.LITERAL, null, parseQuotedString(buf),
+ ValueType.STRING));
break;
case DATE_LIT_QUOTE_CHAR:
- Map.Entry<?,String> dateLit = parseDateLiteralString(buf, db);
- tokens.add(new Token(TokenType.LITERAL, dateLit.getKey(),
- dateLit.getValue()));
+ tokens.add(parseDateLiteralString(buf, context));
break;
case OBJ_NAME_START_CHAR:
tokens.add(new Token(TokenType.OBJ_NAME, parseObjNameString(buf)));
} else {
if(isDigit(c)) {
- Map.Entry<?,String> numLit = maybeParseNumberLiteral(c, buf);
+ Token numLit = maybeParseNumberLiteral(c, buf);
if(numLit != null) {
- tokens.add(new Token(TokenType.LITERAL, numLit.getKey(),
- numLit.getValue()));
+ tokens.add(numLit);
continue;
}
}
return sb.toString();
}
- private static Map.Entry<?,String> parseDateLiteralString(
- ExprBuf buf, DatabaseImpl db)
+ private static Token parseDateLiteralString(
+ ExprBuf buf, ParseContext context)
{
String dateStr = parseStringUntil(buf, DATE_LIT_QUOTE_CHAR, null);
boolean hasTime = (dateStr.indexOf(':') >= 0);
SimpleDateFormat sdf = null;
+ ValueType valType = null;
if(hasDate && hasTime) {
- sdf = buf.getDateTimeFormat(db);
+ sdf = buf.getDateTimeFormat(context);
+ valType = ValueType.DATE_TIME;
} else if(hasDate) {
- sdf = buf.getDateFormat(db);
+ sdf = buf.getDateFormat(context);
+ valType = ValueType.DATE;
} else if(hasTime) {
- sdf = buf.getTimeFormat(db);
+ sdf = buf.getTimeFormat(context);
+ valType = ValueType.TIME;
} else {
throw new IllegalArgumentException("Invalid date time literal " + dateStr +
" " + buf);
}
- // FIXME, do we need to know which "type" it was?
try {
- return newEntry(sdf.parse(dateStr), dateStr);
+ return new Token(TokenType.LITERAL, sdf.parse(dateStr), dateStr, valType);
} catch(ParseException pe) {
throw new IllegalArgumentException(
"Invalid date time literal " + dateStr + " " + buf, pe);
}
}
- private static Map.Entry<?,String> maybeParseNumberLiteral(char firstChar, ExprBuf buf) {
+ private static Token maybeParseNumberLiteral(char firstChar, ExprBuf buf) {
StringBuilder sb = buf.getScratchBuffer().append(firstChar);
boolean hasDigit = isDigit(firstChar);
// what number type to use here?
BigDecimal num = new BigDecimal(numStr);
foundNum = true;
- return newEntry(num, numStr);
+ return new Token(TokenType.LITERAL, num, numStr, ValueType.BIG_DEC);
} catch(NumberFormatException ne) {
throw new IllegalArgumentException(
"Invalid number literal " + numStr + " " + buf, ne);
return _scratch;
}
- public SimpleDateFormat getDateFormat(DatabaseImpl db) {
+ public SimpleDateFormat getDateFormat(ParseContext context) {
if(_dateFmt == null) {
- _dateFmt = newFormat(DATE_FORMAT, db);
+ _dateFmt = context.createDateFormat(DATE_FORMAT);
}
return _dateFmt;
}
- public SimpleDateFormat getTimeFormat(DatabaseImpl db) {
+ public SimpleDateFormat getTimeFormat(ParseContext context) {
if(_timeFmt == null) {
- _timeFmt = newFormat(TIME_FORMAT, db);
+ _timeFmt = context.createDateFormat(TIME_FORMAT);
}
return _timeFmt;
}
- public SimpleDateFormat getDateTimeFormat(DatabaseImpl db) {
+ public SimpleDateFormat getDateTimeFormat(ParseContext context) {
if(_dateTimeFmt == null) {
- _dateTimeFmt = newFormat(DATE_TIME_FORMAT, db);
+ _dateTimeFmt = context.createDateFormat(DATE_TIME_FORMAT);
}
return _dateTimeFmt;
}
- private static SimpleDateFormat newFormat(String str, DatabaseImpl db) {
- return ((db != null) ? db.createDateFormat(str) :
- DatabaseBuilder.createDateFormat(str));
- }
-
@Override
public String toString() {
return "[char " + _pos + "] '" + _str + "'";
private final TokenType _type;
private final Object _val;
private final String _valStr;
+ private final ValueType _valType;
private Token(TokenType type, String val) {
this(type, val, val);
}
private Token(TokenType type, Object val, String valStr) {
+ this(type, val, valStr, null);
+ }
+
+ private Token(TokenType type, Object val, String valStr, ValueType valType) {
_type = type;
- _val = val;
+ _val = ((val != null) ? val : valStr);
_valStr = valStr;
+ _valType = valType;
}
public TokenType getType() {
return _valStr;
}
+ public ValueType getValueType() {
+ return _valType;
+ }
+
@Override
public String toString() {
if(_type == TokenType.SPACE) {
return "' '";
}
String str = "[" + _type + "] '" + _val + "'";
- if(_type == TokenType.LITERAL) {
- str += " (" + _val.getClass() + ")";
+ if(_valType != null) {
+ str += " (" + _valType + ")";
}
return str;
}
package com.healthmarketscience.jackcess.util;
+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 java.util.Set;
import java.util.regex.Pattern;
-import com.healthmarketscience.jackcess.Database;
-import com.healthmarketscience.jackcess.impl.DatabaseImpl;
+import com.healthmarketscience.jackcess.DatabaseBuilder;
import static com.healthmarketscience.jackcess.util.ExpressionTokenizer.Token;
import static com.healthmarketscience.jackcess.util.ExpressionTokenizer.TokenType;
+import com.healthmarketscience.jackcess.util.Expression.*;
/**
*
DEFAULT_VALUE, FIELD_VALIDATOR, RECORD_VALIDATOR;
}
+ public interface ParseContext {
+ public SimpleDateFormat createDateFormat(String formatStr);
+ public Function getExpressionFunction(String name);
+ }
+
+ public static final ParseContext DEFAULT_PARSE_CONTEXT = new ParseContext() {
+ public SimpleDateFormat createDateFormat(String formatStr) {
+ return DatabaseBuilder.createDateFormat(formatStr);
+ }
+ public Function getExpressionFunction(String name) {
+ return DefaultFunctions.getFunction(name);
+ }
+ };
+
private enum WordType {
OP, COMP, LOG_OP, CONST, SPEC_OP_PREFIX, DELIM;
}
private static final String CLOSE_PAREN = ")";
private static final String FUNC_PARAM_SEP = ",";
- private static final Map<String,WordType> WORD_TYPES = new HashMap<String,WordType>();
+ private static final Map<String,WordType> WORD_TYPES =
+ new HashMap<String,WordType>();
static {
setWordType(WordType.OP, "+", "-", "*", "/", "\\", "^", "&", "mod");
setWordType(WordType.COMP, "<", "<=", ">", ">=", "=", "<>");
- setWordType(WordType.LOG_OP, "and", "or", "eqv", "xor");
+ setWordType(WordType.LOG_OP, "and", "or", "eqv", "xor", "imp");
setWordType(WordType.CONST, "true", "false", "null");
setWordType(WordType.SPEC_OP_PREFIX, "is", "like", "between", "in", "not");
// "X is null", "X is not null", "X like P", "X between A and B",
private interface OpType {}
private enum UnaryOp implements OpType {
- NEG("-", false), NOT("Not", true);
+ NEG("-", false) {
+ @Override public Value eval(Value param1) {
+ return BuiltinOperators.negate(param1);
+ }
+ },
+ NOT("Not", true) {
+ @Override public Value eval(Value param1) {
+ return BuiltinOperators.not(param1);
+ }
+ };
private final String _str;
private final boolean _needSpace;
public String toString() {
return _str;
}
+
+ public abstract Value eval(Value param1);
}
private enum BinaryOp implements OpType {
- PLUS("+"), MINUS("-"), MULT("*"), DIV("/"), INT_DIV("\\"), EXP("^"),
- CONCAT("&"), MOD("Mod");
+ PLUS("+") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.add(param1, param2);
+ }
+ },
+ MINUS("-") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.subtract(param1, param2);
+ }
+ },
+ MULT("*") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.multiply(param1, param2);
+ }
+ },
+ DIV("/") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.divide(param1, param2);
+ }
+ },
+ INT_DIV("\\") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.intDivide(param1, param2);
+ }
+ },
+ EXP("^") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.exp(param1, param2);
+ }
+ },
+ CONCAT("&") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.concat(param1, param2);
+ }
+ },
+ MOD("Mod") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.mod(param1, param2);
+ }
+ };
private final String _str;
public String toString() {
return _str;
}
+
+ public abstract Value eval(Value param1, Value param2);
}
private enum CompOp implements OpType {
- LT("<"), LTE("<="), GT(">"), GTE(">="), EQ("="), NE("<>");
+ LT("<") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.lessThan(param1, param2);
+ }
+ },
+ LTE("<=") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.lessThanEq(param1, param2);
+ }
+ },
+ GT(">") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.greaterThan(param1, param2);
+ }
+ },
+ GTE(">=") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.greaterThanEq(param1, param2);
+ }
+ },
+ EQ("=") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.equals(param1, param2);
+ }
+ },
+ NE("<>") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.notEquals(param1, param2);
+ }
+ };
private final String _str;
public String toString() {
return _str;
}
+
+ public abstract Value eval(Value param1, Value param2);
}
private enum LogOp implements OpType {
- AND("And"), OR("Or"), EQV("Eqv"), XOR("Xor");
+ AND("And") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.and(param1, param2);
+ }
+ },
+ OR("Or") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.or(param1, param2);
+ }
+ },
+ EQV("Eqv") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.eqv(param1, param2);
+ }
+ },
+ XOR("Xor") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.xor(param1, param2);
+ }
+ },
+ IMP("Imp") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.imp(param1, param2);
+ }
+ };
private final String _str;
public String toString() {
return _str;
}
+
+ public abstract Value eval(Value param1, Value param2);
}
private enum SpecOp implements OpType {
// note, "NOT" is not actually used as a special operation, always
// replaced with UnaryOp.NOT
- NOT("Not"), IS_NULL("Is Null"), IS_NOT_NULL("Is Not Null"), LIKE("Like"),
- BETWEEN("Between"), NOT_BETWEEN("Not Between"), IN("In"),
- NOT_IN("Not In");
+ NOT("Not") {
+ @Override public Value eval(Value param1, Object param2, Object param3) {
+ throw new UnsupportedOperationException();
+ }
+ },
+ IS_NULL("Is Null") {
+ @Override public Value eval(Value param1, Object param2, Object param3) {
+ return BuiltinOperators.isNull(param1);
+ }
+ },
+ IS_NOT_NULL("Is Not Null") {
+ @Override public Value eval(Value param1, Object param2, Object param3) {
+ return BuiltinOperators.isNotNull(param1);
+ }
+ },
+ LIKE("Like") {
+ @Override public Value eval(Value param1, Object param2, Object param3) {
+ return BuiltinOperators.like(param1, (Pattern)param2);
+ }
+ },
+ BETWEEN("Between") {
+ @Override public Value eval(Value param1, Object param2, Object param3) {
+ return BuiltinOperators.between(param1, (Value)param2, (Value)param3);
+ }
+ },
+ NOT_BETWEEN("Not Between") {
+ @Override public Value eval(Value param1, Object param2, Object param3) {
+ return BuiltinOperators.notBetween(param1, (Value)param2, (Value)param3);
+ }
+ },
+ IN("In") {
+ @Override public Value eval(Value param1, Object param2, Object param3) {
+ return BuiltinOperators.in(param1, (Value[])param2);
+ }
+ },
+ NOT_IN("Not In") {
+ @Override public Value eval(Value param1, Object param2, Object param3) {
+ return BuiltinOperators.notIn(param1, (Value[])param2);
+ }
+ };
private final String _str;
public String toString() {
return _str;
}
+
+ public abstract Value eval(Value param1, Object param2, Object param3);
}
private static final Map<OpType, Integer> PRECENDENCE =
new OpType[]{LogOp.OR},
new OpType[]{LogOp.XOR},
new OpType[]{LogOp.EQV},
+ new OpType[]{LogOp.IMP},
new OpType[]{SpecOp.IN, SpecOp.NOT_IN, SpecOp.BETWEEN,
SpecOp.NOT_BETWEEN});
private static final Expr THIS_COL_VALUE = new Expr() {
- @Override protected Object eval(RowContext ctx) {
+ @Override protected Value eval(RowContext ctx) {
return ctx.getThisColumnValue();
}
@Override protected void toExprString(StringBuilder sb, boolean isDebug) {
}
};
- private static final Expr NULL_VALUE = new EConstValue(null, "Null");
- private static final Expr TRUE_VALUE = new EConstValue(Boolean.TRUE, "True");
- private static final Expr FALSE_VALUE = new EConstValue(Boolean.FALSE, "False");
+ private static final Expr NULL_VALUE = new EConstValue(
+ BuiltinOperators.NULL_VAL, "Null");
+ private static final Expr TRUE_VALUE = new EConstValue(
+ BuiltinOperators.TRUE_VAL, "True");
+ private static final Expr FALSE_VALUE = new EConstValue(
+ BuiltinOperators.FALSE_VAL, "False");
private Expressionator()
{
}
- static String testTokenize(Type exprType, String exprStr, Database db) {
+ static String testTokenize(Type exprType, String exprStr,
+ ParseContext context) {
+ if(context == null) {
+ context = DEFAULT_PARSE_CONTEXT;
+ }
List<Token> tokens = trimSpaces(
- ExpressionTokenizer.tokenize(exprType, exprStr, (DatabaseImpl)db));
+ ExpressionTokenizer.tokenize(exprType, exprStr, context));
if(tokens == null) {
// FIXME, NULL_EXPR?
return tokens.toString();
}
- public static Expr parse(Type exprType, String exprStr, Database db) {
+ public static Expression parse(Type exprType, String exprStr,
+ ParseContext context) {
+
+ if(context == null) {
+ context = DEFAULT_PARSE_CONTEXT;
+ }
// FIXME,restrictions:
// - default value only accepts simple exprs, otherwise becomes literal text
// - record validation cannot refer to outside columns
List<Token> tokens = trimSpaces(
- ExpressionTokenizer.tokenize(exprType, exprStr, (DatabaseImpl)db));
+ ExpressionTokenizer.tokenize(exprType, exprStr, context));
if(tokens == null) {
// FIXME, NULL_EXPR?
return null;
}
- return parseExpression(new TokBuf(exprType, tokens), false);
+ return parseExpression(new TokBuf(exprType, tokens, context), false);
}
private static List<Token> trimSpaces(List<Token> tokens) {
case LITERAL:
- buf.setPendingExpr(new ELiteralValue(t.getValue()));
+ buf.setPendingExpr(new ELiteralValue(t.getValueType(), t.getValue()));
break;
case OP:
buf.next();
List<Expr> params = findParenExprs(buf, true);
- buf.setPendingExpr(
- new EFunc(firstTok.getValueStr(), params));
+ String funcName = firstTok.getValueStr();
+ Function func = buf.getFunction(funcName);
+ if(func == null) {
+ throw new IllegalArgumentException("Could not find function '" +
+ funcName + "' " + buf);
+ }
+ buf.setPendingExpr(new EFunc(func, params));
foundFunc = true;
return true;
Expr expr = buf.takePendingExpr();
- // FIXME
Expr specOpExpr = null;
switch(specOp) {
case IS_NULL:
private final List<Token> _tokens;
private final TokBuf _parent;
private final int _parentOff;
+ private final ParseContext _context;
private int _pos;
private Expr _pendingExpr;
private final boolean _simpleExpr;
- private TokBuf(Type exprType, List<Token> tokens) {
- this(exprType, false, tokens, null, 0);
+ private TokBuf(Type exprType, List<Token> tokens, ParseContext context) {
+ this(exprType, false, tokens, null, 0, context);
}
private TokBuf(List<Token> tokens, TokBuf parent, int parentOff) {
- this(parent._exprType, parent._simpleExpr, tokens, parent, parentOff);
+ this(parent._exprType, parent._simpleExpr, tokens, parent, parentOff,
+ parent._context);
}
private TokBuf(Type exprType, boolean simpleExpr, List<Token> tokens,
- TokBuf parent, int parentOff) {
+ TokBuf parent, int parentOff, ParseContext context) {
_exprType = exprType;
_tokens = tokens;
_parent = parent;
_parentOff = parentOff;
+ _context = context;
if(parent == null) {
// "top-level" expression, determine if it is a simple expression or not
simpleExpr = isSimpleExpression();
return ExpressionTokenizer.newEntry(pos, toks);
}
+ public Function getFunction(String funcName) {
+ return _context.getExpressionFunction(funcName);
+ }
+
@Override
public String toString() {
}
}
+ private static Value[] exprListToValues(
+ List<Expr> exprs, RowContext ctx) {
+ Value[] paramVals = new Value[exprs.size()];
+ for(int i = 0; i < exprs.size(); ++i) {
+ paramVals[i] = exprs.get(i).eval(ctx);
+ }
+ return paramVals;
+ }
+
+ private static Value[] exprListToDelayedValues(
+ List<Expr> exprs, RowContext ctx) {
+ Value[] paramVals = new Value[exprs.size()];
+ for(int i = 0; i < exprs.size(); ++i) {
+ paramVals[i] = new DelayedValue(exprs.get(i), ctx);
+ }
+ return paramVals;
+ }
+
+ private static void literalStrToString(String str, StringBuilder sb) {
+ sb.append("\"")
+ .append(str.replace("\"", "\"\""))
+ .append("\"");
+ }
+
private static Pattern likePatternToRegex(String pattern, Object location) {
StringBuilder sb = new StringBuilder(pattern.length());
Pattern.UNICODE_CASE);
}
+
private interface LeftAssocExpr {
public OpType getOp();
public Expr getLeft();
public void setRight(Expr right);
}
- public static abstract class Expr
+ private static final class DelayedValue implements Value
+ {
+ private Value _val;
+ private final Expr _expr;
+ private final RowContext _ctx;
+
+ private DelayedValue(Expr expr, RowContext ctx) {
+ _expr = expr;
+ _ctx = ctx;
+ }
+
+ private Value getDelegate() {
+ if(_val == null) {
+ _val = _expr.eval(_ctx);
+ }
+ return _val;
+ }
+
+ public ValueType getType() {
+ return getDelegate().getType();
+ }
+
+ public Object get() {
+ return getDelegate().get();
+ }
+ }
+
+
+ private static abstract class Expr implements Expression
{
public Object evalDefault() {
- return eval(null);
+ Value val = eval(null);
+
+ if(val.getType() == ValueType.NULL) {
+ return null;
+ }
+
+ // FIXME, booleans seem to go to -1 (true),0 (false) ...?
+
+ return val.get();
}
- public boolean evalCondition(RowContext ctx) {
- Object val = eval(ctx);
+ public Boolean evalCondition(RowContext ctx) {
+ Value val = eval(ctx);
+
+ if(val.getType() == ValueType.NULL) {
+ return null;
+ }
- if(val instanceof Boolean) {
- return (Boolean)val;
+ if(val.getType() != ValueType.BOOLEAN) {
+ // a single value as a conditional expression seems to act like an
+ // implicit "="
+ // FIXME, what about row validators?
+ val = BuiltinOperators.equals(val, ctx.getThisColumnValue());
}
- // a single value as a conditional expression seems to act like an
- // implicit "="
- return val.equals(ctx.getThisColumnValue());
+ return (Boolean)val.get();
}
@Override
return outerExpr;
}
- protected abstract Object eval(RowContext ctx);
+ protected abstract Value eval(RowContext ctx);
protected abstract void toExprString(StringBuilder sb, boolean isDebug);
}
- public interface RowContext
- {
- public Object getThisColumnValue();
-
- public Object getRowValue(String collectionName, String objName,
- String colName);
- }
-
private static final class EConstValue extends Expr
{
- private final Object _val;
+ private final Value _val;
private final String _str;
- private EConstValue(Object val, String str) {
+ private EConstValue(Value val, String str) {
_val = val;
_str = str;
}
@Override
- protected Object eval(RowContext ctx) {
+ protected Value eval(RowContext ctx) {
return _val;
}
private static final class ELiteralValue extends Expr
{
- private final Object _value;
+ private final Value _val;
- private ELiteralValue(Object value) {
- _value = value;
+ private ELiteralValue(ValueType valType, Object value) {
+ _val = new BuiltinOperators.SimpleValue(valType, value);
}
@Override
- public Object eval(RowContext ctx) {
- return _value;
+ public Value eval(RowContext ctx) {
+ return _val;
}
@Override
protected void toExprString(StringBuilder sb, boolean isDebug) {
- // FIXME, stronger typing?
- if(_value instanceof String) {
- sb.append("\"").append(_value).append("\"");
- } else if(_value instanceof Date) {
- // FIXME Date,Time,DateTime formatting?
- sb.append("#").append(_value).append("#");
+ if(_val.getType() == ValueType.STRING) {
+ literalStrToString((String)_val.get(), sb);
+ } else if(_val.getType().isTemporal()) {
+ // // FIXME Date,Time,DateTime formatting?
+ // sb.append("#").append(_value).append("#");
+ throw new UnsupportedOperationException();
} else {
- sb.append(_value);
- }
+ sb.append(_val.get());
+ }
}
}
}
@Override
- public Object eval(RowContext ctx) {
+ public Value eval(RowContext ctx) {
return ctx.getRowValue(_collectionName, _objName, _fieldName);
}
}
}
- private static abstract class EOp
- {
-
- }
-
- private static abstract class ECond
- {
-
- }
-
private static class EParen extends Expr
{
private final Expr _expr;
}
@Override
- protected Object eval(RowContext ctx) {
+ protected Value eval(RowContext ctx) {
return _expr.eval(ctx);
}
private static class EFunc extends Expr
{
- private final String _name;
+ private final Function _func;
private final List<Expr> _params;
- private EFunc(String name, List<Expr> params) {
- _name = name;
+ private EFunc(Function func, List<Expr> params) {
+ _func = func;
_params = params;
}
@Override
- protected Object eval(RowContext ctx) {
- // FIXME how do func results act for conditional values? (literals become = tests)
-
- return false;
+ protected Value eval(RowContext ctx) {
+ return _func.eval(exprListToValues(_params, ctx));
}
@Override
protected void toExprString(StringBuilder sb, boolean isDebug) {
- sb.append(_name).append("(");
+ sb.append(_func.getName()).append("(");
if(!_params.isEmpty()) {
exprListToString(_params, ",", sb, isDebug);
private static class EBinaryOp extends EBaseBinaryOp
{
-
private EBinaryOp(BinaryOp op, Expr left, Expr right) {
super(op, left, right);
}
@Override
- protected Object eval(RowContext ctx) {
- // FIXME
-
- return null;
+ protected Value eval(RowContext ctx) {
+ return ((BinaryOp)_op).eval(_left.eval(ctx), _right.eval(ctx));
}
}
}
@Override
- protected Object eval(RowContext ctx) {
- // FIXME
-
- return null;
+ protected Value eval(RowContext ctx) {
+ return ((UnaryOp)_op).eval(_expr.eval(ctx));
}
@Override
}
@Override
- protected Object eval(RowContext ctx) {
- // FIXME
-
- return null;
+ protected Value eval(RowContext ctx) {
+ return ((CompOp)_op).eval(_left.eval(ctx), _right.eval(ctx));
}
}
}
@Override
- protected Object eval(RowContext ctx) {
- // FIXME
-
- return null;
+ protected Value eval(final RowContext ctx) {
+
+ // logical operations do short circuit evaluation, so we need to delay
+ // computing results until necessary
+ return ((LogOp)_op).eval(new DelayedValue(_left, ctx),
+ new DelayedValue(_right, ctx));
}
}
}
@Override
- protected Object eval(RowContext ctx) {
- // FIXME
-
- return null;
+ protected Value eval(RowContext ctx) {
+ return _op.eval(_expr.eval(ctx), null, null);
}
@Override
private static class ELikeOp extends ESpecOp
{
+ // FIXME, compile Pattern on first use?
private final Pattern _pattern;
private final String _patternStr;
}
@Override
- protected Object eval(RowContext ctx) {
- // FIXME
-
- return null;
+ protected Value eval(RowContext ctx) {
+ return _op.eval(_expr.eval(ctx), _pattern, null);
}
@Override
protected void toExprString(StringBuilder sb, boolean isDebug) {
_expr.toString(sb, isDebug);
- sb.append(" ").append(_op).append(" \"")
- .append(_patternStr.replace("\"", "\"\""))
- .append("\"");
+ sb.append(" ").append(_op).append(" ");
+ literalStrToString(_patternStr, sb);
if(isDebug) {
sb.append("(").append(_pattern).append(")");
}
}
@Override
- protected Object eval(RowContext ctx) {
- // FIXME
-
- return null;
+ protected Value eval(RowContext ctx) {
+ return _op.eval(_expr.eval(ctx),
+ exprListToDelayedValues(_exprs, ctx), null);
}
@Override
}
@Override
- protected Object eval(RowContext ctx) {
- // FIXME
-
- return null;
+ protected Value eval(RowContext ctx) {
+ return _op.eval(_expr.eval(ctx),
+ new DelayedValue(_startRangeExpr, ctx),
+ new DelayedValue(_endRangeExpr, ctx));
}
@Override