Просмотр исходного кода

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 лет назад
Родитель
Сommit
631911dcf1

+ 10
- 0
src/main/java/com/healthmarketscience/jackcess/expr/Value.java Просмотреть файл

@@ -46,6 +46,16 @@ public interface Value
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) {
return ((start.ordinal() <= ordinal()) && (ordinal() <= end.ordinal()));
}

+ 60
- 69
src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java Просмотреть файл

@@ -52,6 +52,19 @@ public class BuiltinOperators
public static final Value FALSE_VAL = new LongValue(0L);
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() {}

// null propagation rules:
@@ -98,7 +111,8 @@ public class BuiltinOperators
return NULL_VAL;
}

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

switch(mathType) {
case STRING:
@@ -127,7 +141,8 @@ public class BuiltinOperators
return NULL_VAL;
}

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

switch(mathType) {
// case STRING: break; unsupported
@@ -154,9 +169,8 @@ public class BuiltinOperators
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) {
// case STRING: break; unsupported
@@ -180,7 +194,8 @@ public class BuiltinOperators
return NULL_VAL;
}

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

switch(mathType) {
// case STRING: break; unsupported
@@ -214,7 +229,8 @@ public class BuiltinOperators
return NULL_VAL;
}

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

boolean wasDouble = false;
switch(mathType) {
@@ -242,7 +258,8 @@ public class BuiltinOperators
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
double result = Math.pow(param1.getAsDouble(), param2.getAsDouble());
@@ -262,7 +279,8 @@ public class BuiltinOperators
return NULL_VAL;
}

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

boolean wasDouble = false;
switch(mathType) {
@@ -544,7 +562,8 @@ public class BuiltinOperators
Value param1, Value param2)
{
// 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) {
case STRING:
@@ -631,56 +650,8 @@ public class BuiltinOperators
}
}

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 t2 = param2.getType();
@@ -689,8 +660,8 @@ public class BuiltinOperators

if(t1 == t2) {

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

return t1;
@@ -698,10 +669,10 @@ public class BuiltinOperators

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
// to a number
Value.Type numericType = coerceStringToNumeric(param1, param2);
Value.Type numericType = coerceStringToNumeric(param1, param2, cType);
if(numericType != null) {
// string can be coerced to number
return numericType;
@@ -712,6 +683,19 @@ public class BuiltinOperators
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(t1.isIntegral() && t2.isIntegral()) {
return max(t1, t2);
@@ -721,17 +705,24 @@ public class BuiltinOperators
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 t2 = param2.getType();

Value.Type numericType = null;
Value.Type prefType = null;
Value strParam = null;
if(t1.isNumeric()) {
numericType = t1;
prefType = t1;
strParam = param2;
} 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;
} else {
// no numeric type involved
@@ -741,7 +732,7 @@ public class BuiltinOperators
try {
// see if string can be coerced to a number
strParam.getAsBigDecimal();
return numericType;
return prefType;
} catch(NumberFormatException ignored) {
// not a number
}

+ 2
- 1
src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java Просмотреть файл

@@ -350,7 +350,8 @@ class ExpressionTokenizer
}

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) {
throw new IllegalArgumentException(
"Invalid date time literal " + dateStr + " " + buf, pe);

+ 23
- 0
src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java Просмотреть файл

@@ -17,6 +17,7 @@ limitations under the License.
package com.healthmarketscience.jackcess.impl.expr;

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

import com.healthmarketscience.jackcess.DatabaseBuilder;
import com.healthmarketscience.jackcess.TestUtil;
@@ -252,6 +253,28 @@ public class ExpressionatorTest extends TestCase

}

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) {
validateExpr(exprStr, debugStr, exprStr);
}

Загрузка…
Отмена
Сохранить