diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiQuery.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiQuery.java index f3b133bc2..4686a3644 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiQuery.java @@ -20,6 +20,7 @@ import io.ebeaninternal.server.deploy.TableJoin; import io.ebeaninternal.server.query.CancelableQuery; import io.ebeaninternal.server.querydefn.NaturalKeyBindParam; import io.ebeaninternal.server.querydefn.OrmQueryDetail; +import io.ebeaninternal.server.querydefn.OrmQueryProperties; import io.ebeaninternal.server.querydefn.OrmUpdateProperties; import io.ebeaninternal.server.rawsql.SpiRawSql; @@ -30,7 +31,7 @@ import java.util.Set; /** * Object Relational query - Internal extension to Query object. */ -public interface SpiQuery extends Query, TxnProfileEventCodes { +public interface SpiQuery extends Query, SpiQueryFetch, TxnProfileEventCodes { enum Mode { NORMAL(false), LAZYLOAD_MANY(false), LAZYLOAD_BEAN(true), REFRESH_BEAN(true); @@ -289,6 +290,16 @@ public interface SpiQuery extends Query, TxnProfileEventCodes { */ boolean selectAllForLazyLoadProperty(); + /** + * Set the select properties. + */ + void selectProperties(OrmQueryProperties other); + + /** + * Set the fetch properties for the given path. + */ + void fetchProperties(String path, OrmQueryProperties other); + /** * Set the on a secondary query given the label, relativePath and profile location of the parent query. */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiQueryFetch.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiQueryFetch.java new file mode 100644 index 000000000..396827978 --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiQueryFetch.java @@ -0,0 +1,22 @@ +package io.ebeaninternal.api; + +import io.ebean.FetchConfig; + +import java.util.Set; + +/** + * Query select and fetch properties (that avoids parsing). + */ +public interface SpiQueryFetch { + + /** + * Specify the select properties. + */ + void selectProperties(Set properties); + + /** + * Specify the fetch properties for the given path. + */ + void fetchProperties(String name, Set properties, FetchConfig config); + +} diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/DFetchGroupBuilder.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/DFetchGroupBuilder.java index f33985f46..6553b7ea4 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/DFetchGroupBuilder.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/DFetchGroupBuilder.java @@ -11,6 +11,8 @@ import io.ebeaninternal.server.querydefn.SpiFetchGroup; */ class DFetchGroupBuilder implements FetchGroupBuilder { + private static final FetchConfig DEFAULT_FETCH = FetchConfig.ofDefault(); + private static final FetchConfig FETCH_CACHE = FetchConfig.ofCache(); private static final FetchConfig FETCH_QUERY = FetchConfig.ofQuery(); @@ -31,13 +33,13 @@ class DFetchGroupBuilder implements FetchGroupBuilder { @Override public FetchGroupBuilder fetch(String path) { - detail.fetch(path, null, null); + detail.fetchProperties(path, null, DEFAULT_FETCH); return this; } @Override public FetchGroupBuilder fetch(String path, FetchGroup nestedGroup) { - return fetchNested(path, nestedGroup, null); + return fetchNested(path, nestedGroup, DEFAULT_FETCH); } @Override @@ -51,7 +53,6 @@ class DFetchGroupBuilder implements FetchGroupBuilder { } private FetchGroupBuilder fetchNested(String path, FetchGroup nestedGroup, FetchConfig fetchConfig) { - OrmQueryDetail nestedDetail = ((SpiFetchGroup) nestedGroup).underlying(); detail.addNested(path, nestedDetail, fetchConfig); return this; @@ -59,25 +60,25 @@ class DFetchGroupBuilder implements FetchGroupBuilder { @Override public FetchGroupBuilder fetchQuery(String path) { - detail.fetch(path, null, FETCH_QUERY); + detail.fetchProperties(path, null, FETCH_QUERY); return this; } @Override public FetchGroupBuilder fetchCache(String path) { - detail.fetch(path, null, FETCH_CACHE); + detail.fetchProperties(path, null, FETCH_CACHE); return this; } @Override public FetchGroupBuilder fetchLazy(String path) { - detail.fetch(path, null, FETCH_LAZY); + detail.fetchProperties(path, null, FETCH_LAZY); return this; } @Override public FetchGroupBuilder fetch(String path, String properties) { - detail.fetch(path, properties, null); + detail.fetch(path, properties, DEFAULT_FETCH); return this; } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultFetchGroupQuery.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultFetchGroupQuery.java index f7bfdadf1..5da152185 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultFetchGroupQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultFetchGroupQuery.java @@ -25,6 +25,7 @@ import io.ebean.Transaction; import io.ebean.UpdateQuery; import io.ebean.Version; import io.ebean.service.SpiFetchGroupQuery; +import io.ebeaninternal.api.SpiQueryFetch; import io.ebeaninternal.server.querydefn.OrmQueryDetail; import io.ebeaninternal.server.querydefn.SpiFetchGroup; @@ -43,7 +44,7 @@ import java.util.stream.Stream; /** * Implementation of FetchGroup query for use to create FetchGroup via query beans. */ -class DefaultFetchGroupQuery implements SpiFetchGroupQuery { +class DefaultFetchGroupQuery implements SpiFetchGroupQuery, SpiQueryFetch { private static final FetchConfig FETCH_CACHE = FetchConfig.ofCache(); @@ -628,4 +629,14 @@ class DefaultFetchGroupQuery implements SpiFetchGroupQuery { public Query orderById(boolean orderById) { throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup"); } + + @Override + public void selectProperties(Set props) { + detail.selectProperties(props); + } + + @Override + public void fetchProperties(String property, Set columns, FetchConfig config) { + detail.fetchProperties(property, columns, config); + } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java index d625e87be..e19212e96 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -1392,6 +1392,26 @@ public class DefaultOrmQuery implements SpiQuery { return this; } + @Override + public void selectProperties(Set props) { + detail.selectProperties(props); + } + + @Override + public void fetchProperties(String property, Set columns, FetchConfig config) { + detail.fetchProperties(property, columns, config); + } + + @Override + public void selectProperties(OrmQueryProperties properties) { + detail.selectProperties(properties); + } + + @Override + public void fetchProperties(String path, OrmQueryProperties other) { + detail.fetchProperties(path, other); + } + @Override public DefaultOrmQuery select(String columns) { detail.select(columns); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryDetail.java b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryDetail.java index b8837111e..1c441cbf4 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryDetail.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryDetail.java @@ -59,9 +59,9 @@ public class OrmQueryDetail implements Serializable { * Add a nested OrmQueryDetail to this detail. */ public void addNested(String path, OrmQueryDetail other, FetchConfig config) { - fetch(path, other.baseProps.getProperties(), config); + fetchProperties(path, other.baseProps, config); for (Map.Entry entry : other.fetchPaths.entrySet()) { - fetch(path + "." + entry.getKey(), entry.getValue().getProperties(), entry.getValue().getFetchConfig()); + fetchProperties(path + "." + entry.getKey(), entry.getValue(), entry.getValue().getFetchConfig()); } } @@ -133,8 +133,19 @@ 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, null); + public void select(String properties) { + baseProps = new OrmQueryProperties(null, properties, null); + } + + /** + * Set select properties that are already parsed. + */ + public void selectProperties(Set properties) { + baseProps = new OrmQueryProperties(null, properties, OrmQueryProperties.DEFAULT_FETCH); + } + + void selectProperties(OrmQueryProperties other) { + baseProps = new OrmQueryProperties(null, other, OrmQueryProperties.DEFAULT_FETCH); } boolean containsProperty(String property) { @@ -262,10 +273,24 @@ public class OrmQueryDetail implements Serializable { * @param partialProps the properties on the join property to include */ public void fetch(String path, String partialProps, FetchConfig fetchConfig) { - fetch(new OrmQueryProperties(path, partialProps, fetchConfig)); } + /** + * Set fetch properties that are already parsed. + */ + public void fetchProperties(String path, Set properties, FetchConfig fetchConfig) { + fetch(new OrmQueryProperties(path, properties, fetchConfig)); + } + + void fetchProperties(String path, OrmQueryProperties other) { + fetchProperties(path, other, other.getFetchConfig()); + } + + void fetchProperties(String path, OrmQueryProperties other, FetchConfig fetchConfig) { + fetch(new OrmQueryProperties(path, other, fetchConfig)); + } + /** * Add for raw sql etc when the properties are already parsed into a set. */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryProperties.java b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryProperties.java index 704cead7f..e84828f09 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryProperties.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryProperties.java @@ -30,7 +30,7 @@ public class OrmQueryProperties implements Serializable { private final String parentPath; private final String path; - private final String properties; + private final boolean allProperties; private final Set included; private final FetchConfig fetchConfig; private final boolean cache; @@ -77,7 +77,7 @@ public class OrmQueryProperties implements Serializable { public OrmQueryProperties(String path) { this.path = path; this.parentPath = SplitName.parent(path); - this.properties = null; + this.allProperties = false; this.included = null; this.cache = false; this.fetchConfig = DEFAULT_FETCH; @@ -91,7 +91,7 @@ public class OrmQueryProperties implements Serializable { this.path = path; this.parentPath = SplitName.parent(path); OrmQueryPropertiesParser.Response response = OrmQueryPropertiesParser.parse(rawProperties); - this.properties = response.properties; + this.allProperties = response.allProperties; this.included = response.included; if (fetchConfig != null) { this.fetchConfig = fetchConfig; @@ -103,12 +103,25 @@ public class OrmQueryProperties implements Serializable { } public OrmQueryProperties(String path, Set included) { + this(path, included, DEFAULT_FETCH); + } + + OrmQueryProperties(String path, Set included, FetchConfig fetchConfig) { this.path = path; this.parentPath = SplitName.parent(path); this.included = included; - this.properties = String.join(",", included); - this.cache = false; - this.fetchConfig = DEFAULT_FETCH; + this.allProperties = false; + this.fetchConfig = fetchConfig; + this.cache = fetchConfig.isCache(); + } + + OrmQueryProperties(String path, OrmQueryProperties other, FetchConfig fetchConfig) { + this.path = path; + this.parentPath = SplitName.parent(path); + this.allProperties = other.allProperties; + this.included = other.included; + this.cache = other.cache; + this.fetchConfig = fetchConfig; } /** @@ -118,7 +131,7 @@ public class OrmQueryProperties implements Serializable { this.fetchConfig = sourceFetchConfig; this.parentPath = source.parentPath; this.path = source.path; - this.properties = source.properties; + this.allProperties = source.allProperties; this.cache = source.cache; this.filterMany = source.filterMany; this.markForQueryJoin = source.markForQueryJoin; @@ -158,7 +171,7 @@ public class OrmQueryProperties implements Serializable { * Return the expressions used to filter on this path. This should be a many path to use this * method. */ - @SuppressWarnings({"rawtypes","unchecked"}) + @SuppressWarnings({"rawtypes", "unchecked"}) public SpiExpressionList filterMany(Query rootQuery) { if (filterMany == null) { FilterExprPath exprPath = new FilterExprPath(path); @@ -201,9 +214,8 @@ public class OrmQueryProperties implements Serializable { */ @SuppressWarnings("unchecked") public void configureBeanQuery(SpiQuery query) { - - if (properties != null && !properties.isEmpty()) { - query.select(properties); + if (!isEmpty()) { + query.selectProperties(this); } if (filterMany != null) { @@ -219,7 +231,7 @@ public class OrmQueryProperties implements Serializable { for (OrmQueryProperties p : secondaryChildren) { String path = p.getPath(); path = path.substring(trimPath); - query.fetch(path, p.getProperties(), p.getFetchConfig()); + query.fetchProperties(path, p); query.setFilterMany(path, p.getFilterManyTrimPath(trimPath)); } } @@ -230,8 +242,7 @@ public class OrmQueryProperties implements Serializable { } public boolean hasSelectClause() { - if ("*".equals(properties)) { - // explicitly selected all properties + if (allProperties) { return true; } // explicitly selected some properties @@ -241,8 +252,8 @@ public class OrmQueryProperties implements Serializable { /** * Return true if the properties and configuration are empty. */ - public boolean isEmpty() { - return properties == null || properties.isEmpty(); + boolean isEmpty() { + return !allProperties && included == null; } public void asStringDebug(String prefix, StringBuilder sb) { @@ -250,8 +261,10 @@ public class OrmQueryProperties implements Serializable { if (path != null) { sb.append(path).append(" "); } - if (!isEmpty()) { - sb.append("(").append(properties).append(")"); + if (allProperties) { + sb.append("(*)"); + } else if (included != null) { + sb.append("(").append(String.join(",", included)).append(")"); } } @@ -269,17 +282,11 @@ public class OrmQueryProperties implements Serializable { secondaryChildren.add(child); } - /** - * Return the raw properties. - */ - public String getProperties() { - return properties; - } - /** * Return true if this includes all properties on the path. */ public boolean allProperties() { + // this is really "default" properties return included == null; } @@ -326,7 +333,6 @@ public class OrmQueryProperties implements Serializable { if (includedBeanJoin != null && includedBeanJoin.contains(propName)) { return false; } - // all properties included return included == null || included.contains(propName); } @@ -403,7 +409,7 @@ public class OrmQueryProperties implements Serializable { if (path != null) { builder.append(path); } - if (included != null){ + if (included != null) { builder.append("/i").append(included); } if (secondaryQueryJoins != null) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryPropertiesParser.java b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryPropertiesParser.java index f6f925891..568a6165c 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryPropertiesParser.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryPropertiesParser.java @@ -7,72 +7,43 @@ import java.util.Set; /** * Parses the path properties string. */ -class OrmQueryPropertiesParser { +final class OrmQueryPropertiesParser { - private static final Response EMPTY = new Response(); + private static final Response EMPTY = new Response(false, null); + private static final Response ALL = new Response(true, null); /** * Immutable response of the parsed properties and options. */ static class Response { - final String properties; + final boolean allProperties; final Set included; - private Response(String properties, Set included) { - this.properties = properties; + private Response(boolean allProperties, Set included) { + this.allProperties = allProperties; this.included = included; } - - private Response() { - this.properties = ""; - this.included = null; - } } /** * Parses the path properties string returning the parsed properties and options. * In general it is comma delimited with some special strings like +lazy(20). */ - public static Response parse(String rawProperties) { - return new OrmQueryPropertiesParser(rawProperties).parse(); - } - - private final String inputProperties; - private boolean allProperties; - - private OrmQueryPropertiesParser(String inputProperties) { - this.inputProperties = inputProperties; - } - - /** - * Parse the raw string properties input. - */ - private Response parse() { - if (inputProperties == null || inputProperties.isEmpty()) { + static Response parse(String rawProperties) { + if (rawProperties == null || rawProperties.isEmpty()) { return EMPTY; } - if (inputProperties.equals("*")) { - return new Response("*", null); + if (rawProperties.equals("*")) { + return ALL; } - Set fields = splitRawSelect(inputProperties); - for (String val : fields) { - if (val.equals("*")) { - allProperties = true; - break; - } - } - String properties = allProperties ? "*" : inputProperties; - if (fields.isEmpty()) { - fields = null; - } - return new Response(properties, fields); + return new Response(false, splitRawSelect(rawProperties)); } /** * Split allowing 'dynamic function based properties'. */ - private Set splitRawSelect(String inputProperties) { + private static Set splitRawSelect(String inputProperties) { return DSelectColumnsParser.parse(inputProperties); } diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/querydefn/OrmQueryPropertiesParserTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/querydefn/OrmQueryPropertiesParserTest.java index 5160c664e..a7c103b83 100644 --- a/ebean-core/src/test/java/io/ebeaninternal/server/querydefn/OrmQueryPropertiesParserTest.java +++ b/ebean-core/src/test/java/io/ebeaninternal/server/querydefn/OrmQueryPropertiesParserTest.java @@ -11,7 +11,8 @@ public class OrmQueryPropertiesParserTest { OrmQueryPropertiesParser.Response res = OrmQueryPropertiesParser.parse(null); assertAllDefaults(res); - assertThat(res.properties).isEqualTo(""); + assertThat(res.allProperties).isFalse(); + assertThat(res.included).isNull(); } @Test @@ -19,7 +20,8 @@ public class OrmQueryPropertiesParserTest { OrmQueryPropertiesParser.Response res = OrmQueryPropertiesParser.parse(""); assertAllDefaults(res); - assertThat(res.properties).isEqualTo(""); + assertThat(res.allProperties).isFalse(); + assertThat(res.included).isNull(); } @Test @@ -27,13 +29,15 @@ public class OrmQueryPropertiesParserTest { OrmQueryPropertiesParser.Response res = OrmQueryPropertiesParser.parse("*"); assertAllDefaults(res); - assertThat(res.properties).isEqualTo("*"); + assertThat(res.allProperties).isTrue(); + assertThat(res.included).isNull(); } @Test public void when_no_spaces() { OrmQueryPropertiesParser.Response res = OrmQueryPropertiesParser.parse("id,name"); + assertThat(res.allProperties).isFalse(); assertThat(res.included).containsExactly("id", "name"); } @@ -41,6 +45,7 @@ public class OrmQueryPropertiesParserTest { public void when_spaced() { OrmQueryPropertiesParser.Response res = OrmQueryPropertiesParser.parse("id, name"); + assertThat(res.allProperties).isFalse(); assertThat(res.included).containsExactly("id", "name"); } @@ -48,6 +53,7 @@ public class OrmQueryPropertiesParserTest { public void when_formula() { OrmQueryPropertiesParser.Response res = OrmQueryPropertiesParser.parse("a,MD5(id::text) as b,c"); + assertThat(res.allProperties).isFalse(); assertThat(res.included).containsExactly("a", "MD5(id::text) as b", "c"); } diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/querydefn/OrmQueryPropertiesTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/querydefn/OrmQueryPropertiesTest.java index b2ed90fcb..8df439a58 100644 --- a/ebean-core/src/test/java/io/ebeaninternal/server/querydefn/OrmQueryPropertiesTest.java +++ b/ebean-core/src/test/java/io/ebeaninternal/server/querydefn/OrmQueryPropertiesTest.java @@ -14,17 +14,12 @@ public class OrmQueryPropertiesTest { return sb.toString(); } - @Test(expected = NullPointerException.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(); + assertThat(p1.getIncluded()).isEmpty(); } @Test @@ -34,7 +29,7 @@ public class OrmQueryPropertiesTest { set.add("name"); OrmQueryProperties p1 = new OrmQueryProperties(null, set); - assertThat(p1.getProperties()).isEqualTo("name"); + assertThat(p1.getIncluded()).containsOnly("name"); assertThat(p1.allProperties()).isFalse(); } @@ -47,7 +42,7 @@ public class OrmQueryPropertiesTest { set.add("startDate"); OrmQueryProperties p1 = new OrmQueryProperties(null, set); - assertThat(p1.getProperties()).isEqualTo("id,name,startDate"); + assertThat(p1.getIncluded()).containsOnly("id", "name", "startDate"); assertThat(p1.allProperties()).isFalse(); } diff --git a/ebean-core/src/test/java/org/tests/basic/lob/TestBasicClobNoVer.java b/ebean-core/src/test/java/org/tests/basic/lob/TestBasicClobNoVer.java index 0d791db50..f33a42a42 100644 --- a/ebean-core/src/test/java/org/tests/basic/lob/TestBasicClobNoVer.java +++ b/ebean-core/src/test/java/org/tests/basic/lob/TestBasicClobNoVer.java @@ -1,12 +1,10 @@ package org.tests.basic.lob; import io.ebean.BaseTestCase; -import io.ebean.Ebean; -import io.ebean.EbeanServer; +import io.ebean.DB; import io.ebean.Query; import org.tests.model.basic.EBasicClobNoVer; import org.ebeantest.LoggedSqlCollector; -import org.junit.Assert; import org.junit.Test; import java.util.List; @@ -21,53 +19,50 @@ public class TestBasicClobNoVer extends BaseTestCase { EBasicClobNoVer entity = new EBasicClobNoVer(); entity.setName("test"); entity.setDescription("initialClobValue"); - EbeanServer server = Ebean.getServer(null); - server.save(entity); - - - String sqlNoClob = "select t0.id, t0.name from ebasic_clob_no_ver t0 where t0.id = ?"; - String sqlWithClob = "select t0.id, t0.name, t0.description from ebasic_clob_no_ver t0 where t0.id = ?"; - + DB.save(entity); // Clob by default is Fetch Lazy - Query defaultQuery = Ebean.find(EBasicClobNoVer.class).setId(entity.getId()); + Query defaultQuery = DB.find(EBasicClobNoVer.class).setId(entity.getId()); defaultQuery.findOne(); String sql = sqlOf(defaultQuery, 2); - Assert.assertTrue("Clob is fetch lazy by default", sql.contains(sqlNoClob)); + // default SQL select excludes clob + String sqlNoClob = "select t0.id, t0.name from ebasic_clob_no_ver t0 where t0.id = ?"; + assertThat(sql).contains(sqlNoClob); // Explicitly select * including Clob - Query explicitQuery = Ebean.find(EBasicClobNoVer.class).setId(entity.getId()).select("*"); + Query explicitQuery = DB.find(EBasicClobNoVer.class).setId(entity.getId()).select("*"); explicitQuery.findOne(); sql = sqlOf(explicitQuery, 2); - Assert.assertTrue("Explicitly include Clob", sql.contains(sqlWithClob)); + // Explicitly include Clob + String sqlWithClob = "select t0.id, t0.name, t0.description from ebasic_clob_no_ver t0 where t0.id = ?"; + assertThat(sql).contains(sqlWithClob); // Update description to test refresh EBasicClobNoVer updateBean = new EBasicClobNoVer(); updateBean.setId(entity.getId()); updateBean.setDescription("modified"); - Ebean.update(updateBean); + DB.update(updateBean); // Test refresh function - Assert.assertEquals("initialClobValue", entity.getDescription()); + assertThat(entity.getDescription()).isEqualTo("initialClobValue"); LoggedSqlCollector.start(); // Refresh query includes all properties - server.refresh(entity); + DB.refresh(entity); // Assert all properties fetched in refresh List loggedSql = LoggedSqlCollector.stop(); - Assert.assertEquals(1, loggedSql.size()); + assertThat(loggedSql).hasSize(1); assertThat(trimSql(loggedSql.get(0), 2)).contains(sqlWithClob); - Assert.assertEquals("modified", entity.getDescription()); - + assertThat(entity.getDescription()).isEqualTo("modified"); } } diff --git a/ebean-querybean/pom.xml b/ebean-querybean/pom.xml index 2fe2570a4..0bdeeb257 100644 --- a/ebean-querybean/pom.xml +++ b/ebean-querybean/pom.xml @@ -6,16 +6,6 @@ io.ebean 12.7.1-SNAPSHOT - - - - - - - - scm:git:git@github.com:ebean-orm/ebean.git - ebean-parent-12.6.5 - ebean querybean Ebean querybean support @@ -78,6 +68,13 @@ test + + io.ebean + ebean-test + 12.7.1-SNAPSHOT + test + + org.avaje.composite junit diff --git a/ebean-querybean/src/main/java/io/ebean/typequery/TQAssocBean.java b/ebean-querybean/src/main/java/io/ebean/typequery/TQAssocBean.java index 9f56b6b13..c54862151 100644 --- a/ebean-querybean/src/main/java/io/ebean/typequery/TQAssocBean.java +++ b/ebean-querybean/src/main/java/io/ebean/typequery/TQAssocBean.java @@ -1,6 +1,11 @@ package io.ebean.typequery; import io.ebean.ExpressionList; +import io.ebean.FetchConfig; +import io.ebeaninternal.api.SpiQueryFetch; + +import java.util.LinkedHashSet; +import java.util.Set; /** * Base type for associated beans. @@ -11,6 +16,11 @@ import io.ebean.ExpressionList; @SuppressWarnings("rawtypes") public abstract class TQAssocBean extends TQProperty { + private static final FetchConfig FETCH_DEFAULT = FetchConfig.ofDefault(); + private static final FetchConfig FETCH_QUERY = FetchConfig.ofQuery(); + private static final FetchConfig FETCH_LAZY = FetchConfig.ofLazy(); + private static final FetchConfig FETCH_CACHE = FetchConfig.ofCache(); + /** * Construct with a property name and root instance. * @@ -88,9 +98,8 @@ public abstract class TQAssocBean extends TQProperty { /** * Deprecated in favor of fetch(). - * - * @deprecated */ + @Deprecated public R fetchAll() { return fetch(); } @@ -100,8 +109,7 @@ public abstract class TQAssocBean extends TQProperty { */ @SafeVarargs protected final R fetchProperties(TQProperty... props) { - ((TQRootBean) _root).query().fetch(_name, properties(props)); - return _root; + return fetchWithProperties(FETCH_DEFAULT, props); } /** @@ -109,8 +117,7 @@ public abstract class TQAssocBean extends TQProperty { */ @SafeVarargs protected final R fetchQueryProperties(TQProperty... props) { - ((TQRootBean) _root).query().fetchQuery(_name, properties(props)); - return _root; + return fetchWithProperties(FETCH_QUERY, props); } /** @@ -118,8 +125,7 @@ public abstract class TQAssocBean extends TQProperty { */ @SafeVarargs protected final R fetchCacheProperties(TQProperty... props) { - ((TQRootBean) _root).query().fetchCache(_name, properties(props)); - return _root; + return fetchWithProperties(FETCH_CACHE, props); } /** @@ -127,23 +133,26 @@ public abstract class TQAssocBean extends TQProperty { */ @SafeVarargs protected final R fetchLazyProperties(TQProperty... props) { - ((TQRootBean) _root).query().fetchLazy(_name, properties(props)); + return fetchWithProperties(FETCH_LAZY, props); + } + + @SafeVarargs + private final R fetchWithProperties(FetchConfig config, TQProperty... props) { + spiQuery().fetchProperties(_name, properties(props), config); return _root; } - /** - * Append the properties as a comma delimited string. - */ + private final SpiQueryFetch spiQuery() { + return (SpiQueryFetch)((TQRootBean) _root).query(); + } + @SafeVarargs - protected final String properties(TQProperty... props) { - StringBuilder selectProps = new StringBuilder(50); - for (int i = 0; i < props.length; i++) { - if (i > 0) { - selectProps.append(","); - } - selectProps.append(props[i].propertyName()); + private final Set properties(TQProperty... props) { + Set set = new LinkedHashSet<>(); + for (TQProperty prop : props) { + set.add(prop.propertyName()); } - return selectProps.toString(); + return set; } /** diff --git a/ebean-querybean/src/main/java/io/ebean/typequery/TQRootBean.java b/ebean-querybean/src/main/java/io/ebean/typequery/TQRootBean.java index 72876a00d..de8f846a6 100644 --- a/ebean-querybean/src/main/java/io/ebean/typequery/TQRootBean.java +++ b/ebean-querybean/src/main/java/io/ebean/typequery/TQRootBean.java @@ -25,6 +25,7 @@ import io.ebean.search.TextQueryString; import io.ebean.search.TextSimple; import io.ebean.service.SpiFetchGroupQuery; import io.ebean.text.PathProperties; +import io.ebeaninternal.api.SpiQueryFetch; import io.ebeaninternal.server.util.ArrayStack; import javax.annotation.Nonnull; @@ -32,6 +33,7 @@ import javax.annotation.Nullable; import java.sql.Connection; import java.sql.Timestamp; import java.util.Collection; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; @@ -278,22 +280,21 @@ public abstract class TQRootBean { */ @SafeVarargs public final R select(TQProperty... properties) { - StringBuilder selectProps = new StringBuilder(50); - for (int i = 0; i < properties.length; i++) { - if (i > 0) { - selectProps.append(","); - } - selectProps.append(properties[i].propertyName()); - } - query.select(selectProps.toString()); + ((SpiQueryFetch)query).selectProperties(properties(properties)); return root; } + private Set properties(TQProperty[] properties) { + Set props = new LinkedHashSet<>(); + for (TQProperty property : properties) { + props.add(property.propertyName()); + } + return props; + } + /** * Specify a path to load including all its properties. - *

- * The same as {@link #fetch(String, String)} with the fetchProperties as "*". - *

+ * *
{@code
    *
    * List customers =
diff --git a/ebean-querybean/src/test/java/org/querytest/QOrderTest.java b/ebean-querybean/src/test/java/org/querytest/QOrderTest.java
index ab98f0e67..2f470fdb5 100644
--- a/ebean-querybean/src/test/java/org/querytest/QOrderTest.java
+++ b/ebean-querybean/src/test/java/org/querytest/QOrderTest.java
@@ -1,11 +1,17 @@
 package org.querytest;
 
+import io.ebean.DB;
 import io.ebean.FetchGroup;
+import io.ebean.test.LoggedSql;
 import org.example.domain.Order;
 import org.example.domain.query.QCustomer;
 import org.example.domain.query.QOrder;
 import org.junit.Test;
 
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
 public class QOrderTest {
 
   private static final QCustomer cu = QCustomer.alias();
@@ -17,10 +23,14 @@ public class QOrderTest {
     .customer.fetchCache(cu.name, cu.status, cu.registered, cu.comments)
     .buildFetchGroup();
 
+  private static final FetchGroup fg2 = QOrder.forFetchGroup()
+    .select(or.status)
+    .customer.fetch(cu.name)
+    .buildFetchGroup();
+
   @Test
   public void fetchCache() {
 
-
     new QOrder()
       .status.eq(Order.Status.NEW)
       .customer.fetchCache(cu.name, cu.registered)
@@ -35,10 +45,72 @@ public class QOrderTest {
   @Test
   public void viaFetchGraph() {
 
+    DB.getDefault();
+    LoggedSql.start();
+
     new QOrder()
       .status.eq(Order.Status.NEW)
       .select(fg)
       .findList();
+
+
+    final List sql = LoggedSql.stop();
+    assertThat(sql).hasSize(1);
+    assertThat(sql.get(0)).contains("select t0.id, t0.status, t0.ship_date, t0.customer_id from o_order t0 where");
+  }
+
+  @Test
+  public void viaFetchGraph_withJoin() {
+
+    DB.getDefault();
+    LoggedSql.start();
+
+    new QOrder()
+      .status.eq(Order.Status.NEW)
+      .select(fg2)
+      .findList();
+
+
+    final List sql = LoggedSql.stop();
+    assertThat(sql).hasSize(1);
+    assertThat(sql.get(0)).contains("select t0.id, t0.status, t1.id, t1.name from o_order t0 join be_customer t1 on t1.id = t0.customer_id where");
+  }
+
+  @Test
+  public void select_partial() {
+
+    DB.getDefault();
+    LoggedSql.start();
+
+    final QOrder o = QOrder.alias();
+
+    new QOrder()
+      .select(o.status, o.orderDate)
+      .findList();
+
+    final List sql = LoggedSql.stop();
+    assertThat(sql).hasSize(1);
+    assertThat(sql.get(0)).contains("select t0.id, t0.status, t0.order_date from o_order t0");
+  }
+
+  @Test
+  public void fetch_partial() {
+
+    DB.getDefault();
+    LoggedSql.start();
+
+    final QOrder o = QOrder.alias();
+    final QCustomer c = QCustomer.alias();
+
+    new QOrder()
+      .select(o.status)
+      .customer.fetch(c.email, c.name)
+      .findList();
+
+    final List sql = LoggedSql.stop();
+    assertThat(sql).hasSize(1);
+    assertThat(sql.get(0)).contains("select t0.id, t0.status, t1.id, t1.email, t1.name from o_order t0 join be_customer t1 on t1.id = t0.customer_id");
+
   }
 
 }