From 1be30f167100caad994b27408657734511c05fc7 Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Thu, 4 Aug 2016 16:41:50 +1200 Subject: [PATCH] #125 - ENH: Add support for query returning a List of single Attribute type --- .../java/com/avaje/ebean/EbeanServer.java | 34 +++ .../java/com/avaje/ebean/ExpressionList.java | 80 +++++-- src/main/java/com/avaje/ebean/Query.java | 32 +++ .../com/avaje/ebeaninternal/api/SpiQuery.java | 23 ++ .../server/core/DefaultServer.java | 17 ++ .../server/core/OrmQueryEngine.java | 8 + .../server/core/OrmQueryRequest.java | 8 + .../server/core/SpiOrmQueryRequest.java | 5 + .../expression/DefaultExpressionList.java | 5 + .../server/expression/JunctionExpression.java | 5 + .../server/query/CQueryBuilder.java | 31 ++- .../server/query/CQueryEngine.java | 24 +- .../server/query/CQueryFetchBase.java | 208 ++++++++++++++++++ .../server/query/CQueryFetchIds.java | 188 +--------------- .../query/CQueryFetchSingleAttribute.java | 71 ++++++ .../server/query/CQueryPlan.java | 3 + .../server/query/DefaultOrmQueryEngine.java | 6 + .../server/query/SqlTreeBuilder.java | 2 +- .../server/query/SqlTreeNode.java | 7 + .../server/query/SqlTreeNodeBean.java | 5 + .../server/query/SqlTreeNodeExtraJoin.java | 6 + .../query/SqlTreeNodeManyWhereJoin.java | 6 + .../server/querydefn/DefaultOrmQuery.java | 27 +++ .../ebeaninternal/api/TDSpiEbeanServer.java | 5 + .../query/other/TestQuerySingleAttribute.java | 128 +++++++++++ 25 files changed, 716 insertions(+), 218 deletions(-) create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/CQueryFetchBase.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/query/CQueryFetchSingleAttribute.java create mode 100644 src/test/java/com/avaje/tests/query/other/TestQuerySingleAttribute.java diff --git a/src/main/java/com/avaje/ebean/EbeanServer.java b/src/main/java/com/avaje/ebean/EbeanServer.java index 93f1af715..2956b51ce 100644 --- a/src/main/java/com/avaje/ebean/EbeanServer.java +++ b/src/main/java/com/avaje/ebean/EbeanServer.java @@ -988,6 +988,40 @@ public interface EbeanServer { */ Map findMap(Query query, Transaction transaction); + /** + * Execute the query returning a list of values for a single property. + * + *

Example 1:

+ *
{@code
+   *
+   *  List names =
+   *    Ebean.find(Customer.class)
+   *      .select("name")
+   *      .orderBy().asc("name")
+   *      .findSingleAttributeList();
+   *
+   * }
+ * + *

Example 2:

+ *
{@code
+   *
+   *  List names =
+   *    Ebean.find(Customer.class)
+   *      .setDistinct(true)
+   *      .select("name")
+   *      .where().eq("status", Customer.Status.NEW)
+   *      .orderBy().asc("name")
+   *      .setMaxRows(100)
+   *      .findSingleAttributeList();
+   *
+   * }
+ * + * @return the list of values for the selected property + * + * @see Query#findSingleAttributeList() + */ + List findSingleAttributeList(Query query, Transaction transaction); + /** * Execute the query returning at most one entity bean or null (if no matching * bean is found). diff --git a/src/main/java/com/avaje/ebean/ExpressionList.java b/src/main/java/com/avaje/ebean/ExpressionList.java index cd7cf962d..37a034d0b 100644 --- a/src/main/java/com/avaje/ebean/ExpressionList.java +++ b/src/main/java/com/avaje/ebean/ExpressionList.java @@ -31,7 +31,7 @@ import java.util.Set; * more methods than you would initially expect (the ones duplicated from * Query). *

- * + * * @see Query#where() */ public interface ExpressionList { @@ -83,14 +83,14 @@ public interface ExpressionList { /** * Add an orderBy clause to the query. - * + * * @see Query#orderBy(String) */ Query orderBy(String orderBy); /** * Add an orderBy clause to the query. - * + * * @see Query#orderBy(String) */ Query setOrderBy(String orderBy); @@ -164,14 +164,14 @@ public interface ExpressionList { /** * Execute the query returning a list. - * + * * @see Query#findList() */ List findList(); /** * Execute the query returning the list of Id's. - * + * * @see Query#findIds() */ List findIds(); @@ -193,14 +193,14 @@ public interface ExpressionList { /** * Execute the query returning a set. - * + * * @see Query#findSet() */ Set findSet(); /** * Execute the query returning a map. - * + * * @see Query#findMap() */ Map findMap(); @@ -210,6 +210,38 @@ public interface ExpressionList { */ Map findMap(String keyProperty, Class keyType); + /** + * Execute the query returning a list of values for a single property. + * + *

Example 1:

+ *
{@code
+   *
+   *  List names =
+   *    Ebean.find(Customer.class)
+   *      .select("name")
+   *      .orderBy().asc("name")
+   *      .findSingleAttributeList();
+   *
+   * }
+ * + *

Example 2:

+ *
{@code
+   *
+   *  List names =
+   *    Ebean.find(Customer.class)
+   *      .setDistinct(true)
+   *      .select("name")
+   *      .where().eq("status", Customer.Status.NEW)
+   *      .orderBy().asc("name")
+   *      .setMaxRows(100)
+   *      .findSingleAttributeList();
+   *
+   * }
+ * + * @return the list of values for the selected property + */ + List findSingleAttributeList(); + /** * Execute the query returning a single bean or null (if no matching * bean is found). @@ -232,7 +264,7 @@ public interface ExpressionList { * execution status (isDone etc) and get the value (with or without a * timeout). *

- * + * * @return a Future object for the row count query */ FutureRowCount findFutureCount(); @@ -251,7 +283,7 @@ public interface ExpressionList { * execution status (isDone etc) and get the value (with or without a * timeout). *

- * + * * @return a Future object for the list of Id's */ FutureIds findFutureIds(); @@ -263,7 +295,7 @@ public interface ExpressionList { * execution status (isDone etc) and get the value (with or without a * timeout). *

- * + * * @return a Future object for the list result of the query */ FutureList findFutureList(); @@ -326,7 +358,7 @@ public interface ExpressionList { /** * Specify specific properties to fetch on the main/root bean (aka partial * object). - * + * * @see Query#select(String) */ Query select(String properties); @@ -351,28 +383,28 @@ public interface ExpressionList { /** * Set the first row to fetch. - * + * * @see Query#setFirstRow(int) */ Query setFirstRow(int firstRow); /** * Set the maximum number of rows to fetch. - * + * * @see Query#setMaxRows(int) */ Query setMaxRows(int maxRows); /** * Set the name of the property which values become the key of a map. - * + * * @see Query#setMapKey(String) */ Query setMapKey(String mapKey); /** * Set to true to use the query for executing this query. - * + * * @see Query#setUseCache(boolean) */ Query setUseCache(boolean useCache); @@ -633,7 +665,7 @@ public interface ExpressionList { * To get control over the options you can create an ExampleExpression and set * those options such as case insensitive etc. *

- * + * *
{@code
    *
    * // create an example bean and set the properties
@@ -641,26 +673,26 @@ public interface ExpressionList {
    * Customer example = new Customer();
    * example.setName("Rob%");
    * example.setNotes("%something%");
-   * 
+   *
    * List<Customer> list = Ebean.find(Customer.class).where()
    *     // pass the bean into the where() clause
    *     .exampleLike(example)
    *     // you can add other expressions to the same query
    *     .gt("id", 2).findList();
-   * 
+   *
    * }
- * + * * Similarly you can create an ExampleExpression - * + * *
{@code
    *
    * Customer example = new Customer();
    * example.setName("Rob%");
    * example.setNotes("%something%");
-   * 
+   *
    * // create a ExampleExpression with more control
    * ExampleExpression qbe = new ExampleExpression(example, true, LikeType.EQUAL_TO).includeZeros();
-   * 
+   *
    * List list = Ebean.find(Customer.class).where().add(qbe).findList();
    *
    * }
@@ -762,7 +794,7 @@ public interface ExpressionList { * Exists expression */ ExpressionList exists(Query subQuery); - + /** * Not exists expression */ @@ -789,7 +821,7 @@ public interface ExpressionList { * Expression where all the property names in the map are equal to the * corresponding value. *

- * + * * @param propertyMap * a map keyed by property names. */ diff --git a/src/main/java/com/avaje/ebean/Query.java b/src/main/java/com/avaje/ebean/Query.java index fc7ff5ba9..3baee31d8 100644 --- a/src/main/java/com/avaje/ebean/Query.java +++ b/src/main/java/com/avaje/ebean/Query.java @@ -677,6 +677,38 @@ public interface Query { */ Map findMap(String keyProperty, Class keyType); + /** + * Execute the query returning a list of values for a single property. + * + *

Example 1:

+ *
{@code
+   *
+   *  List names =
+   *    Ebean.find(Customer.class)
+   *      .select("name")
+   *      .orderBy().asc("name")
+   *      .findSingleAttributeList();
+   *
+   * }
+ * + *

Example 2:

+ *
{@code
+   *
+   *  List names =
+   *    Ebean.find(Customer.class)
+   *      .setDistinct(true)
+   *      .select("name")
+   *      .where().eq("status", Customer.Status.NEW)
+   *      .orderBy().asc("name")
+   *      .setMaxRows(100)
+   *      .findSingleAttributeList();
+   *
+   * }
+ * + * @return the list of values for the selected property + */ +
List findSingleAttributeList(); + /** * Execute the query returning either a single bean or null (if no matching * bean is found). diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java index 6ca8ca878..7c9c10c62 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java @@ -78,6 +78,11 @@ public interface SpiQuery extends Query { */ ID_LIST, + /** + * Find single attribute. + */ + ATTRIBUTE, + /** * Find rowCount. */ @@ -341,6 +346,24 @@ public interface SpiQuery extends Query { */ void setSelectId(); + /** + * Mark the query as selecting a single attribute. + */ + void setSingleAttribute(); + + /** + * Return true if this is singleAttribute query. + */ + boolean isSingleAttribute(); + + /** + * Return true if the query should include the Id property. + *

+ * distinct and single attribute queries exclude the Id property. + *

+ */ + boolean isWithId(); + /** * Set a filter to a join path. */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java index 75f91947e..e6f062dcd 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java @@ -1152,6 +1152,23 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } } + @Override + public
List findSingleAttributeList(Query query, Transaction t) { + + SpiOrmQueryRequest request = createQueryRequest(Type.ATTRIBUTE, query, t); + Object result = request.getFromQueryCache(); + if (result != null) { + return (List) result; + } + try { + request.initTransIfRequired(); + return (List) request.findSingleAttributeList(); + + } finally { + request.endTransIfRequired(); + } + } + public int findCount(Query query, Transaction t) { SpiQuery copy = ((SpiQuery) query).copy(); diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryEngine.java b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryEngine.java index 02464b5bc..a51b3a5b2 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryEngine.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryEngine.java @@ -21,6 +21,14 @@ public interface OrmQueryEngine { */ BeanCollection findMany(OrmQueryRequest request); + /** + * Execute the findSingleAttributeList query. + */ + List findSingleAttributeList(OrmQueryRequest request); + + /** + * Execute the findVersions query. + */ List> findVersions(OrmQueryRequest request); /** diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java index 0aa5a472b..e3d8a2126 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java @@ -373,6 +373,14 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe return (Map) queryEngine.findMany(this); } + /** + * Execute the findSingleAttributeList query. + */ + @Override + public List findSingleAttributeList() { + return queryEngine.findSingleAttributeList(this); + } + /** * Return a bean specific finder if one has been set. */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/SpiOrmQueryRequest.java b/src/main/java/com/avaje/ebeaninternal/server/core/SpiOrmQueryRequest.java index b5f22603a..ae0013f1a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/SpiOrmQueryRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/SpiOrmQueryRequest.java @@ -107,6 +107,11 @@ public interface SpiOrmQueryRequest extends DocQueryRequest { */ Map findMap(); + /** + * Execute the findSingleAttributeList query. + */ + List findSingleAttributeList(); + /** * Try to get the query result from the query cache. */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionList.java b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionList.java index 1d84c4029..83f1198b6 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionList.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionList.java @@ -388,6 +388,11 @@ public class DefaultExpressionList implements SpiExpressionList { return query.findMap(keyProperty, keyType); } + @Override + public List findSingleAttributeList() { + return query.findSingleAttributeList(); + } + @Override public T findUnique() { return query.findUnique(); diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java index e6bb77918..6db932350 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java @@ -400,6 +400,11 @@ class JunctionExpression implements SpiJunction, SpiExpression, Expression return exprList.findMap(keyProperty, keyType); } + @Override + public List findSingleAttributeList() { + return exprList.findSingleAttributeList(); + } + @Override public PagedList findPagedList() { return exprList.findPagedList(); 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 846c06ffe..73036ac06 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java @@ -165,13 +165,35 @@ public class CQueryBuilder { return StringHelper.replaceString(sql, "${RTA}", replaceWith); } + public CQueryFetchSingleAttribute buildFetchAttributeQuery(OrmQueryRequest request) { + + SpiQuery query = request.getQuery(); + query.setSingleAttribute(); + + CQueryPredicates predicates = new CQueryPredicates(binder, request); + CQueryPlan queryPlan = request.getQueryPlan(); + if (queryPlan != null) { + predicates.prepare(false); + return new CQueryFetchSingleAttribute(request, predicates, queryPlan); + } + + // use RawSql or generated Sql + predicates.prepare(true); + + SqlTree sqlTree = createSqlTree(request, predicates, getHistorySupport(query), getDraftSupport(query)); + SqlLimitResponse s = buildSql(null, request, predicates, sqlTree); + + queryPlan = new CQueryPlan(request, s.getSql(), sqlTree, false, s.isIncludesRowNumberColumn(), predicates.getLogWhereSql()); + request.putQueryPlan(queryPlan); + return new CQueryFetchSingleAttribute(request, predicates, queryPlan); + } + /** * Build the row count query. */ public CQueryFetchIds buildFetchIdsQuery(OrmQueryRequest request) { SpiQuery query = request.getQuery(); - query.setSelectId(); CQueryPredicates predicates = new CQueryPredicates(binder, request); @@ -179,8 +201,7 @@ public class CQueryBuilder { if (queryPlan != null) { // skip building the SqlTree and Sql string predicates.prepare(false); - String sql = queryPlan.getSql(); - return new CQueryFetchIds(request, predicates, sql); + return new CQueryFetchIds(request, predicates, queryPlan.getSql()); } // use RawSql or generated Sql @@ -450,8 +471,8 @@ public class CQueryBuilder { } sb.append(select.getSelectSql()); - if (query.isDistinctQuery() && dbOrderBy != null) { - // add the orderby columns to the select clause (due to distinct) + if (query.isDistinctQuery() && dbOrderBy != null && !query.isSingleAttribute()) { + // add the orderBy columns to the select clause (due to distinct) sb.append(", ").append(convertDbOrderByForSelect(dbOrderBy)); } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryEngine.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryEngine.java index 0818ca8ca..bc105a514 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryEngine.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryEngine.java @@ -88,6 +88,27 @@ public class CQueryEngine { } } + /** + * Build and execute the findSingleAttributeList query. + */ + public List findSingleAttributeList(OrmQueryRequest request) { + + CQueryFetchSingleAttribute rcQuery = queryBuilder.buildFetchAttributeQuery(request); + try { + List list = rcQuery.findList(); + if (request.isLogSql()) { + logGeneratedSql(request, rcQuery.getGeneratedSql(), rcQuery.getBindLog()); + } + if (request.isLogSummary()) { + request.getTransaction().logSummary(rcQuery.getSummary()); + } + return list; + + } catch (SQLException e) { + throw CQuery.createPersistenceException(e, request.getTransaction(), rcQuery.getBindLog(), rcQuery.getGeneratedSql()); + } + } + /** * Build and execute the find Id's query. */ @@ -97,15 +118,12 @@ public class CQueryEngine { try { BeanIdList list = rcQuery.findIds(); - if (request.isLogSql()) { logGeneratedSql(request, rcQuery.getGeneratedSql(), rcQuery.getBindLog()); } - if (request.isLogSummary()) { request.getTransaction().logSummary(rcQuery.getSummary()); } - if (request.getQuery().isFutureFetch()) { // end the transaction for futureFindIds (it had it's own one) logger.debug("Future findIds completed!"); diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryFetchBase.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryFetchBase.java new file mode 100644 index 000000000..b25813bfc --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryFetchBase.java @@ -0,0 +1,208 @@ +package com.avaje.ebeaninternal.server.query; + +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.bean.EntityBeanIntercept; +import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.api.SpiQuery.Mode; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.deploy.DbReadContext; +import com.avaje.ebeaninternal.server.type.DataReader; +import com.avaje.ebeaninternal.server.type.RsetDataReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Map; + +/** + * Base compiled query request for single attribute queries. + */ +public abstract class CQueryFetchBase { + + private static final Logger logger = LoggerFactory.getLogger(CQueryFetchBase.class); + + /** + * The overall find request wrapper object. + */ + protected final OrmQueryRequest request; + + protected final BeanDescriptor desc; + + protected final SpiQuery query; + + /** + * Where clause predicates. + */ + protected final CQueryPredicates predicates; + + /** + * The final sql that is generated. + */ + protected final String sql; + + protected RsetDataReader dataReader; + + /** + * The statement used to create the resultSet. + */ + protected PreparedStatement pstmt; + + protected String bindLog; + + protected int executionTimeMicros; + + protected int rowCount; + + protected final int maxRows; + + /** + * Create the Sql select based on the request. + */ + public CQueryFetchBase(OrmQueryRequest request, CQueryPredicates predicates, String sql) { + + this.request = request; + this.query = request.getQuery(); + this.sql = sql; + this.maxRows = query.getMaxRows(); + + query.setGeneratedSql(sql); + + this.desc = request.getBeanDescriptor(); + this.predicates = predicates; + } + + /** + * Return the bind log. + */ + public String getBindLog() { + return bindLog; + } + + /** + * Return the generated sql. + */ + public String getGeneratedSql() { + return sql; + } + + protected ResultSet prepareExecute() throws SQLException { + + SpiTransaction t = request.getTransaction(); + Connection conn = t.getInternalConnection(); + pstmt = conn.prepareStatement(sql); + + if (query.getBufferFetchSizeHint() > 0) { + pstmt.setFetchSize(query.getBufferFetchSizeHint()); + } + if (query.getTimeout() > 0) { + pstmt.setQueryTimeout(query.getTimeout()); + } + + bindLog = predicates.bind(pstmt, conn); + + ResultSet rset = pstmt.executeQuery(); + dataReader = new RsetDataReader(request.getDataTimeZone(), rset); + return rset; + } + + /** + * Close the resources. + *

+ * The jdbc resultSet and statement need to be closed. Its important that + * this method is called. + *

+ */ + protected void close() { + try { + if (dataReader != null) { + dataReader.close(); + dataReader = null; + } + } catch (SQLException e) { + logger.error("Error closing DataReader", e); + } + try { + if (pstmt != null) { + pstmt.close(); + pstmt = null; + } + } catch (SQLException e) { + logger.error("Error closing PreparedStatement", e); + } + } + + + protected class DbContext implements DbReadContext { + + public void propagateState(Object e) { + throw new RuntimeException("Not Called"); + } + + public Mode getQueryMode() { + return Mode.NORMAL; + } + + public DataReader getDataReader() { + return dataReader; + } + + public Boolean isReadOnly() { + return Boolean.FALSE; + } + + @Override + public boolean isDisableLazyLoading() { + return false; + } + + public boolean isRawSql() { + return false; + } + + public void register(String path, EntityBeanIntercept ebi) { + } + + public void register(String path, BeanCollection bc) { + } + + public BeanPropertyAssocMany getManyProperty() { + // always null + return null; + } + + public PersistenceContext getPersistenceContext() { + // always null + return null; + } + + public boolean isAutoTuneProfiling() { + return false; + } + + public void profileBean(EntityBeanIntercept ebi, String prefix) { + // no-op + } + + public void setCurrentPrefix(String currentPrefix, Map pathMap) { + // no-op + } + + public void setLazyLoadedChildBean(EntityBean loadedBean, Object lazyLoadParentId) { + // no-op + } + + @Override + public boolean isDraftQuery() { + return false; + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryFetchIds.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryFetchIds.java index 7000e6558..96ecbc8ef 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryFetchIds.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryFetchIds.java @@ -1,86 +1,25 @@ package com.avaje.ebeaninternal.server.query; -import com.avaje.ebean.bean.BeanCollection; -import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebean.bean.EntityBeanIntercept; -import com.avaje.ebean.bean.PersistenceContext; import com.avaje.ebeaninternal.api.BeanIdList; -import com.avaje.ebeaninternal.api.SpiQuery; -import com.avaje.ebeaninternal.api.SpiQuery.Mode; -import com.avaje.ebeaninternal.api.SpiTransaction; import com.avaje.ebeaninternal.server.core.OrmQueryRequest; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; import com.avaje.ebeaninternal.server.deploy.DbReadContext; -import com.avaje.ebeaninternal.server.type.DataReader; -import com.avaje.ebeaninternal.server.type.RsetDataReader; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import java.sql.Connection; -import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Map; /** * Executes the select row count query. */ -public class CQueryFetchIds { - - private static final Logger logger = LoggerFactory.getLogger(CQueryFetchIds.class); - - /** - * The overall find request wrapper object. - */ - private final OrmQueryRequest request; - - private final BeanDescriptor desc; - - private final SpiQuery query; - - /** - * Where clause predicates. - */ - private final CQueryPredicates predicates; - - /** - * The final sql that is generated. - */ - private final String sql; - - private RsetDataReader dataReader; - - /** - * The statement used to create the resultSet. - */ - private PreparedStatement pstmt; - - private String bindLog; - - private int executionTimeMicros; - - private int rowCount; - - private final int maxRows; +public class CQueryFetchIds extends CQueryFetchBase { /** * Create the Sql select based on the request. */ public CQueryFetchIds(OrmQueryRequest request, CQueryPredicates predicates, String sql) { - - this.request = request; - this.query = request.getQuery(); - this.sql = sql; - this.maxRows = query.getMaxRows(); - - query.setGeneratedSql(sql); - - this.desc = request.getBeanDescriptor(); - this.predicates = predicates; + super(request, predicates, sql); } /** @@ -97,20 +36,6 @@ public class CQueryFetchIds { return sb.toString(); } - /** - * Return the bind log. - */ - public String getBindLog() { - return bindLog; - } - - /** - * Return the generated sql. - */ - public String getGeneratedSql() { - return sql; - } - /** * Execute the query returning the row count. */ @@ -131,22 +56,7 @@ public class CQueryFetchIds { BeanIdList result = new BeanIdList(idList); - SpiTransaction t = request.getTransaction(); - Connection conn = t.getInternalConnection(); - pstmt = conn.prepareStatement(sql); - - if (query.getBufferFetchSizeHint() > 0) { - pstmt.setFetchSize(query.getBufferFetchSizeHint()); - } - - if (query.getTimeout() > 0) { - pstmt.setQueryTimeout(query.getTimeout()); - } - - bindLog = predicates.bind(pstmt, conn); - - ResultSet rset = pstmt.executeQuery(); - dataReader = new RsetDataReader(request.getDataTimeZone(), rset); + ResultSet rset = prepareExecute(); boolean hitMaxRows = false; boolean hasMoreRows = false; @@ -183,96 +93,4 @@ public class CQueryFetchIds { } } - /** - * Close the resources. - *

- * The jdbc resultSet and statement need to be closed. Its important that - * this method is called. - *

- */ - private void close() { - try { - if (dataReader != null) { - dataReader.close(); - dataReader = null; - } - } catch (SQLException e) { - logger.error("Error closing DataReader", e); - } - try { - if (pstmt != null) { - pstmt.close(); - pstmt = null; - } - } catch (SQLException e) { - logger.error("Error closing PreparedStatement", e); - } - } - - - class DbContext implements DbReadContext { - - public void propagateState(Object e) { - throw new RuntimeException("Not Called"); - } - - public Mode getQueryMode() { - return Mode.NORMAL; - } - - public DataReader getDataReader() { - return dataReader; - } - - public Boolean isReadOnly() { - return Boolean.FALSE; - } - - @Override - public boolean isDisableLazyLoading() { - return false; - } - - public boolean isRawSql() { - return false; - } - - public void register(String path, EntityBeanIntercept ebi) { - } - - public void register(String path, BeanCollection bc) { - } - - public BeanPropertyAssocMany getManyProperty() { - // always null - return null; - } - - public PersistenceContext getPersistenceContext() { - // always null - return null; - } - - public boolean isAutoTuneProfiling() { - return false; - } - - public void profileBean(EntityBeanIntercept ebi, String prefix) { - // no-op - } - - public void setCurrentPrefix(String currentPrefix, Map pathMap) { - // no-op - } - - public void setLazyLoadedChildBean(EntityBean loadedBean, Object lazyLoadParentId) { - // no-op - } - - @Override - public boolean isDraftQuery() { - return false; - } - } - } diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryFetchSingleAttribute.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryFetchSingleAttribute.java new file mode 100644 index 000000000..324c80cac --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryFetchSingleAttribute.java @@ -0,0 +1,71 @@ +package com.avaje.ebeaninternal.server.query; + +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.type.ScalarType; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +/** + * Executes the select row count query. + */ +public class CQueryFetchSingleAttribute extends CQueryFetchBase { + + private final BeanProperty property; + + private final ScalarType scalarType; + + /** + * Create the Sql select based on the request. + */ + public CQueryFetchSingleAttribute(OrmQueryRequest request, CQueryPredicates predicates, CQueryPlan plan) { + super(request, predicates, plan.getSql()); + this.property = plan.getSingleProperty(); + this.scalarType = property.getScalarType(); + } + + /** + * Return a summary description of this query. + */ + public String getSummary() { + StringBuilder sb = new StringBuilder(80); + sb.append("FindAttr exeMicros[").append(executionTimeMicros) + .append("] rows[").append(rowCount) + .append("] type[").append(desc.getName()) + .append("] predicates[").append(predicates.getLogWhereSql()) + .append("] bind[").append(bindLog).append("]"); + + return sb.toString(); + } + + /** + * Execute the query returning the row count. + */ + public List findList() throws SQLException { + + long startNano = System.nanoTime(); + try { + + List result = new ArrayList(); + + ResultSet rset = prepareExecute(); + while (rset.next()) { + result.add(scalarType.read(dataReader)); + dataReader.resetColumnPosition(); + rowCount++; + } + + long exeNano = System.nanoTime() - startNano; + executionTimeMicros = (int) exeNano / 1000; + + return result; + + } finally { + close(); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlan.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlan.java index f4f831cef..6c490bf17 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlan.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlan.java @@ -266,4 +266,7 @@ public class CQueryPlan { return stats.getLastQueryTime(); } + public BeanProperty getSingleProperty() { + return sqlTree.getRootNode().getSingleProperty(); + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/DefaultOrmQueryEngine.java b/src/main/java/com/avaje/ebeaninternal/server/query/DefaultOrmQueryEngine.java index 3342637d4..963e72e93 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/DefaultOrmQueryEngine.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/DefaultOrmQueryEngine.java @@ -71,6 +71,12 @@ public class DefaultOrmQueryEngine implements OrmQueryEngine { return queryEngine.findIds(request); } + @Override + public List findSingleAttributeList(OrmQueryRequest request) { + flushJdbcBatchOnQuery(request); + return queryEngine.findSingleAttributeList(request); + } + public QueryIterator findIterate(OrmQueryRequest request) { // LIMITATION: You can not use QueryIterator to load bean cache 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 aef2012fb..835fd97ed 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java @@ -260,7 +260,7 @@ public class SqlTreeBuilder { // Optional many property for lazy loading query BeanPropertyAssocMany lazyLoadMany = (query == null) ? null : query.getLazyLoadMany(); - boolean withId = !rawNoId && !subQuery && (query == null || !query.isDistinct()); + boolean withId = !rawNoId && !subQuery && (query == null || query.isWithId()); return new SqlTreeNodeRoot(desc, props, myList, withId, includeJoin, lazyLoadMany, SpiQuery.TemporalMode.of(query), disableLazyLoad); } else if (prop instanceof BeanPropertyAssocMany) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNode.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNode.java index 9aa8ac951..0313c351c 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNode.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNode.java @@ -3,6 +3,7 @@ package com.avaje.ebeaninternal.server.query; import com.avaje.ebean.Version; import com.avaje.ebean.bean.EntityBean; import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; import com.avaje.ebeaninternal.server.deploy.DbReadContext; import com.avaje.ebeaninternal.server.deploy.DbSqlContext; @@ -62,4 +63,10 @@ public interface SqlTreeNode { * Return true if the query has a many join. */ boolean hasMany(); + + /** + * Return the property for singleAttribute query. + */ + BeanProperty getSingleProperty(); + } diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java index 29d7abe14..64e7894cc 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java @@ -137,6 +137,11 @@ public class SqlTreeNodeBean implements SqlTreeNode { pathMap = createPathMap(prefix, desc); } + @Override + public BeanProperty getSingleProperty() { + return properties[0]; + } + private Map createPathMap(String prefix, BeanDescriptor desc) { BeanPropertyAssocMany[] manys = desc.propertiesMany(); diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java index ae5862267..2a7fd0cfb 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java @@ -3,6 +3,7 @@ package com.avaje.ebeaninternal.server.query; import com.avaje.ebean.Version; import com.avaje.ebean.bean.EntityBean; import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc; import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; import com.avaje.ebeaninternal.server.deploy.DbReadContext; @@ -55,6 +56,11 @@ public class SqlTreeNodeExtraJoin implements SqlTreeNode { // nothing to do here } + @Override + public BeanProperty getSingleProperty() { + throw new IllegalStateException("No expected"); + } + /** * Return true if the extra join is a many join. *

diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java index 46c793428..b3c390761 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java @@ -3,6 +3,7 @@ package com.avaje.ebeaninternal.server.query; import com.avaje.ebean.Version; import com.avaje.ebean.bean.EntityBean; import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc; import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; @@ -39,6 +40,11 @@ public class SqlTreeNodeManyWhereJoin implements SqlTreeNode { this.parentPrefix = split[0]; } + @Override + public BeanProperty getSingleProperty() { + throw new IllegalStateException("No expected"); + } + @Override public void addAsOfTableAlias(SpiQuery query) { // do nothing here ... 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 24bdea098..b3d3622db 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -192,6 +192,8 @@ public class DefaultOrmQuery implements SpiQuery { */ private boolean forUpdate; + private boolean singleAttribute; + /** * Set to true if this query has been tuned by autoTune. */ @@ -534,6 +536,26 @@ public class DefaultOrmQuery implements SpiQuery { select(beanDescriptor.getIdBinder().getIdProperty()); } + @Override + public void setSingleAttribute() { + this.singleAttribute = true; + } + + /** + * Return true if this is a single attribute query. + */ + public boolean isSingleAttribute() { + return singleAttribute; + } + + /** + * Return true if the Id should be included in the query. + */ + @Override + public boolean isWithId() { + return !distinct && !singleAttribute; + } + @Override public NaturalKeyBindParam getNaturalKeyBindParam() { NaturalKeyBindParam namedBind = null; @@ -1133,6 +1155,11 @@ public class DefaultOrmQuery implements SpiQuery { return (Map) findMap(); } + @Override + public List findSingleAttributeList() { + return (List)server.findSingleAttributeList(this, null); + } + @Override public T findUnique() { return server.findUnique(this, null); diff --git a/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java b/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java index 345a4472f..030abb658 100644 --- a/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java +++ b/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java @@ -520,6 +520,11 @@ public class TDSpiEbeanServer implements SpiEbeanServer { return null; } + @Override + public List findSingleAttributeList(Query query, Transaction transaction) { + return null; + } + @Override public T findUnique(Query query, Transaction transaction) { return null; diff --git a/src/test/java/com/avaje/tests/query/other/TestQuerySingleAttribute.java b/src/test/java/com/avaje/tests/query/other/TestQuerySingleAttribute.java new file mode 100644 index 000000000..7fddbceb3 --- /dev/null +++ b/src/test/java/com/avaje/tests/query/other/TestQuerySingleAttribute.java @@ -0,0 +1,128 @@ +package com.avaje.tests.query.other; + +import com.avaje.ebean.BaseTestCase; +import com.avaje.ebean.Ebean; +import com.avaje.ebean.Query; +import com.avaje.tests.model.basic.Customer; +import com.avaje.tests.model.basic.ResetBasicData; +import org.junit.Test; + +import java.sql.Date; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TestQuerySingleAttribute extends BaseTestCase { + + + @Test + public void exampleUsage() { + + ResetBasicData.reset(); + + List names = + Ebean.find(Customer.class) + .setDistinct(true) + .select("name") + .where().eq("status", Customer.Status.NEW) + .orderBy().asc("name") + .setMaxRows(100) + .findSingleAttributeList(); + + assertThat(names).isNotNull(); + } + + @Test + public void exampleUsage_otherType() { + + ResetBasicData.reset(); + + List dates = + Ebean.find(Customer.class) + .setDistinct(true) + .select("anniversary") + .where().isNotNull("anniversary") + .orderBy().asc("anniversary") + .findSingleAttributeList(); + + assertThat(dates).isNotNull(); + } + + @Test + public void withOrderBy() { + + Query query = + Ebean.find(Customer.class) + .setDistinct(true) + .select("name") + .where().eq("status", Customer.Status.NEW) + .orderBy().asc("name") + .setMaxRows(100); + + query.findSingleAttributeList(); + assertThat(query.getGeneratedSql()).contains("select distinct t0.name c0 from o_customer t0 where t0.status = ? order by t0.name "); + } + + @Test + public void basic() { + + ResetBasicData.reset(); + + Query query = Ebean.find(Customer.class).select("name"); + + List names = query.findSingleAttributeList();//String.class); + + assertThat(query.getGeneratedSql()).contains("select t0.name c0 from o_customer t0"); + assertThat(names).isNotNull(); + } + + @Test + public void distinctAndWhere() { + + ResetBasicData.reset(); + + Query query = Ebean.find(Customer.class) + .setDistinct(true) + .select("name") + .where().eq("status", Customer.Status.NEW) + .query(); + + List names = query.findSingleAttributeList(); + + assertThat(query.getGeneratedSql()).contains("select distinct t0.name c0 from o_customer t0 where t0.status = ? "); + assertThat(names).isNotNull(); + } + + @Test + public void distinctWhereWithJoin() { + + ResetBasicData.reset(); + + Query query = Ebean.find(Customer.class) + .setDistinct(true) + .select("name") + .where().eq("status", Customer.Status.NEW) + .istartsWith("billingAddress.city", "auck") + .query(); + + List names = query.findSingleAttributeList(); + + assertThat(query.getGeneratedSql()).contains("select distinct t0.name c0 from o_customer t0 left outer join o_address t1 on t1.id = t0.billing_address_id where t0.status = ? and lower(t1.city) like ?"); + assertThat(names).isNotNull(); + } + + + @Test + public void queryPlan_expect_differentPlans() { + + ResetBasicData.reset(); + + Query query = Ebean.find(Customer.class).select("name"); + query.findSingleAttributeList(); + assertThat(query.getGeneratedSql()).contains("select t0.name c0 from o_customer t0"); + + Query query2 = Ebean.find(Customer.class).select("name"); + query2.findList(); + assertThat(query2.getGeneratedSql()).contains("select t0.id c0, t0.name c1 from o_customer t0"); + } +}