mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
#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:
@@ -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.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user