mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
#1331 - ENH: Support Aggregation formula with findSingleAttribute() ... max, min, avg, count and count(distinct ..)
This commit is contained in:
@@ -62,11 +62,13 @@ import io.ebeaninternal.server.el.ElPropertyValue;
|
||||
import io.ebeaninternal.server.persist.DmlUtil;
|
||||
import io.ebeaninternal.server.query.CQueryPlan;
|
||||
import io.ebeaninternal.server.query.CQueryPlanStatsCollector;
|
||||
import io.ebeaninternal.server.query.SqlTreeProperty;
|
||||
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
|
||||
import io.ebeaninternal.server.rawsql.SpiRawSql;
|
||||
import io.ebeaninternal.server.text.json.ReadJson;
|
||||
import io.ebeaninternal.server.text.json.SpiJsonWriter;
|
||||
import io.ebeaninternal.server.type.DataBind;
|
||||
import io.ebeaninternal.server.type.ScalarType;
|
||||
import io.ebeaninternal.util.SortByClause;
|
||||
import io.ebeaninternal.util.SortByClauseParser;
|
||||
import io.ebeanservice.docstore.api.DocStoreBeanAdapter;
|
||||
@@ -112,6 +114,8 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
|
||||
private final ConcurrentHashMap<String, ElComparator<T>> comparatorCache = new ConcurrentHashMap<>();
|
||||
|
||||
private final ConcurrentHashMap<String, SqlTreeProperty> dynamicProperty = new ConcurrentHashMap<>();
|
||||
|
||||
private final Map<String, SpiRawSql> namedRawSql;
|
||||
|
||||
private final Map<String, String> namedQuery;
|
||||
@@ -1055,6 +1059,13 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
return owner.getEncryptKey(tableName, columnName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Scalar type for the given JDBC type.
|
||||
*/
|
||||
public ScalarType<?> getScalarType(int jdbcType) {
|
||||
return owner.getScalarType(jdbcType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this bean type has a default select clause that is not
|
||||
* simply select all properties.
|
||||
@@ -2437,6 +2448,37 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a 'dynamic property' used to read a formula.
|
||||
*/
|
||||
private SqlTreeProperty findSqlTreeFormula(String formulaExpression) {
|
||||
|
||||
return dynamicProperty.computeIfAbsent(formulaExpression, (formula) -> {
|
||||
FormulaPropertyPath propertyFormula = new FormulaPropertyPath(formula);
|
||||
if (!propertyFormula.isFormula()) {
|
||||
throw new IllegalStateException("unable to parse formula [" + formula + "} on bean type " + fullName);
|
||||
}
|
||||
String baseName = propertyFormula.basePropertyName();
|
||||
BeanProperty base = _findBeanProperty(baseName);
|
||||
if (base == null) {
|
||||
throw new IllegalStateException("unable to find property [" + baseName + "] from formula [" + formula + "} on bean type " + fullName);
|
||||
}
|
||||
return propertyFormula.formulaProperty(base);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a property that is part of the SQL tree.
|
||||
*
|
||||
* The property can be a dynamic formula or a well known bean property.
|
||||
*/
|
||||
public SqlTreeProperty findSqlTreeProperty(String propName) {
|
||||
if (propName.indexOf('(') > -1) {
|
||||
return findSqlTreeFormula(propName);
|
||||
}
|
||||
return _findBeanProperty(propName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a BeanProperty including searching the inheritance hierarchy.
|
||||
* <p>
|
||||
|
||||
@@ -52,7 +52,9 @@ import io.ebeaninternal.server.properties.BeanPropertiesReader;
|
||||
import io.ebeaninternal.server.properties.BeanPropertyAccess;
|
||||
import io.ebeaninternal.server.properties.EnhanceBeanPropertyAccess;
|
||||
import io.ebeaninternal.server.query.CQueryPlan;
|
||||
import io.ebeaninternal.server.type.ScalarType;
|
||||
import io.ebeaninternal.server.type.ScalarTypeInteger;
|
||||
import io.ebeaninternal.server.type.TypeManager;
|
||||
import io.ebeaninternal.xmlmapping.XmlMappingReader;
|
||||
import io.ebeaninternal.xmlmapping.model.XmAliasMapping;
|
||||
import io.ebeaninternal.xmlmapping.model.XmColumnMapping;
|
||||
@@ -139,6 +141,8 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
|
||||
private final MultiValueBind multiValueBind;
|
||||
|
||||
private final TypeManager typeManager;
|
||||
|
||||
private int entityBeanCount;
|
||||
|
||||
private final boolean updateChangesOnly;
|
||||
@@ -222,6 +226,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
this.dbIdentity = config.getDatabasePlatform().getDbIdentity();
|
||||
this.deplyInherit = config.getDeployInherit();
|
||||
this.deployUtil = config.getDeployUtil();
|
||||
this.typeManager = deployUtil.getTypeManager();
|
||||
|
||||
this.beanManagerFactory = new BeanManagerFactory(config.getDatabasePlatform());
|
||||
|
||||
@@ -260,6 +265,11 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScalarType<?> getScalarType(int jdbcType) {
|
||||
return typeManager.getScalarType(jdbcType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the AsOfViewSuffix based on the DbHistorySupport.
|
||||
*/
|
||||
@@ -1463,7 +1473,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
prop.setGetter(beanPropertyAccess.getGetter(propertyIndex));
|
||||
prop.setSetter(beanPropertyAccess.getSetter(propertyIndex));
|
||||
if (prop.isAggregation()) {
|
||||
prop.setAggregationPrefix(DetermineAggPath.manyPath(prop.getAggregation(), desc));
|
||||
prop.setAggregationPrefix(DetermineAggPath.manyPath(prop.getRawAggregation(), desc));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import io.ebean.config.ServerConfig;
|
||||
import io.ebeaninternal.server.cache.SpiCacheManager;
|
||||
import io.ebeaninternal.server.deploy.id.IdBinder;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
|
||||
import io.ebeaninternal.server.type.ScalarType;
|
||||
import io.ebeanservice.docstore.api.DocStoreBeanAdapter;
|
||||
|
||||
/**
|
||||
@@ -60,4 +61,9 @@ public interface BeanDescriptorMap {
|
||||
* Create a doc store specific adapter for this bean type.
|
||||
*/
|
||||
<T> DocStoreBeanAdapter<T> createDocStoreBeanAdapter(BeanDescriptor<T> descriptor, DeployBeanDescriptor<T> deploy);
|
||||
|
||||
/**
|
||||
* Return the scalarType for the given JDBC type.
|
||||
*/
|
||||
ScalarType<?> getScalarType(int jdbcType);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import io.ebeaninternal.server.properties.BeanPropertyGetter;
|
||||
import io.ebeaninternal.server.properties.BeanPropertySetter;
|
||||
import io.ebeaninternal.server.query.SqlBeanLoad;
|
||||
import io.ebeaninternal.server.query.SqlJoinType;
|
||||
import io.ebeaninternal.server.query.SqlTreeProperty;
|
||||
import io.ebeaninternal.server.text.json.ReadJson;
|
||||
import io.ebeaninternal.server.text.json.SpiJsonWriter;
|
||||
import io.ebeaninternal.server.type.DataBind;
|
||||
@@ -55,7 +56,7 @@ import java.util.Set;
|
||||
* Description of a property of a bean. Includes its deployment information such
|
||||
* as database column mapping information.
|
||||
*/
|
||||
public class BeanProperty implements ElPropertyValue, Property {
|
||||
public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(BeanProperty.class);
|
||||
|
||||
@@ -321,7 +322,7 @@ public class BeanProperty implements ElPropertyValue, Property {
|
||||
|
||||
this.dbColumn = tableAliasIntern(descriptor, deploy.getDbColumn(), false, null);
|
||||
this.dbComment = deploy.getDbComment();
|
||||
this.aggregation = deploy.getAggregation();
|
||||
this.aggregation = deploy.parseAggregation();
|
||||
this.sqlFormulaJoin = InternString.intern(deploy.getSqlFormulaJoin());
|
||||
this.sqlFormulaSelect = InternString.intern(deploy.getSqlFormulaSelect());
|
||||
this.formula = sqlFormulaSelect != null;
|
||||
@@ -575,8 +576,7 @@ public class BeanProperty implements ElPropertyValue, Property {
|
||||
public void appendSelect(DbSqlContext ctx, boolean subQuery) {
|
||||
|
||||
if (aggregation != null) {
|
||||
ctx.appendRawColumn(aggregation);
|
||||
|
||||
ctx.appendFormulaSelect(aggregation);
|
||||
} else if (formula) {
|
||||
ctx.appendFormulaSelect(sqlFormulaSelect);
|
||||
|
||||
|
||||
@@ -64,6 +64,11 @@ public interface DbSqlContext {
|
||||
*/
|
||||
void appendColumn(String column);
|
||||
|
||||
/**
|
||||
* Parse and add formula with standard table alias replacement.
|
||||
*/
|
||||
void appendParseSelect(String parseSelect);
|
||||
|
||||
/**
|
||||
* Append a Sql Formula select. This converts the "${ta}" keyword to the
|
||||
* current table alias.
|
||||
|
||||
@@ -27,6 +27,8 @@ public abstract class DeployParser {
|
||||
*/
|
||||
protected static final char PERIOD = '.';
|
||||
|
||||
protected static final char OPEN_BRACKET = '(';
|
||||
|
||||
protected boolean encrypted;
|
||||
|
||||
protected String source;
|
||||
@@ -43,6 +45,8 @@ public abstract class DeployParser {
|
||||
|
||||
protected char wordTerminator;
|
||||
|
||||
private StringBuilder wordBuffer;
|
||||
|
||||
protected abstract String convertWord();
|
||||
|
||||
public abstract String getDeployWord(String expression);
|
||||
@@ -77,7 +81,9 @@ public abstract class DeployParser {
|
||||
priorWord = deployWord;
|
||||
}
|
||||
if (pos < sourceLength) {
|
||||
sb.append(wordTerminator);
|
||||
if (wordTerminator != OPEN_BRACKET) {
|
||||
sb.append(wordTerminator);
|
||||
}
|
||||
if (wordTerminator == SINGLE_QUOTE) {
|
||||
readLiteral();
|
||||
}
|
||||
@@ -97,7 +103,7 @@ public abstract class DeployParser {
|
||||
return false;
|
||||
}
|
||||
|
||||
StringBuilder wordBuffer = new StringBuilder();
|
||||
wordBuffer = new StringBuilder();
|
||||
wordBuffer.append(source.charAt(pos));
|
||||
while (++pos < sourceLength) {
|
||||
char ch = source.charAt(pos);
|
||||
@@ -172,6 +178,11 @@ public abstract class DeployParser {
|
||||
* return true if the char is a letter, digit or underscore.
|
||||
*/
|
||||
private boolean isWordPart(char ch) {
|
||||
if (ch == OPEN_BRACKET) {
|
||||
// include in the 'word' such that "count(" formula doesn't clash with property "count"
|
||||
wordBuffer.append(ch);
|
||||
return false;
|
||||
}
|
||||
return Character.isLetterOrDigit(ch) || ch == UNDERSCORE || ch == PERIOD;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import java.util.Set;
|
||||
/**
|
||||
* Converts logical property names to database columns using a Map.
|
||||
*/
|
||||
public final class DeployPropertyParserMap extends DeployParser {
|
||||
public class DeployPropertyParserMap extends DeployParser {
|
||||
|
||||
private final Map<String, String> map;
|
||||
|
||||
|
||||
@@ -12,6 +12,10 @@ class DetermineAggPath {
|
||||
*/
|
||||
static String manyPath(String aggregation, DeployBeanDescriptor<?> desc) {
|
||||
DetermineAggPath.Path path = paths(aggregation);
|
||||
if (path.length() == 1) {
|
||||
// a top level aggregation (so here we need to exclude Id property)
|
||||
return null;
|
||||
}
|
||||
return path.getManyPath(0, desc);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebeaninternal.server.query.SqlBeanLoad;
|
||||
import io.ebeaninternal.server.type.ScalarType;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
|
||||
/**
|
||||
* Dynamic property based on aggregation (max, min, avg, count).
|
||||
*/
|
||||
class DynamicPropertyAggregationFormula extends DynamicPropertyBase {
|
||||
|
||||
private final String parsedAggregation;
|
||||
|
||||
private final BeanProperty asTarget;
|
||||
|
||||
DynamicPropertyAggregationFormula(String name, ScalarType<?> scalarType, String parsedAggregation, BeanProperty asTarget) {
|
||||
super(name, name, null, scalarType);
|
||||
this.parsedAggregation = parsedAggregation;
|
||||
this.asTarget = asTarget;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DynamicPropertyFormula[" + parsedAggregation + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAggregation() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(SqlBeanLoad sqlBeanLoad) {
|
||||
|
||||
try {
|
||||
Object value = scalarType.read(sqlBeanLoad.ctx().getDataReader());
|
||||
if (asTarget != null) {
|
||||
sqlBeanLoad.load(asTarget, value);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new PersistenceException("Error loading on " + fullName, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendSelect(DbSqlContext ctx, boolean subQuery) {
|
||||
ctx.appendParseSelect(parsedAggregation);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebeaninternal.server.query.SqlJoinType;
|
||||
import io.ebeaninternal.server.query.SqlTreeProperty;
|
||||
import io.ebeaninternal.server.type.ScalarType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Abstract base for dynamic properties.
|
||||
*/
|
||||
abstract class DynamicPropertyBase implements SqlTreeProperty {
|
||||
|
||||
final String name;
|
||||
final String fullName;
|
||||
final String elPrefix;
|
||||
final ScalarType<?> scalarType;
|
||||
|
||||
public DynamicPropertyBase(String name, String fullName, String elPrefix, ScalarType<?> scalarType) {
|
||||
this.name = name;
|
||||
this.fullName = fullName;
|
||||
this.elPrefix = elPrefix;
|
||||
this.scalarType = scalarType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFullBeanName() {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isId() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmbedded() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElPrefix() {
|
||||
return elPrefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScalarType<?> getScalarType() {
|
||||
return scalarType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buildRawSqlSelectChain(String prefix, List<String> selectChain) {
|
||||
// do nothing, only for RawSql
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadIgnore(DbReadContext ctx) {
|
||||
scalarType.loadIgnore(ctx.getDataReader());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
|
||||
// do not add to from usually
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
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 Pattern pattern = Pattern.compile("(max|min|avg|count)\\((.*)\\)");
|
||||
|
||||
private static final String DISTINCT_ = "distinct ";
|
||||
|
||||
private final String aggType;
|
||||
|
||||
private final String baseName;
|
||||
|
||||
private boolean countDistinct;
|
||||
|
||||
FormulaPropertyPath(String propName) {
|
||||
|
||||
Matcher matcher = pattern.matcher(propName);
|
||||
if (matcher.find()) {
|
||||
aggType = matcher.group(1);
|
||||
baseName = trimDistinct(matcher.group(2));
|
||||
|
||||
} else {
|
||||
aggType = null;
|
||||
baseName = null;
|
||||
}
|
||||
}
|
||||
|
||||
private String trimDistinct(String propertyName) {
|
||||
if (propertyName.startsWith(DISTINCT_)){
|
||||
countDistinct = true;
|
||||
return propertyName.substring(DISTINCT_.length());
|
||||
} else {
|
||||
return propertyName;
|
||||
}
|
||||
}
|
||||
|
||||
boolean isFormula() {
|
||||
return aggType != null;
|
||||
}
|
||||
|
||||
String aggType() {
|
||||
return aggType;
|
||||
}
|
||||
|
||||
String basePropertyName() {
|
||||
return baseName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a bean property dynamically for the formula in the select clause.
|
||||
*/
|
||||
SqlTreeProperty formulaProperty(BeanProperty base) {
|
||||
|
||||
String parsedAggregation = buildFormula(base);
|
||||
String name = logicalName();
|
||||
|
||||
ScalarType<?> scalarType = base.getScalarType();
|
||||
if (isCount()) {
|
||||
// count maps to Long / BIGINT
|
||||
scalarType = base.getBeanDescriptor().getScalarType(Types.BIGINT);
|
||||
}
|
||||
|
||||
return new DynamicPropertyAggregationFormula(name, scalarType, parsedAggregation, null);
|
||||
}
|
||||
|
||||
private String buildFormula(BeanProperty base) {
|
||||
if (countDistinct) {
|
||||
return "count(distinct ${}"+base.getDbColumn()+")";
|
||||
} else {
|
||||
return aggType+"(${}"+base.getDbColumn()+")";
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isCount() {
|
||||
return aggType.equals("count");
|
||||
}
|
||||
|
||||
private String logicalName() {
|
||||
return aggType+Character.toUpperCase(baseName.charAt(0))+baseName.substring(1);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import io.ebeaninternal.server.deploy.ChainedBeanPersistListener;
|
||||
import io.ebeaninternal.server.deploy.ChainedBeanPostConstructListener;
|
||||
import io.ebeaninternal.server.deploy.ChainedBeanPostLoad;
|
||||
import io.ebeaninternal.server.deploy.ChainedBeanQueryAdapter;
|
||||
import io.ebeaninternal.server.deploy.DeployPropertyParserMap;
|
||||
import io.ebeaninternal.server.deploy.IndexDefinition;
|
||||
import io.ebeaninternal.server.deploy.InheritInfo;
|
||||
import io.ebeaninternal.server.deploy.TableJoin;
|
||||
@@ -849,18 +850,18 @@ public class DeployBeanDescriptor<T> {
|
||||
this.idGeneratorName = PlatformIdGenerator.AUTO_UUID;
|
||||
|
||||
switch (serverConfig.getUuidVersion()) {
|
||||
case VERSION1:
|
||||
this.idGenerator = UuidV1IdGenerator.getInstance(serverConfig.getUuidStateFile());
|
||||
break;
|
||||
case VERSION1:
|
||||
this.idGenerator = UuidV1IdGenerator.getInstance(serverConfig.getUuidStateFile());
|
||||
break;
|
||||
|
||||
case VERSION1RND:
|
||||
this.idGenerator = UuidV1RndIdGenerator.INSTANCE;
|
||||
break;
|
||||
case VERSION1RND:
|
||||
this.idGenerator = UuidV1RndIdGenerator.INSTANCE;
|
||||
break;
|
||||
|
||||
case VERSION4:
|
||||
default:
|
||||
this.idGenerator = UuidV4IdGenerator.INSTANCE;
|
||||
break;
|
||||
case VERSION4:
|
||||
default:
|
||||
this.idGenerator = UuidV4IdGenerator.INSTANCE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1168,4 +1169,38 @@ public class DeployBeanDescriptor<T> {
|
||||
}
|
||||
namedRawSql.put(name, rawSql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the aggregation formula into expressions with table alias placeholders.
|
||||
*/
|
||||
public String parse(String aggregation) {
|
||||
return new Parser(this).parse(aggregation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parser for top level properties into EL expressions (table alias placeholders).
|
||||
*/
|
||||
private static class Parser extends DeployPropertyParserMap {
|
||||
|
||||
private final DeployBeanDescriptor<?> descriptor;
|
||||
|
||||
Parser(DeployBeanDescriptor<?> descriptor) {
|
||||
super(null);
|
||||
this.descriptor = descriptor;
|
||||
}
|
||||
|
||||
public String getDeployWord(String expression) {
|
||||
return descriptor.getDeployWord(expression);
|
||||
}
|
||||
}
|
||||
|
||||
private String getDeployWord(String expression) {
|
||||
if (expression.charAt(expression.length() - 1) == '(') {
|
||||
return null;
|
||||
}
|
||||
// use 'current' table alias - refer BeanProperty appendSelect() for aggregation
|
||||
DeployBeanProperty property = propMap.get(expression);
|
||||
return (property == null) ? null : "${ta}." + property.getDbColumn();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -175,6 +175,7 @@ public class DeployBeanProperty {
|
||||
|
||||
private String aggregationPrefix;
|
||||
private String aggregation;
|
||||
private String aggregationParsed;
|
||||
|
||||
private String sqlFormulaSelect;
|
||||
private String sqlFormulaJoin;
|
||||
@@ -615,10 +616,23 @@ public class DeployBeanProperty {
|
||||
return aggregation != null;
|
||||
}
|
||||
|
||||
public String getAggregation() {
|
||||
/**
|
||||
* Get the raw/logical aggregation formula.
|
||||
*/
|
||||
public String getRawAggregation() {
|
||||
return aggregation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parsed aggregation formula with table alias placeholders.
|
||||
*/
|
||||
public String parseAggregation() {
|
||||
if (aggregation != null) {
|
||||
aggregationParsed = desc.parse(aggregation);
|
||||
}
|
||||
return aggregationParsed;
|
||||
}
|
||||
|
||||
public void setAggregation(String aggregation) {
|
||||
this.aggregation = aggregation;
|
||||
this.dbRead = true;
|
||||
@@ -629,9 +643,9 @@ public class DeployBeanProperty {
|
||||
/**
|
||||
* Set the path to the aggregation.
|
||||
*/
|
||||
public void setAggregationPrefix(String aggregationPrefix) {
|
||||
this.aggregationPrefix = aggregationPrefix;
|
||||
this.aggregation = aggregation.replace(aggregationPrefix, "u1");
|
||||
public void setAggregationPrefix(String prefix) {
|
||||
this.aggregationPrefix = prefix;
|
||||
this.aggregation = (prefix == null) ? aggregation : aggregation.replace(aggregationPrefix, "u1");
|
||||
}
|
||||
|
||||
public String getElPrefix() {
|
||||
@@ -644,7 +658,7 @@ public class DeployBeanProperty {
|
||||
|
||||
public String getElPlaceHolder() {
|
||||
if (aggregation != null) {
|
||||
return aggregation;
|
||||
return aggregationParsed;
|
||||
} else if (sqlFormulaSelect != null) {
|
||||
return sqlFormulaSelect;
|
||||
} else {
|
||||
|
||||
@@ -273,13 +273,19 @@ class DefaultDbSqlContext implements DbSqlContext {
|
||||
sb.append(" ");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendParseSelect(String parseSelect) {
|
||||
String converted = alias.parse(parseSelect);
|
||||
sb.append(COMMA);
|
||||
sb.append(converted);
|
||||
appendColumnAlias();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendFormulaSelect(String sqlFormulaSelect) {
|
||||
|
||||
String tableAlias = tableAliasStack.peek();
|
||||
String converted = StringHelper.replaceString(sqlFormulaSelect, tableAliasPlaceHolder,
|
||||
tableAlias);
|
||||
|
||||
String converted = StringHelper.replaceString(sqlFormulaSelect, tableAliasPlaceHolder, tableAlias);
|
||||
sb.append(COMMA);
|
||||
sb.append(converted);
|
||||
|
||||
|
||||
@@ -44,6 +44,13 @@ public class SqlBeanLoad {
|
||||
return lazyLoading;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB read context.
|
||||
*/
|
||||
public DbReadContext ctx() {
|
||||
return ctx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the resultSet index 1.
|
||||
*/
|
||||
@@ -86,4 +93,14 @@ public class SqlBeanLoad {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the given value into the property.
|
||||
*/
|
||||
public void load(BeanProperty target, Object dbVal) {
|
||||
if (!refreshLoading) {
|
||||
target.setValue(bean, dbVal);
|
||||
} else {
|
||||
target.setValueIntercept(bean, dbVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -418,7 +418,7 @@ public final class SqlTreeBuilder {
|
||||
|
||||
// make sure we only included the base/embedded bean once
|
||||
if (!selectProps.containsProperty(baseName)) {
|
||||
BeanProperty p = desc.findBeanProperty(baseName);
|
||||
SqlTreeProperty p = desc.findSqlTreeProperty(baseName);
|
||||
if (p == null) {
|
||||
logger.error("property [" + propName + "] not found on " + desc + " for query - excluding it.");
|
||||
|
||||
@@ -436,7 +436,7 @@ public final class SqlTreeBuilder {
|
||||
} else {
|
||||
// find the property including searching the
|
||||
// sub class hierarchy if required
|
||||
BeanProperty p = desc.findBeanProperty(propName);
|
||||
SqlTreeProperty p = desc.findSqlTreeProperty(propName);
|
||||
if (p == null) {
|
||||
logger.error("property [" + propName + "] not found on " + desc + " for query - excluding it.");
|
||||
p = desc.findBeanProperty("id");
|
||||
|
||||
@@ -48,7 +48,7 @@ class SqlTreeNodeBean implements SqlTreeNode {
|
||||
*/
|
||||
private final boolean partialObject;
|
||||
|
||||
protected final BeanProperty[] properties;
|
||||
protected final SqlTreeProperty[] properties;
|
||||
|
||||
/**
|
||||
* Extra where clause added by Where annotation on associated many.
|
||||
@@ -87,6 +87,7 @@ class SqlTreeNodeBean implements SqlTreeNode {
|
||||
private boolean intersectionAsOfTableAlias;
|
||||
|
||||
private final boolean aggregation;
|
||||
private final boolean aggregationRoot;
|
||||
|
||||
/**
|
||||
* Construct for leaf node.
|
||||
@@ -124,13 +125,15 @@ class SqlTreeNodeBean implements SqlTreeNode {
|
||||
this.nodeBeanProp = beanProp;
|
||||
this.extraWhere = (beanProp == null) ? null : beanProp.getExtraWhere();
|
||||
|
||||
this.aggregation = props.isAggregation();
|
||||
this.aggregationRoot = props.isAggregationRoot();
|
||||
|
||||
// the bean has an Id property and we want to use it
|
||||
this.readId = withId && (desc.getIdProperty() != null);
|
||||
this.readId = !aggregationRoot && withId && (desc.getIdProperty() != null);
|
||||
this.disableLazyLoad = disableLazyLoad || !readId || desc.isRawSqlBased() || temporalVersions;
|
||||
|
||||
this.partialObject = props.isPartialObject();
|
||||
this.properties = props.getProps();
|
||||
this.aggregation = props.isAggregation();
|
||||
this.children = myChildren == null ? NO_CHILDREN : myChildren.toArray(new SqlTreeNode[myChildren.size()]);
|
||||
|
||||
pathMap = createPathMap(prefix, desc);
|
||||
@@ -186,7 +189,7 @@ class SqlTreeNodeBean implements SqlTreeNode {
|
||||
}
|
||||
idBinder.buildRawSqlSelectChain(prefix, selectChain);
|
||||
}
|
||||
for (BeanProperty property : properties) {
|
||||
for (SqlTreeProperty property : properties) {
|
||||
property.buildRawSqlSelectChain(prefix, selectChain);
|
||||
}
|
||||
// recursively continue reading...
|
||||
@@ -289,14 +292,14 @@ class SqlTreeNodeBean implements SqlTreeNode {
|
||||
|
||||
if (inheritInfo == null) {
|
||||
// normal behavior with no inheritance
|
||||
for (BeanProperty property : properties) {
|
||||
for (SqlTreeProperty property : properties) {
|
||||
property.load(sqlBeanLoad);
|
||||
}
|
||||
|
||||
} else {
|
||||
// take account of inheritance and due to subclassing approach
|
||||
// need to get a 'local' version of the property
|
||||
for (BeanProperty property : properties) {
|
||||
for (SqlTreeProperty property : properties) {
|
||||
// get a local version of the BeanProperty
|
||||
BeanProperty p = localDesc.getBeanProperty(property.getName());
|
||||
if (p != null) {
|
||||
@@ -416,7 +419,7 @@ class SqlTreeNodeBean implements SqlTreeNode {
|
||||
if (readId) {
|
||||
appendSelectId(ctx, idBinder.getBeanProperty());
|
||||
}
|
||||
for (BeanProperty property : properties) {
|
||||
for (SqlTreeProperty property : properties) {
|
||||
if (!property.isAggregation()) {
|
||||
property.appendSelect(ctx, subQuery);
|
||||
}
|
||||
@@ -483,9 +486,9 @@ class SqlTreeNodeBean implements SqlTreeNode {
|
||||
/**
|
||||
* Append the properties to the buffer.
|
||||
*/
|
||||
private void appendSelect(DbSqlContext ctx, boolean subQuery, BeanProperty[] props) {
|
||||
private void appendSelect(DbSqlContext ctx, boolean subQuery, SqlTreeProperty[] props) {
|
||||
|
||||
for (BeanProperty prop : props) {
|
||||
for (SqlTreeProperty prop : props) {
|
||||
prop.appendSelect(ctx, subQuery);
|
||||
}
|
||||
}
|
||||
@@ -543,7 +546,7 @@ class SqlTreeNodeBean implements SqlTreeNode {
|
||||
// join and return SqlJoinType to use for child joins
|
||||
joinType = appendFromBaseTable(ctx, joinType);
|
||||
|
||||
for (BeanProperty property : properties) {
|
||||
for (SqlTreeProperty property : properties) {
|
||||
// usually nothing... except for 1-1 Exported
|
||||
property.appendFrom(ctx, joinType);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebeaninternal.api.ManyWhereJoins;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -21,7 +20,7 @@ public class SqlTreeProperties {
|
||||
/**
|
||||
* The bean properties in order.
|
||||
*/
|
||||
private final List<BeanProperty> propsList = new ArrayList<>();
|
||||
private final List<SqlTreeProperty> propsList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Maintain a list of property names to detect embedded bean additions.
|
||||
@@ -32,6 +31,8 @@ public class SqlTreeProperties {
|
||||
|
||||
private boolean aggregation;
|
||||
|
||||
private String aggregationPath;
|
||||
|
||||
SqlTreeProperties() {
|
||||
}
|
||||
|
||||
@@ -39,17 +40,17 @@ public class SqlTreeProperties {
|
||||
return propNames.contains(propName);
|
||||
}
|
||||
|
||||
public void add(BeanProperty[] props) {
|
||||
public void add(SqlTreeProperty[] props) {
|
||||
propsList.addAll(Arrays.asList(props));
|
||||
}
|
||||
|
||||
public void add(BeanProperty prop) {
|
||||
public void add(SqlTreeProperty prop) {
|
||||
propsList.add(prop);
|
||||
propNames.add(prop.getName());
|
||||
}
|
||||
|
||||
public BeanProperty[] getProps() {
|
||||
return propsList.toArray(new BeanProperty[propsList.size()]);
|
||||
public SqlTreeProperty[] getProps() {
|
||||
return propsList.toArray(new SqlTreeProperty[propsList.size()]);
|
||||
}
|
||||
|
||||
boolean isPartialObject() {
|
||||
@@ -77,7 +78,6 @@ public class SqlTreeProperties {
|
||||
boolean requireSqlDistinct(ManyWhereJoins manyWhereJoins) {
|
||||
String joinProperty = aggregationJoin();
|
||||
if (joinProperty != null) {
|
||||
aggregation = true;
|
||||
manyWhereJoins.addAggregationJoin(joinProperty);
|
||||
return false;
|
||||
} else {
|
||||
@@ -97,12 +97,21 @@ public class SqlTreeProperties {
|
||||
*/
|
||||
private String aggregationJoin() {
|
||||
if (!allProperties) {
|
||||
for (BeanProperty beanProperty : propsList) {
|
||||
for (SqlTreeProperty beanProperty : propsList) {
|
||||
if (beanProperty.isAggregation()) {
|
||||
return beanProperty.getElPrefix();
|
||||
aggregation = true;
|
||||
aggregationPath = beanProperty.getElPrefix();
|
||||
return aggregationPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if a top level aggregation which means the Id property must be excluded.
|
||||
*/
|
||||
public boolean isAggregationRoot() {
|
||||
return aggregation && (aggregationPath == null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebeaninternal.server.deploy.DbReadContext;
|
||||
import io.ebeaninternal.server.deploy.DbSqlContext;
|
||||
import io.ebeaninternal.server.type.ScalarType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A property in the SQL Tree.
|
||||
*
|
||||
* A BeanProperty or a dynamically created property based on formula.
|
||||
*/
|
||||
public interface SqlTreeProperty {
|
||||
|
||||
/**
|
||||
* Return the property name.
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Return the full property name (for error messages).
|
||||
*/
|
||||
String getFullBeanName();
|
||||
|
||||
/**
|
||||
* Return true if the property is the Id.
|
||||
*/
|
||||
boolean isId();
|
||||
|
||||
/**
|
||||
* Return true if the property is an embedded type.
|
||||
*/
|
||||
boolean isEmbedded();
|
||||
|
||||
/**
|
||||
* Return true if the property is an aggregation.
|
||||
*/
|
||||
boolean isAggregation();
|
||||
|
||||
/**
|
||||
* Return the Expression language prefix (join path).
|
||||
*/
|
||||
String getElPrefix();
|
||||
|
||||
/**
|
||||
* Return the underlying scalar type for the property (for findSingleAttribute).
|
||||
*/
|
||||
ScalarType<?> getScalarType();
|
||||
|
||||
/**
|
||||
* For RawSql build the select chain.
|
||||
*/
|
||||
void buildRawSqlSelectChain(String prefix, List<String> selectChain);
|
||||
|
||||
/**
|
||||
* Load into the bean (from the DataReader/ResultSet).
|
||||
*/
|
||||
void load(SqlBeanLoad sqlBeanLoad);
|
||||
|
||||
/**
|
||||
* Ignore the property (moving the column index position without reading).
|
||||
*/
|
||||
void loadIgnore(DbReadContext ctx);
|
||||
|
||||
/**
|
||||
* Append to the select clause.
|
||||
*/
|
||||
void appendSelect(DbSqlContext ctx, boolean subQuery);
|
||||
|
||||
/**
|
||||
* Append to the from clause.
|
||||
*/
|
||||
void appendFrom(DbSqlContext ctx, SqlJoinType joinType);
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user