Browse Source

add more type coercion, string and date to num

git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/branches/exprs@1084 f203690c-595d-4dc9-a70b-905162fa7fd2
tags/jackcess-2.2.0
James Ahlborn 7 years ago
parent
commit
631911dcf1

+ 10
- 0
src/main/java/com/healthmarketscience/jackcess/expr/Value.java View File

return((ordinal() <= DOUBLE.ordinal()) ? DOUBLE : BIG_DEC); return((ordinal() <= DOUBLE.ordinal()) ? DOUBLE : BIG_DEC);
} }


public Type getPreferredNumericType() {
if(isNumeric()) {
return this;
}
if(isTemporal()) {
return ((this == DATE) ? LONG : DOUBLE);
}
return null;
}

private boolean inRange(Type start, Type end) { private boolean inRange(Type start, Type end) {
return ((start.ordinal() <= ordinal()) && (ordinal() <= end.ordinal())); return ((start.ordinal() <= ordinal()) && (ordinal() <= end.ordinal()));
} }

+ 60
- 69
src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java View File

public static final Value FALSE_VAL = new LongValue(0L); public static final Value FALSE_VAL = new LongValue(0L);
public static final Value EMPTY_STR_VAL = new StringValue(""); public static final Value EMPTY_STR_VAL = new StringValue("");


private enum CoercionType {
SIMPLE(true, true), GENERAL(false, true), COMPARE(false, false);

final boolean _preferTemporal;
final boolean _allowCoerceStringToNum;

private CoercionType(boolean preferTemporal,
boolean allowCoerceStringToNum) {
_preferTemporal = preferTemporal;
_allowCoerceStringToNum = allowCoerceStringToNum;
}
}

private BuiltinOperators() {} private BuiltinOperators() {}


// null propagation rules: // null propagation rules:
return NULL_VAL; return NULL_VAL;
} }


Value.Type mathType = getSimpleMathTypePrecedence(param1, param2);
Value.Type mathType = getMathTypePrecedence(param1, param2,
CoercionType.SIMPLE);


switch(mathType) { switch(mathType) {
case STRING: case STRING:
return NULL_VAL; return NULL_VAL;
} }


Value.Type mathType = getSimpleMathTypePrecedence(param1, param2);
Value.Type mathType = getMathTypePrecedence(param1, param2,
CoercionType.SIMPLE);


switch(mathType) { switch(mathType) {
// case STRING: break; unsupported // case STRING: break; unsupported
return NULL_VAL; return NULL_VAL;
} }


// FIXME, string will convert to number if one is number and one is a string parseable as a number!!!

Value.Type mathType = getGeneralMathTypePrecedence(param1, param2);
Value.Type mathType = getMathTypePrecedence(param1, param2,
CoercionType.GENERAL);


switch(mathType) { switch(mathType) {
// case STRING: break; unsupported // case STRING: break; unsupported
return NULL_VAL; return NULL_VAL;
} }


Value.Type mathType = getGeneralMathTypePrecedence(param1, param2);
Value.Type mathType = getMathTypePrecedence(param1, param2,
CoercionType.GENERAL);


switch(mathType) { switch(mathType) {
// case STRING: break; unsupported // case STRING: break; unsupported
return NULL_VAL; return NULL_VAL;
} }


Value.Type mathType = getGeneralMathTypePrecedence(param1, param2);
Value.Type mathType = getMathTypePrecedence(param1, param2,
CoercionType.GENERAL);


boolean wasDouble = false; boolean wasDouble = false;
switch(mathType) { switch(mathType) {
return NULL_VAL; return NULL_VAL;
} }


Value.Type mathType = getGeneralMathTypePrecedence(param1, param2);
Value.Type mathType = getMathTypePrecedence(param1, param2,
CoercionType.GENERAL);


// jdk only supports general pow() as doubles, let's go with that // jdk only supports general pow() as doubles, let's go with that
double result = Math.pow(param1.getAsDouble(), param2.getAsDouble()); double result = Math.pow(param1.getAsDouble(), param2.getAsDouble());
return NULL_VAL; return NULL_VAL;
} }


Value.Type mathType = getGeneralMathTypePrecedence(param1, param2);
Value.Type mathType = getMathTypePrecedence(param1, param2,
CoercionType.GENERAL);


boolean wasDouble = false; boolean wasDouble = false;
switch(mathType) { switch(mathType) {
Value param1, Value param2) Value param1, Value param2)
{ {
// note that comparison does not do string to num coercion // note that comparison does not do string to num coercion
Value.Type compareType = getGeneralMathTypePrecedence(param1, param2, false);
Value.Type compareType = getMathTypePrecedence(param1, param2,
CoercionType.COMPARE);


switch(compareType) { switch(compareType) {
case STRING: case STRING:
} }
} }


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((t1 == Value.Type.STRING) || (t2 == Value.Type.STRING)) {

// see if this is mixed string/numeric and the string can be coerced to
// a number
Value.Type numericType = coerceStringToNumeric(param1, param2);
if(numericType != null) {
// string can be coerced to number
return numericType;
}

// string always wins
return Value.Type.STRING;
}

// 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 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 getGeneralMathTypePrecedence(
Value param1, Value param2)
{
return getGeneralMathTypePrecedence(param1, param2, true);
}

private static Value.Type getGeneralMathTypePrecedence(
Value param1, Value param2, boolean allowCoerceToNum)
private static Value.Type getMathTypePrecedence(
Value param1, Value param2, CoercionType cType)
{ {
Value.Type t1 = param1.getType(); Value.Type t1 = param1.getType();
Value.Type t2 = param2.getType(); Value.Type t2 = param2.getType();


if(t1 == t2) { if(t1 == t2) {


if(t1.isTemporal()) {
return Value.Type.DOUBLE;
if(!cType._preferTemporal && t1.isTemporal()) {
return t1.getPreferredNumericType();
} }


return t1; return t1;


if((t1 == Value.Type.STRING) || (t2 == Value.Type.STRING)) { if((t1 == Value.Type.STRING) || (t2 == Value.Type.STRING)) {


if(allowCoerceToNum) {
if(cType._allowCoerceStringToNum) {
// see if this is mixed string/numeric and the string can be coerced // see if this is mixed string/numeric and the string can be coerced
// to a number // to a number
Value.Type numericType = coerceStringToNumeric(param1, param2);
Value.Type numericType = coerceStringToNumeric(param1, param2, cType);
if(numericType != null) { if(numericType != null) {
// string can be coerced to number // string can be coerced to number
return numericType; return numericType;
return Value.Type.STRING; return Value.Type.STRING;
} }


// for "simple" math, keep as date/times
if(cType._preferTemporal &&
(t1.isTemporal() || t2.isTemporal())) {
return (t1.isTemporal() ?
(t2.isTemporal() ?
// for mixed temporal types, always go to date/time
Value.Type.DATE_TIME : t1) :
t2);
}

t1 = t1.getPreferredNumericType();
t2 = t2.getPreferredNumericType();

// if both types are integral, choose "largest" // if both types are integral, choose "largest"
if(t1.isIntegral() && t2.isIntegral()) { if(t1.isIntegral() && t2.isIntegral()) {
return max(t1, t2); return max(t1, t2);
return max(t1.getPreferredFPType(), t2.getPreferredFPType()); return max(t1.getPreferredFPType(), t2.getPreferredFPType());
} }


private static Value.Type coerceStringToNumeric(Value param1, Value param2) {
private static Value.Type coerceStringToNumeric(
Value param1, Value param2, CoercionType cType) {
Value.Type t1 = param1.getType(); Value.Type t1 = param1.getType();
Value.Type t2 = param2.getType(); Value.Type t2 = param2.getType();


Value.Type numericType = null;
Value.Type prefType = null;
Value strParam = null; Value strParam = null;
if(t1.isNumeric()) { if(t1.isNumeric()) {
numericType = t1;
prefType = t1;
strParam = param2; strParam = param2;
} else if(t2.isNumeric()) { } else if(t2.isNumeric()) {
numericType = t2;
prefType = t2;
strParam = param1;
} else if(t1.isTemporal()) {
prefType = (cType._preferTemporal ? t1 : t1.getPreferredNumericType());
strParam = param2;
} else if(t2.isTemporal()) {
prefType = (cType._preferTemporal ? t2 : t2.getPreferredNumericType());
strParam = param1; strParam = param1;
} else { } else {
// no numeric type involved // no numeric type involved
try { try {
// see if string can be coerced to a number // see if string can be coerced to a number
strParam.getAsBigDecimal(); strParam.getAsBigDecimal();
return numericType;
return prefType;
} catch(NumberFormatException ignored) { } catch(NumberFormatException ignored) {
// not a number // not a number
} }

+ 2
- 1
src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java View File

} }


try { try {
return new Token(TokenType.LITERAL, sdf.parse(dateStr), dateStr, valType);
return new Token(TokenType.LITERAL, sdf.parse(dateStr), dateStr, valType,
sdf);
} catch(ParseException pe) { } catch(ParseException pe) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Invalid date time literal " + dateStr + " " + buf, pe); "Invalid date time literal " + dateStr + " " + buf, pe);

+ 23
- 0
src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java View File

package com.healthmarketscience.jackcess.impl.expr; package com.healthmarketscience.jackcess.impl.expr;


import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date;


import com.healthmarketscience.jackcess.DatabaseBuilder; import com.healthmarketscience.jackcess.DatabaseBuilder;
import com.healthmarketscience.jackcess.TestUtil; import com.healthmarketscience.jackcess.TestUtil;


} }


public void testTypeCoercion() throws Exception
{
assertEquals("foobar", eval("=\"foo\" + \"bar\""));

assertEquals("12foo", eval("=12 + \"foo\""));
assertEquals("foo12", eval("=\"foo\" + 12"));

assertEquals(37L, eval("=\"25\" + 12"));
assertEquals(37L, eval("=12 + \"25\""));

evalFail(("=12 - \"foo\""), RuntimeException.class);
evalFail(("=\"foo\" - 12"), RuntimeException.class);

assertEquals("foo1225", eval("=\"foo\" + 12 + 25"));
assertEquals("37foo", eval("=12 + 25 + \"foo\""));
assertEquals("foo37", eval("=\"foo\" + (12 + 25)"));
assertEquals("25foo12", eval("=\"25foo\" + 12"));

assertEquals(new Date(1485579600000L), eval("=#1/1/2017# + 27"));
assertEquals(128208L, eval("=#1/1/2017# * 3"));
}

private static void validateExpr(String exprStr, String debugStr) { private static void validateExpr(String exprStr, String debugStr) {
validateExpr(exprStr, debugStr, exprStr); validateExpr(exprStr, debugStr, exprStr);
} }

Loading…
Cancel
Save