#1333 - ENH: Support "dynamic formula" with findSingleAttributeList() ... e.g. .select("concat(lastName,', ',firstName)")

Support cast of formula like ... select("max(updtime)::Instant")
This commit is contained in:
Rob Bygrave
2018-03-06 20:58:30 +13:00
parent e1af7c29eb
commit 873c14faaf
8 changed files with 208 additions and 74 deletions
@@ -1066,6 +1066,10 @@ public class BeanDescriptor<T> implements BeanType<T> {
return owner.getScalarType(jdbcType);
}
public ScalarType<?> getScalarType(String cast) {
return owner.getScalarType(cast);
}
/**
* Return true if this bean type has a default select clause that is not
* simply select all properties.
@@ -265,6 +265,11 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
}
}
@Override
public ScalarType<?> getScalarType(String cast) {
return typeManager.getScalarType(cast);
}
@Override
public ScalarType<?> getScalarType(int jdbcType) {
return typeManager.getScalarType(jdbcType);
@@ -66,4 +66,9 @@ public interface BeanDescriptorMap {
* Return the scalarType for the given JDBC type.
*/
ScalarType<?> getScalarType(int jdbcType);
/**
* Return the scalarType for the given logical type.
*/
ScalarType<?> getScalarType(String cast);
}
@@ -5,15 +5,11 @@ import io.ebeaninternal.server.query.SqlTreeProperty;
import io.ebeaninternal.server.type.ScalarType;
import java.sql.Types;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class FormulaPropertyPath {
private static final String[] AGG_FUNCTIONS = {"count", "max", "min", "avg"};
private static final Pattern pattern = Pattern.compile("([a-zA-Z]*)\\((.*)\\)");
private static final String DISTINCT_ = "distinct ";
private final BeanDescriptor<?> descriptor;
@@ -26,18 +22,46 @@ class FormulaPropertyPath {
private boolean countDistinct;
private String cast;
private String alias;
FormulaPropertyPath(BeanDescriptor<?> descriptor, String formula) {
this.descriptor = descriptor;
this.formula = formula;
Matcher matcher = pattern.matcher(formula);
if (!matcher.find()) {
int openBracket = formula.indexOf('(');
int closeBracket = formula.lastIndexOf(')');
if (openBracket == -1 || closeBracket == -1) {
throw new IllegalStateException("Unable to parse formula [" + formula + "]");
}
//int groupCount = matcher.groupCount();
outerFunction = matcher.group(1);
internalExpression = trimDistinct(matcher.group(2));
outerFunction = formula.substring(0, openBracket).trim();
internalExpression = trimDistinct(formula.substring(openBracket+1, closeBracket));
if (closeBracket < formula.length() -1) {
// ::CastType as foo
String suffix = formula.substring(closeBracket+1).trim();
parseSuffix(suffix);
}
}
private void parseSuffix(String suffix) {
String[] split = suffix.split(" ");
if (split.length == 1) {
if (split[0].startsWith("::")) {
cast = split[0].substring(2);
} else {
alias = split[0];
}
} else if (split.length == 2) {
cast = "as".equals(split[0]) ? null : split[0].substring(2);
alias = split[1];
} else if (split.length == 3) {
cast = split[0].substring(2);
alias = split[2];
}
}
private String trimDistinct(String propertyName) {
@@ -49,14 +73,21 @@ class FormulaPropertyPath {
}
}
String aggType() {
String outerFunction() {
return outerFunction;
}
String basePropertyName() {
String internalExpression() {
return internalExpression;
}
String cast() {
return cast;
}
String alias() {
return alias;
}
SqlTreeProperty build() {
@@ -66,7 +97,13 @@ class FormulaPropertyPath {
ElPropertyDeploy firstProp = parser.getFirstProp();
ScalarType<?> scalarType;
if (isCount()) {
if (cast != null) {
scalarType = descriptor.getScalarType(cast);
if (scalarType == null) {
throw new IllegalStateException("Unable to find scalarType for cast of ["+cast+"] on formula [" + formula + "] for type " + descriptor);
}
} else if (isCount()) {
scalarType = descriptor.getScalarType(Types.BIGINT);
} else if (isConcat()) {
@@ -81,8 +118,14 @@ class FormulaPropertyPath {
}
}
String logicalName = (alias == null) ? formula : alias;
BeanProperty targetProperty = null;
if (alias != null) {
targetProperty = descriptor._findBeanProperty(alias);
}
String parsedAggregation = buildFormula(parsed);
return new DynamicPropertyAggregationFormula(formula, scalarType, parsedAggregation, isAggregate(), null);
return new DynamicPropertyAggregationFormula(logicalName, scalarType, parsedAggregation, isAggregate(), targetProperty);
}
private boolean isAggregate() {
@@ -83,6 +83,8 @@ public final class DefaultTypeManager implements TypeManager {
private final ConcurrentHashMap<Integer, ScalarType<?>> nativeMap;
private final ConcurrentHashMap<String, ScalarType<?>> logicalMap;
private final DefaultTypeFactory extraTypeFactory;
private final ScalarType<?> hstoreType = new ScalarTypePostgresHstore();
@@ -179,6 +181,7 @@ public final class DefaultTypeManager implements TypeManager {
this.jsonDateTime = config.getJsonDateTime();
this.typeMap = new ConcurrentHashMap<>();
this.nativeMap = new ConcurrentHashMap<>();
this.logicalMap = new ConcurrentHashMap<>();
boolean objectMapperPresent = config.getClassLoadConfig().isJacksonObjectMapperPresent();
this.objectMapper = (objectMapperPresent) ? initObjectMapper(config) : null;
@@ -289,6 +292,11 @@ public final class DefaultTypeManager implements TypeManager {
}
}
@Override
public ScalarType<?> getScalarType(String cast) {
return logicalMap.get(cast);
}
/**
* Return the ScalarType for the given jdbc type as per java.sql.Types.
*/
@@ -842,30 +850,35 @@ public final class DefaultTypeManager implements TypeManager {
if (config.getClassLoadConfig().isJavaTimePresent()) {
logger.debug("Registering java.time data types");
typeMap.put(java.time.Period.class, new ScalarTypePeriod());
typeMap.put(java.time.LocalDate.class, new ScalarTypeLocalDate());
typeMap.put(java.time.LocalDateTime.class, new ScalarTypeLocalDateTime(mode));
typeMap.put(OffsetDateTime.class, new ScalarTypeOffsetDateTime(mode));
typeMap.put(ZonedDateTime.class, new ScalarTypeZonedDateTime(mode));
typeMap.put(Instant.class, new ScalarTypeInstant(mode));
addType(java.time.Period.class, new ScalarTypePeriod());
addType(java.time.LocalDate.class, new ScalarTypeLocalDate());
addType(java.time.LocalDateTime.class, new ScalarTypeLocalDateTime(mode));
addType(OffsetDateTime.class, new ScalarTypeOffsetDateTime(mode));
addType(ZonedDateTime.class, new ScalarTypeZonedDateTime(mode));
addType(Instant.class, new ScalarTypeInstant(mode));
typeMap.put(DayOfWeek.class, new ScalarTypeDayOfWeek());
typeMap.put(Month.class, new ScalarTypeMonth());
typeMap.put(Year.class, new ScalarTypeYear());
typeMap.put(YearMonth.class, new ScalarTypeYearMonthDate());
typeMap.put(MonthDay.class, new ScalarTypeMonthDay());
typeMap.put(OffsetTime.class, new ScalarTypeOffsetTime());
typeMap.put(ZoneId.class, new ScalarTypeZoneId());
typeMap.put(ZoneOffset.class, new ScalarTypeZoneOffset());
addType(DayOfWeek.class, new ScalarTypeDayOfWeek());
addType(Month.class, new ScalarTypeMonth());
addType(Year.class, new ScalarTypeYear());
addType(YearMonth.class, new ScalarTypeYearMonthDate());
addType(MonthDay.class, new ScalarTypeMonthDay());
addType(OffsetTime.class, new ScalarTypeOffsetTime());
addType(ZoneId.class, new ScalarTypeZoneId());
addType(ZoneOffset.class, new ScalarTypeZoneOffset());
boolean localTimeNanos = config.isLocalTimeWithNanos();
typeMap.put(java.time.LocalTime.class, (localTimeNanos) ? new ScalarTypeLocalTimeWithNanos() : new ScalarTypeLocalTime());
addType(java.time.LocalTime.class, (localTimeNanos) ? new ScalarTypeLocalTimeWithNanos() : new ScalarTypeLocalTime());
boolean durationNanos = config.isDurationWithNanos();
typeMap.put(Duration.class, (durationNanos) ? new ScalarTypeDurationWithNanos() : new ScalarTypeDuration());
addType(Duration.class, (durationNanos) ? new ScalarTypeDurationWithNanos() : new ScalarTypeDuration());
}
}
private void addType(Class<?> clazz, ScalarType<?> scalarType) {
typeMap.put(clazz, scalarType);
logicalMap.putIfAbsent(clazz.getSimpleName(), scalarType);
}
/**
* Detect if Joda classes are in the classpath and if so register the Joda data types.
*/
@@ -876,20 +889,20 @@ public final class DefaultTypeManager implements TypeManager {
if (config.getClassLoadConfig().isJodaTimePresent()) {
// Joda classes are in the classpath so register the types
logger.debug("Registering Joda data types");
typeMap.put(LocalDateTime.class, new ScalarTypeJodaLocalDateTime(mode));
typeMap.put(DateTime.class, new ScalarTypeJodaDateTime(mode));
typeMap.put(LocalDate.class, new ScalarTypeJodaLocalDate());
typeMap.put(org.joda.time.DateMidnight.class, new ScalarTypeJodaDateMidnight());
typeMap.put(org.joda.time.Period.class, new ScalarTypeJodaPeriod());
addType(LocalDateTime.class, new ScalarTypeJodaLocalDateTime(mode));
addType(DateTime.class, new ScalarTypeJodaDateTime(mode));
addType(LocalDate.class, new ScalarTypeJodaLocalDate());
addType(org.joda.time.DateMidnight.class, new ScalarTypeJodaDateMidnight());
addType(org.joda.time.Period.class, new ScalarTypeJodaPeriod());
String jodaLocalTimeMode = config.getJodaLocalTimeMode();
if ("normal".equalsIgnoreCase(jodaLocalTimeMode)) {
// use the expected/normal local time zone
typeMap.put(LocalTime.class, new ScalarTypeJodaLocalTime());
addType(LocalTime.class, new ScalarTypeJodaLocalTime());
logger.debug("registered ScalarTypeJodaLocalTime");
} else if ("utc".equalsIgnoreCase(jodaLocalTimeMode)) {
// use the old UTC based
typeMap.put(LocalTime.class, new ScalarTypeJodaLocalTimeUTC());
addType(LocalTime.class, new ScalarTypeJodaLocalTimeUTC());
logger.debug("registered ScalarTypeJodaLocalTimeUTC");
}
}
@@ -908,17 +921,17 @@ public final class DefaultTypeManager implements TypeManager {
nativeMap.put(DbPlatformType.HSTORE, hstoreType);
ScalarType<?> utilDateType = extraTypeFactory.createUtilDate(mode);
typeMap.put(java.util.Date.class, utilDateType);
addType(java.util.Date.class, utilDateType);
ScalarType<?> calType = extraTypeFactory.createCalendar(mode);
typeMap.put(Calendar.class, calType);
addType(Calendar.class, calType);
ScalarType<?> mathBigIntType = extraTypeFactory.createMathBigInteger();
typeMap.put(BigInteger.class, mathBigIntType);
addType(BigInteger.class, mathBigIntType);
ScalarTypeBool booleanType = extraTypeFactory.createBoolean();
typeMap.put(Boolean.class, booleanType);
typeMap.put(boolean.class, booleanType);
addType(Boolean.class, booleanType);
addType(boolean.class, booleanType);
// register the boolean literals to the platform for DDL default values
databasePlatform.setDbTrueLiteral(booleanType.getDbTrueLiteral());
@@ -934,31 +947,31 @@ public final class DefaultTypeManager implements TypeManager {
ServerConfig.DbUuid dbUuid = config.getDbTypeConfig().getDbUuid();
if (offlineMigrationGeneration || (databasePlatform.isNativeUuidType() && dbUuid.useNativeType())) {
typeMap.put(UUID.class, new ScalarTypeUUIDNative());
addType(UUID.class, new ScalarTypeUUIDNative());
} else {
// Store UUID as binary(16) or varchar(40)
ScalarType<?> uuidType = dbUuid.useBinary() ? new ScalarTypeUUIDBinary(dbUuid.useBinaryOptimized()) : new ScalarTypeUUIDVarchar();
typeMap.put(UUID.class, uuidType);
addType(UUID.class, uuidType);
}
typeMap.put(File.class, fileType);
typeMap.put(InetAddress.class, inetAddressType);
typeMap.put(Locale.class, localeType);
typeMap.put(Currency.class, currencyType);
typeMap.put(TimeZone.class, timeZoneType);
typeMap.put(URL.class, urlType);
typeMap.put(URI.class, uriType);
addType(File.class, fileType);
addType(InetAddress.class, inetAddressType);
addType(Locale.class, localeType);
addType(Currency.class, currencyType);
addType(TimeZone.class, timeZoneType);
addType(URL.class, urlType);
addType(URI.class, uriType);
// String types
typeMap.put(char[].class, charArrayType);
typeMap.put(char.class, charType);
typeMap.put(String.class, stringType);
addType(char[].class, charArrayType);
addType(char.class, charType);
addType(String.class, stringType);
nativeMap.put(Types.VARCHAR, stringType);
nativeMap.put(Types.CHAR, stringType);
nativeMap.put(Types.LONGVARCHAR, longVarcharType);
// Class<?>
typeMap.put(Class.class, classType);
addType(Class.class, classType);
if (platformClobType == Types.CLOB) {
nativeMap.put(Types.CLOB, clobType);
@@ -972,7 +985,7 @@ public final class DefaultTypeManager implements TypeManager {
}
// Binary type
typeMap.put(byte[].class, varbinaryType);
addType(byte[].class, varbinaryType);
nativeMap.put(Types.BINARY, binaryType);
nativeMap.put(Types.VARBINARY, varbinaryType);
nativeMap.put(Types.LONGVARBINARY, longVarbinaryType);
@@ -989,43 +1002,43 @@ public final class DefaultTypeManager implements TypeManager {
}
// Number types
typeMap.put(Byte.class, byteType);
typeMap.put(byte.class, byteType);
addType(Byte.class, byteType);
addType(byte.class, byteType);
nativeMap.put(Types.TINYINT, byteType);
typeMap.put(Short.class, shortType);
typeMap.put(short.class, shortType);
addType(Short.class, shortType);
addType(short.class, shortType);
nativeMap.put(Types.SMALLINT, shortType);
typeMap.put(Integer.class, integerType);
typeMap.put(int.class, integerType);
addType(Integer.class, integerType);
addType(int.class, integerType);
nativeMap.put(Types.INTEGER, integerType);
typeMap.put(Long.class, longType);
typeMap.put(long.class, longType);
addType(Long.class, longType);
addType(long.class, longType);
nativeMap.put(Types.BIGINT, longType);
typeMap.put(Double.class, doubleType);
typeMap.put(double.class, doubleType);
addType(Double.class, doubleType);
addType(double.class, doubleType);
nativeMap.put(Types.FLOAT, doubleType);// no this is not a bug
nativeMap.put(Types.DOUBLE, doubleType);
typeMap.put(Float.class, floatType);
typeMap.put(float.class, floatType);
addType(Float.class, floatType);
addType(float.class, floatType);
nativeMap.put(Types.REAL, floatType);// no this is not a bug
typeMap.put(BigDecimal.class, bigDecimalType);
addType(BigDecimal.class, bigDecimalType);
nativeMap.put(Types.DECIMAL, bigDecimalType);
nativeMap.put(Types.NUMERIC, bigDecimalType);
// Temporal types
typeMap.put(Time.class, timeType);
addType(Time.class, timeType);
nativeMap.put(Types.TIME, timeType);
typeMap.put(Date.class, dateType);
addType(Date.class, dateType);
nativeMap.put(Types.DATE, dateType);
ScalarType<?> timestampType = new ScalarTypeTimestamp(mode);
typeMap.put(Timestamp.class, timestampType);
addType(Timestamp.class, timestampType);
nativeMap.put(Types.TIMESTAMP, timestampType);
}
@@ -21,6 +21,11 @@ public interface TypeManager {
@SuppressWarnings("rawtypes")
void addEnumType(ScalarType<?> type, Class<? extends Enum> myEnumClass);
/**
* Return the scalar type for the given logical type.
*/
ScalarType<?> getScalarType(String cast);
/**
* Return the ScalarType for a given jdbc type.
*