diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java index 6b580119ff..b90db30163 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java @@ -2362,6 +2362,12 @@ ElPropertyValue buildElGetValue(String propName, ElPropertyChainBuilder chain, b if (assocProp == null) { return null; } + // this method is an entry-point, although it introduces recursive calls via + // buildElPropertyValue -> createElPropertyValue -> buildElGetValue (back to here) + // it seams we can initialize ElPropertyChainBuilder at this point and skip further checks. + if (chain == null) { + chain = new ElPropertyChainBuilder(propName); + } String remainder = propName.substring(basePos + 1); return assocProp.buildElPropertyValue(propName, remainder, chain, propertyDeploy); } @@ -2373,9 +2379,7 @@ ElPropertyValue buildElGetValue(String propName, ElPropertyChainBuilder chain, b if (property == null) { throw new PersistenceException("No property found for [" + propName + "] in expression " + chain.expression()); } - if (property.containsMany()) { - chain.setContainsMany(); - } + return chain.add(property).build(); } @@ -2824,9 +2828,7 @@ public ExtraJoin extraJoin(String propertyPath) { BeanProperty beanProperty = elGetValue.beanProperty(); if (beanProperty instanceof BeanPropertyAssoc) { BeanPropertyAssoc assocProp = (BeanPropertyAssoc) beanProperty; - if (!assocProp.isEmbedded()) { - return new ExtraJoin(assocProp, elGetValue.containsMany()); - } + return new ExtraJoin(assocProp, elGetValue.containsMany()); } } return null; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssoc.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssoc.java index ef10e0dbc6..966b2e3094 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssoc.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssoc.java @@ -161,13 +161,7 @@ public boolean hasForeignKeyIndex() { ElPropertyValue createElPropertyValue(String propName, String remainder, ElPropertyChainBuilder chain, boolean propertyDeploy) { // associated or embedded bean BeanDescriptor embDesc = targetDescriptor(); - if (chain == null) { - chain = new ElPropertyChainBuilder(isEmbedded(), propName); - } chain.add(this); - if (containsMany()) { - chain.setContainsMany(); - } return embDesc.buildElGetValue(remainder, chain, propertyDeploy); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java index 5bb4fe2c02..544a31946c 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java @@ -210,17 +210,17 @@ Object naturalKeyVal(Map values) { @Override public ElPropertyValue buildElPropertyValue(String propName, String remainder, ElPropertyChainBuilder chain, boolean propertyDeploy) { if (embedded) { - BeanProperty embProp = embeddedPropsMap.get(remainder); + String embName = remainder; + int basePos = remainder.indexOf('.'); + if (basePos > -1) { + embName = remainder.substring(0, basePos); + } + BeanProperty embProp = embeddedPropsMap.get(embName); + if (embProp == null) { - String msg = "Embedded Property " + remainder + " not found in " + fullName(); + String msg = "Embedded Property " + embName + " not found in " + fullName(); throw new PersistenceException(msg); } - if (chain == null) { - chain = new ElPropertyChainBuilder(true, propName); - } - chain.add(this); - chain.setEmbedded(true); - return chain.add(embProp).build(); } return createElPropertyValue(propName, remainder, chain, propertyDeploy); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/el/ElPropertyChain.java b/ebean-core/src/main/java/io/ebeaninternal/server/el/ElPropertyChain.java index 12d376bed0..f023764346 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/el/ElPropertyChain.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/el/ElPropertyChain.java @@ -37,38 +37,32 @@ public final class ElPropertyChain implements ElPropertyValue { private final ScalarType scalarType; private final ElPropertyValue lastElPropertyValue; - public ElPropertyChain(boolean containsMany, boolean embedded, String expression, ElPropertyValue[] chain) { - this.containsMany = containsMany; + public ElPropertyChain(String expression, boolean containsMany, ElPropertyValue chain[]) { this.chain = chain; this.expression = expression; + this.containsMany = containsMany; + int dotPos = expression.lastIndexOf('.'); if (dotPos > -1) { this.name = expression.substring(dotPos + 1); - if (embedded) { - int embPos = expression.lastIndexOf('.', dotPos - 1); - this.prefix = embPos == -1 ? null : expression.substring(0, embPos); - - } else { - this.prefix = expression.substring(0, dotPos); - } + this.prefix = expression.substring(0, dotPos); } else { this.prefix = null; this.name = expression; } - this.assocId = chain[chain.length - 1].isAssocId(); - - this.last = chain.length - 1; - this.lastBeanProperty = chain[chain.length - 1].beanProperty(); + this.last = this.chain.length - 1; + this.lastElPropertyValue = this.chain[this.last]; + this.assocId = this.lastElPropertyValue.isAssocId(); + this.lastBeanProperty = lastElPropertyValue.beanProperty(); if (lastBeanProperty != null) { this.scalarType = lastBeanProperty.scalarType(); } else { // case for nested compound type (non-scalar) this.scalarType = null; } - this.lastElPropertyValue = chain[chain.length - 1]; - this.placeHolder = placeHolder(prefix, lastElPropertyValue, false); - this.placeHolderEncrypted = placeHolder(prefix, lastElPropertyValue, true); + this.placeHolder = placeHolder(this.prefix, lastElPropertyValue, false); + this.placeHolderEncrypted = placeHolder(this.prefix, lastElPropertyValue, true); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/el/ElPropertyChainBuilder.java b/ebean-core/src/main/java/io/ebeaninternal/server/el/ElPropertyChainBuilder.java index 409f17bc70..d372330333 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/el/ElPropertyChainBuilder.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/el/ElPropertyChainBuilder.java @@ -17,14 +17,12 @@ public final class ElPropertyChainBuilder { private final String expression; private final List chain = new ArrayList<>(); - private boolean embedded; - private boolean containsMany; + private boolean containsMany = false; /** * Create with the original expression. */ - public ElPropertyChainBuilder(boolean embedded, String expression) { - this.embedded = embedded; + public ElPropertyChainBuilder(String expression) { this.expression = expression; } @@ -32,10 +30,6 @@ public boolean isContainsMany() { return containsMany; } - public void setContainsMany() { - this.containsMany = true; - } - public String expression() { return expression; } @@ -48,6 +42,9 @@ public ElPropertyChainBuilder add(ElPropertyValue element) { throw new NullPointerException("element null in expression " + expression); } chain.add(element); + if (element.containsMany()) { + containsMany = true; + } return this; } @@ -55,13 +52,6 @@ public ElPropertyChainBuilder add(ElPropertyValue element) { * Build the immutable ElGetChain from the build information. */ public ElPropertyChain build() { - return new ElPropertyChain(containsMany, embedded, expression, chain.toArray(new ElPropertyValue[0])); - } - - /** - * Permits to set whole chain as embedded when the leaf is embedded - */ - public void setEmbedded(boolean embedded) { - this.embedded = embedded; + return new ElPropertyChain(expression, containsMany, chain.toArray(new ElPropertyValue[0])); } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeAlias.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeAlias.java index 3a5e7bdc7d..fc6459e04e 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeAlias.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeAlias.java @@ -32,41 +32,44 @@ final class SqlTreeAlias { /** * Add joins to support where predicates */ - void addManyWhereJoins(Set manyWhereJoins) { + void addManyWhereJoins(Set manyWhereJoins, STreeType desc) { if (manyWhereJoins != null) { for (String include : manyWhereJoins) { - addPropertyJoin(include, manyWhereJoinProps); + addPropertyJoin(include, manyWhereJoinProps, desc); } } } - private void addEmbeddedPropertyJoin(String embProp) { + private boolean addEmbeddedPropertyJoin(String embProp) { if (embeddedPropertyJoins == null) { embeddedPropertyJoins = new HashSet<>(); } - embeddedPropertyJoins.add(embProp); + return embeddedPropertyJoins.add(embProp); } /** * Add joins. */ public void addJoin(Set propJoins, STreeType desc) { - if (propJoins != null) { - for (String propJoin : propJoins) { - if (desc.isEmbeddedPath(propJoin)) { - addEmbeddedPropertyJoin(propJoin); - } else { - addPropertyJoin(propJoin, joinProps); - } - } + if (propJoins == null) { + return; + } + for (String propJoin : propJoins) { + addPropertyJoin(propJoin, joinProps, desc); } } - private void addPropertyJoin(String include, TreeSet set) { - if (set.add(include)) { + private void addPropertyJoin(String include, TreeSet set, STreeType desc) { + boolean added = false; + if (desc.isEmbeddedPath(include)) { + added = addEmbeddedPropertyJoin(include); + } else { + added = set.add(include); + } + if (added) { String[] split = SplitName.split(include); if (split[0] != null) { - addPropertyJoin(split[0], set); + addPropertyJoin(split[0], set, desc); } } } @@ -89,7 +92,7 @@ private void mapEmbeddedPropertyAlias() { for (String propJoin : embeddedPropertyJoins) { String[] split = SplitName.split(propJoin); // the table alias of the parent path - String alias = tableAlias(split[0]); + String alias = tableAliasManyWhere(split[0]); aliasMap.put(propJoin, alias); } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java index 9b828fa9db..a5a55c4df4 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java @@ -220,7 +220,7 @@ private SqlTreeNode buildRootNode(STreeType desc) { if (!rawSql) { alias.addJoin(queryDetail.getFetchPaths(), desc); alias.addJoin(predicates.predicateIncludes(), desc); - alias.addManyWhereJoins(manyWhereJoins.propertyNames()); + alias.addManyWhereJoins(manyWhereJoins.propertyNames(), desc); // build set of table alias alias.buildAlias(); predicates.parseTableAlias(alias); @@ -362,7 +362,8 @@ private void buildExtraJoins(STreeType desc, List myList) { // the 'select' part of the query. We may need to add other joins to // support the predicates or order by clauses. - // remove ManyWhereJoins from the predicateIncludes + // remove ManyWhereJoins from the predicateIncludes and orderByIncludes + predicates.orderByIncludes().removeAll(manyWhereJoins.propertyNames()); predicateIncludes.removeAll(manyWhereJoins.propertyNames()); predicateIncludes.addAll(predicates.orderByIncludes()); @@ -649,11 +650,15 @@ private void createExtraJoin(String includeProp) { * Create a SqlTreeNodeExtraJoin, register and return it. */ private SqlTreeNodeExtraJoin createJoinLeaf(String propertyName) { + SqlTreeNodeExtraJoin extraJoin = joinRegister.get(propertyName); + if (extraJoin != null) { + return extraJoin; + } ExtraJoin extra = desc.extraJoin(propertyName); if (extra == null) { return null; } else { - SqlTreeNodeExtraJoin extraJoin = new SqlTreeNodeExtraJoin(propertyName, extra.property(), extra.isContainsMany(), temporalMode); + extraJoin = new SqlTreeNodeExtraJoin(propertyName, extra.property(), extra.isContainsMany(), temporalMode); joinRegister.put(propertyName, extraJoin); return extraJoin; } @@ -674,24 +679,25 @@ private SqlTreeNodeExtraJoin findExtraJoinRoot(String includeProp, SqlTreeNodeEx // no parent possible(parent is root) return childJoin; - } else { - // look in register ... - String parentPropertyName = includeProp.substring(0, dotPos); - if (selectIncludes.contains(parentPropertyName)) { - // parent already handled by select - return childJoin; - } - - SqlTreeNodeExtraJoin parentJoin = joinRegister.get(parentPropertyName); - if (parentJoin == null) { - // we need to create this the parent implicitly... - parentJoin = createJoinLeaf(parentPropertyName); - } - - parentJoin.addChild(childJoin); - childJoin = parentJoin; + } + String parentPropertyName = includeProp.substring(0, dotPos); + if (desc.isEmbeddedPath(parentPropertyName)) { + // digging in embedded property + // so we have to join parent property path if any includeProp = parentPropertyName; + continue; + } + // look in register ... + if (selectIncludes.contains(parentPropertyName)) { + // parent already handled by select + return childJoin; } + + SqlTreeNodeExtraJoin parentJoin = createJoinLeaf(parentPropertyName); + + parentJoin.addChild(childJoin); + childJoin = parentJoin; + includeProp = parentPropertyName; } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java index ada05c4c97..052d71055e 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java @@ -83,6 +83,9 @@ public void appendFrom(DbSqlContext ctx, SqlJoinType currentJoinType) { * intersection table if this is a ManyToMany node. */ private void appendFromBaseTable(DbSqlContext ctx, SqlJoinType joinType) { + if (nodeBeanProp.isEmbedded()) { + return; + } String alias = ctx.tableAliasManyWhere(prefix); String parentAlias = ctx.tableAliasManyWhere(parentPrefix);