diff --git a/src/main/java/com/avaje/ebeaninternal/server/autotune/service/ProfileOrigin.java b/src/main/java/com/avaje/ebeaninternal/server/autotune/service/ProfileOrigin.java index 626285149..c89a9db87 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/autotune/service/ProfileOrigin.java +++ b/src/main/java/com/avaje/ebeaninternal/server/autotune/service/ProfileOrigin.java @@ -110,7 +110,7 @@ public class ProfileOrigin { Collection pathProperties = pathProps.getPathProps(); for (Props props : pathProperties) { if (!props.isEmpty()) { - detail.addFetch(props.getPath(), props.getPropertiesAsString(), null); + detail.fetch(props.getPath(), props.getPropertiesAsString(), null); } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java index fada56d65..2e14bd300 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java @@ -1252,7 +1252,7 @@ public class BeanDescriptor implements MetaBeanInfo, SpiBeanType { OrmQueryDetail detail = query.getDetail(); for (int i = 0; i < propertiesMany.length; i++) { - if (detail.includes(propertiesMany[i].getName())) { + if (detail.includesPath(propertiesMany[i].getName())) { return propertiesMany[i]; } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java index 9f5bf3181..ba9acce78 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java @@ -353,7 +353,7 @@ public class CQueryBuilder { // transfer PathProperties into OrmQueryDetail for (String path : pathProps.getPaths()) { - detail.getChunk(path, true).setDefaultProperties(null, pathProps.get(path)); + detail.fetch(path, pathProps.get(path)); } // check if @Id property included in RawSql diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java index a4de477ac..285f412a1 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java @@ -140,7 +140,7 @@ public class SqlTreeBuilder { boolean includeJoins = (alias == null) ? false : alias.isIncludeJoins(); return new SqlTree(summary.toString(), rootNode, selectSql, fromSql, inheritanceWhereSql, encryptedProps, - manyProperty, queryDetail.getIncludes(), includeJoins); + manyProperty, queryDetail.getFetchPaths(), includeJoins); } private String buildSelectClause() { @@ -183,7 +183,7 @@ public class SqlTreeBuilder { rootNode = buildSelectChain(null, null, desc, null); if (!rawSql) { - alias.addJoin(queryDetail.getIncludes(), desc); + alias.addJoin(queryDetail.getFetchPaths(), desc); alias.addJoin(predicates.getPredicateIncludes(), desc); alias.addManyWhereJoins(manyWhereJoins.getPropertyNames()); @@ -471,7 +471,7 @@ public class SqlTreeBuilder { return false; } - if (queryDetail.includes(propName)) { + if (queryDetail.includesPath(propName)) { if (manyProperty != null) { // only one many associated allowed to be included in fetch @@ -498,7 +498,7 @@ public class SqlTreeBuilder { */ private boolean isIncludeBean(String prefix) { - if (queryDetail.includes(prefix)) { + if (queryDetail.includesPath(prefix)) { // explicitly included summary.append(", ").append(prefix); String[] splitNames = SplitName.split(prefix); diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java index 2fb6afb81..7d05cec6f 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -1008,7 +1008,7 @@ public class DefaultOrmQuery implements SpiQuery { } public DefaultOrmQuery fetch(String property, String columns, FetchConfig config) { - detail.addFetch(property, columns, config); + detail.fetch(property, columns, config); return this; } diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java index b9d643b67..4f6383c3b 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java @@ -13,12 +13,12 @@ import javax.persistence.PersistenceException; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; /** * Represents the internal structure of an Object Relational query. @@ -46,8 +46,6 @@ public class OrmQueryDetail implements Serializable { */ private LinkedHashMap fetchPaths = new LinkedHashMap(8); - private LinkedHashSet includes = new LinkedHashSet(8); - /** * Return a deep copy of the OrmQueryDetail. */ @@ -57,7 +55,6 @@ public class OrmQueryDetail implements Serializable { for (Map.Entry entry : fetchPaths.entrySet()) { copy.fetchPaths.put(entry.getKey(), entry.getValue().copy()); } - copy.includes = new LinkedHashSet(includes); return copy; } @@ -170,7 +167,7 @@ public class OrmQueryDetail implements Serializable { * set the properties to include on the base / root entity. */ public void select(String columns) { - baseProps = new OrmQueryProperties(null, columns); + baseProps = new OrmQueryProperties(null, columns, null); } public boolean containsProperty(String property) { @@ -215,7 +212,6 @@ public class OrmQueryDetail implements Serializable { for (int i = 0; i < matchingPaths.size(); i++) { String path = matchingPaths.get(i); - includes.remove(path); OrmQueryProperties secQuery = fetchPaths.remove(path); if (secQuery != null) { props.add(secQuery); @@ -228,7 +224,6 @@ public class OrmQueryDetail implements Serializable { // remove join to secondary query from the main query // and add to this secondary query pass2It.remove(); - includes.remove(pass2Prop.getPath()); secQuery.add(pass2Prop); } } @@ -257,29 +252,24 @@ public class OrmQueryDetail implements Serializable { OrmQueryProperties tunedRoot = tunedDetail.getChunk(null, false); if (tunedRoot != null) { tuned = true; - baseProps.setTunedProperties(tunedRoot); - + baseProps = tunedRoot; for (OrmQueryProperties tunedChunk : tunedDetail.fetchPaths.values()) { - OrmQueryProperties chunk = getChunk(tunedChunk.getPath(), false); - if (chunk != null) { - // set the properties to select - chunk.setTunedProperties(tunedChunk); - } else { - // add a missing join - putFetchPath(tunedChunk.copy()); - } + fetch(tunedChunk.copy()); } } return tuned; } /** - * Matches a join() method of the query. + * Add or replace the fetch detail. */ - public void putFetchPath(OrmQueryProperties chunk) { + protected void fetch(OrmQueryProperties chunk) { String path = chunk.getPath(); - fetchPaths.put(path, chunk); - includes.add(path); + if (path == null) { + baseProps = chunk; + } else { + fetchPaths.put(path, chunk); + } } /** @@ -289,7 +279,6 @@ public class OrmQueryDetail implements Serializable { *

*/ public void clear() { - includes.clear(); fetchPaths.clear(); } @@ -299,11 +288,16 @@ public class OrmQueryDetail implements Serializable { * @param path the property to join * @param partialProps the properties on the join property to include */ - public void addFetch(String path, String partialProps, FetchConfig fetchConfig) { + public void fetch(String path, String partialProps, FetchConfig fetchConfig) { - OrmQueryProperties chunk = getChunk(path, true); - chunk.setProperties(partialProps); - chunk.setFetchConfig(fetchConfig); + fetch(new OrmQueryProperties(path, partialProps, fetchConfig)); + } + + /** + * Add for raw sql etc when the properties are already parsed into a set. + */ + public void fetch(String path, LinkedHashSet properties) { + fetch(new OrmQueryProperties(path, properties)); } public void sortFetchPaths(BeanDescriptor d) { @@ -438,15 +432,14 @@ public class OrmQueryDetail implements Serializable { public void setDefaultSelectClause(BeanDescriptor desc) { if (desc.hasDefaultSelectClause() && !hasSelectClause()) { - baseProps.setDefaultProperties(desc.getDefaultSelectClause(), desc.getDefaultSelectClauseSet()); + baseProps = new OrmQueryProperties(null, desc.getDefaultSelectClause()); } for (OrmQueryProperties joinProps : fetchPaths.values()) { if (!joinProps.hasSelectClause()) { BeanDescriptor assocDesc = desc.getBeanDescriptor(joinProps.getPath()); if (assocDesc.hasDefaultSelectClause()) { - // use the default select clause - joinProps.setDefaultProperties(assocDesc.getDefaultSelectClause(), assocDesc.getDefaultSelectClauseSet()); + fetch(joinProps.getPath(), assocDesc.getDefaultSelectClause(), joinProps.getFetchConfig()); } } } @@ -489,7 +482,7 @@ public class OrmQueryDetail implements Serializable { OrmQueryProperties props = fetchPaths.get(path); if (create && props == null) { props = new OrmQueryProperties(path); - putFetchPath(props); + fetch(props); return props; } else { @@ -498,9 +491,9 @@ public class OrmQueryDetail implements Serializable { } /** - * Return true if the property is included. + * Return true if the fetch path is included. */ - public boolean includes(String path) { + public boolean includesPath(String path) { OrmQueryProperties chunk = fetchPaths.get(path); @@ -509,9 +502,9 @@ public class OrmQueryDetail implements Serializable { } /** - * Return the property includes for this detail. + * Return the fetch paths for this detail. */ - public HashSet getIncludes() { - return includes; + public Set getFetchPaths() { + return fetchPaths.keySet(); } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailParser.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailParser.java index a54d56da7..7b2109288 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailParser.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailParser.java @@ -64,8 +64,7 @@ public class OrmQueryDetailParser { private void process() { if (isFetch()) { - OrmQueryProperties props = readFindFetch(); - detail.putFetchPath(props); + detail.fetch(readFindFetch()); } else if (parser.isMatch("where")) { readWhere(); @@ -157,7 +156,7 @@ public class OrmQueryDetailParser { private void readSelect() { String path = null; String props = parser.nextWord(); - if (props != null && props.startsWith("(")) { + if (props.startsWith("(")) { props = props.substring(1, props.length() - 1); OrmQueryProperties base = new OrmQueryProperties(path, props); detail.setBase(base); diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java index 2d2b12c09..625537773 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java @@ -28,11 +28,11 @@ public class OrmQueryProperties implements Serializable { private static final long serialVersionUID = -8785582703966455658L; - private String parentPath; - private String path; + private final String parentPath; + private final String path; - private String rawProperties; - private String trimmedProperties; + private final String rawProperties; + private final String trimmedProperties; /** * NB: -1 means no +query, 0 means use the default batch size. @@ -55,7 +55,7 @@ public class OrmQueryProperties implements Serializable { * Note this SHOULD be a LinkedHashSet to preserve order of the properties. This is to make using * SqlSelect easier with predictable property/column ordering. */ - private LinkedHashSet included; + private final LinkedHashSet included; /** * Included bean joins. @@ -83,25 +83,110 @@ public class OrmQueryProperties implements Serializable { private SpiExpressionList filterMany; /** - * Construct with a given path (null == root path). + * Construct for root so path (and parentPath) are null. + */ + public OrmQueryProperties() { + this((String)null); + } + + /** + * Construct with a given path. */ public OrmQueryProperties(String path) { this.path = path; this.parentPath = SplitName.parent(path); + this.rawProperties = null; + this.trimmedProperties = null; + this.included = null; + } + + public OrmQueryProperties(String path, String rawProperties) { + this(path, rawProperties, null); + } + + public OrmQueryProperties(String path, String rawProperties, FetchConfig fetchConfig) { + + OrmQueryPropertiesParser.Response response = OrmQueryPropertiesParser.parse(rawProperties); + + this.path = path; + this.parentPath = SplitName.parent(path); + this.rawProperties = rawProperties; + this.trimmedProperties = response.properties; + this.included = response.included; + this.lazyFetchBatch = response.lazyFetchBatch; + this.queryFetchBatch = response.queryFetchBatch; + this.cache = response.cache; + this.readOnly = response.readOnly; + + if (fetchConfig != null) { + this.fetchConfig = fetchConfig; + lazyFetchBatch = fetchConfig.getLazyBatchSize(); + queryFetchBatch = fetchConfig.getQueryBatchSize(); + queryFetchAll = fetchConfig.isQueryAll(); + } + } + + public OrmQueryProperties(String path, LinkedHashSet parsedProperties) { + if (parsedProperties == null) { + throw new IllegalArgumentException("parsedProperties is null"); + } + + this.path = path; + this.parentPath = SplitName.parent(path); + // for rawSql parsedProperties can be empty (when only fetching Id property) + this.included = parsedProperties; + this.rawProperties = join(parsedProperties); + this.trimmedProperties = rawProperties; + this.lazyFetchBatch = -1; + this.queryFetchBatch = -1; + this.cache = false; + this.readOnly = false; + this.queryFetchAll = false; } /** - * Construct for root so path (and parentPath) are null. + * Join the set of properties into a comma delimited string. */ - public OrmQueryProperties() { + private String join(LinkedHashSet parsedProperties) { + StringBuilder sb = new StringBuilder(50); + boolean first = true; + for (String property : parsedProperties) { + if (first) { + first = false; + } else { + sb.append(","); + } + sb.append(property); + } + return sb.toString(); } /** - * Used by query language parser. + * Copy constructor. */ - public OrmQueryProperties(String path, String properties) { - this(path); - setProperties(properties); + private OrmQueryProperties(OrmQueryProperties source) { + + this.parentPath = source.parentPath; + this.path = source.path; + this.rawProperties = source.rawProperties; + this.trimmedProperties = source.trimmedProperties; + this.cache = source.cache; + this.readOnly = source.readOnly; + this.queryFetchAll = source.queryFetchAll; + this.queryFetchBatch = source.queryFetchBatch; + this.lazyFetchBatch = source.lazyFetchBatch; + this.filterMany = source.filterMany; + this.included = (source.included == null) ? null : new LinkedHashSet(source.included); + if (includedBeanJoin != null) { + this.includedBeanJoin = new HashSet(source.includedBeanJoin); + } + } + + /** + * Creates a copy of the OrmQueryProperties. + */ + public OrmQueryProperties copy() { + return new OrmQueryProperties(this); } /** @@ -115,41 +200,10 @@ public class OrmQueryProperties implements Serializable { orderBy.add(orderProp); } - /** - * Set the Fetch configuration options for this path. - */ - public void setFetchConfig(FetchConfig fetchConfig) { - if (fetchConfig != null) { - this.fetchConfig = fetchConfig; - lazyFetchBatch = fetchConfig.getLazyBatchSize(); - queryFetchBatch = fetchConfig.getQueryBatchSize(); - queryFetchAll = fetchConfig.isQueryAll(); - } - } - public FetchConfig getFetchConfig() { return fetchConfig; } - /** - * Set the comma delimited properties to fetch for this path. - *

- * This can include the +query and +lazy type hints. - *

- */ - public void setProperties(String rawProperties) { - - OrmQueryPropertiesParser.Response response = OrmQueryPropertiesParser.parse(rawProperties); - - this.rawProperties = rawProperties; - this.trimmedProperties = response.properties; - this.included = response.included; - this.lazyFetchBatch = response.lazyFetchBatch; - this.queryFetchBatch = response.queryFetchBatch; - this.cache = response.cache; - this.readOnly = response.readOnly; - } - /** * Return the expressions used to filter on this path. This should be a many path to use this * method. @@ -193,28 +247,6 @@ public class OrmQueryProperties implements Serializable { this.filterMany = filterMany; } - /** - * Set the properties from deployment default FetchTypes. - */ - public void setDefaultProperties(String properties, LinkedHashSet included) { - this.rawProperties = properties; - this.trimmedProperties = properties; - this.included = included; - } - - /** - * Set the properties from a matching AutoTune tuned properties. - */ - public void setTunedProperties(OrmQueryProperties tunedProperties) { - if (tunedProperties.hasProperties()) { - this.rawProperties = tunedProperties.rawProperties; - this.trimmedProperties = tunedProperties.trimmedProperties; - this.included = tunedProperties.included; - this.queryFetchBatch = Math.max(queryFetchBatch, tunedProperties.queryFetchBatch); - this.lazyFetchBatch = Math.max(lazyFetchBatch, tunedProperties.lazyFetchBatch); - } - } - /** * Define the select and joins for this query. */ @@ -249,30 +281,6 @@ public class OrmQueryProperties implements Serializable { } } - /** - * Creates a copy of the OrmQueryProperties. - */ - public OrmQueryProperties copy() { - OrmQueryProperties copy = new OrmQueryProperties(); - copy.parentPath = parentPath; - copy.path = path; - copy.rawProperties = rawProperties; - copy.trimmedProperties = trimmedProperties; - copy.cache = cache; - copy.readOnly = readOnly; - copy.queryFetchAll = queryFetchAll; - copy.queryFetchBatch = queryFetchBatch; - copy.lazyFetchBatch = lazyFetchBatch; - copy.filterMany = filterMany; - if (included != null) { - copy.included = new LinkedHashSet(included); - } - if (includedBeanJoin != null) { - copy.includedBeanJoin = new HashSet(includedBeanJoin); - } - return copy; - } - public boolean hasSelectClause() { if ("*".equals(trimmedProperties)) { // explicitly selected all properties diff --git a/src/test/java/com/avaje/ebeaninternal/server/core/DefaultServer_createOrmQueryRequestTest.java b/src/test/java/com/avaje/ebeaninternal/server/core/DefaultServer_createOrmQueryRequestTest.java index fb98c62d8..7d7b0caf2 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/core/DefaultServer_createOrmQueryRequestTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/core/DefaultServer_createOrmQueryRequestTest.java @@ -11,18 +11,18 @@ import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail; import com.avaje.tests.model.basic.Order; import org.junit.Test; -import static org.assertj.core.api.StrictAssertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; public class DefaultServer_createOrmQueryRequestTest extends BaseTestCase { - static DefaultServer defaultServer = (DefaultServer)Ebean.getDefaultServer(); + static DefaultServer defaultServer = (DefaultServer) Ebean.getDefaultServer(); Query query() { return defaultServer.find(Order.class); } OrmQueryRequest queryRequest(Query query) { - return (OrmQueryRequest)defaultServer.createQueryRequest(SpiQuery.Type.LIST, query, null); + return (OrmQueryRequest) defaultServer.createQueryRequest(SpiQuery.Type.LIST, query, null); } OrmQueryDetail detail(Query query) { @@ -87,26 +87,26 @@ public class DefaultServer_createOrmQueryRequestTest extends BaseTestCase { // with the fetch of customer the foreign key must be added to the root query assertDifferent(detail(query().select("id,name")), - detail(query().select("id,name").fetch("customer",new FetchConfig().query()))); + detail(query().select("id,name").fetch("customer", new FetchConfig().query()))); } @Test public void when_additional_fetch_V2_then_different() { - assertDifferent(detail(query().select("id,name")), detail(query().select("id,name").fetch("customer","id"))); + assertDifferent(detail(query().select("id,name")), detail(query().select("id,name").fetch("customer", "id"))); } @Test public void when_fetchConfig_then_differentPlan() throws Exception { - DefaultOrmQuery query1 = (DefaultOrmQuery)Ebean.find(Order.class) + DefaultOrmQuery query1 = (DefaultOrmQuery) Ebean.find(Order.class) .select("status, shipDate") .fetch("details", "orderQty, unitPrice", new FetchConfig().query()) .fetch("details.product", "sku, name"); - DefaultOrmQuery query2 = (DefaultOrmQuery)Ebean.find(Order.class) + DefaultOrmQuery query2 = (DefaultOrmQuery) Ebean.find(Order.class) .select("status, shipDate") .fetch("details", "orderQty, unitPrice") .fetch("details.product", "sku, name"); @@ -114,6 +114,171 @@ public class DefaultServer_createOrmQueryRequestTest extends BaseTestCase { assertDifferent(detail(query1), detail(query2)); } + @Test + public void testJoinOrder_when_fetchJoins_expect_detailJoinsPreserveOrder() { + + Query query = Ebean.find(Order.class) + .select("status, orderDate") + .fetch("customer", "name") + .fetch("details"); + + OrmQueryRequest queryRequest = queryRequest(query); + OrmQueryDetail detail = queryRequest.getQuery().getDetail(); + + assertThat(detail.getFetchPaths()).containsExactly("customer", "details"); + } + + @Test + public void testJoinOrder_when_fetchJoinsAndWhere_expect_fetchJoinsOnlyInFetchPaths() { + + Query query = Ebean.find(Order.class) + .select("status, orderDate") + .fetch("details") + .where().eq("customer.name", "rob").query(); + + OrmQueryRequest queryRequest = queryRequest(query); + OrmQueryDetail detail = queryRequest.getQuery().getDetail(); + + assertThat(detail.getFetchPaths()).containsExactly("details"); + } + + @Test + public void testJoinOrder_when_queryFetch_expect_getFetchPaths_doesNotIncludeQueryJoin() { + + Query query = Ebean.find(Order.class) + .select("status, orderDate") + .fetch("customer", "name") + .fetch("details", new FetchConfig().query()); + + OrmQueryRequest queryRequest = queryRequest(query); + OrmQueryDetail detail = queryRequest.getQuery().getDetail(); + + assertThat(detail.getFetchPaths()).containsExactly("customer"); + } + + @Test + public void testJoinOrder_when_lazyFetch_expect_getFetchPaths_doesNotIncludeQueryJoin() { + + Query query = Ebean.find(Order.class) + .select("status, orderDate") + .fetch("customer", "name") + .fetch("details", new FetchConfig().lazy()); + + OrmQueryRequest queryRequest = queryRequest(query); + OrmQueryDetail detail = queryRequest.getQuery().getDetail(); + + assertThat(detail.getFetchPaths()).containsExactly("customer"); + } + + @Test + public void testJoinOrder_when_lazyFetchAndHasChildren_expect_getFetchPaths_doesNotIncludeJoinOrChild() { + + Query query = Ebean.find(Order.class) + .select("status, orderDate") + .fetch("customer", "name") + .fetch("details", new FetchConfig().lazy()) + .fetch("details.product"); + + OrmQueryRequest queryRequest = queryRequest(query); + OrmQueryDetail detail = queryRequest.getQuery().getDetail(); + + assertThat(detail.getFetchPaths()).containsExactly("customer"); + } + + @Test + public void testJoinOrder_when_fetchMany_expect_getFetchPaths_containsAllInOrder() { + + Query query = Ebean.find(Order.class) + .select("status, orderDate") + .fetch("details") + .fetch("details.product") + .fetch("customer", "name"); + + OrmQueryRequest queryRequest = queryRequest(query); + OrmQueryDetail detail = queryRequest.getQuery().getDetail(); + + assertThat(detail.getFetchPaths()).containsExactly("details", "details.product", "customer"); + } + + @Test + public void testJoinOrder_when_queryJoin_expect_getFetchPaths_excludesQueryJoinAndChildren() { + + Query query = Ebean.find(Order.class) + .select("status, orderDate") + .fetch("details", new FetchConfig().query()) + .fetch("details.product") + .fetch("customer", "name"); + + OrmQueryRequest queryRequest = queryRequest(query); + OrmQueryDetail detail = queryRequest.getQuery().getDetail(); + + assertThat(detail.getFetchPaths()).containsExactly("customer"); + } + + @Test + public void test_removeJoinToMany_when_multipleManyPaths() { + + Query query = Ebean.find(Order.class) + .fetch("details") + .fetch("details.product") + .fetch("customer") + .fetch("customer.contacts"); // second many path + + OrmQueryRequest queryRequest = queryRequest(query); + OrmQueryDetail detail = queryRequest.getQuery().getDetail(); + + assertThat(detail.getFetchPaths()).containsExactly("details", "details.product", "customer"); + } + + @Test + public void test_removeAllJoinToMany_when_firstRow() { + + Query query = Ebean.find(Order.class) + .setFirstRow(1) + .fetch("details") // many path + .fetch("details.product") + .fetch("customer") + .fetch("customer.contacts"); // many path + + OrmQueryRequest queryRequest = queryRequest(query); + OrmQueryDetail detail = queryRequest.getQuery().getDetail(); + + assertThat(detail.getFetchPaths()).containsExactly("customer"); + } + + @Test + public void test_removeAllJoinToMany_when_maxRows() { + + Query query = Ebean.find(Order.class) + .setMaxRows(1) + .fetch("details") // many path + .fetch("details.product") + .fetch("customer") + .fetch("customer.contacts"); // many path + + OrmQueryRequest queryRequest = queryRequest(query); + OrmQueryDetail detail = queryRequest.getQuery().getDetail(); + + assertThat(detail.getFetchPaths()).containsExactly("customer"); + } + + @Test + public void test_removeJoinToMany_when_filterMany() { + + Query query = Ebean.find(Order.class) + .fetch("details") + .fetch("details.product") + .fetch("customer") + .fetch("customer.contacts") + .filterMany("details").eq("orderQuantity", 10) + .query(); + + OrmQueryRequest queryRequest = queryRequest(query); + OrmQueryDetail detail = queryRequest.getQuery().getDetail(); + + assertThat(detail.getFetchPaths()).containsExactly("customer", "customer.contacts"); + } + private void assertSame(OrmQueryDetail detail1, OrmQueryDetail detail2) { assertThat(detail1.isSameByPlan(detail2)).isTrue(); assertThat(detail1.queryPlanHash()).isEqualTo(detail2.queryPlanHash()); diff --git a/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailTest.java b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailTest.java index b12516431..31c3dac5a 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetailTest.java @@ -3,7 +3,9 @@ package com.avaje.ebeaninternal.server.querydefn; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.*; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; public class OrmQueryDetailTest { @@ -68,4 +70,49 @@ public class OrmQueryDetailTest { assertNull(root.getPath()); assertThat(root.getIncluded()).containsExactly("name"); } + + @Test + public void getFetchPaths_when_noFetches_then_expect_empty() { + + assertThat(new OrmQueryDetail().getFetchPaths()).isEmpty(); + + OrmQueryDetail detail = new OrmQueryDetail(); + detail.select("foo"); + + assertThat(detail.getFetchPaths()).isEmpty(); + } + + @Test + public void getFetchPaths_when_oneFetch() { + + OrmQueryDetail detail = new OrmQueryDetail(); + detail.select("foo"); + detail.fetch("customer", null, null); + + assertThat(detail.getFetchPaths()).containsExactly("customer"); + } + + @Test + public void getFetchPaths_when_multipleFetch_expect_preserveOrder() { + + OrmQueryDetail detail = new OrmQueryDetail(); + detail.select("foo"); + detail.fetch("customer", null, null); + detail.fetch("details", null, null); + + assertThat(detail.getFetchPaths()).containsExactly("customer", "details"); + } + + @Test + public void getFetchPaths_when_multipleFetch_expect_preserveOrder_v2() { + + OrmQueryDetail detail = new OrmQueryDetail(); + detail.select("foo"); + detail.fetch("details", null, null); + detail.fetch("customer", null, null); + detail.fetch("details.product", null, null); + + assertThat(detail.getFetchPaths()).containsExactly("details", "customer", "details.product"); + } + } \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPropertiesTest.java b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPropertiesTest.java index b1cc03494..8e50cc033 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPropertiesTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPropertiesTest.java @@ -2,6 +2,8 @@ package com.avaje.ebeaninternal.server.querydefn; import org.junit.Test; +import java.util.LinkedHashSet; + import static org.assertj.core.api.Assertions.assertThat; public class OrmQueryPropertiesTest { @@ -12,6 +14,43 @@ public class OrmQueryPropertiesTest { return sb.toString(); } + @Test(expected = IllegalArgumentException.class) + public void construct_with_propertySet_when_null() { + new OrmQueryProperties(null, (LinkedHashSet)null); + } + + @Test + public void construct_with_propertySet_when_empty() { + + OrmQueryProperties p1 = new OrmQueryProperties(null, new LinkedHashSet()); + assertThat(p1.getProperties()).isEqualTo(""); + assertThat(p1.allProperties()).isFalse(); + } + + @Test + public void construct_with_propertySet_when_one() { + + LinkedHashSet set = new LinkedHashSet(); + set.add("name"); + OrmQueryProperties p1 = new OrmQueryProperties(null, set); + + assertThat(p1.getProperties()).isEqualTo("name"); + assertThat(p1.allProperties()).isFalse(); + } + + @Test + public void construct_with_propertySet_when_some() { + + LinkedHashSet set = new LinkedHashSet(); + set.add("id"); + set.add("name"); + set.add("startDate"); + OrmQueryProperties p1 = new OrmQueryProperties(null, set); + + assertThat(p1.getProperties()).isEqualTo("id,name,startDate"); + assertThat(p1.allProperties()).isFalse(); + } + @Test public void append_when_empty() { diff --git a/src/test/java/com/avaje/tests/query/TestQueryFindIterate.java b/src/test/java/com/avaje/tests/query/TestQueryFindIterate.java index b4ce33d75..9f60c8c51 100644 --- a/src/test/java/com/avaje/tests/query/TestQueryFindIterate.java +++ b/src/test/java/com/avaje/tests/query/TestQueryFindIterate.java @@ -13,6 +13,7 @@ import com.avaje.tests.model.basic.ResetBasicData; import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -150,8 +151,8 @@ public class TestQueryFindIterate extends BaseTestCase { List loggedSql = LoggedSqlCollector.stop(); assertEquals("Got SQL: "+loggedSql, 2, loggedSql.size()); - assertTrue(loggedSql.get(0).contains("select t0.id c0, t0.status c1, t0.order_date c2, t1.id c3, t1.name c4, t2.id c5, t2.order_qty c6, t2.ship_qty")); - assertTrue(loggedSql.get(1).contains("select t0.order_id c0, t0.id c1, t0.ship_time c2, t0.cretime c3, t0.updtime c4, t0.version c5, t0.order_id c6 from or_order_ship")); + assertThat(loggedSql.get(0)).contains("select t0.id c0, t0.status c1, t0.order_date c2, t1.id c3, t1.name c4, t2.id c5, t2.order_qty c6, t2.ship_qty"); + assertThat(loggedSql.get(1)).contains("select t0.order_id c0, t0.id c1, t0.ship_time c2, t0.cretime c3, t0.updtime c4, t0.version c5, t0.order_id c6 from or_order_ship"); } @Test(expected=PersistenceException.class)