From e3cccfa9a35ac4e8738acf1bd6e2ead3a4989576 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Sat, 9 May 2015 01:10:56 +1200 Subject: [PATCH] No effective change - change newline char --- .../server/query/BaseFuture.java | 92 +- .../server/query/BeanCollectionWrapper.java | 404 ++--- .../ebeaninternal/server/query/CQuery.java | 1602 ++++++++--------- .../server/query/CQueryBuilder.java | 822 ++++----- .../server/query/CQueryBuilderRawSql.java | 296 +-- .../server/query/CQueryEngine.java | 688 +++---- .../server/query/CQueryFetchIds.java | 566 +++--- .../server/query/CQueryIteratorSimple.java | 90 +- .../query/CQueryIteratorWithBuffer.java | 134 +- .../server/query/CQueryOrderBy.java | 154 +- .../server/query/CQueryPlanRawSql.java | 108 +- .../server/query/CQueryPredicates.java | 1150 ++++++------ .../server/query/CQueryRowCount.java | 346 ++-- .../server/query/CallableQuery.java | 72 +- .../server/query/CallableQueryIds.java | 72 +- .../server/query/CallableQueryList.java | 72 +- .../server/query/CallableQueryRowCount.java | 74 +- .../server/query/CallableSqlQueryList.java | 82 +- .../server/query/CancelableQuery.java | 38 +- .../ebeaninternal/server/query/Constants.java | 54 +- .../server/query/DefaultOrmQueryEngine.java | 250 +-- .../query/DefaultRelationalQueryEngine.java | 462 ++--- .../server/query/DefaultSqlRow.java | 414 ++--- .../server/query/LimitOffsetPagedList.java | 244 +-- .../server/query/QueryFutureIds.java | 86 +- .../server/query/QueryFutureList.java | 136 +- .../server/query/QueryFutureRowCount.java | 78 +- .../query/RawSqlSelectClauseBuilder.java | 198 +- .../server/query/SqlBeanLoad.java | 196 +- .../ebeaninternal/server/query/SqlTree.java | 310 ++-- .../server/query/SqlTreeBuilder.java | 1332 +++++++------- .../server/query/SqlTreeNodeBean.java | 1050 +++++------ .../query/SqlTreeNodeManyWhereJoin.java | 214 +-- .../server/query/SqlTreeProperties.java | 196 +- 34 files changed, 6041 insertions(+), 6041 deletions(-) diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/BaseFuture.java b/src/main/java/com/avaje/ebeaninternal/server/query/BaseFuture.java index a0854cae7..d8641fb8d 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/BaseFuture.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/BaseFuture.java @@ -1,46 +1,46 @@ -package com.avaje.ebeaninternal.server.query; - -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.FutureTask; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -/** - * A base object for query Future objects. - * - * @author rbygrave - * - * @param the entity bean type - */ -public abstract class BaseFuture implements Future { - - protected final FutureTask futureTask; - - public BaseFuture(FutureTask futureTask) { - this.futureTask = futureTask; - } - - public boolean cancel(boolean mayInterruptIfRunning) { - return futureTask.cancel(mayInterruptIfRunning); - } - - public T get() throws InterruptedException, ExecutionException { - return futureTask.get(); - } - - public T get(long timeout, TimeUnit unit) - throws InterruptedException, ExecutionException, TimeoutException { - - return futureTask.get(timeout, unit); - } - - public boolean isCancelled() { - return futureTask.isCancelled(); - } - - public boolean isDone() { - return futureTask.isDone(); - } - -} +package com.avaje.ebeaninternal.server.query; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * A base object for query Future objects. + * + * @author rbygrave + * + * @param the entity bean type + */ +public abstract class BaseFuture implements Future { + + protected final FutureTask futureTask; + + public BaseFuture(FutureTask futureTask) { + this.futureTask = futureTask; + } + + public boolean cancel(boolean mayInterruptIfRunning) { + return futureTask.cancel(mayInterruptIfRunning); + } + + public T get() throws InterruptedException, ExecutionException { + return futureTask.get(); + } + + public T get(long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + + return futureTask.get(timeout, unit); + } + + public boolean isCancelled() { + return futureTask.isCancelled(); + } + + public boolean isDone() { + return futureTask.isDone(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/BeanCollectionWrapper.java b/src/main/java/com/avaje/ebeaninternal/server/query/BeanCollectionWrapper.java index 9baf86501..e024ee53f 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/BeanCollectionWrapper.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/BeanCollectionWrapper.java @@ -1,202 +1,202 @@ -package com.avaje.ebeaninternal.server.query; - -import java.util.Collection; -import java.util.Map; - -import com.avaje.ebean.bean.BeanCollection; -import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebeaninternal.api.SpiQuery; -import com.avaje.ebeaninternal.server.core.OrmQueryRequest; -import com.avaje.ebeaninternal.server.core.RelationalQueryRequest; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; -import com.avaje.ebeaninternal.server.util.BeanCollectionFactory; -import com.avaje.ebeaninternal.server.util.BeanCollectionParams; - -/** - * Wraps a BeanCollection with helper methods to add beans. - *

- * Helps adding the bean to the underlying set list or map. - *

- */ -public final class BeanCollectionWrapper { - - /** - * Flag set if this builds a Map rather than a Collection. - */ - private final boolean isMap; - - /** - * The type. - */ - private final SpiQuery.Type queryType; - - /** - * A property name used as key for a Map. - */ - private final String mapKey; - - /** - * The actual BeanCollection. - */ - private final BeanCollection beanCollection; - - /** - * Collection type of BeanCollection. - */ - private final Collection collection; - - /** - * Map type of BeanCollection. - */ - private final Map map; - - /** - * The associated BeanDescriptor. - */ - private final BeanDescriptor desc; - - /** - * The number of rows added. - */ - private int rowCount; - - public BeanCollectionWrapper(RelationalQueryRequest request) { - - this.desc = null; - this.queryType = request.getQueryType(); - this.mapKey = request.getQuery().getMapKey(); - this.isMap = SpiQuery.Type.MAP.equals(queryType); - - this.beanCollection = createBeanCollection(queryType); - this.collection = getCollection(isMap); - this.map = getMap(isMap); - } - - /** - * Create based on a Find. - */ - public BeanCollectionWrapper(OrmQueryRequest request) { - - this.desc = request.getBeanDescriptor(); - this.queryType = request.getQueryType(); - this.mapKey = request.getQuery().getMapKey(); - this.isMap = SpiQuery.Type.MAP.equals(queryType); - - this.beanCollection = createBeanCollection(queryType); - this.collection = getCollection(isMap); - this.map = getMap(isMap); - } - - /** - * Create based on a ManyType and mapKey. Note the mapKey is only used if - * the manyType is a Map. - *

- * modifyListening is set to true if this is a collection used to hold - * ManyToMany associated objects. - *

- */ - public BeanCollectionWrapper(BeanPropertyAssocMany manyProp) { - - this.queryType = manyProp.getManyType().getQueryType(); - this.mapKey = manyProp.getMapKey(); - this.desc = manyProp.getTargetDescriptor(); - this.isMap = SpiQuery.Type.MAP.equals(queryType); - - this.beanCollection = createBeanCollection(queryType); - this.collection = getCollection(isMap); - this.map = getMap(isMap); - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - private Map getMap(boolean isMap) { - return isMap ? (Map)beanCollection : null; - } - - @SuppressWarnings("unchecked") - private Collection getCollection(boolean isMap) { - return isMap ? null : (Collection)beanCollection ; - } - - /** - * Return the underlying BeanCollection. - */ - public BeanCollection getBeanCollection() { - return beanCollection; - } - - /** - * Create a BeanCollection of the correct type. - */ - private BeanCollection createBeanCollection(SpiQuery.Type manyType) { - BeanCollectionParams p = new BeanCollectionParams(manyType); - return BeanCollectionFactory.create(p); - } - - /** - * Return true if this wraps a Map rather than a set or list. - */ - public boolean isMap() { - return isMap; - } - - /** - * Return the number of rows added to this wrapper. - */ - public int size() { - return rowCount; - } - - /** - * Add the bean to the collection held in this wrapper. - */ - public void add(EntityBean bean) { - add(bean, beanCollection); - } - - /** - * Add the bean to the collection passed. - * - * @param bean - * the bean to add - * @param collection - * the collection or map to add the bean to - */ - @SuppressWarnings({ "unchecked", "rawtypes" }) - public void add(EntityBean bean, Object collection) { - if (bean == null) { - return; - } - rowCount++; - if (isMap) { - Object keyValue = null; - if (mapKey != null) { - // use the value for the property - keyValue = desc.getValue(bean, mapKey); - } else { - // use the uniqueId for this - keyValue = desc.getId(bean); - } - - Map mapColl = (Map) collection; - mapColl.put(keyValue, bean); - } else { - ((Collection) collection).add(bean); - } - } - - /** - * Specifically add to a Collection. - */ - public void addToCollection(Object bean) { - collection.add(bean); - } - - /** - * Specifically add to this as a Map with a known key. - */ - public void addToMap(Object bean, Object key) { - map.put(key, bean); - } - -} +package com.avaje.ebeaninternal.server.query; + +import java.util.Collection; +import java.util.Map; + +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.core.RelationalQueryRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.util.BeanCollectionFactory; +import com.avaje.ebeaninternal.server.util.BeanCollectionParams; + +/** + * Wraps a BeanCollection with helper methods to add beans. + *

+ * Helps adding the bean to the underlying set list or map. + *

+ */ +public final class BeanCollectionWrapper { + + /** + * Flag set if this builds a Map rather than a Collection. + */ + private final boolean isMap; + + /** + * The type. + */ + private final SpiQuery.Type queryType; + + /** + * A property name used as key for a Map. + */ + private final String mapKey; + + /** + * The actual BeanCollection. + */ + private final BeanCollection beanCollection; + + /** + * Collection type of BeanCollection. + */ + private final Collection collection; + + /** + * Map type of BeanCollection. + */ + private final Map map; + + /** + * The associated BeanDescriptor. + */ + private final BeanDescriptor desc; + + /** + * The number of rows added. + */ + private int rowCount; + + public BeanCollectionWrapper(RelationalQueryRequest request) { + + this.desc = null; + this.queryType = request.getQueryType(); + this.mapKey = request.getQuery().getMapKey(); + this.isMap = SpiQuery.Type.MAP.equals(queryType); + + this.beanCollection = createBeanCollection(queryType); + this.collection = getCollection(isMap); + this.map = getMap(isMap); + } + + /** + * Create based on a Find. + */ + public BeanCollectionWrapper(OrmQueryRequest request) { + + this.desc = request.getBeanDescriptor(); + this.queryType = request.getQueryType(); + this.mapKey = request.getQuery().getMapKey(); + this.isMap = SpiQuery.Type.MAP.equals(queryType); + + this.beanCollection = createBeanCollection(queryType); + this.collection = getCollection(isMap); + this.map = getMap(isMap); + } + + /** + * Create based on a ManyType and mapKey. Note the mapKey is only used if + * the manyType is a Map. + *

+ * modifyListening is set to true if this is a collection used to hold + * ManyToMany associated objects. + *

+ */ + public BeanCollectionWrapper(BeanPropertyAssocMany manyProp) { + + this.queryType = manyProp.getManyType().getQueryType(); + this.mapKey = manyProp.getMapKey(); + this.desc = manyProp.getTargetDescriptor(); + this.isMap = SpiQuery.Type.MAP.equals(queryType); + + this.beanCollection = createBeanCollection(queryType); + this.collection = getCollection(isMap); + this.map = getMap(isMap); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private Map getMap(boolean isMap) { + return isMap ? (Map)beanCollection : null; + } + + @SuppressWarnings("unchecked") + private Collection getCollection(boolean isMap) { + return isMap ? null : (Collection)beanCollection ; + } + + /** + * Return the underlying BeanCollection. + */ + public BeanCollection getBeanCollection() { + return beanCollection; + } + + /** + * Create a BeanCollection of the correct type. + */ + private BeanCollection createBeanCollection(SpiQuery.Type manyType) { + BeanCollectionParams p = new BeanCollectionParams(manyType); + return BeanCollectionFactory.create(p); + } + + /** + * Return true if this wraps a Map rather than a set or list. + */ + public boolean isMap() { + return isMap; + } + + /** + * Return the number of rows added to this wrapper. + */ + public int size() { + return rowCount; + } + + /** + * Add the bean to the collection held in this wrapper. + */ + public void add(EntityBean bean) { + add(bean, beanCollection); + } + + /** + * Add the bean to the collection passed. + * + * @param bean + * the bean to add + * @param collection + * the collection or map to add the bean to + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void add(EntityBean bean, Object collection) { + if (bean == null) { + return; + } + rowCount++; + if (isMap) { + Object keyValue = null; + if (mapKey != null) { + // use the value for the property + keyValue = desc.getValue(bean, mapKey); + } else { + // use the uniqueId for this + keyValue = desc.getId(bean); + } + + Map mapColl = (Map) collection; + mapColl.put(keyValue, bean); + } else { + ((Collection) collection).add(bean); + } + } + + /** + * Specifically add to a Collection. + */ + public void addToCollection(Object bean) { + collection.add(bean); + } + + /** + * Specifically add to this as a Map with a known key. + */ + public void addToMap(Object bean, Object key) { + map.put(key, bean); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java index 6c0855ed1..7c9de5e82 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java @@ -1,801 +1,801 @@ -package com.avaje.ebeaninternal.server.query; - -import com.avaje.ebean.QueryIterator; -import com.avaje.ebean.bean.*; -import com.avaje.ebeaninternal.api.SpiExpressionList; -import com.avaje.ebeaninternal.api.SpiQuery; -import com.avaje.ebeaninternal.api.SpiQuery.Mode; -import com.avaje.ebeaninternal.api.SpiTransaction; -import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager; -import com.avaje.ebeaninternal.server.core.Message; -import com.avaje.ebeaninternal.server.core.OrmQueryRequest; -import com.avaje.ebeaninternal.server.core.SpiOrmQueryRequest; -import com.avaje.ebeaninternal.server.deploy.*; -import com.avaje.ebeaninternal.server.el.ElPropertyValue; -import com.avaje.ebeaninternal.server.lib.util.StringHelper; -import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties; -import com.avaje.ebeaninternal.server.type.DataBind; -import com.avaje.ebeaninternal.server.type.DataReader; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.persistence.PersistenceException; -import java.lang.ref.WeakReference; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -/** - * An object that represents a SqlSelect statement. - *

- * The SqlSelect is based on a tree (Object Graph). The tree is traversed to see - * what parts are included in the tree according to the value of - * find.getInclude(); - *

- *

- * The tree structure is flattened into a SqlSelectChain. The SqlSelectChain is - * the key object used in reading the flat resultSet back into Objects. - *

- */ -public class CQuery implements DbReadContext, CancelableQuery { - - private static final Logger logger = LoggerFactory.getLogger(CQuery.class); - - private static final int GLOBAL_ROW_LIMIT = Integer.valueOf(System.getProperty("ebean.query.globallimit","1000000")); - - /** - * The resultSet rows read. - */ - private int rowCount; - - /** - * The number of master EntityBeans loaded. - */ - private int loadedBeanCount; - - /** - * Flag set when no more rows are in the resultSet. - */ - private boolean noMoreRows; - /** - * Id of loaded 'master' bean. - */ - private Object loadedBeanId; - /** - * Flag set when 'master' bean changed. - */ - private boolean loadedBeanChanged; - - /** - * The 'master' bean just loaded. - */ - private EntityBean loadedBean; - - private final BeanPropertyAssocMany lazyLoadManyProperty; - - private Object lazyLoadParentId; - - private EntityBean lazyLoadParentBean; - - /** - * Holds the previous loaded bean. - */ - private EntityBean prevLoadedBean; - - /** - * The detail bean just loaded. - */ - private EntityBean loadedManyBean; - - /** - * The previous 'detail' collection remembered so that for manyToMany we can - * turn on the modify listening. - */ - private Object prevDetailCollection; - - /** - * The current 'detail' collection being populated. - */ - private Object currentDetailCollection; - - /** - * The 'master' collection being populated. - */ - private final BeanCollection collection; - /** - * The help for the 'master' collection. - */ - private final BeanCollectionHelp help; - - /** - * The overall find request wrapper object. - */ - private final OrmQueryRequest request; - - private final BeanDescriptor desc; - - private final SpiQuery query; - - private Map currentPathMap; - - private String currentPrefix; - - /** - * Flag set true when reading 'master' and 'detail' beans. - */ - private final boolean manyIncluded; - - /** - * Where clause predicates. - */ - private final CQueryPredicates predicates; - - /** - * Object handling the SELECT generation and reading. - */ - private final SqlTree sqlTree; - - private final boolean rawSql; - - /** - * The final sql that is generated. - */ - private final String sql; - - /** - * Where clause to show in logs when using an existing query plan. - */ - private final String logWhereSql; - - /** - * Set to true if the row number column is included in the sql. - */ - private final boolean rowNumberIncluded; - - /** - * Tree that knows how to build the master and detail beans from the - * resultSet. - */ - private final SqlTreeNode rootNode; - - /** - * For master detail query. - */ - private final BeanPropertyAssocMany manyProperty; - - /** - * The many property Expression language object. - */ - private final ElPropertyValue manyPropertyEl; - - private final int maxRowsLimit; - - private DataReader dataReader; - - /** - * The statement used to create the resultSet. - */ - private PreparedStatement pstmt; - - private boolean cancelled; - - private String bindLog; - - private final CQueryPlan queryPlan; - - - private final Mode queryMode; - - private final boolean autoFetchProfiling; - - private final ObjectGraphNode objectGraphNode; - - private final AutoFetchManager autoFetchManager; - - private final WeakReference autoFetchManagerRef; - - - private final Boolean readOnly; - - private final SpiExpressionList filterMany; - - private long startNano; - - private long executionTimeMicros; - - private BeanCollectionAdd currentDetailAdd; - - /** - * Create the Sql select based on the request. - */ - @SuppressWarnings("unchecked") - public CQuery(OrmQueryRequest request, CQueryPredicates predicates, CQueryPlan queryPlan) { - this.request = request; - this.queryPlan = queryPlan; - this.query = request.getQuery(); - this.queryMode = query.getMode(); - this.lazyLoadManyProperty = query.getLazyLoadForParentsProperty(); - - this.readOnly = request.isReadOnly(); - - this.autoFetchManager = query.getAutoFetchManager(); - this.autoFetchProfiling = autoFetchManager != null; - this.objectGraphNode = query.getParentNode(); - this.autoFetchManagerRef = autoFetchProfiling ? new WeakReference( - autoFetchManager) : null; - - // set the generated sql back to the query - // so its available to the user... - query.setGeneratedSql(queryPlan.getSql()); - - this.sqlTree = queryPlan.getSqlTree(); - this.rootNode = sqlTree.getRootNode(); - - this.manyProperty = sqlTree.getManyProperty(); - this.manyPropertyEl = sqlTree.getManyPropertyEl(); - this.manyIncluded = sqlTree.isManyIncluded(); - if (manyIncluded) { - // get filter to put on the collection for reuse with refresh - String manyPropertyName = sqlTree.getManyPropertyName(); - OrmQueryProperties chunk = query.getDetail().getChunk(manyPropertyName, false); - this.filterMany = (chunk == null) ? null : chunk.getFilterMany(); - } else { - this.filterMany = null; - } - - this.sql = queryPlan.getSql(); - this.rawSql = queryPlan.isRawSql(); - this.rowNumberIncluded = queryPlan.isRowNumberIncluded(); - this.logWhereSql = queryPlan.getLogWhereSql(); - this.desc = request.getBeanDescriptor(); - this.predicates = predicates; - this.maxRowsLimit = query.getMaxRows() > 0 ? query.getMaxRows() : GLOBAL_ROW_LIMIT; - this.help = createHelp(request); - this.collection = (help != null ? help.createEmptyNoParent() : null); - } - - private BeanCollectionHelp createHelp(OrmQueryRequest request) { - if (request.isFindById()) { - return null; - } else { - SpiQuery.Type manyType = request.getQuery().getType(); - if (manyType == null) { - // subQuery compiled for InQueryExpression - return null; - } - return BeanCollectionHelpFactory.create(request); - } - } - - public Boolean isReadOnly() { - return readOnly; - } - - public void propagateState(Object e) { - if (Boolean.TRUE.equals(readOnly)) { - if (e instanceof EntityBean) { - ((EntityBean) e)._ebean_getIntercept().setReadOnly(true); - } - } - } - - public DataReader getDataReader() { - return dataReader; - } - - public Mode getQueryMode() { - return queryMode; - } - - public CQueryPredicates getPredicates() { - return predicates; - } - - public SpiOrmQueryRequest getQueryRequest() { - return request; - } - - public void cancel() { - synchronized (this) { - this.cancelled = true; - if (pstmt != null) { - try { - pstmt.cancel(); - } catch (SQLException e) { - String msg = "Error cancelling query"; - throw new PersistenceException(msg, e); - } - } - } - } - - /** - * Prepare bind and execute query with Forward only hints. - */ - public boolean prepareBindExecuteQueryForwardOnly(boolean dbPlatformForwardOnlyHint) throws SQLException { - return prepareBindExecuteQueryWithOption(dbPlatformForwardOnlyHint); - } - - /** - * Prepare bind and execute the query normally. - */ - public boolean prepareBindExecuteQuery() throws SQLException { - return prepareBindExecuteQueryWithOption(false); - } - - private boolean prepareBindExecuteQueryWithOption(boolean forwardOnlyHint) throws SQLException { - - synchronized (this) { - if (cancelled || query.isCancelled()) { - // cancelled before we started - cancelled = true; - return false; - } - - startNano = System.nanoTime(); - - // prepare - SpiTransaction t = request.getTransaction(); - Connection conn = t.getInternalConnection(); - - if (query.isRawSql()) { - ResultSet suppliedResultSet = query.getRawSql().getResultSet(); - if (suppliedResultSet != null) { - // this is a user supplied ResultSet so use that - dataReader = queryPlan.createDataReader(suppliedResultSet); - bindLog = ""; - return true; - } - } - - if (forwardOnlyHint) { - // Use forward only hints for large resultset processing (Issue 56, MySql specific) - pstmt = conn.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); - pstmt.setFetchSize(Integer.MIN_VALUE); - } else { - pstmt = conn.prepareStatement(sql); - } - - if (query.getTimeout() > 0) { - pstmt.setQueryTimeout(query.getTimeout()); - } - if (query.getBufferFetchSizeHint() > 0) { - pstmt.setFetchSize(query.getBufferFetchSizeHint()); - } - - DataBind dataBind = new DataBind(pstmt); - - // bind keys for encrypted properties - queryPlan.bindEncryptedProperties(dataBind); - - bindLog = predicates.bind(dataBind); - - // executeQuery - ResultSet rset = pstmt.executeQuery(); - dataReader = queryPlan.createDataReader(rset); - - return true; - } - } - - /** - * Close the resources. - *

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

- */ - public void close() { - try { - if (dataReader != null) { - dataReader.close(); - dataReader = null; - } - } catch (SQLException e) { - logger.error(null, e); - } - try { - if (pstmt != null) { - pstmt.close(); - pstmt = null; - } - } catch (SQLException e) { - logger.error(null, e); - } - } - - /** - * Return the persistence context. - */ - public PersistenceContext getPersistenceContext() { - return request.getPersistenceContext(); - } - - public void setLoadedBean(EntityBean bean, Object id, Object lazyLoadParentId) { - if (id != null && id.equals(loadedBeanId)) { - // master/detail loading with master bean - // unchanged. NB Using id to avoid any issue - // with equals not being implemented - - } else { - if (manyIncluded) { - if (rowCount > 1) { - loadedBeanChanged = true; - } - this.prevLoadedBean = loadedBean; - this.loadedBeanId = id; - } - - this.loadedBean = bean; - - if (lazyLoadParentId != null) { - if (!lazyLoadParentId.equals(this.lazyLoadParentId)) { - // get the appropriate parent bean from the persistence context - this.lazyLoadParentBean = (EntityBean)getPersistenceContext().get(lazyLoadManyProperty.getBeanDescriptor().getBeanType(), lazyLoadParentId); - this.lazyLoadParentId = lazyLoadParentId; - } - - // add the loadedBean to the appropriate collection of lazyLoadParentBean - lazyLoadManyProperty.addBeanToCollectionWithCreate(lazyLoadParentBean, loadedBean); - } - } - } - - public void setLoadedManyBean(EntityBean manyValue) { - this.loadedManyBean = manyValue; - } - - /** - * Return the last read bean. - */ - public EntityBean getLoadedBean() { - if (manyIncluded) { - if (prevDetailCollection instanceof BeanCollection) { - ((BeanCollection) prevDetailCollection).setModifyListening(manyProperty.getModifyListenMode()); - - } else if (currentDetailCollection instanceof BeanCollection) { - ((BeanCollection) currentDetailCollection).setModifyListening(manyProperty.getModifyListenMode()); - } - } - - if (prevLoadedBean != null) { - return prevLoadedBean; - } else { - return loadedBean; - } - } - - /** - * Read a row from the result set returning a bean. - *

- * If the query includes a many then the first object in the returned array is - * the one/master and the second the many/detail. - *

- */ - private boolean readRow() throws SQLException { - - synchronized (this) { - if (cancelled) { - return false; - } - - if (!dataReader.next()) { - return false; - } - - rowCount++; - dataReader.resetColumnPosition(); - - if (rowNumberIncluded) { - // row_number() column used for limit features - dataReader.incrementPos(1); - } - - rootNode.load(this, null); - - return true; - } - } - - public long getQueryExecutionTimeMicros() { - return executionTimeMicros; - } - - public boolean readBean() throws SQLException { - - boolean result = readBeanInternal(); - - updateExecutionStatistics(); - - return result; - } - - private boolean readBeanInternal() throws SQLException { - - if (loadedBeanCount >= maxRowsLimit) { - return false; - } - - if (!manyIncluded) { - // simple query... no details... - return readRow(); - } - - if (noMoreRows) { - return false; - } - - if (rowCount == 0) { - if (!readRow()) { - // no rows at all... - return false; - } else { - createNewDetailCollection(); - } - } - - if (readIntoCurrentDetailCollection()) { - createNewDetailCollection(); - // return prevLoadedBean - return true; - - } else { - // return loadedBean - prevDetailCollection = null; - prevLoadedBean = null; - noMoreRows = true; - return true; - } - } - - private boolean readIntoCurrentDetailCollection() throws SQLException { - while (readRow()) { - if (loadedBeanChanged) { - loadedBeanChanged = false; - return true; - } else { - addToCurrentDetailCollection(); - } - } - return false; - } - - private void createNewDetailCollection() { - prevDetailCollection = currentDetailCollection; - if (queryMode.equals(Mode.LAZYLOAD_MANY)) { - // just populate the current collection - currentDetailCollection = manyPropertyEl.elGetValue(loadedBean); - } else { - // create a new collection to populate and assign to the bean - currentDetailCollection = manyProperty.createEmpty(loadedBean); - manyPropertyEl.elSetValue(loadedBean, currentDetailCollection, false); - } - - if (filterMany != null) { - // remember the for use with a refresh - ((BeanCollection) currentDetailCollection).setFilterMany(filterMany); - } - - // the manyKey is always null for this case, just using default mapKey on the property - currentDetailAdd = manyProperty.getBeanCollectionAdd(currentDetailCollection, null); - addToCurrentDetailCollection(); - } - - private void addToCurrentDetailCollection() { - if (loadedManyBean != null) { - currentDetailAdd.addBean(loadedManyBean); - } - } - - public BeanCollection readCollection() throws SQLException { - - readTheRows(); - - updateExecutionStatistics(); - - return collection; - } - - protected void updateExecutionStatistics() { - try { - long exeNano = System.nanoTime() - startNano; - executionTimeMicros = TimeUnit.NANOSECONDS.toMicros(exeNano); - - if (autoFetchProfiling) { - autoFetchManager - .collectQueryInfo(objectGraphNode, loadedBeanCount, executionTimeMicros); - } - queryPlan.executionTime(loadedBeanCount, executionTimeMicros, objectGraphNode); - - } catch (Exception e) { - logger.error(null, e); - } - } - - public QueryIterator readIterate(int bufferSize, OrmQueryRequest request) { - - if (bufferSize > 0) { - return new CQueryIteratorWithBuffer(this, request, bufferSize); - - } else { - return new CQueryIteratorSimple(this, request); - } - } - - private void readTheRows() throws SQLException { - while (hasNextBean()) { - // add to the list/set/map - help.add(collection, getLoadedBean()); - } - } - - protected boolean hasNextBean() throws SQLException { - - if (!readBeanInternal()) { - return false; - - } else { - loadedBeanCount++; - return true; - } - } - - public String getLoadedRowDetail() { - if (!manyIncluded) { - return String.valueOf(rowCount); - } else { - return loadedBeanCount + ":" + rowCount; - } - } - - public void register(String path, EntityBeanIntercept ebi) { - - path = getPath(path); - request.getGraphContext().register(path, ebi); - } - - public void register(String path, BeanCollection bc) { - - path = getPath(path); - request.getGraphContext().register(path, bc); - } - - /** - * Return the query name. - */ - public String getName() { - return query.getName(); - } - - /** - * Return true if this is a raw sql query as opposed to Ebean generated sql. - */ - public boolean isRawSql() { - return rawSql; - } - - /** - * Return the where predicate for display in the transaction log. - */ - public String getLogWhereSql() { - return logWhereSql; - } - - /** - * Return the property that is associated with the many. There can only be one - * per SqlSelect. This can be null. - */ - public BeanPropertyAssocMany getManyProperty() { - return manyProperty; - } - - /** - * Get the summary of the sql. - */ - public String getSummary() { - return sqlTree.getSummary(); - } - - public String getBindLog() { - return bindLog; - } - - public SpiTransaction getTransaction() { - return request.getTransaction(); - } - - public String getBeanType() { - return desc.getFullName(); - } - - /** - * Return the short bean name. - */ - public String getBeanName() { - return desc.getName(); - } - - /** - * Return the generated sql. - */ - public String getGeneratedSql() { - return sql; - } - - /** - * Create a PersistenceException including interesting information like the - * bindLog and sql used. - */ - public PersistenceException createPersistenceException(SQLException e) { - - return createPersistenceException(e, getTransaction(), bindLog, sql); - } - - /** - * Create a PersistenceException including interesting information like the - * bindLog and sql used. - */ - public static PersistenceException createPersistenceException(SQLException e, SpiTransaction t, - String bindLog, String sql) { - - if (t.isLogSummary()) { - // log the error to the transaction log - String errMsg = StringHelper.replaceStringMulti(e.getMessage(), new String[] { "\r", "\n" }, - "\\n "); - String msg = "ERROR executing query: bindLog[" + bindLog + "] error[" + errMsg + "]"; - t.logSummary(msg); - } - - // ensure 'rollback' is logged if queryOnly transaction - t.getConnection(); - - // build a decent error message for the exception - String m = Message.msg("fetch.sqlerror", e.getMessage(), bindLog, sql); - return new PersistenceException(m, e); - } - - /** - * Should we create profileNodes for beans created in this query. - *

- * This is true for all queries except lazy load bean queries. - *

- */ - public boolean isAutoFetchProfiling() { - // need query.isProfiling() because we just take the data - // from the lazy loaded or refreshed beans and put it into the already - // existing beans which are already collecting usage information - return autoFetchProfiling && query.isUsageProfiling(); - } - - private String getPath(String propertyName) { - - if (currentPrefix == null) { - return propertyName; - } else if (propertyName == null) { - return currentPrefix; - } - - String path = currentPathMap.get(propertyName); - if (path != null) { - return path; - } else { - return currentPrefix + "." + propertyName; - } - } - - public void profileBean(EntityBeanIntercept ebi, String prefix) { - - ObjectGraphNode node = request.getGraphContext().getObjectGraphNode(prefix); - - ebi.setNodeUsageCollector(new NodeUsageCollector(node, autoFetchManagerRef)); - } - - public void setCurrentPrefix(String currentPrefix, Map currentPathMap) { - this.currentPrefix = currentPrefix; - this.currentPathMap = currentPathMap; - } - -} +package com.avaje.ebeaninternal.server.query; + +import com.avaje.ebean.QueryIterator; +import com.avaje.ebean.bean.*; +import com.avaje.ebeaninternal.api.SpiExpressionList; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.api.SpiQuery.Mode; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager; +import com.avaje.ebeaninternal.server.core.Message; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.core.SpiOrmQueryRequest; +import com.avaje.ebeaninternal.server.deploy.*; +import com.avaje.ebeaninternal.server.el.ElPropertyValue; +import com.avaje.ebeaninternal.server.lib.util.StringHelper; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties; +import com.avaje.ebeaninternal.server.type.DataBind; +import com.avaje.ebeaninternal.server.type.DataReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.persistence.PersistenceException; +import java.lang.ref.WeakReference; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * An object that represents a SqlSelect statement. + *

+ * The SqlSelect is based on a tree (Object Graph). The tree is traversed to see + * what parts are included in the tree according to the value of + * find.getInclude(); + *

+ *

+ * The tree structure is flattened into a SqlSelectChain. The SqlSelectChain is + * the key object used in reading the flat resultSet back into Objects. + *

+ */ +public class CQuery implements DbReadContext, CancelableQuery { + + private static final Logger logger = LoggerFactory.getLogger(CQuery.class); + + private static final int GLOBAL_ROW_LIMIT = Integer.valueOf(System.getProperty("ebean.query.globallimit","1000000")); + + /** + * The resultSet rows read. + */ + private int rowCount; + + /** + * The number of master EntityBeans loaded. + */ + private int loadedBeanCount; + + /** + * Flag set when no more rows are in the resultSet. + */ + private boolean noMoreRows; + /** + * Id of loaded 'master' bean. + */ + private Object loadedBeanId; + /** + * Flag set when 'master' bean changed. + */ + private boolean loadedBeanChanged; + + /** + * The 'master' bean just loaded. + */ + private EntityBean loadedBean; + + private final BeanPropertyAssocMany lazyLoadManyProperty; + + private Object lazyLoadParentId; + + private EntityBean lazyLoadParentBean; + + /** + * Holds the previous loaded bean. + */ + private EntityBean prevLoadedBean; + + /** + * The detail bean just loaded. + */ + private EntityBean loadedManyBean; + + /** + * The previous 'detail' collection remembered so that for manyToMany we can + * turn on the modify listening. + */ + private Object prevDetailCollection; + + /** + * The current 'detail' collection being populated. + */ + private Object currentDetailCollection; + + /** + * The 'master' collection being populated. + */ + private final BeanCollection collection; + /** + * The help for the 'master' collection. + */ + private final BeanCollectionHelp help; + + /** + * The overall find request wrapper object. + */ + private final OrmQueryRequest request; + + private final BeanDescriptor desc; + + private final SpiQuery query; + + private Map currentPathMap; + + private String currentPrefix; + + /** + * Flag set true when reading 'master' and 'detail' beans. + */ + private final boolean manyIncluded; + + /** + * Where clause predicates. + */ + private final CQueryPredicates predicates; + + /** + * Object handling the SELECT generation and reading. + */ + private final SqlTree sqlTree; + + private final boolean rawSql; + + /** + * The final sql that is generated. + */ + private final String sql; + + /** + * Where clause to show in logs when using an existing query plan. + */ + private final String logWhereSql; + + /** + * Set to true if the row number column is included in the sql. + */ + private final boolean rowNumberIncluded; + + /** + * Tree that knows how to build the master and detail beans from the + * resultSet. + */ + private final SqlTreeNode rootNode; + + /** + * For master detail query. + */ + private final BeanPropertyAssocMany manyProperty; + + /** + * The many property Expression language object. + */ + private final ElPropertyValue manyPropertyEl; + + private final int maxRowsLimit; + + private DataReader dataReader; + + /** + * The statement used to create the resultSet. + */ + private PreparedStatement pstmt; + + private boolean cancelled; + + private String bindLog; + + private final CQueryPlan queryPlan; + + + private final Mode queryMode; + + private final boolean autoFetchProfiling; + + private final ObjectGraphNode objectGraphNode; + + private final AutoFetchManager autoFetchManager; + + private final WeakReference autoFetchManagerRef; + + + private final Boolean readOnly; + + private final SpiExpressionList filterMany; + + private long startNano; + + private long executionTimeMicros; + + private BeanCollectionAdd currentDetailAdd; + + /** + * Create the Sql select based on the request. + */ + @SuppressWarnings("unchecked") + public CQuery(OrmQueryRequest request, CQueryPredicates predicates, CQueryPlan queryPlan) { + this.request = request; + this.queryPlan = queryPlan; + this.query = request.getQuery(); + this.queryMode = query.getMode(); + this.lazyLoadManyProperty = query.getLazyLoadForParentsProperty(); + + this.readOnly = request.isReadOnly(); + + this.autoFetchManager = query.getAutoFetchManager(); + this.autoFetchProfiling = autoFetchManager != null; + this.objectGraphNode = query.getParentNode(); + this.autoFetchManagerRef = autoFetchProfiling ? new WeakReference( + autoFetchManager) : null; + + // set the generated sql back to the query + // so its available to the user... + query.setGeneratedSql(queryPlan.getSql()); + + this.sqlTree = queryPlan.getSqlTree(); + this.rootNode = sqlTree.getRootNode(); + + this.manyProperty = sqlTree.getManyProperty(); + this.manyPropertyEl = sqlTree.getManyPropertyEl(); + this.manyIncluded = sqlTree.isManyIncluded(); + if (manyIncluded) { + // get filter to put on the collection for reuse with refresh + String manyPropertyName = sqlTree.getManyPropertyName(); + OrmQueryProperties chunk = query.getDetail().getChunk(manyPropertyName, false); + this.filterMany = (chunk == null) ? null : chunk.getFilterMany(); + } else { + this.filterMany = null; + } + + this.sql = queryPlan.getSql(); + this.rawSql = queryPlan.isRawSql(); + this.rowNumberIncluded = queryPlan.isRowNumberIncluded(); + this.logWhereSql = queryPlan.getLogWhereSql(); + this.desc = request.getBeanDescriptor(); + this.predicates = predicates; + this.maxRowsLimit = query.getMaxRows() > 0 ? query.getMaxRows() : GLOBAL_ROW_LIMIT; + this.help = createHelp(request); + this.collection = (help != null ? help.createEmptyNoParent() : null); + } + + private BeanCollectionHelp createHelp(OrmQueryRequest request) { + if (request.isFindById()) { + return null; + } else { + SpiQuery.Type manyType = request.getQuery().getType(); + if (manyType == null) { + // subQuery compiled for InQueryExpression + return null; + } + return BeanCollectionHelpFactory.create(request); + } + } + + public Boolean isReadOnly() { + return readOnly; + } + + public void propagateState(Object e) { + if (Boolean.TRUE.equals(readOnly)) { + if (e instanceof EntityBean) { + ((EntityBean) e)._ebean_getIntercept().setReadOnly(true); + } + } + } + + public DataReader getDataReader() { + return dataReader; + } + + public Mode getQueryMode() { + return queryMode; + } + + public CQueryPredicates getPredicates() { + return predicates; + } + + public SpiOrmQueryRequest getQueryRequest() { + return request; + } + + public void cancel() { + synchronized (this) { + this.cancelled = true; + if (pstmt != null) { + try { + pstmt.cancel(); + } catch (SQLException e) { + String msg = "Error cancelling query"; + throw new PersistenceException(msg, e); + } + } + } + } + + /** + * Prepare bind and execute query with Forward only hints. + */ + public boolean prepareBindExecuteQueryForwardOnly(boolean dbPlatformForwardOnlyHint) throws SQLException { + return prepareBindExecuteQueryWithOption(dbPlatformForwardOnlyHint); + } + + /** + * Prepare bind and execute the query normally. + */ + public boolean prepareBindExecuteQuery() throws SQLException { + return prepareBindExecuteQueryWithOption(false); + } + + private boolean prepareBindExecuteQueryWithOption(boolean forwardOnlyHint) throws SQLException { + + synchronized (this) { + if (cancelled || query.isCancelled()) { + // cancelled before we started + cancelled = true; + return false; + } + + startNano = System.nanoTime(); + + // prepare + SpiTransaction t = request.getTransaction(); + Connection conn = t.getInternalConnection(); + + if (query.isRawSql()) { + ResultSet suppliedResultSet = query.getRawSql().getResultSet(); + if (suppliedResultSet != null) { + // this is a user supplied ResultSet so use that + dataReader = queryPlan.createDataReader(suppliedResultSet); + bindLog = ""; + return true; + } + } + + if (forwardOnlyHint) { + // Use forward only hints for large resultset processing (Issue 56, MySql specific) + pstmt = conn.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); + pstmt.setFetchSize(Integer.MIN_VALUE); + } else { + pstmt = conn.prepareStatement(sql); + } + + if (query.getTimeout() > 0) { + pstmt.setQueryTimeout(query.getTimeout()); + } + if (query.getBufferFetchSizeHint() > 0) { + pstmt.setFetchSize(query.getBufferFetchSizeHint()); + } + + DataBind dataBind = new DataBind(pstmt); + + // bind keys for encrypted properties + queryPlan.bindEncryptedProperties(dataBind); + + bindLog = predicates.bind(dataBind); + + // executeQuery + ResultSet rset = pstmt.executeQuery(); + dataReader = queryPlan.createDataReader(rset); + + return true; + } + } + + /** + * Close the resources. + *

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

+ */ + public void close() { + try { + if (dataReader != null) { + dataReader.close(); + dataReader = null; + } + } catch (SQLException e) { + logger.error(null, e); + } + try { + if (pstmt != null) { + pstmt.close(); + pstmt = null; + } + } catch (SQLException e) { + logger.error(null, e); + } + } + + /** + * Return the persistence context. + */ + public PersistenceContext getPersistenceContext() { + return request.getPersistenceContext(); + } + + public void setLoadedBean(EntityBean bean, Object id, Object lazyLoadParentId) { + if (id != null && id.equals(loadedBeanId)) { + // master/detail loading with master bean + // unchanged. NB Using id to avoid any issue + // with equals not being implemented + + } else { + if (manyIncluded) { + if (rowCount > 1) { + loadedBeanChanged = true; + } + this.prevLoadedBean = loadedBean; + this.loadedBeanId = id; + } + + this.loadedBean = bean; + + if (lazyLoadParentId != null) { + if (!lazyLoadParentId.equals(this.lazyLoadParentId)) { + // get the appropriate parent bean from the persistence context + this.lazyLoadParentBean = (EntityBean)getPersistenceContext().get(lazyLoadManyProperty.getBeanDescriptor().getBeanType(), lazyLoadParentId); + this.lazyLoadParentId = lazyLoadParentId; + } + + // add the loadedBean to the appropriate collection of lazyLoadParentBean + lazyLoadManyProperty.addBeanToCollectionWithCreate(lazyLoadParentBean, loadedBean); + } + } + } + + public void setLoadedManyBean(EntityBean manyValue) { + this.loadedManyBean = manyValue; + } + + /** + * Return the last read bean. + */ + public EntityBean getLoadedBean() { + if (manyIncluded) { + if (prevDetailCollection instanceof BeanCollection) { + ((BeanCollection) prevDetailCollection).setModifyListening(manyProperty.getModifyListenMode()); + + } else if (currentDetailCollection instanceof BeanCollection) { + ((BeanCollection) currentDetailCollection).setModifyListening(manyProperty.getModifyListenMode()); + } + } + + if (prevLoadedBean != null) { + return prevLoadedBean; + } else { + return loadedBean; + } + } + + /** + * Read a row from the result set returning a bean. + *

+ * If the query includes a many then the first object in the returned array is + * the one/master and the second the many/detail. + *

+ */ + private boolean readRow() throws SQLException { + + synchronized (this) { + if (cancelled) { + return false; + } + + if (!dataReader.next()) { + return false; + } + + rowCount++; + dataReader.resetColumnPosition(); + + if (rowNumberIncluded) { + // row_number() column used for limit features + dataReader.incrementPos(1); + } + + rootNode.load(this, null); + + return true; + } + } + + public long getQueryExecutionTimeMicros() { + return executionTimeMicros; + } + + public boolean readBean() throws SQLException { + + boolean result = readBeanInternal(); + + updateExecutionStatistics(); + + return result; + } + + private boolean readBeanInternal() throws SQLException { + + if (loadedBeanCount >= maxRowsLimit) { + return false; + } + + if (!manyIncluded) { + // simple query... no details... + return readRow(); + } + + if (noMoreRows) { + return false; + } + + if (rowCount == 0) { + if (!readRow()) { + // no rows at all... + return false; + } else { + createNewDetailCollection(); + } + } + + if (readIntoCurrentDetailCollection()) { + createNewDetailCollection(); + // return prevLoadedBean + return true; + + } else { + // return loadedBean + prevDetailCollection = null; + prevLoadedBean = null; + noMoreRows = true; + return true; + } + } + + private boolean readIntoCurrentDetailCollection() throws SQLException { + while (readRow()) { + if (loadedBeanChanged) { + loadedBeanChanged = false; + return true; + } else { + addToCurrentDetailCollection(); + } + } + return false; + } + + private void createNewDetailCollection() { + prevDetailCollection = currentDetailCollection; + if (queryMode.equals(Mode.LAZYLOAD_MANY)) { + // just populate the current collection + currentDetailCollection = manyPropertyEl.elGetValue(loadedBean); + } else { + // create a new collection to populate and assign to the bean + currentDetailCollection = manyProperty.createEmpty(loadedBean); + manyPropertyEl.elSetValue(loadedBean, currentDetailCollection, false); + } + + if (filterMany != null) { + // remember the for use with a refresh + ((BeanCollection) currentDetailCollection).setFilterMany(filterMany); + } + + // the manyKey is always null for this case, just using default mapKey on the property + currentDetailAdd = manyProperty.getBeanCollectionAdd(currentDetailCollection, null); + addToCurrentDetailCollection(); + } + + private void addToCurrentDetailCollection() { + if (loadedManyBean != null) { + currentDetailAdd.addBean(loadedManyBean); + } + } + + public BeanCollection readCollection() throws SQLException { + + readTheRows(); + + updateExecutionStatistics(); + + return collection; + } + + protected void updateExecutionStatistics() { + try { + long exeNano = System.nanoTime() - startNano; + executionTimeMicros = TimeUnit.NANOSECONDS.toMicros(exeNano); + + if (autoFetchProfiling) { + autoFetchManager + .collectQueryInfo(objectGraphNode, loadedBeanCount, executionTimeMicros); + } + queryPlan.executionTime(loadedBeanCount, executionTimeMicros, objectGraphNode); + + } catch (Exception e) { + logger.error(null, e); + } + } + + public QueryIterator readIterate(int bufferSize, OrmQueryRequest request) { + + if (bufferSize > 0) { + return new CQueryIteratorWithBuffer(this, request, bufferSize); + + } else { + return new CQueryIteratorSimple(this, request); + } + } + + private void readTheRows() throws SQLException { + while (hasNextBean()) { + // add to the list/set/map + help.add(collection, getLoadedBean()); + } + } + + protected boolean hasNextBean() throws SQLException { + + if (!readBeanInternal()) { + return false; + + } else { + loadedBeanCount++; + return true; + } + } + + public String getLoadedRowDetail() { + if (!manyIncluded) { + return String.valueOf(rowCount); + } else { + return loadedBeanCount + ":" + rowCount; + } + } + + public void register(String path, EntityBeanIntercept ebi) { + + path = getPath(path); + request.getGraphContext().register(path, ebi); + } + + public void register(String path, BeanCollection bc) { + + path = getPath(path); + request.getGraphContext().register(path, bc); + } + + /** + * Return the query name. + */ + public String getName() { + return query.getName(); + } + + /** + * Return true if this is a raw sql query as opposed to Ebean generated sql. + */ + public boolean isRawSql() { + return rawSql; + } + + /** + * Return the where predicate for display in the transaction log. + */ + public String getLogWhereSql() { + return logWhereSql; + } + + /** + * Return the property that is associated with the many. There can only be one + * per SqlSelect. This can be null. + */ + public BeanPropertyAssocMany getManyProperty() { + return manyProperty; + } + + /** + * Get the summary of the sql. + */ + public String getSummary() { + return sqlTree.getSummary(); + } + + public String getBindLog() { + return bindLog; + } + + public SpiTransaction getTransaction() { + return request.getTransaction(); + } + + public String getBeanType() { + return desc.getFullName(); + } + + /** + * Return the short bean name. + */ + public String getBeanName() { + return desc.getName(); + } + + /** + * Return the generated sql. + */ + public String getGeneratedSql() { + return sql; + } + + /** + * Create a PersistenceException including interesting information like the + * bindLog and sql used. + */ + public PersistenceException createPersistenceException(SQLException e) { + + return createPersistenceException(e, getTransaction(), bindLog, sql); + } + + /** + * Create a PersistenceException including interesting information like the + * bindLog and sql used. + */ + public static PersistenceException createPersistenceException(SQLException e, SpiTransaction t, + String bindLog, String sql) { + + if (t.isLogSummary()) { + // log the error to the transaction log + String errMsg = StringHelper.replaceStringMulti(e.getMessage(), new String[] { "\r", "\n" }, + "\\n "); + String msg = "ERROR executing query: bindLog[" + bindLog + "] error[" + errMsg + "]"; + t.logSummary(msg); + } + + // ensure 'rollback' is logged if queryOnly transaction + t.getConnection(); + + // build a decent error message for the exception + String m = Message.msg("fetch.sqlerror", e.getMessage(), bindLog, sql); + return new PersistenceException(m, e); + } + + /** + * Should we create profileNodes for beans created in this query. + *

+ * This is true for all queries except lazy load bean queries. + *

+ */ + public boolean isAutoFetchProfiling() { + // need query.isProfiling() because we just take the data + // from the lazy loaded or refreshed beans and put it into the already + // existing beans which are already collecting usage information + return autoFetchProfiling && query.isUsageProfiling(); + } + + private String getPath(String propertyName) { + + if (currentPrefix == null) { + return propertyName; + } else if (propertyName == null) { + return currentPrefix; + } + + String path = currentPathMap.get(propertyName); + if (path != null) { + return path; + } else { + return currentPrefix + "." + propertyName; + } + } + + public void profileBean(EntityBeanIntercept ebi, String prefix) { + + ObjectGraphNode node = request.getGraphContext().getObjectGraphNode(prefix); + + ebi.setNodeUsageCollector(new NodeUsageCollector(node, autoFetchManagerRef)); + } + + public void setCurrentPrefix(String currentPrefix, Map currentPathMap) { + this.currentPrefix = currentPrefix; + this.currentPathMap = currentPathMap; + } + +} 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 bf733cd5a..1cc52fa52 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java @@ -1,411 +1,411 @@ -package com.avaje.ebeaninternal.server.query; - -import com.avaje.ebean.RawSql; -import com.avaje.ebean.RawSql.ColumnMapping; -import com.avaje.ebean.RawSql.ColumnMapping.Column; -import com.avaje.ebean.RawSqlBuilder; -import com.avaje.ebean.config.dbplatform.DatabasePlatform; -import com.avaje.ebean.config.dbplatform.SqlLimitRequest; -import com.avaje.ebean.config.dbplatform.SqlLimitResponse; -import com.avaje.ebean.config.dbplatform.SqlLimiter; -import com.avaje.ebean.text.PathProperties; -import com.avaje.ebeaninternal.api.ManyWhereJoins; -import com.avaje.ebeaninternal.api.SpiQuery; -import com.avaje.ebeaninternal.server.core.OrmQueryRequest; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.deploy.BeanProperty; -import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; -import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; -import com.avaje.ebeaninternal.server.el.ElPropertyValue; -import com.avaje.ebeaninternal.server.persist.Binder; -import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail; -import com.avaje.ebeaninternal.server.querydefn.OrmQueryLimitRequest; - -import javax.persistence.PersistenceException; -import java.util.Iterator; -import java.util.Set; - -/** - * Generates the SQL SELECT statements taking into account the physical - * deployment properties. - */ -public class CQueryBuilder implements Constants { - - private final String tableAliasPlaceHolder; - private final String columnAliasPrefix; - - private final SqlLimiter sqlLimiter; - - private final RawSqlSelectClauseBuilder sqlSelectBuilder; - private final CQueryBuilderRawSql rawSqlHandler; - - private final Binder binder; - - private final boolean selectCountWithAlias; - - private DatabasePlatform dbPlatform; - - /** - * Create the SqlGenSelect. - */ - public CQueryBuilder(DatabasePlatform dbPlatform, Binder binder) { - - this.binder = binder; - this.tableAliasPlaceHolder = dbPlatform.getTableAliasPlaceHolder(); - this.columnAliasPrefix = dbPlatform.getColumnAliasPrefix(); - this.sqlSelectBuilder = new RawSqlSelectClauseBuilder(dbPlatform, binder); - - this.sqlLimiter = dbPlatform.getSqlLimiter(); - this.rawSqlHandler = new CQueryBuilderRawSql(sqlLimiter, dbPlatform); - - this.selectCountWithAlias = dbPlatform.isSelectCountWithAlias(); - - this.dbPlatform = dbPlatform; - } - - /** - * split the order by claus on the field delimiter and prefix each field with - * the relation name - */ - public static String prefixOrderByFields(String name, String orderBy) { - StringBuilder sb = new StringBuilder(); - for (String token : orderBy.split(",")) { - if (sb.length() > 0) { - sb.append(", "); - } - - sb.append(name); - sb.append("."); - sb.append(token.trim()); - } - - return sb.toString(); - } - - /** - * Build the row count query. - */ - public CQueryFetchIds buildFetchIdsQuery(OrmQueryRequest request) { - - SpiQuery query = request.getQuery(); - - query.setSelectId(); - - CQueryPredicates predicates = new CQueryPredicates(binder, request); - CQueryPlan queryPlan = request.getQueryPlan(); - if (queryPlan != null) { - // skip building the SqlTree and Sql string - predicates.prepare(false); - String sql = queryPlan.getSql(); - return new CQueryFetchIds(request, predicates, sql); - } - - // use RawSql or generated Sql - predicates.prepare(true); - - SqlTree sqlTree = createSqlTree(request, predicates); - SqlLimitResponse s = buildSql(null, request, predicates, sqlTree); - String sql = s.getSql(); - - // cache the query plan - queryPlan = new CQueryPlan(request, sql, sqlTree, false, s.isIncludesRowNumberColumn(), predicates.getLogWhereSql()); - - request.putQueryPlan(queryPlan); - return new CQueryFetchIds(request, predicates, sql); - } - - /** - * Build the row count query. - */ - public CQueryRowCount buildRowCountQuery(OrmQueryRequest request) { - - SpiQuery query = request.getQuery(); - - // always set the order by to null for row count query - query.setOrder(null); - query.setFirstRow(0); - query.setMaxRows(0); - - ManyWhereJoins manyWhereJoins = query.getManyWhereJoins(); - - boolean hasMany = manyWhereJoins.isHasMany(); - if (manyWhereJoins.isSelectId()) { - // just select the id property - query.setSelectId(); - } else { - // select the id and the required formula properties - query.select(manyWhereJoins.getFormulaProperties()); - } - - String sqlSelect = "select count(*)"; - if (hasMany) { - // need to count distinct id's ... - query.setSqlDistinct(true); - sqlSelect = null; - } - - CQueryPredicates predicates = new CQueryPredicates(binder, request); - CQueryPlan queryPlan = request.getQueryPlan(); - if (queryPlan != null) { - // skip building the SqlTree and Sql string - predicates.prepare(false); - String sql = queryPlan.getSql(); - return new CQueryRowCount(request, predicates, sql); - } - - predicates.prepare(true); - - SqlTree sqlTree = createSqlTree(request, predicates); - SqlLimitResponse s = buildSql(sqlSelect, request, predicates, sqlTree); - String sql = s.getSql(); - if (hasMany || query.isRawSql()) { - sql = "select count(*) from ( " + sql + ")"; - if (selectCountWithAlias) { - sql += " as c"; - } - } - - // cache the query plan - queryPlan = new CQueryPlan(request, sql, sqlTree, false, s.isIncludesRowNumberColumn(), predicates.getLogWhereSql()); - request.putQueryPlan(queryPlan); - - return new CQueryRowCount(request, predicates, sql); - } - - /** - * Return the SQL Select statement as a String. Converts logical property - * names to physical deployment column names. - */ - public CQuery buildQuery(OrmQueryRequest request) { - - if (request.isSqlSelect()) { - return sqlSelectBuilder.build(request); - } - - CQueryPredicates predicates = new CQueryPredicates(binder, request); - - CQueryPlan queryPlan = request.getQueryPlan(); - if (queryPlan != null) { - // Reuse the query plan so skip generating SqlTree and SQL. - // We do prepare and bind the new parameters - predicates.prepare(false); - return new CQuery(request, predicates, queryPlan); - } - - // RawSql or Generated Sql query - - // Prepare the where, having and order by clauses. - // This also parses them from logical property names to - // database columns and determines 'includes'. - - // We need to check these 'includes' for extra joins - // that are not included via select - predicates.prepare(true); - - // Build the tree structure that represents the query. - SqlTree sqlTree = createSqlTree(request, predicates); - SqlLimitResponse res = buildSql(null, request, predicates, sqlTree); - - boolean rawSql = request.isRawSql(); - if (rawSql) { - queryPlan = new CQueryPlanRawSql(request, res, sqlTree, predicates.getLogWhereSql()); - - } else { - queryPlan = new CQueryPlan(request, res, sqlTree, rawSql, predicates.getLogWhereSql()); - } - - // cache the query plan because we can reuse it and also - // gather query performance statistics based on it. - request.putQueryPlan(queryPlan); - - return new CQuery(request, predicates, queryPlan); - } - - /** - * Build the SqlTree. - *

- * The SqlTree is immutable after construction and so is safe to use by - * concurrent threads. - *

- *

- * The predicates is used to add additional joins that come from the where or - * order by clauses that are not already included for the select clause. - *

- */ - private SqlTree createSqlTree(OrmQueryRequest request, CQueryPredicates predicates) { - - if (request.isRawSql()) { - return createRawSqlSqlTree(request, predicates); - } - - return new SqlTreeBuilder(tableAliasPlaceHolder, columnAliasPrefix, request, predicates).build(); - } - - private SqlTree createRawSqlSqlTree(OrmQueryRequest request, CQueryPredicates predicates) { - - BeanDescriptor descriptor = request.getBeanDescriptor(); - ColumnMapping columnMapping = request.getQuery().getRawSql().getColumnMapping(); - - PathProperties pathProps = new PathProperties(); - - // convert list of columns into (tree like) PathProperties - Iterator it = columnMapping.getColumns(); - while (it.hasNext()) { - RawSql.ColumnMapping.Column column = it.next(); - String propertyName = column.getPropertyName(); - if (!RawSqlBuilder.IGNORE_COLUMN.equals(propertyName)) { - - ElPropertyValue el = descriptor.getElGetValue(propertyName); - if (el == null) { - throw new PersistenceException("Property [" + propertyName + "] not found on " + descriptor.getFullName()); - } else { - BeanProperty beanProperty = el.getBeanProperty(); - if (beanProperty.isId() || beanProperty.isDiscriminator()) { - // For @Id properties we chop off the last part of the path - propertyName = SplitName.parent(propertyName); - } else if (beanProperty instanceof BeanPropertyAssocOne) { - String msg = "Column [" + column.getDbColumn() + "] mapped to complex Property[" + propertyName + "]"; - msg += ". It should be mapped to a simple property (proably the Id property). "; - throw new PersistenceException(msg); - } - if (propertyName != null) { - String[] pathProp = SplitName.split(propertyName); - pathProps.addToPath(pathProp[0], pathProp[1]); - } - } - } - } - - OrmQueryDetail detail = new OrmQueryDetail(); - - // transfer PathProperties into OrmQueryDetail - for (String path : pathProps.getPaths()) { - Set props = pathProps.get(path); - detail.getChunk(path, true).setDefaultProperties(null, props); - } - - // build SqlTree based on OrmQueryDetail of the RawSql - return new SqlTreeBuilder(request, predicates, detail).build(); - } - - private SqlLimitResponse buildSql(String selectClause, OrmQueryRequest request, CQueryPredicates predicates, SqlTree select) { - - SpiQuery query = request.getQuery(); - - RawSql rawSql = query.getRawSql(); - if (rawSql != null) { - return rawSqlHandler.buildSql(request, predicates, rawSql.getSql()); - } - - BeanPropertyAssocMany manyProp = select.getManyProperty(); - - boolean useSqlLimiter = false; - - StringBuilder sb = new StringBuilder(500); - - String dbOrderBy = predicates.getDbOrderBy(); - - if (selectClause != null) { - sb.append(selectClause); - - } else { - - useSqlLimiter = (query.hasMaxRowsOrFirstRow() && manyProp == null); - - if (!useSqlLimiter) { - sb.append("select "); - if (query.isDistinctQuery()) { - sb.append("distinct "); - } - } - - sb.append(select.getSelectSql()); - if (query.isDistinctQuery() && dbOrderBy != null) { - // add the orderby columns to the select clause (due to distinct) - sb.append(", ").append(convertDbOrderByForSelect(dbOrderBy)); - } - } - - sb.append(" from "); - - // build the from clause potentially with joins - // required only for the predicates - sb.append(select.getFromSql()); - - String inheritanceWhere = select.getInheritanceWhereSql(); - - boolean hasWhere = false; - if (inheritanceWhere.length() > 0) { - sb.append(" where"); - sb.append(inheritanceWhere); - hasWhere = true; - } - - if (request.isFindById() || query.getId() != null) { - if (hasWhere) { - sb.append(" and "); - } else { - sb.append(" where "); - } - - BeanDescriptor desc = request.getBeanDescriptor(); - String idSql = desc.getIdBinderIdSql(); - if (idSql.isEmpty()) { - throw new IllegalStateException("Executing FindById query on entity bean " + desc.getName() - + " that doesn't have an @Id property??"); - } - sb.append(idSql).append(" "); - hasWhere = true; - } - - String dbWhere = predicates.getDbWhere(); - if (!isEmpty(dbWhere)) { - if (!hasWhere) { - hasWhere = true; - sb.append(" where "); - } else { - sb.append(" and "); - } - sb.append(dbWhere); - } - - String dbFilterMany = predicates.getDbFilterMany(); - if (!isEmpty(dbFilterMany)) { - if (!hasWhere) { - sb.append(" where "); - } else { - sb.append("and "); - } - sb.append(dbFilterMany); - } - - - if (dbOrderBy != null) { - sb.append(" order by ").append(dbOrderBy); - } - - if (useSqlLimiter) { - // use LIMIT/OFFSET, ROW_NUMBER() or rownum type SQL query limitation - SqlLimitRequest r = new OrmQueryLimitRequest(sb.toString(), dbOrderBy, query, dbPlatform); - return sqlLimiter.limit(r); - - } else { - return new SqlLimitResponse(dbPlatform.completeSql(sb.toString(), query), false); - } - - } - - /** - * Convert the dbOrderBy clause to be safe for adding to select. This is done when 'distinct' is - * used. - */ - private String convertDbOrderByForSelect(String dbOrderBy) { - // just remove the ASC and DESC keywords - return dbOrderBy.replaceAll("(?i)\\b asc\\b|\\b desc\\b", ""); - } - - private boolean isEmpty(String s) { - return s == null || s.length() == 0; - } - -} +package com.avaje.ebeaninternal.server.query; + +import com.avaje.ebean.RawSql; +import com.avaje.ebean.RawSql.ColumnMapping; +import com.avaje.ebean.RawSql.ColumnMapping.Column; +import com.avaje.ebean.RawSqlBuilder; +import com.avaje.ebean.config.dbplatform.DatabasePlatform; +import com.avaje.ebean.config.dbplatform.SqlLimitRequest; +import com.avaje.ebean.config.dbplatform.SqlLimitResponse; +import com.avaje.ebean.config.dbplatform.SqlLimiter; +import com.avaje.ebean.text.PathProperties; +import com.avaje.ebeaninternal.api.ManyWhereJoins; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.el.ElPropertyValue; +import com.avaje.ebeaninternal.server.persist.Binder; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryLimitRequest; + +import javax.persistence.PersistenceException; +import java.util.Iterator; +import java.util.Set; + +/** + * Generates the SQL SELECT statements taking into account the physical + * deployment properties. + */ +public class CQueryBuilder implements Constants { + + private final String tableAliasPlaceHolder; + private final String columnAliasPrefix; + + private final SqlLimiter sqlLimiter; + + private final RawSqlSelectClauseBuilder sqlSelectBuilder; + private final CQueryBuilderRawSql rawSqlHandler; + + private final Binder binder; + + private final boolean selectCountWithAlias; + + private DatabasePlatform dbPlatform; + + /** + * Create the SqlGenSelect. + */ + public CQueryBuilder(DatabasePlatform dbPlatform, Binder binder) { + + this.binder = binder; + this.tableAliasPlaceHolder = dbPlatform.getTableAliasPlaceHolder(); + this.columnAliasPrefix = dbPlatform.getColumnAliasPrefix(); + this.sqlSelectBuilder = new RawSqlSelectClauseBuilder(dbPlatform, binder); + + this.sqlLimiter = dbPlatform.getSqlLimiter(); + this.rawSqlHandler = new CQueryBuilderRawSql(sqlLimiter, dbPlatform); + + this.selectCountWithAlias = dbPlatform.isSelectCountWithAlias(); + + this.dbPlatform = dbPlatform; + } + + /** + * split the order by claus on the field delimiter and prefix each field with + * the relation name + */ + public static String prefixOrderByFields(String name, String orderBy) { + StringBuilder sb = new StringBuilder(); + for (String token : orderBy.split(",")) { + if (sb.length() > 0) { + sb.append(", "); + } + + sb.append(name); + sb.append("."); + sb.append(token.trim()); + } + + return sb.toString(); + } + + /** + * Build the row count query. + */ + public CQueryFetchIds buildFetchIdsQuery(OrmQueryRequest request) { + + SpiQuery query = request.getQuery(); + + query.setSelectId(); + + CQueryPredicates predicates = new CQueryPredicates(binder, request); + CQueryPlan queryPlan = request.getQueryPlan(); + if (queryPlan != null) { + // skip building the SqlTree and Sql string + predicates.prepare(false); + String sql = queryPlan.getSql(); + return new CQueryFetchIds(request, predicates, sql); + } + + // use RawSql or generated Sql + predicates.prepare(true); + + SqlTree sqlTree = createSqlTree(request, predicates); + SqlLimitResponse s = buildSql(null, request, predicates, sqlTree); + String sql = s.getSql(); + + // cache the query plan + queryPlan = new CQueryPlan(request, sql, sqlTree, false, s.isIncludesRowNumberColumn(), predicates.getLogWhereSql()); + + request.putQueryPlan(queryPlan); + return new CQueryFetchIds(request, predicates, sql); + } + + /** + * Build the row count query. + */ + public CQueryRowCount buildRowCountQuery(OrmQueryRequest request) { + + SpiQuery query = request.getQuery(); + + // always set the order by to null for row count query + query.setOrder(null); + query.setFirstRow(0); + query.setMaxRows(0); + + ManyWhereJoins manyWhereJoins = query.getManyWhereJoins(); + + boolean hasMany = manyWhereJoins.isHasMany(); + if (manyWhereJoins.isSelectId()) { + // just select the id property + query.setSelectId(); + } else { + // select the id and the required formula properties + query.select(manyWhereJoins.getFormulaProperties()); + } + + String sqlSelect = "select count(*)"; + if (hasMany) { + // need to count distinct id's ... + query.setSqlDistinct(true); + sqlSelect = null; + } + + CQueryPredicates predicates = new CQueryPredicates(binder, request); + CQueryPlan queryPlan = request.getQueryPlan(); + if (queryPlan != null) { + // skip building the SqlTree and Sql string + predicates.prepare(false); + String sql = queryPlan.getSql(); + return new CQueryRowCount(request, predicates, sql); + } + + predicates.prepare(true); + + SqlTree sqlTree = createSqlTree(request, predicates); + SqlLimitResponse s = buildSql(sqlSelect, request, predicates, sqlTree); + String sql = s.getSql(); + if (hasMany || query.isRawSql()) { + sql = "select count(*) from ( " + sql + ")"; + if (selectCountWithAlias) { + sql += " as c"; + } + } + + // cache the query plan + queryPlan = new CQueryPlan(request, sql, sqlTree, false, s.isIncludesRowNumberColumn(), predicates.getLogWhereSql()); + request.putQueryPlan(queryPlan); + + return new CQueryRowCount(request, predicates, sql); + } + + /** + * Return the SQL Select statement as a String. Converts logical property + * names to physical deployment column names. + */ + public CQuery buildQuery(OrmQueryRequest request) { + + if (request.isSqlSelect()) { + return sqlSelectBuilder.build(request); + } + + CQueryPredicates predicates = new CQueryPredicates(binder, request); + + CQueryPlan queryPlan = request.getQueryPlan(); + if (queryPlan != null) { + // Reuse the query plan so skip generating SqlTree and SQL. + // We do prepare and bind the new parameters + predicates.prepare(false); + return new CQuery(request, predicates, queryPlan); + } + + // RawSql or Generated Sql query + + // Prepare the where, having and order by clauses. + // This also parses them from logical property names to + // database columns and determines 'includes'. + + // We need to check these 'includes' for extra joins + // that are not included via select + predicates.prepare(true); + + // Build the tree structure that represents the query. + SqlTree sqlTree = createSqlTree(request, predicates); + SqlLimitResponse res = buildSql(null, request, predicates, sqlTree); + + boolean rawSql = request.isRawSql(); + if (rawSql) { + queryPlan = new CQueryPlanRawSql(request, res, sqlTree, predicates.getLogWhereSql()); + + } else { + queryPlan = new CQueryPlan(request, res, sqlTree, rawSql, predicates.getLogWhereSql()); + } + + // cache the query plan because we can reuse it and also + // gather query performance statistics based on it. + request.putQueryPlan(queryPlan); + + return new CQuery(request, predicates, queryPlan); + } + + /** + * Build the SqlTree. + *

+ * The SqlTree is immutable after construction and so is safe to use by + * concurrent threads. + *

+ *

+ * The predicates is used to add additional joins that come from the where or + * order by clauses that are not already included for the select clause. + *

+ */ + private SqlTree createSqlTree(OrmQueryRequest request, CQueryPredicates predicates) { + + if (request.isRawSql()) { + return createRawSqlSqlTree(request, predicates); + } + + return new SqlTreeBuilder(tableAliasPlaceHolder, columnAliasPrefix, request, predicates).build(); + } + + private SqlTree createRawSqlSqlTree(OrmQueryRequest request, CQueryPredicates predicates) { + + BeanDescriptor descriptor = request.getBeanDescriptor(); + ColumnMapping columnMapping = request.getQuery().getRawSql().getColumnMapping(); + + PathProperties pathProps = new PathProperties(); + + // convert list of columns into (tree like) PathProperties + Iterator it = columnMapping.getColumns(); + while (it.hasNext()) { + RawSql.ColumnMapping.Column column = it.next(); + String propertyName = column.getPropertyName(); + if (!RawSqlBuilder.IGNORE_COLUMN.equals(propertyName)) { + + ElPropertyValue el = descriptor.getElGetValue(propertyName); + if (el == null) { + throw new PersistenceException("Property [" + propertyName + "] not found on " + descriptor.getFullName()); + } else { + BeanProperty beanProperty = el.getBeanProperty(); + if (beanProperty.isId() || beanProperty.isDiscriminator()) { + // For @Id properties we chop off the last part of the path + propertyName = SplitName.parent(propertyName); + } else if (beanProperty instanceof BeanPropertyAssocOne) { + String msg = "Column [" + column.getDbColumn() + "] mapped to complex Property[" + propertyName + "]"; + msg += ". It should be mapped to a simple property (proably the Id property). "; + throw new PersistenceException(msg); + } + if (propertyName != null) { + String[] pathProp = SplitName.split(propertyName); + pathProps.addToPath(pathProp[0], pathProp[1]); + } + } + } + } + + OrmQueryDetail detail = new OrmQueryDetail(); + + // transfer PathProperties into OrmQueryDetail + for (String path : pathProps.getPaths()) { + Set props = pathProps.get(path); + detail.getChunk(path, true).setDefaultProperties(null, props); + } + + // build SqlTree based on OrmQueryDetail of the RawSql + return new SqlTreeBuilder(request, predicates, detail).build(); + } + + private SqlLimitResponse buildSql(String selectClause, OrmQueryRequest request, CQueryPredicates predicates, SqlTree select) { + + SpiQuery query = request.getQuery(); + + RawSql rawSql = query.getRawSql(); + if (rawSql != null) { + return rawSqlHandler.buildSql(request, predicates, rawSql.getSql()); + } + + BeanPropertyAssocMany manyProp = select.getManyProperty(); + + boolean useSqlLimiter = false; + + StringBuilder sb = new StringBuilder(500); + + String dbOrderBy = predicates.getDbOrderBy(); + + if (selectClause != null) { + sb.append(selectClause); + + } else { + + useSqlLimiter = (query.hasMaxRowsOrFirstRow() && manyProp == null); + + if (!useSqlLimiter) { + sb.append("select "); + if (query.isDistinctQuery()) { + sb.append("distinct "); + } + } + + sb.append(select.getSelectSql()); + if (query.isDistinctQuery() && dbOrderBy != null) { + // add the orderby columns to the select clause (due to distinct) + sb.append(", ").append(convertDbOrderByForSelect(dbOrderBy)); + } + } + + sb.append(" from "); + + // build the from clause potentially with joins + // required only for the predicates + sb.append(select.getFromSql()); + + String inheritanceWhere = select.getInheritanceWhereSql(); + + boolean hasWhere = false; + if (inheritanceWhere.length() > 0) { + sb.append(" where"); + sb.append(inheritanceWhere); + hasWhere = true; + } + + if (request.isFindById() || query.getId() != null) { + if (hasWhere) { + sb.append(" and "); + } else { + sb.append(" where "); + } + + BeanDescriptor desc = request.getBeanDescriptor(); + String idSql = desc.getIdBinderIdSql(); + if (idSql.isEmpty()) { + throw new IllegalStateException("Executing FindById query on entity bean " + desc.getName() + + " that doesn't have an @Id property??"); + } + sb.append(idSql).append(" "); + hasWhere = true; + } + + String dbWhere = predicates.getDbWhere(); + if (!isEmpty(dbWhere)) { + if (!hasWhere) { + hasWhere = true; + sb.append(" where "); + } else { + sb.append(" and "); + } + sb.append(dbWhere); + } + + String dbFilterMany = predicates.getDbFilterMany(); + if (!isEmpty(dbFilterMany)) { + if (!hasWhere) { + sb.append(" where "); + } else { + sb.append("and "); + } + sb.append(dbFilterMany); + } + + + if (dbOrderBy != null) { + sb.append(" order by ").append(dbOrderBy); + } + + if (useSqlLimiter) { + // use LIMIT/OFFSET, ROW_NUMBER() or rownum type SQL query limitation + SqlLimitRequest r = new OrmQueryLimitRequest(sb.toString(), dbOrderBy, query, dbPlatform); + return sqlLimiter.limit(r); + + } else { + return new SqlLimitResponse(dbPlatform.completeSql(sb.toString(), query), false); + } + + } + + /** + * Convert the dbOrderBy clause to be safe for adding to select. This is done when 'distinct' is + * used. + */ + private String convertDbOrderByForSelect(String dbOrderBy) { + // just remove the ASC and DESC keywords + return dbOrderBy.replaceAll("(?i)\\b asc\\b|\\b desc\\b", ""); + } + + private boolean isEmpty(String s) { + return s == null || s.length() == 0; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilderRawSql.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilderRawSql.java index ee1826d93..b639ca602 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilderRawSql.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilderRawSql.java @@ -1,148 +1,148 @@ -package com.avaje.ebeaninternal.server.query; - -import com.avaje.ebean.RawSql; -import com.avaje.ebean.config.dbplatform.DatabasePlatform; -import com.avaje.ebean.config.dbplatform.SqlLimitResponse; -import com.avaje.ebean.config.dbplatform.SqlLimiter; -import com.avaje.ebeaninternal.api.BindParams; -import com.avaje.ebeaninternal.api.SpiQuery; -import com.avaje.ebeaninternal.server.core.OrmQueryRequest; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.querydefn.OrmQueryLimitRequest; -import com.avaje.ebeaninternal.server.util.BindParamsParser; - -public class CQueryBuilderRawSql implements Constants { - - private final SqlLimiter sqlLimiter; - private final DatabasePlatform dbPlatform; - - CQueryBuilderRawSql(SqlLimiter sqlLimiter, DatabasePlatform dbPlatform) { - this.sqlLimiter = sqlLimiter; - this.dbPlatform = dbPlatform; - } - - /** - * Build the full SQL Select statement for the request. - */ - public SqlLimitResponse buildSql(OrmQueryRequest request, CQueryPredicates predicates, RawSql.Sql rsql) { - - if (rsql == null) { - // this is a ResultSet based RawSql query - just use some placeholder for the SQL - return new SqlLimitResponse("--ResultSetBasedRawSql", false); - } - - if (!rsql.isParsed()){ - String sql = rsql.getUnparsedSql(); - BindParams bindParams = request.getQuery().getBindParams(); - if (bindParams != null && bindParams.requiresNamedParamsPrepare()){ - // convert named parameters into positioned parameters - sql = BindParamsParser.parse(bindParams, sql); - } - - return new SqlLimitResponse(sql, false); - } - - String orderBy = getOrderBy(predicates, rsql); - - // build the actual sql String - String sql = buildMainQuery(orderBy, request, predicates, rsql); - - SpiQuery query = request.getQuery(); - if (query.hasMaxRowsOrFirstRow() && sqlLimiter != null) { - // wrap with a limit offset or ROW_NUMBER() etc - return sqlLimiter.limit(new OrmQueryLimitRequest(sql, orderBy, query, dbPlatform)); - - } else { - // add back select keyword (it was removed to support sqlQueryLimiter) - String prefix = "select "+ (rsql.isDistinct() ? "distinct " : ""); - sql = prefix + sql; - return new SqlLimitResponse(sql, false); - } - } - - private String buildMainQuery(String orderBy, OrmQueryRequest request, CQueryPredicates predicates, RawSql.Sql sql) { - - StringBuilder sb = new StringBuilder(); - sb.append(sql.getPreFrom()); - sb.append(" "); - - String s = sql.getPreWhere(); - BindParams bindParams = request.getQuery().getBindParams(); - if (bindParams != null && bindParams.requiresNamedParamsPrepare()){ - // convert named parameters into positioned parameters - // Named Parameters only allowed prior to dynamic where - // clause (so not allowed in having etc - use unparsed) - s = BindParamsParser.parse(bindParams, s); - } - sb.append(s); - sb.append(" "); - - String dynamicWhere = null; - if (request.getQuery().getId() != null) { - // need to convert this as well. This avoids the - // assumption that id has its proper dbColumn assigned - // which may change if using multiple raw sql statements - // against the same bean. - BeanDescriptor descriptor = request.getBeanDescriptor(); - //FIXME: I think this is broken... needs to be logical - // and then parsed for RawSqlSelect... - dynamicWhere = descriptor.getIdBinderIdSql(); - } - - String dbWhere = predicates.getDbWhere(); - if (!isEmpty(dbWhere)) { - if (dynamicWhere == null) { - dynamicWhere = dbWhere; - } else { - dynamicWhere += " and " + dbWhere; - } - } - - if (!isEmpty(dynamicWhere)) { - if (sql.isAndWhereExpr()) { - sb.append(" and "); - } else { - sb.append(" where "); - } - sb.append(dynamicWhere); - sb.append(" "); - } - - String preHaving = sql.getPreHaving(); - if (!isEmpty(preHaving)) { - sb.append(preHaving); - sb.append(" "); - } - - String dbHaving = predicates.getDbHaving(); - if (!isEmpty(dbHaving)) { - sb.append(" "); - if (sql.isAndHavingExpr()) { - sb.append("and "); - } else { - sb.append("having "); - } - sb.append(dbHaving); - sb.append(" "); - } - - if (!isEmpty(orderBy)) { - sb.append(" ").append(sql.getOrderByPrefix()).append(" ").append(orderBy); - } - - return sb.toString().trim(); - } - - private boolean isEmpty(String s) { - return s == null || s.length() == 0; - } - - private String getOrderBy(CQueryPredicates predicates, RawSql.Sql sql) { - String orderBy = predicates.getDbOrderBy(); - if (orderBy != null) { - return orderBy; - } else { - return sql.getOrderBy(); - } - } -} +package com.avaje.ebeaninternal.server.query; + +import com.avaje.ebean.RawSql; +import com.avaje.ebean.config.dbplatform.DatabasePlatform; +import com.avaje.ebean.config.dbplatform.SqlLimitResponse; +import com.avaje.ebean.config.dbplatform.SqlLimiter; +import com.avaje.ebeaninternal.api.BindParams; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryLimitRequest; +import com.avaje.ebeaninternal.server.util.BindParamsParser; + +public class CQueryBuilderRawSql implements Constants { + + private final SqlLimiter sqlLimiter; + private final DatabasePlatform dbPlatform; + + CQueryBuilderRawSql(SqlLimiter sqlLimiter, DatabasePlatform dbPlatform) { + this.sqlLimiter = sqlLimiter; + this.dbPlatform = dbPlatform; + } + + /** + * Build the full SQL Select statement for the request. + */ + public SqlLimitResponse buildSql(OrmQueryRequest request, CQueryPredicates predicates, RawSql.Sql rsql) { + + if (rsql == null) { + // this is a ResultSet based RawSql query - just use some placeholder for the SQL + return new SqlLimitResponse("--ResultSetBasedRawSql", false); + } + + if (!rsql.isParsed()){ + String sql = rsql.getUnparsedSql(); + BindParams bindParams = request.getQuery().getBindParams(); + if (bindParams != null && bindParams.requiresNamedParamsPrepare()){ + // convert named parameters into positioned parameters + sql = BindParamsParser.parse(bindParams, sql); + } + + return new SqlLimitResponse(sql, false); + } + + String orderBy = getOrderBy(predicates, rsql); + + // build the actual sql String + String sql = buildMainQuery(orderBy, request, predicates, rsql); + + SpiQuery query = request.getQuery(); + if (query.hasMaxRowsOrFirstRow() && sqlLimiter != null) { + // wrap with a limit offset or ROW_NUMBER() etc + return sqlLimiter.limit(new OrmQueryLimitRequest(sql, orderBy, query, dbPlatform)); + + } else { + // add back select keyword (it was removed to support sqlQueryLimiter) + String prefix = "select "+ (rsql.isDistinct() ? "distinct " : ""); + sql = prefix + sql; + return new SqlLimitResponse(sql, false); + } + } + + private String buildMainQuery(String orderBy, OrmQueryRequest request, CQueryPredicates predicates, RawSql.Sql sql) { + + StringBuilder sb = new StringBuilder(); + sb.append(sql.getPreFrom()); + sb.append(" "); + + String s = sql.getPreWhere(); + BindParams bindParams = request.getQuery().getBindParams(); + if (bindParams != null && bindParams.requiresNamedParamsPrepare()){ + // convert named parameters into positioned parameters + // Named Parameters only allowed prior to dynamic where + // clause (so not allowed in having etc - use unparsed) + s = BindParamsParser.parse(bindParams, s); + } + sb.append(s); + sb.append(" "); + + String dynamicWhere = null; + if (request.getQuery().getId() != null) { + // need to convert this as well. This avoids the + // assumption that id has its proper dbColumn assigned + // which may change if using multiple raw sql statements + // against the same bean. + BeanDescriptor descriptor = request.getBeanDescriptor(); + //FIXME: I think this is broken... needs to be logical + // and then parsed for RawSqlSelect... + dynamicWhere = descriptor.getIdBinderIdSql(); + } + + String dbWhere = predicates.getDbWhere(); + if (!isEmpty(dbWhere)) { + if (dynamicWhere == null) { + dynamicWhere = dbWhere; + } else { + dynamicWhere += " and " + dbWhere; + } + } + + if (!isEmpty(dynamicWhere)) { + if (sql.isAndWhereExpr()) { + sb.append(" and "); + } else { + sb.append(" where "); + } + sb.append(dynamicWhere); + sb.append(" "); + } + + String preHaving = sql.getPreHaving(); + if (!isEmpty(preHaving)) { + sb.append(preHaving); + sb.append(" "); + } + + String dbHaving = predicates.getDbHaving(); + if (!isEmpty(dbHaving)) { + sb.append(" "); + if (sql.isAndHavingExpr()) { + sb.append("and "); + } else { + sb.append("having "); + } + sb.append(dbHaving); + sb.append(" "); + } + + if (!isEmpty(orderBy)) { + sb.append(" ").append(sql.getOrderByPrefix()).append(" ").append(orderBy); + } + + return sb.toString().trim(); + } + + private boolean isEmpty(String s) { + return s == null || s.length() == 0; + } + + private String getOrderBy(CQueryPredicates predicates, RawSql.Sql sql) { + String orderBy = predicates.getDbOrderBy(); + if (orderBy != null) { + return orderBy; + } else { + return sql.getOrderBy(); + } + } +} 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 c4a829785..e186546b9 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryEngine.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryEngine.java @@ -1,344 +1,344 @@ -package com.avaje.ebeaninternal.server.query; - -import java.sql.SQLException; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.avaje.ebean.QueryIterator; -import com.avaje.ebean.bean.BeanCollection; -import com.avaje.ebean.bean.BeanCollectionTouched; -import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebean.bean.ObjectGraphNode; -import com.avaje.ebean.config.dbplatform.DatabasePlatform; -import com.avaje.ebeaninternal.api.BeanIdList; -import com.avaje.ebeaninternal.api.SpiQuery; -import com.avaje.ebeaninternal.server.core.OrmQueryRequest; -import com.avaje.ebeaninternal.server.lib.util.Str; -import com.avaje.ebeaninternal.server.persist.Binder; -import com.avaje.ebeaninternal.server.transaction.TransactionManager; - -/** - * Handles the Object Relational fetching. - */ -public class CQueryEngine { - - private static final Logger logger = LoggerFactory.getLogger(CQueryEngine.class); - - private static final int defaultSecondaryQueryBatchSize = 100; - - private final boolean forwardOnlyHintOnFindIterate; - - private final CQueryBuilder queryBuilder; - - public CQueryEngine(DatabasePlatform dbPlatform, Binder binder) { - this.forwardOnlyHintOnFindIterate = dbPlatform.isForwardOnlyHintOnFindIterate(); - this.queryBuilder = new CQueryBuilder(dbPlatform, binder); - } - - public CQuery buildQuery(OrmQueryRequest request) { - return queryBuilder.buildQuery(request); - } - - /** - * Build and execute the find Id's query. - */ - public BeanIdList findIds(OrmQueryRequest request) { - - CQueryFetchIds rcQuery = queryBuilder.buildFetchIdsQuery(request); - try { - - - BeanIdList list = rcQuery.findIds(); - - if (request.isLogSql()) { - String logSql = rcQuery.getGeneratedSql(); - if (TransactionManager.SQL_LOGGER.isTraceEnabled()) { - logSql = Str.add(logSql, "; --bind(", rcQuery.getBindLog(), ")"); - } - request.logSql(logSql); - } - - if (request.isLogSummary()) { - request.getTransaction().logSummary(rcQuery.getSummary()); - } - - if (!list.isFetchingInBackground() && request.getQuery().isFutureFetch()) { - // end the transaction for futureFindIds (it had it's own one) - logger.debug("Future findIds completed!"); - request.getTransaction().end(); - } - - return list; - - } catch (SQLException e) { - throw CQuery.createPersistenceException(e, request.getTransaction(), rcQuery.getBindLog(), rcQuery.getGeneratedSql()); - } - } - - /** - * Build and execute the row count query. - */ - public int findRowCount(OrmQueryRequest request) { - - CQueryRowCount rcQuery = queryBuilder.buildRowCountQuery(request); - try { - - int rowCount = rcQuery.findRowCount(); - - if (request.isLogSql()) { - String logSql = rcQuery.getGeneratedSql(); - if (TransactionManager.SQL_LOGGER.isTraceEnabled()) { - logSql= Str.add(logSql, "; --bind(", rcQuery.getBindLog(), ")"); - } - request.logSql(logSql); - } - - if (request.isLogSummary()) { - request.getTransaction().logSummary(rcQuery.getSummary()); - } - - if (request.getQuery().isFutureFetch()) { - logger.debug("Future findRowCount completed!"); - request.getTransaction().end(); - } - - return rowCount; - - } catch (SQLException e) { - throw CQuery.createPersistenceException(e, request.getTransaction(), rcQuery.getBindLog(), rcQuery.getGeneratedSql()); - } - } - - /** - * Read many beans using an iterator (except you need to close() the iterator - * when you have finished). - */ - public QueryIterator findIterate(OrmQueryRequest request) { - - CQuery cquery = queryBuilder.buildQuery(request); - request.setCancelableQuery(cquery); - - try { - - if (!cquery.prepareBindExecuteQueryForwardOnly(forwardOnlyHintOnFindIterate)) { - // query has been cancelled already - logger.trace("Future fetch already cancelled"); - return null; - } - - if (request.isLogSql()) { - logSql(cquery); - } - - // first check batch sizes set on query joins - int iterateBufferSize = request.getSecondaryQueriesMinBatchSize(defaultSecondaryQueryBatchSize); - if (iterateBufferSize < 1) { - // not set on query joins so check if batch size set on query itself - int queryBatch = request.getQuery().getLazyLoadBatchSize(); - if (queryBatch > 0) { - iterateBufferSize = queryBatch; - } - } - - QueryIterator readIterate = cquery.readIterate(iterateBufferSize, request); - - if (request.isLogSummary()) { - logFindManySummary(cquery); - } - - return readIterate; - - } catch (SQLException e) { - throw cquery.createPersistenceException(e); - } - } - - /** - * Find a list/map/set of beans. - */ - public BeanCollection findMany(OrmQueryRequest request) { - - CQuery cquery = queryBuilder.buildQuery(request); - request.setCancelableQuery(cquery); - - try { - if (!cquery.prepareBindExecuteQuery()) { - // query has been cancelled already - logger.trace("Future fetch already cancelled"); - return null; - } - - if (request.isLogSql()) { - logSql(cquery); - } - - BeanCollection beanCollection = cquery.readCollection(); - - BeanCollectionTouched collectionTouched = request.getQuery().getBeanCollectionTouched(); - if (collectionTouched != null) { - // register a listener that wants to be notified when the - // bean collection is first used - beanCollection.setBeanCollectionTouched(collectionTouched); - } - - if (request.isLogSummary()) { - logFindManySummary(cquery); - } - - request.executeSecondaryQueries(); - - return beanCollection; - - } catch (SQLException e) { - throw cquery.createPersistenceException(e); - - } finally { - if (cquery != null) { - cquery.close(); - } - if (request.getQuery().isFutureFetch()) { - // end the transaction for futureFindIds - // as it had it's own transaction - logger.debug("Future fetch completed!"); - request.getTransaction().end(); - } - } - } - - /** - * Find and return a single bean using its unique id. - */ - @SuppressWarnings("unchecked") - public T find(OrmQueryRequest request) { - - EntityBean bean = null; - - CQuery cquery = queryBuilder.buildQuery(request); - - try { - cquery.prepareBindExecuteQuery(); - - if (request.isLogSql()) { - logSql(cquery); - } - - if (cquery.readBean()) { - bean = cquery.getLoadedBean(); - } - - if (request.isLogSummary()) { - logFindBeanSummary(cquery); - } - - request.executeSecondaryQueries(); - - return (T)bean; - - } catch (SQLException e) { - throw cquery.createPersistenceException(e); - - } finally { - cquery.close(); - } - } - - /** - * Log the generated SQL to the transaction log. - */ - private void logSql(CQuery query) { - - String sql = query.getGeneratedSql(); - if (TransactionManager.SQL_LOGGER.isTraceEnabled()) { - sql= Str.add(sql, "; --bind(", query.getBindLog(), ")"); - } - query.getTransaction().logSql(sql); - } - - /** - * Log the FindById summary to the transaction log. - */ - private void logFindBeanSummary(CQuery q) { - - SpiQuery query = q.getQueryRequest().getQuery(); - String loadMode = query.getLoadMode(); - String loadDesc = query.getLoadDescription(); - String lazyLoadProp = query.getLazyLoadProperty(); - ObjectGraphNode node = query.getParentNode(); - String originKey; - if (node == null || node.getOriginQueryPoint() == null) { - originKey = null; - } else { - originKey = node.getOriginQueryPoint().getKey(); - } - - StringBuilder msg = new StringBuilder(200); - msg.append("FindBean "); - if (loadMode != null) { - msg.append("mode[").append(loadMode).append("] "); - } - msg.append("type[").append(q.getBeanName()).append("] "); - if (query.isAutofetchTuned()) { - msg.append("tuned[true] "); - } - if (originKey != null) { - msg.append("origin[").append(originKey).append("] "); - } - if (lazyLoadProp != null) { - msg.append("lazyLoadProp[").append(lazyLoadProp).append("] "); - } - if (loadDesc != null) { - msg.append("load[").append(loadDesc).append("] "); - } - msg.append("exeMicros[").append(q.getQueryExecutionTimeMicros()); - msg.append("] rows[").append(q.getLoadedRowDetail()); - msg.append("] bind[").append(q.getBindLog()).append("]"); - - q.getTransaction().logSummary(msg.toString()); - } - - /** - * Log the FindMany to the transaction log. - */ - private void logFindManySummary(CQuery q) { - - SpiQuery query = q.getQueryRequest().getQuery(); - String loadMode = query.getLoadMode(); - String loadDesc = query.getLoadDescription(); - String lazyLoadProp = query.getLazyLoadProperty(); - ObjectGraphNode node = query.getParentNode(); - - String originKey; - if (node == null || node.getOriginQueryPoint() == null) { - originKey = null; - } else { - originKey = node.getOriginQueryPoint().getKey(); - } - - StringBuilder msg = new StringBuilder(200); - msg.append("FindMany "); - if (loadMode != null) { - msg.append("mode[").append(loadMode).append("] "); - } - msg.append("type[").append(q.getBeanName()).append("] "); - if (query.isAutofetchTuned()) { - msg.append("tuned[true] "); - } - if (originKey != null) { - msg.append("origin[").append(originKey).append("] "); - } - if (lazyLoadProp != null) { - msg.append("lazyLoadProp[").append(lazyLoadProp).append("] "); - } - if (loadDesc != null) { - msg.append("load[").append(loadDesc).append("] "); - } - msg.append("exeMicros[").append(q.getQueryExecutionTimeMicros()); - msg.append("] rows[").append(q.getLoadedRowDetail()); - msg.append("] name[").append(q.getName()); - msg.append("] predicates[").append(q.getLogWhereSql()); - msg.append("] bind[").append(q.getBindLog()).append("]"); - - q.getTransaction().logSummary(msg.toString()); - } -} +package com.avaje.ebeaninternal.server.query; + +import java.sql.SQLException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.avaje.ebean.QueryIterator; +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebean.bean.BeanCollectionTouched; +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.bean.ObjectGraphNode; +import com.avaje.ebean.config.dbplatform.DatabasePlatform; +import com.avaje.ebeaninternal.api.BeanIdList; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.lib.util.Str; +import com.avaje.ebeaninternal.server.persist.Binder; +import com.avaje.ebeaninternal.server.transaction.TransactionManager; + +/** + * Handles the Object Relational fetching. + */ +public class CQueryEngine { + + private static final Logger logger = LoggerFactory.getLogger(CQueryEngine.class); + + private static final int defaultSecondaryQueryBatchSize = 100; + + private final boolean forwardOnlyHintOnFindIterate; + + private final CQueryBuilder queryBuilder; + + public CQueryEngine(DatabasePlatform dbPlatform, Binder binder) { + this.forwardOnlyHintOnFindIterate = dbPlatform.isForwardOnlyHintOnFindIterate(); + this.queryBuilder = new CQueryBuilder(dbPlatform, binder); + } + + public CQuery buildQuery(OrmQueryRequest request) { + return queryBuilder.buildQuery(request); + } + + /** + * Build and execute the find Id's query. + */ + public BeanIdList findIds(OrmQueryRequest request) { + + CQueryFetchIds rcQuery = queryBuilder.buildFetchIdsQuery(request); + try { + + + BeanIdList list = rcQuery.findIds(); + + if (request.isLogSql()) { + String logSql = rcQuery.getGeneratedSql(); + if (TransactionManager.SQL_LOGGER.isTraceEnabled()) { + logSql = Str.add(logSql, "; --bind(", rcQuery.getBindLog(), ")"); + } + request.logSql(logSql); + } + + if (request.isLogSummary()) { + request.getTransaction().logSummary(rcQuery.getSummary()); + } + + if (!list.isFetchingInBackground() && request.getQuery().isFutureFetch()) { + // end the transaction for futureFindIds (it had it's own one) + logger.debug("Future findIds completed!"); + request.getTransaction().end(); + } + + return list; + + } catch (SQLException e) { + throw CQuery.createPersistenceException(e, request.getTransaction(), rcQuery.getBindLog(), rcQuery.getGeneratedSql()); + } + } + + /** + * Build and execute the row count query. + */ + public int findRowCount(OrmQueryRequest request) { + + CQueryRowCount rcQuery = queryBuilder.buildRowCountQuery(request); + try { + + int rowCount = rcQuery.findRowCount(); + + if (request.isLogSql()) { + String logSql = rcQuery.getGeneratedSql(); + if (TransactionManager.SQL_LOGGER.isTraceEnabled()) { + logSql= Str.add(logSql, "; --bind(", rcQuery.getBindLog(), ")"); + } + request.logSql(logSql); + } + + if (request.isLogSummary()) { + request.getTransaction().logSummary(rcQuery.getSummary()); + } + + if (request.getQuery().isFutureFetch()) { + logger.debug("Future findRowCount completed!"); + request.getTransaction().end(); + } + + return rowCount; + + } catch (SQLException e) { + throw CQuery.createPersistenceException(e, request.getTransaction(), rcQuery.getBindLog(), rcQuery.getGeneratedSql()); + } + } + + /** + * Read many beans using an iterator (except you need to close() the iterator + * when you have finished). + */ + public QueryIterator findIterate(OrmQueryRequest request) { + + CQuery cquery = queryBuilder.buildQuery(request); + request.setCancelableQuery(cquery); + + try { + + if (!cquery.prepareBindExecuteQueryForwardOnly(forwardOnlyHintOnFindIterate)) { + // query has been cancelled already + logger.trace("Future fetch already cancelled"); + return null; + } + + if (request.isLogSql()) { + logSql(cquery); + } + + // first check batch sizes set on query joins + int iterateBufferSize = request.getSecondaryQueriesMinBatchSize(defaultSecondaryQueryBatchSize); + if (iterateBufferSize < 1) { + // not set on query joins so check if batch size set on query itself + int queryBatch = request.getQuery().getLazyLoadBatchSize(); + if (queryBatch > 0) { + iterateBufferSize = queryBatch; + } + } + + QueryIterator readIterate = cquery.readIterate(iterateBufferSize, request); + + if (request.isLogSummary()) { + logFindManySummary(cquery); + } + + return readIterate; + + } catch (SQLException e) { + throw cquery.createPersistenceException(e); + } + } + + /** + * Find a list/map/set of beans. + */ + public BeanCollection findMany(OrmQueryRequest request) { + + CQuery cquery = queryBuilder.buildQuery(request); + request.setCancelableQuery(cquery); + + try { + if (!cquery.prepareBindExecuteQuery()) { + // query has been cancelled already + logger.trace("Future fetch already cancelled"); + return null; + } + + if (request.isLogSql()) { + logSql(cquery); + } + + BeanCollection beanCollection = cquery.readCollection(); + + BeanCollectionTouched collectionTouched = request.getQuery().getBeanCollectionTouched(); + if (collectionTouched != null) { + // register a listener that wants to be notified when the + // bean collection is first used + beanCollection.setBeanCollectionTouched(collectionTouched); + } + + if (request.isLogSummary()) { + logFindManySummary(cquery); + } + + request.executeSecondaryQueries(); + + return beanCollection; + + } catch (SQLException e) { + throw cquery.createPersistenceException(e); + + } finally { + if (cquery != null) { + cquery.close(); + } + if (request.getQuery().isFutureFetch()) { + // end the transaction for futureFindIds + // as it had it's own transaction + logger.debug("Future fetch completed!"); + request.getTransaction().end(); + } + } + } + + /** + * Find and return a single bean using its unique id. + */ + @SuppressWarnings("unchecked") + public T find(OrmQueryRequest request) { + + EntityBean bean = null; + + CQuery cquery = queryBuilder.buildQuery(request); + + try { + cquery.prepareBindExecuteQuery(); + + if (request.isLogSql()) { + logSql(cquery); + } + + if (cquery.readBean()) { + bean = cquery.getLoadedBean(); + } + + if (request.isLogSummary()) { + logFindBeanSummary(cquery); + } + + request.executeSecondaryQueries(); + + return (T)bean; + + } catch (SQLException e) { + throw cquery.createPersistenceException(e); + + } finally { + cquery.close(); + } + } + + /** + * Log the generated SQL to the transaction log. + */ + private void logSql(CQuery query) { + + String sql = query.getGeneratedSql(); + if (TransactionManager.SQL_LOGGER.isTraceEnabled()) { + sql= Str.add(sql, "; --bind(", query.getBindLog(), ")"); + } + query.getTransaction().logSql(sql); + } + + /** + * Log the FindById summary to the transaction log. + */ + private void logFindBeanSummary(CQuery q) { + + SpiQuery query = q.getQueryRequest().getQuery(); + String loadMode = query.getLoadMode(); + String loadDesc = query.getLoadDescription(); + String lazyLoadProp = query.getLazyLoadProperty(); + ObjectGraphNode node = query.getParentNode(); + String originKey; + if (node == null || node.getOriginQueryPoint() == null) { + originKey = null; + } else { + originKey = node.getOriginQueryPoint().getKey(); + } + + StringBuilder msg = new StringBuilder(200); + msg.append("FindBean "); + if (loadMode != null) { + msg.append("mode[").append(loadMode).append("] "); + } + msg.append("type[").append(q.getBeanName()).append("] "); + if (query.isAutofetchTuned()) { + msg.append("tuned[true] "); + } + if (originKey != null) { + msg.append("origin[").append(originKey).append("] "); + } + if (lazyLoadProp != null) { + msg.append("lazyLoadProp[").append(lazyLoadProp).append("] "); + } + if (loadDesc != null) { + msg.append("load[").append(loadDesc).append("] "); + } + msg.append("exeMicros[").append(q.getQueryExecutionTimeMicros()); + msg.append("] rows[").append(q.getLoadedRowDetail()); + msg.append("] bind[").append(q.getBindLog()).append("]"); + + q.getTransaction().logSummary(msg.toString()); + } + + /** + * Log the FindMany to the transaction log. + */ + private void logFindManySummary(CQuery q) { + + SpiQuery query = q.getQueryRequest().getQuery(); + String loadMode = query.getLoadMode(); + String loadDesc = query.getLoadDescription(); + String lazyLoadProp = query.getLazyLoadProperty(); + ObjectGraphNode node = query.getParentNode(); + + String originKey; + if (node == null || node.getOriginQueryPoint() == null) { + originKey = null; + } else { + originKey = node.getOriginQueryPoint().getKey(); + } + + StringBuilder msg = new StringBuilder(200); + msg.append("FindMany "); + if (loadMode != null) { + msg.append("mode[").append(loadMode).append("] "); + } + msg.append("type[").append(q.getBeanName()).append("] "); + if (query.isAutofetchTuned()) { + msg.append("tuned[true] "); + } + if (originKey != null) { + msg.append("origin[").append(originKey).append("] "); + } + if (lazyLoadProp != null) { + msg.append("lazyLoadProp[").append(lazyLoadProp).append("] "); + } + if (loadDesc != null) { + msg.append("load[").append(loadDesc).append("] "); + } + msg.append("exeMicros[").append(q.getQueryExecutionTimeMicros()); + msg.append("] rows[").append(q.getLoadedRowDetail()); + msg.append("] name[").append(q.getName()); + msg.append("] predicates[").append(q.getLogWhereSql()); + msg.append("] bind[").append(q.getBindLog()).append("]"); + + q.getTransaction().logSummary(msg.toString()); + } +} 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 bed8f42ef..fd76b783f 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryFetchIds.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryFetchIds.java @@ -1,283 +1,283 @@ -package com.avaje.ebeaninternal.server.query; - -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; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -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.core.SpiOrmQueryRequest; -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.DataBind; -import com.avaje.ebeaninternal.server.type.DataReader; -import com.avaje.ebeaninternal.server.type.RsetDataReader; - -/** - * 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 long startNano; - - private int executionTimeMicros; - - private int rowCount; - - private final int maxRows; - - /** - * 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; - - } - - /** - * Return a summary description of this query. - */ - public String getSummary() { - StringBuilder sb = new StringBuilder(); - sb.append("FindIds 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(); - } - - /** - * Return the bind log. - */ - public String getBindLog() { - return bindLog; - } - - /** - * Return the generated sql. - */ - public String getGeneratedSql() { - return sql; - } - - public SpiOrmQueryRequest getQueryRequest() { - return request; - } - - /** - * Execute the query returning the row count. - */ - public BeanIdList findIds() throws SQLException { - - startNano = System.nanoTime(); - - try { - // get the list that we are going to put the id's into. - // This was already set so that it is available to be - // read by other threads (it is a synchronised list) - List idList = query.getIdList(); - if (idList == null){ - // running in foreground thread (not FutureIds query) - idList = Collections.synchronizedList(new ArrayList()); - query.setIdList(idList); - } - - 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(new DataBind(pstmt)); - - ResultSet rset = pstmt.executeQuery(); - dataReader = new RsetDataReader(rset); - - boolean hitMaxRows = false; - boolean hasMoreRows = false; - rowCount = 0; - - DbReadContext ctx = new DbContext(); - - while (rset.next()){ - Object idValue = desc.getIdBinder().read(ctx); - idList.add(idValue); - // reset back to 0 - dataReader.resetColumnPosition(); - rowCount++; - - if (maxRows > 0 && rowCount == maxRows) { - hitMaxRows = true; - hasMoreRows = rset.next(); - break; - - } - } - - if (hitMaxRows){ - result.setHasMore(hasMoreRows); - } - - long exeNano = System.nanoTime() - startNano; - executionTimeMicros = (int)exeNano/1000; - - return result; - - } finally { - close(); - } - } - - /** - * 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(null, e); - } - try { - if (pstmt != null) { - pstmt.close(); - pstmt = null; - } - } catch (SQLException e) { - logger.error(null, 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; - } - - 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 isAutoFetchProfiling() { - return false; - } - - public void profileBean(EntityBeanIntercept ebi, String prefix) { - // no-op - } - - public void setCurrentPrefix(String currentPrefix,Map pathMap) { - // no-op - } - - public void setLoadedBean(EntityBean loadedBean, Object id, Object lazyLoadParentId) { - // no-op - } - - public void setLoadedManyBean(EntityBean loadedBean) { - // no-op - } - - } - -} +package com.avaje.ebeaninternal.server.query; + +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; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +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.core.SpiOrmQueryRequest; +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.DataBind; +import com.avaje.ebeaninternal.server.type.DataReader; +import com.avaje.ebeaninternal.server.type.RsetDataReader; + +/** + * 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 long startNano; + + private int executionTimeMicros; + + private int rowCount; + + private final int maxRows; + + /** + * 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; + + } + + /** + * Return a summary description of this query. + */ + public String getSummary() { + StringBuilder sb = new StringBuilder(); + sb.append("FindIds 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(); + } + + /** + * Return the bind log. + */ + public String getBindLog() { + return bindLog; + } + + /** + * Return the generated sql. + */ + public String getGeneratedSql() { + return sql; + } + + public SpiOrmQueryRequest getQueryRequest() { + return request; + } + + /** + * Execute the query returning the row count. + */ + public BeanIdList findIds() throws SQLException { + + startNano = System.nanoTime(); + + try { + // get the list that we are going to put the id's into. + // This was already set so that it is available to be + // read by other threads (it is a synchronised list) + List idList = query.getIdList(); + if (idList == null){ + // running in foreground thread (not FutureIds query) + idList = Collections.synchronizedList(new ArrayList()); + query.setIdList(idList); + } + + 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(new DataBind(pstmt)); + + ResultSet rset = pstmt.executeQuery(); + dataReader = new RsetDataReader(rset); + + boolean hitMaxRows = false; + boolean hasMoreRows = false; + rowCount = 0; + + DbReadContext ctx = new DbContext(); + + while (rset.next()){ + Object idValue = desc.getIdBinder().read(ctx); + idList.add(idValue); + // reset back to 0 + dataReader.resetColumnPosition(); + rowCount++; + + if (maxRows > 0 && rowCount == maxRows) { + hitMaxRows = true; + hasMoreRows = rset.next(); + break; + + } + } + + if (hitMaxRows){ + result.setHasMore(hasMoreRows); + } + + long exeNano = System.nanoTime() - startNano; + executionTimeMicros = (int)exeNano/1000; + + return result; + + } finally { + close(); + } + } + + /** + * 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(null, e); + } + try { + if (pstmt != null) { + pstmt.close(); + pstmt = null; + } + } catch (SQLException e) { + logger.error(null, 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; + } + + 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 isAutoFetchProfiling() { + return false; + } + + public void profileBean(EntityBeanIntercept ebi, String prefix) { + // no-op + } + + public void setCurrentPrefix(String currentPrefix,Map pathMap) { + // no-op + } + + public void setLoadedBean(EntityBean loadedBean, Object id, Object lazyLoadParentId) { + // no-op + } + + public void setLoadedManyBean(EntityBean loadedBean) { + // no-op + } + + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryIteratorSimple.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryIteratorSimple.java index 9e72d8760..a0fea37c3 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryIteratorSimple.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryIteratorSimple.java @@ -1,46 +1,46 @@ -package com.avaje.ebeaninternal.server.query; - -import java.sql.SQLException; - -import javax.persistence.PersistenceException; - -import com.avaje.ebean.QueryIterator; -import com.avaje.ebeaninternal.server.core.OrmQueryRequest; - -/** - * QueryIterator that does not require a buffer for secondary queries. - */ -class CQueryIteratorSimple implements QueryIterator { - - private final CQuery cquery; - private final OrmQueryRequest request; - - CQueryIteratorSimple(CQuery cquery, OrmQueryRequest request) { - this.cquery = cquery; - this.request = request; - } - - public boolean hasNext() { - try { - request.flushPersistenceContextOnIterate(); - return cquery.hasNextBean(); - } catch (SQLException e) { - throw cquery.createPersistenceException(e); - } - } - - @SuppressWarnings("unchecked") - public T next() { - return (T)cquery.getLoadedBean(); - } - - public void close() { - cquery.updateExecutionStatistics(); - cquery.close(); - request.endTransIfRequired(); - } - - public void remove() { - throw new PersistenceException("Remove not allowed"); - } +package com.avaje.ebeaninternal.server.query; + +import java.sql.SQLException; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.QueryIterator; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; + +/** + * QueryIterator that does not require a buffer for secondary queries. + */ +class CQueryIteratorSimple implements QueryIterator { + + private final CQuery cquery; + private final OrmQueryRequest request; + + CQueryIteratorSimple(CQuery cquery, OrmQueryRequest request) { + this.cquery = cquery; + this.request = request; + } + + public boolean hasNext() { + try { + request.flushPersistenceContextOnIterate(); + return cquery.hasNextBean(); + } catch (SQLException e) { + throw cquery.createPersistenceException(e); + } + } + + @SuppressWarnings("unchecked") + public T next() { + return (T)cquery.getLoadedBean(); + } + + public void close() { + cquery.updateExecutionStatistics(); + cquery.close(); + request.endTransIfRequired(); + } + + public void remove() { + throw new PersistenceException("Remove not allowed"); + } } \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryIteratorWithBuffer.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryIteratorWithBuffer.java index ac7ebc3a6..7d39b14e5 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryIteratorWithBuffer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryIteratorWithBuffer.java @@ -1,68 +1,68 @@ -package com.avaje.ebeaninternal.server.query; - -import java.sql.SQLException; -import java.util.ArrayList; - -import javax.persistence.PersistenceException; - -import com.avaje.ebean.QueryIterator; -import com.avaje.ebeaninternal.server.core.OrmQueryRequest; - -/** - * A QueryIterator that uses a buffer to execute secondary queries periodically. - */ -class CQueryIteratorWithBuffer implements QueryIterator { - - private final CQuery cquery; - private final int bufferSize; - private final OrmQueryRequest request; - private final ArrayList buffer; - - private boolean moreToLoad = true; - - CQueryIteratorWithBuffer(CQuery cquery, OrmQueryRequest request, int bufferSize) { - this.cquery = cquery; - this.request = request; - this.bufferSize = bufferSize; - this.buffer = new ArrayList(bufferSize); - } - - @SuppressWarnings("unchecked") - public boolean hasNext() { - try { - if (buffer.isEmpty() && moreToLoad) { - // load buffer - request.flushPersistenceContextOnIterate(); - - int i = -1; - while (moreToLoad && ++i < bufferSize) { - if (cquery.hasNextBean()) { - buffer.add((T)cquery.getLoadedBean()); - } else { - moreToLoad = false; - } - } - // execute secondary queries - request.executeSecondaryQueries(); - } - return !buffer.isEmpty(); - - } catch (SQLException e) { - throw cquery.createPersistenceException(e); - } - } - - public T next() { - return buffer.remove(0); - } - - public void close() { - cquery.updateExecutionStatistics(); - cquery.close(); - request.endTransIfRequired(); - } - - public void remove() { - throw new PersistenceException("Remove not allowed"); - } +package com.avaje.ebeaninternal.server.query; + +import java.sql.SQLException; +import java.util.ArrayList; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.QueryIterator; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; + +/** + * A QueryIterator that uses a buffer to execute secondary queries periodically. + */ +class CQueryIteratorWithBuffer implements QueryIterator { + + private final CQuery cquery; + private final int bufferSize; + private final OrmQueryRequest request; + private final ArrayList buffer; + + private boolean moreToLoad = true; + + CQueryIteratorWithBuffer(CQuery cquery, OrmQueryRequest request, int bufferSize) { + this.cquery = cquery; + this.request = request; + this.bufferSize = bufferSize; + this.buffer = new ArrayList(bufferSize); + } + + @SuppressWarnings("unchecked") + public boolean hasNext() { + try { + if (buffer.isEmpty() && moreToLoad) { + // load buffer + request.flushPersistenceContextOnIterate(); + + int i = -1; + while (moreToLoad && ++i < bufferSize) { + if (cquery.hasNextBean()) { + buffer.add((T)cquery.getLoadedBean()); + } else { + moreToLoad = false; + } + } + // execute secondary queries + request.executeSecondaryQueries(); + } + return !buffer.isEmpty(); + + } catch (SQLException e) { + throw cquery.createPersistenceException(e); + } + } + + public T next() { + return buffer.remove(0); + } + + public void close() { + cquery.updateExecutionStatistics(); + cquery.close(); + request.endTransIfRequired(); + } + + public void remove() { + throw new PersistenceException("Remove not allowed"); + } } \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryOrderBy.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryOrderBy.java index aa985670c..4b790f5e6 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryOrderBy.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryOrderBy.java @@ -1,77 +1,77 @@ -package com.avaje.ebeaninternal.server.query; - -import java.util.List; - -import com.avaje.ebean.OrderBy; -import com.avaje.ebean.OrderBy.Property; -import com.avaje.ebeaninternal.api.SpiQuery; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.deploy.BeanProperty; -import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc; -import com.avaje.ebeaninternal.server.deploy.id.IdBinder; -import com.avaje.ebeaninternal.server.el.ElPropertyValue; - -/** - * Creates the order by expression clause. - */ -public class CQueryOrderBy { - - private final BeanDescriptor desc; - - private final SpiQuery query; - - /** - * Create the logical order by clause. - */ - public static String parse(BeanDescriptor desc, SpiQuery query) { - return new CQueryOrderBy(desc, query).parseInternal(); - } - - private CQueryOrderBy(BeanDescriptor desc, SpiQuery query) { - this.desc = desc; - this.query = query; - } - - private String parseInternal() { - - OrderBy orderBy = query.getOrderBy(); - if (orderBy == null){ - return null; - } - - StringBuilder sb = new StringBuilder(); - - List properties = orderBy.getProperties(); - if (properties.isEmpty()){ - // order by clause removed by filterMany() - return null; - } - for (int i = 0; i < properties.size(); i++) { - if (i > 0){ - sb.append(", "); - } - Property p = properties.get(i); - String expression = parseProperty(p); - sb.append(expression); - } - return sb.toString(); - } - - private String parseProperty(Property p) { - - String propName = p.getProperty(); - ElPropertyValue el = desc.getElGetValue(propName); - if (el == null){ - return p.toStringFormat(); - } - - BeanProperty beanProperty = el.getBeanProperty(); - if (beanProperty instanceof BeanPropertyAssoc){ - BeanPropertyAssoc ap = (BeanPropertyAssoc)beanProperty; - IdBinder idBinder = ap.getTargetDescriptor().getIdBinder(); - return idBinder.getOrderBy(el.getElName(), p.isAscending()); - } - - return p.toStringFormat(); - } -} +package com.avaje.ebeaninternal.server.query; + +import java.util.List; + +import com.avaje.ebean.OrderBy; +import com.avaje.ebean.OrderBy.Property; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc; +import com.avaje.ebeaninternal.server.deploy.id.IdBinder; +import com.avaje.ebeaninternal.server.el.ElPropertyValue; + +/** + * Creates the order by expression clause. + */ +public class CQueryOrderBy { + + private final BeanDescriptor desc; + + private final SpiQuery query; + + /** + * Create the logical order by clause. + */ + public static String parse(BeanDescriptor desc, SpiQuery query) { + return new CQueryOrderBy(desc, query).parseInternal(); + } + + private CQueryOrderBy(BeanDescriptor desc, SpiQuery query) { + this.desc = desc; + this.query = query; + } + + private String parseInternal() { + + OrderBy orderBy = query.getOrderBy(); + if (orderBy == null){ + return null; + } + + StringBuilder sb = new StringBuilder(); + + List properties = orderBy.getProperties(); + if (properties.isEmpty()){ + // order by clause removed by filterMany() + return null; + } + for (int i = 0; i < properties.size(); i++) { + if (i > 0){ + sb.append(", "); + } + Property p = properties.get(i); + String expression = parseProperty(p); + sb.append(expression); + } + return sb.toString(); + } + + private String parseProperty(Property p) { + + String propName = p.getProperty(); + ElPropertyValue el = desc.getElGetValue(propName); + if (el == null){ + return p.toStringFormat(); + } + + BeanProperty beanProperty = el.getBeanProperty(); + if (beanProperty instanceof BeanPropertyAssoc){ + BeanPropertyAssoc ap = (BeanPropertyAssoc)beanProperty; + IdBinder idBinder = ap.getTargetDescriptor().getIdBinder(); + return idBinder.getOrderBy(el.getElName(), p.isAscending()); + } + + return p.toStringFormat(); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlanRawSql.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlanRawSql.java index d4db15cfc..21083b027 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlanRawSql.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlanRawSql.java @@ -1,54 +1,54 @@ -package com.avaje.ebeaninternal.server.query; - -import java.sql.ResultSet; -import java.util.List; - -import com.avaje.ebean.RawSql.ColumnMapping; -import com.avaje.ebean.config.dbplatform.SqlLimitResponse; -import com.avaje.ebeaninternal.server.core.OrmQueryRequest; -import com.avaje.ebeaninternal.server.deploy.InheritInfo; -import com.avaje.ebeaninternal.server.type.DataReader; -import com.avaje.ebeaninternal.server.type.RsetDataReaderIndexed; - -/** - * RawSql based query plan. - */ -public class CQueryPlanRawSql extends CQueryPlan { - - private final int[] rsetIndexPositions; - - public CQueryPlanRawSql(OrmQueryRequest request, SqlLimitResponse sqlRes, SqlTree sqlTree, String logWhereSql) { - - super(request, sqlRes, sqlTree, true, logWhereSql); - - this.rsetIndexPositions = createIndexPositions(request, sqlTree); - } - - public DataReader createDataReader(ResultSet rset) { - - return new RsetDataReaderIndexed(rset, rsetIndexPositions, isRowNumberIncluded()); - } - - private int[] createIndexPositions(OrmQueryRequest request, SqlTree sqlTree) { - - List chain = sqlTree.buildSelectExpressionChain(); - ColumnMapping columnMapping = request.getQuery().getRawSql().getColumnMapping(); - - InheritInfo inheritInfo = request.getBeanDescriptor().getInheritInfo(); - boolean addDiscriminator = inheritInfo != null; - int offset = addDiscriminator ? 1 : 0; - - int[] indexPositions = new int[chain.size() + offset]; - if (addDiscriminator) { - // discriminator column must always be first in the query - indexPositions[0] = 1; - } - for (int i = 0; i < chain.size(); i++) { - String expr = chain.get(i); - int indexPos = 1 + columnMapping.getIndexPosition(expr); - indexPositions[i + offset] = indexPos; - } - - return indexPositions; - } -} +package com.avaje.ebeaninternal.server.query; + +import java.sql.ResultSet; +import java.util.List; + +import com.avaje.ebean.RawSql.ColumnMapping; +import com.avaje.ebean.config.dbplatform.SqlLimitResponse; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.deploy.InheritInfo; +import com.avaje.ebeaninternal.server.type.DataReader; +import com.avaje.ebeaninternal.server.type.RsetDataReaderIndexed; + +/** + * RawSql based query plan. + */ +public class CQueryPlanRawSql extends CQueryPlan { + + private final int[] rsetIndexPositions; + + public CQueryPlanRawSql(OrmQueryRequest request, SqlLimitResponse sqlRes, SqlTree sqlTree, String logWhereSql) { + + super(request, sqlRes, sqlTree, true, logWhereSql); + + this.rsetIndexPositions = createIndexPositions(request, sqlTree); + } + + public DataReader createDataReader(ResultSet rset) { + + return new RsetDataReaderIndexed(rset, rsetIndexPositions, isRowNumberIncluded()); + } + + private int[] createIndexPositions(OrmQueryRequest request, SqlTree sqlTree) { + + List chain = sqlTree.buildSelectExpressionChain(); + ColumnMapping columnMapping = request.getQuery().getRawSql().getColumnMapping(); + + InheritInfo inheritInfo = request.getBeanDescriptor().getInheritInfo(); + boolean addDiscriminator = inheritInfo != null; + int offset = addDiscriminator ? 1 : 0; + + int[] indexPositions = new int[chain.size() + offset]; + if (addDiscriminator) { + // discriminator column must always be first in the query + indexPositions[0] = 1; + } + for (int i = 0; i < chain.size(); i++) { + String expr = chain.get(i); + int indexPos = 1 + columnMapping.getIndexPosition(expr); + indexPositions[i + offset] = indexPos; + } + + return indexPositions; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java index 8ec32e4a1..21853340f 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java @@ -1,575 +1,575 @@ -package com.avaje.ebeaninternal.server.query; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Set; - -import com.avaje.ebean.RawSql; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.avaje.ebeaninternal.api.BindParams; -import com.avaje.ebeaninternal.api.BindParams.OrderedList; -import com.avaje.ebeaninternal.api.SpiExpressionList; -import com.avaje.ebeaninternal.api.SpiQuery; -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.DeployParser; -import com.avaje.ebeaninternal.server.persist.Binder; -import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties; -import com.avaje.ebeaninternal.server.type.DataBind; -import com.avaje.ebeaninternal.server.util.BindParamsParser; -import com.avaje.ebeaninternal.util.DefaultExpressionRequest; - -/** - * Compile Query Predicates. - *

- * This includes the where and having expressions which can be made up of - * Strings with named parameters or Expression objects. - *

- *

- * This builds the appropriate bits of where and having clauses and binds the - * named parameters and expression values into the prepared statement. - *

- */ -public class CQueryPredicates { - - private static final Logger logger = LoggerFactory.getLogger(CQueryPredicates.class); - - private final Binder binder; - - private final OrmQueryRequest request; - - private final SpiQuery query; - - private final Object idValue; - - /** - * Flag set if this is a SqlSelect type query. - */ - private boolean rawSql; - - /** - * Named bind parameters. - */ - private final BindParams bindParams; - - /** - * Named bind parameters for the having clause. - */ - private OrderedList havingNamedParams; - - /** - * Bind values from the where expressions. - */ - private ArrayList filterManyExprBindValues; - - /** - * SQL generated from the where expressions. - */ - private String filterManyExprSql; - - /** - * Bind values from the where expressions. - */ - private ArrayList whereExprBindValues; - - /** - * SQL generated from the where expressions. - */ - private String whereExprSql; - - /** - * SQL generated from where with named parameters. - */ - private String whereRawSql; - - /** - * Bind values for having expression. - */ - private ArrayList havingExprBindValues; - - /** - * SQL generated from the having expression. - */ - private String havingExprSql; - - /** - * SQL generated from having with named parameters. - */ - private String havingRawSql; - - private String dbHaving; - - /** - * logicalWhere with property names converted to db columns. - */ - private String dbWhere; - - /** - * Filter than can apply to a many fetch join. - */ - private String dbFilterMany; - - /** - * The order by clause. - */ - private String logicalOrderBy; - - private String dbOrderBy; - - /** - * Includes from where and order by clauses. - */ - private Set predicateIncludes; - - private Set orderByIncludes; - - public CQueryPredicates(Binder binder, OrmQueryRequest request) { - this.binder = binder; - this.request = request; - this.query = request.getQuery(); - this.bindParams = query.getBindParams(); - this.idValue = query.getId(); - } - - public String bind(DataBind dataBind) throws SQLException { - - StringBuilder bindLog = new StringBuilder(); - - if (idValue != null) { - // this is a find by id type query... - request.getBeanDescriptor().bindId(dataBind, idValue); - bindLog.append(idValue); - } - - if (bindParams != null) { - // bind named and positioned parameters... - binder.bind(bindParams, dataBind, bindLog); - } - - if (whereExprBindValues != null) { - - for (int i = 0; i < whereExprBindValues.size(); i++) { - Object bindValue = whereExprBindValues.get(i); - binder.bindObject(dataBind, bindValue); - if (i > 0 || idValue != null) { - bindLog.append(","); - } - bindLog.append(bindValue); - } - } - - if (filterManyExprBindValues != null) { - - for (int i = 0; i < filterManyExprBindValues.size(); i++) { - Object bindValue = filterManyExprBindValues.get(i); - binder.bindObject(dataBind, bindValue); - if (i > 0 || idValue != null) { - bindLog.append(","); - } - bindLog.append(bindValue); - } - } - - if (havingNamedParams != null) { - // bind named parameters in having... - bindLog.append(" havingNamed "); - binder.bind(havingNamedParams.list(), dataBind, bindLog); - } - - if (havingExprBindValues != null) { - // bind having expression... - bindLog.append(" having "); - for (int i = 0; i < havingExprBindValues.size(); i++) { - Object bindValue = havingExprBindValues.get(i); - binder.bindObject(dataBind, bindValue); - if (i > 0) { - bindLog.append(","); - } - bindLog.append(bindValue); - } - } - - return bindLog.toString(); - } - - private void buildBindHavingRawSql(boolean buildSql, boolean parseRaw, DeployParser deployParser) { - if (buildSql || bindParams != null) { - // having clause with named parameters... - havingRawSql = query.getAdditionalHaving(); - if (parseRaw) { - havingRawSql = deployParser.parse(havingRawSql); - } - if (havingRawSql != null && bindParams != null) { - // convert and order named parameters if required - havingNamedParams = BindParamsParser.parseNamedParams(bindParams, havingRawSql); - havingRawSql = havingNamedParams.getPreparedSql(); - } - } - } - - /** - * Convert named parameters into an OrderedList. - */ - private void buildBindWhereRawSql(boolean buildSql, boolean parseRaw, DeployParser parser) { - if (buildSql || bindParams != null) { - whereRawSql = buildWhereRawSql(); - boolean hasRaw = !"".equals(whereRawSql); - if (hasRaw && parseRaw) { - // parse with encrypted property awareness. This means that if we have - // an encrypted property we will insert special named parameter place - // holders for binding the encryption key values - parser.setEncrypted(true); - whereRawSql = parser.parse(whereRawSql); - parser.setEncrypted(false); - } - - if (bindParams != null) { - if (hasRaw) { - whereRawSql = BindParamsParser.parse(bindParams, whereRawSql, request.getBeanDescriptor()); - - } else if (query.isRawSql() && !buildSql) { - // RawSql query hit cached query plan. Need to convert - // named parameters into positioned parameters so that - // the named parameters are bound - RawSql.Sql sql = query.getRawSql().getSql(); - String s = sql.isParsed() ? sql.getPreWhere() : sql.getUnparsedSql(); - if (bindParams.requiresNamedParamsPrepare()) { - BindParamsParser.parse(bindParams, s); - } - } - } - } - } - - private String buildWhereRawSql() { - // this is the where part of a OQL query which - // may contain bind parameters... - String whereRaw = query.getRawWhereClause(); - if (whereRaw == null) { - whereRaw = ""; - } - // add any additional stuff to the where clause - String additionalWhere = query.getAdditionalWhere(); - if (additionalWhere != null) { - whereRaw += additionalWhere; - } - return whereRaw; - } - - public void prepare(boolean buildSql) { - - DeployParser deployParser = request.createDeployParser(); - prepare(buildSql, true, deployParser); - } - - public void prepareRawSql(DeployParser deployParser) { - prepare(true, false, deployParser); - } - - /** - * This combines the sql from named/positioned parameters and expressions. - */ - private void prepare(boolean buildSql, boolean parseRaw, DeployParser deployParser) { - - buildBindWhereRawSql(buildSql, parseRaw, deployParser); - buildBindHavingRawSql(buildSql, parseRaw, deployParser); - - SpiExpressionList whereExp = query.getWhereExpressions(); - if (whereExp != null) { - DefaultExpressionRequest whereReq = new DefaultExpressionRequest(request, deployParser); - whereExprBindValues = whereExp.buildBindValues(whereReq); - if (buildSql) { - whereExprSql = whereExp.buildSql(whereReq); - } - } - - BeanPropertyAssocMany manyProperty = request.getManyProperty(); - if (manyProperty != null) { - OrmQueryProperties chunk = query.getDetail().getChunk(manyProperty.getName(), false); - SpiExpressionList filterMany = chunk.getFilterMany(); - if (filterMany != null) { - DefaultExpressionRequest filterReq = new DefaultExpressionRequest(request, deployParser); - filterManyExprBindValues = filterMany.buildBindValues(filterReq); - if (buildSql) { - filterManyExprSql = filterMany.buildSql(filterReq); - } - } - } - - // having expression - SpiExpressionList havingExpr = query.getHavingExpressions(); - if (havingExpr != null) { - DefaultExpressionRequest havingReq = new DefaultExpressionRequest(request, deployParser); - havingExprBindValues = havingExpr.buildBindValues(havingReq); - if (buildSql) { - havingExprSql = havingExpr.buildSql(havingReq); - } - } - - if (buildSql) { - parsePropertiesToDbColumns(deployParser); - } - - } - - /** - * Parse/Convert property names to database columns in the where and order by - * clauses etc. - */ - private void parsePropertiesToDbColumns(DeployParser deployParser) { - - // order by is dependent on the manyProperty (if there is one) - logicalOrderBy = deriveOrderByWithMany(request.getManyProperty()); - if (logicalOrderBy != null) { - dbOrderBy = deployParser.parse(logicalOrderBy); - } - - // create a copy of the includes required to support the orderBy - orderByIncludes = new HashSet(deployParser.getIncludes()); - - dbWhere = deriveWhere(deployParser); - dbFilterMany = deriveFilterMany(deployParser); - dbHaving = deriveHaving(deployParser); - - // all includes including ones for manyWhere clause - predicateIncludes = deployParser.getIncludes(); - } - - private String deriveFilterMany(DeployParser deployParser) { - if (isEmpty(filterManyExprSql)) { - return null; - } else { - return deployParser.parse(filterManyExprSql); - } - } - - private String deriveWhere(DeployParser deployParser) { - return parse(whereRawSql, whereExprSql, deployParser); - } - - /** - * Replace the table alias place holders. - */ - public void parseTableAlias(SqlTreeAlias alias) { - if (dbWhere != null) { - // use the where table alias - dbWhere = alias.parseWhere(dbWhere); - } - if (dbFilterMany != null) { - // use the select table alias - dbFilterMany = alias.parse(dbFilterMany); - } - if (dbHaving != null) { - dbHaving = alias.parseWhere(dbHaving); - } - if (dbOrderBy != null) { - dbOrderBy = alias.parse(dbOrderBy); - } - } - - private boolean isEmpty(String s) { - return s == null || s.length() == 0; - } - - private String parse(String raw, String expr, DeployParser deployParser) { - - StringBuilder sb = new StringBuilder(); - if (!isEmpty(raw)) { - sb.append(raw); - } - if (!isEmpty(expr)) { - if (sb.length() > 0) { - sb.append(" and "); - } - sb.append(deployParser.parse(expr)); - } - return sb.toString(); - } - - private String deriveHaving(DeployParser deployParser) { - return parse(havingRawSql, havingExprSql, deployParser); - } - - private String parseOrderBy() { - - return CQueryOrderBy.parse(request.getBeanDescriptor(), query); - } - - /** - * There is a many property so we need to make sure the ordering is - * appropriate. - */ - private String deriveOrderByWithMany(BeanPropertyAssocMany manyProp) { - - if (manyProp == null) { - return parseOrderBy(); - } - - String orderBy = parseOrderBy(); - - BeanDescriptor desc = request.getBeanDescriptor(); - String orderById = desc.getDefaultOrderBy(); - - if (orderBy == null) { - orderBy = orderById; - } - - // check for default ordering on the many property... - String manyOrderBy = manyProp.getFetchOrderBy(); - if (manyOrderBy != null) { - orderBy = orderBy + ", " + CQueryBuilder.prefixOrderByFields(manyProp.getName(), manyOrderBy); - } - - if (request.isFindById()) { - // only one master bean so should be fine... - return orderBy; - } - - if (orderBy.startsWith(orderById)) { - return orderBy; - } - - // more than one top level row may be returned so - // we need to make sure their is an order by on the - // top level first (to ensure master/detail construction). - - int manyPos = orderBy.indexOf(manyProp.getName()); - int idPos = orderBy.indexOf(" " + orderById); - - if (manyPos == -1) { - // no ordering of the many - if (idPos == -1) { - // append the orderById so that master level objects are ordered - // even if the orderBy is not unique for the master object - return orderBy + ", " + orderById; - } - // orderById is already in the order by clause - return orderBy; - } - - if (idPos > -1 && idPos < manyPos) { - // its all ok, id property appears before a many property - - } else { - if (idPos > manyPos) { - // there was an error with the order by... - String msg = "A Query on [" + desc + "] includes a join to a 'many' association [" + manyProp.getName(); - msg += "] with an incorrect orderBy [" + orderBy + "]. The id property [" + orderById + "]"; - msg += " must come before the many property [" + manyProp.getName() + "] in the orderBy."; - msg += " Ebean has automatically modified the orderBy clause to do this."; - - logger.warn(msg); - } - - // the id needs to come before the manyPropName - orderBy = orderBy.substring(0, manyPos) + orderById + ", " + orderBy.substring(manyPos); - } - - return orderBy; - } - - /** - * Return the bind values for the where expression. - */ - public ArrayList getWhereExprBindValues() { - return whereExprBindValues; - } - - /** - * Return the db column version of the combined where clause. - */ - public String getDbHaving() { - return dbHaving; - } - - /** - * Return the db column version of the combined where clause. - */ - public String getDbWhere() { - return dbWhere; - } - - /** - * Return a db filter for filtering many fetch joins. - */ - public String getDbFilterMany() { - return dbFilterMany; - } - - /** - * Return the db column version of the order by clause. - */ - public String getDbOrderBy() { - return dbOrderBy; - } - - /** - * Return the includes required for the where and order by clause. - */ - public Set getPredicateIncludes() { - return predicateIncludes; - } - - /** - * Return the orderBy includes. - */ - public Set getOrderByIncludes() { - return orderByIncludes; - } - - /** - * The where sql with named bind parameters converted to ?. - */ - public String getWhereRawSql() { - return whereRawSql; - } - - /** - * The where sql from the expression objects. - */ - public String getWhereExpressionSql() { - return whereExprSql; - } - - /** - * The having sql with named bind parameters converted to ?. - */ - public String getHavingRawSql() { - return havingRawSql; - } - - /** - * The having sql from the expression objects. - */ - public String getHavingExpressionSql() { - return havingExprSql; - } - - public String getLogWhereSql() { - if (rawSql) { - return ""; - } - if (dbWhere == null && dbFilterMany == null) { - return ""; - } - StringBuilder sb = new StringBuilder(); - if (dbWhere != null) { - sb.append(dbWhere); - } - if (dbFilterMany != null) { - if (sb.length() > 0) { - sb.append(" and "); - } - sb.append(dbFilterMany); - } - String logPred = sb.toString(); - if (logPred.length() > 400) { - logPred = logPred.substring(0, 400) + " ..."; - } - return logPred; - } -} +package com.avaje.ebeaninternal.server.query; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +import com.avaje.ebean.RawSql; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.avaje.ebeaninternal.api.BindParams; +import com.avaje.ebeaninternal.api.BindParams.OrderedList; +import com.avaje.ebeaninternal.api.SpiExpressionList; +import com.avaje.ebeaninternal.api.SpiQuery; +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.DeployParser; +import com.avaje.ebeaninternal.server.persist.Binder; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties; +import com.avaje.ebeaninternal.server.type.DataBind; +import com.avaje.ebeaninternal.server.util.BindParamsParser; +import com.avaje.ebeaninternal.util.DefaultExpressionRequest; + +/** + * Compile Query Predicates. + *

+ * This includes the where and having expressions which can be made up of + * Strings with named parameters or Expression objects. + *

+ *

+ * This builds the appropriate bits of where and having clauses and binds the + * named parameters and expression values into the prepared statement. + *

+ */ +public class CQueryPredicates { + + private static final Logger logger = LoggerFactory.getLogger(CQueryPredicates.class); + + private final Binder binder; + + private final OrmQueryRequest request; + + private final SpiQuery query; + + private final Object idValue; + + /** + * Flag set if this is a SqlSelect type query. + */ + private boolean rawSql; + + /** + * Named bind parameters. + */ + private final BindParams bindParams; + + /** + * Named bind parameters for the having clause. + */ + private OrderedList havingNamedParams; + + /** + * Bind values from the where expressions. + */ + private ArrayList filterManyExprBindValues; + + /** + * SQL generated from the where expressions. + */ + private String filterManyExprSql; + + /** + * Bind values from the where expressions. + */ + private ArrayList whereExprBindValues; + + /** + * SQL generated from the where expressions. + */ + private String whereExprSql; + + /** + * SQL generated from where with named parameters. + */ + private String whereRawSql; + + /** + * Bind values for having expression. + */ + private ArrayList havingExprBindValues; + + /** + * SQL generated from the having expression. + */ + private String havingExprSql; + + /** + * SQL generated from having with named parameters. + */ + private String havingRawSql; + + private String dbHaving; + + /** + * logicalWhere with property names converted to db columns. + */ + private String dbWhere; + + /** + * Filter than can apply to a many fetch join. + */ + private String dbFilterMany; + + /** + * The order by clause. + */ + private String logicalOrderBy; + + private String dbOrderBy; + + /** + * Includes from where and order by clauses. + */ + private Set predicateIncludes; + + private Set orderByIncludes; + + public CQueryPredicates(Binder binder, OrmQueryRequest request) { + this.binder = binder; + this.request = request; + this.query = request.getQuery(); + this.bindParams = query.getBindParams(); + this.idValue = query.getId(); + } + + public String bind(DataBind dataBind) throws SQLException { + + StringBuilder bindLog = new StringBuilder(); + + if (idValue != null) { + // this is a find by id type query... + request.getBeanDescriptor().bindId(dataBind, idValue); + bindLog.append(idValue); + } + + if (bindParams != null) { + // bind named and positioned parameters... + binder.bind(bindParams, dataBind, bindLog); + } + + if (whereExprBindValues != null) { + + for (int i = 0; i < whereExprBindValues.size(); i++) { + Object bindValue = whereExprBindValues.get(i); + binder.bindObject(dataBind, bindValue); + if (i > 0 || idValue != null) { + bindLog.append(","); + } + bindLog.append(bindValue); + } + } + + if (filterManyExprBindValues != null) { + + for (int i = 0; i < filterManyExprBindValues.size(); i++) { + Object bindValue = filterManyExprBindValues.get(i); + binder.bindObject(dataBind, bindValue); + if (i > 0 || idValue != null) { + bindLog.append(","); + } + bindLog.append(bindValue); + } + } + + if (havingNamedParams != null) { + // bind named parameters in having... + bindLog.append(" havingNamed "); + binder.bind(havingNamedParams.list(), dataBind, bindLog); + } + + if (havingExprBindValues != null) { + // bind having expression... + bindLog.append(" having "); + for (int i = 0; i < havingExprBindValues.size(); i++) { + Object bindValue = havingExprBindValues.get(i); + binder.bindObject(dataBind, bindValue); + if (i > 0) { + bindLog.append(","); + } + bindLog.append(bindValue); + } + } + + return bindLog.toString(); + } + + private void buildBindHavingRawSql(boolean buildSql, boolean parseRaw, DeployParser deployParser) { + if (buildSql || bindParams != null) { + // having clause with named parameters... + havingRawSql = query.getAdditionalHaving(); + if (parseRaw) { + havingRawSql = deployParser.parse(havingRawSql); + } + if (havingRawSql != null && bindParams != null) { + // convert and order named parameters if required + havingNamedParams = BindParamsParser.parseNamedParams(bindParams, havingRawSql); + havingRawSql = havingNamedParams.getPreparedSql(); + } + } + } + + /** + * Convert named parameters into an OrderedList. + */ + private void buildBindWhereRawSql(boolean buildSql, boolean parseRaw, DeployParser parser) { + if (buildSql || bindParams != null) { + whereRawSql = buildWhereRawSql(); + boolean hasRaw = !"".equals(whereRawSql); + if (hasRaw && parseRaw) { + // parse with encrypted property awareness. This means that if we have + // an encrypted property we will insert special named parameter place + // holders for binding the encryption key values + parser.setEncrypted(true); + whereRawSql = parser.parse(whereRawSql); + parser.setEncrypted(false); + } + + if (bindParams != null) { + if (hasRaw) { + whereRawSql = BindParamsParser.parse(bindParams, whereRawSql, request.getBeanDescriptor()); + + } else if (query.isRawSql() && !buildSql) { + // RawSql query hit cached query plan. Need to convert + // named parameters into positioned parameters so that + // the named parameters are bound + RawSql.Sql sql = query.getRawSql().getSql(); + String s = sql.isParsed() ? sql.getPreWhere() : sql.getUnparsedSql(); + if (bindParams.requiresNamedParamsPrepare()) { + BindParamsParser.parse(bindParams, s); + } + } + } + } + } + + private String buildWhereRawSql() { + // this is the where part of a OQL query which + // may contain bind parameters... + String whereRaw = query.getRawWhereClause(); + if (whereRaw == null) { + whereRaw = ""; + } + // add any additional stuff to the where clause + String additionalWhere = query.getAdditionalWhere(); + if (additionalWhere != null) { + whereRaw += additionalWhere; + } + return whereRaw; + } + + public void prepare(boolean buildSql) { + + DeployParser deployParser = request.createDeployParser(); + prepare(buildSql, true, deployParser); + } + + public void prepareRawSql(DeployParser deployParser) { + prepare(true, false, deployParser); + } + + /** + * This combines the sql from named/positioned parameters and expressions. + */ + private void prepare(boolean buildSql, boolean parseRaw, DeployParser deployParser) { + + buildBindWhereRawSql(buildSql, parseRaw, deployParser); + buildBindHavingRawSql(buildSql, parseRaw, deployParser); + + SpiExpressionList whereExp = query.getWhereExpressions(); + if (whereExp != null) { + DefaultExpressionRequest whereReq = new DefaultExpressionRequest(request, deployParser); + whereExprBindValues = whereExp.buildBindValues(whereReq); + if (buildSql) { + whereExprSql = whereExp.buildSql(whereReq); + } + } + + BeanPropertyAssocMany manyProperty = request.getManyProperty(); + if (manyProperty != null) { + OrmQueryProperties chunk = query.getDetail().getChunk(manyProperty.getName(), false); + SpiExpressionList filterMany = chunk.getFilterMany(); + if (filterMany != null) { + DefaultExpressionRequest filterReq = new DefaultExpressionRequest(request, deployParser); + filterManyExprBindValues = filterMany.buildBindValues(filterReq); + if (buildSql) { + filterManyExprSql = filterMany.buildSql(filterReq); + } + } + } + + // having expression + SpiExpressionList havingExpr = query.getHavingExpressions(); + if (havingExpr != null) { + DefaultExpressionRequest havingReq = new DefaultExpressionRequest(request, deployParser); + havingExprBindValues = havingExpr.buildBindValues(havingReq); + if (buildSql) { + havingExprSql = havingExpr.buildSql(havingReq); + } + } + + if (buildSql) { + parsePropertiesToDbColumns(deployParser); + } + + } + + /** + * Parse/Convert property names to database columns in the where and order by + * clauses etc. + */ + private void parsePropertiesToDbColumns(DeployParser deployParser) { + + // order by is dependent on the manyProperty (if there is one) + logicalOrderBy = deriveOrderByWithMany(request.getManyProperty()); + if (logicalOrderBy != null) { + dbOrderBy = deployParser.parse(logicalOrderBy); + } + + // create a copy of the includes required to support the orderBy + orderByIncludes = new HashSet(deployParser.getIncludes()); + + dbWhere = deriveWhere(deployParser); + dbFilterMany = deriveFilterMany(deployParser); + dbHaving = deriveHaving(deployParser); + + // all includes including ones for manyWhere clause + predicateIncludes = deployParser.getIncludes(); + } + + private String deriveFilterMany(DeployParser deployParser) { + if (isEmpty(filterManyExprSql)) { + return null; + } else { + return deployParser.parse(filterManyExprSql); + } + } + + private String deriveWhere(DeployParser deployParser) { + return parse(whereRawSql, whereExprSql, deployParser); + } + + /** + * Replace the table alias place holders. + */ + public void parseTableAlias(SqlTreeAlias alias) { + if (dbWhere != null) { + // use the where table alias + dbWhere = alias.parseWhere(dbWhere); + } + if (dbFilterMany != null) { + // use the select table alias + dbFilterMany = alias.parse(dbFilterMany); + } + if (dbHaving != null) { + dbHaving = alias.parseWhere(dbHaving); + } + if (dbOrderBy != null) { + dbOrderBy = alias.parse(dbOrderBy); + } + } + + private boolean isEmpty(String s) { + return s == null || s.length() == 0; + } + + private String parse(String raw, String expr, DeployParser deployParser) { + + StringBuilder sb = new StringBuilder(); + if (!isEmpty(raw)) { + sb.append(raw); + } + if (!isEmpty(expr)) { + if (sb.length() > 0) { + sb.append(" and "); + } + sb.append(deployParser.parse(expr)); + } + return sb.toString(); + } + + private String deriveHaving(DeployParser deployParser) { + return parse(havingRawSql, havingExprSql, deployParser); + } + + private String parseOrderBy() { + + return CQueryOrderBy.parse(request.getBeanDescriptor(), query); + } + + /** + * There is a many property so we need to make sure the ordering is + * appropriate. + */ + private String deriveOrderByWithMany(BeanPropertyAssocMany manyProp) { + + if (manyProp == null) { + return parseOrderBy(); + } + + String orderBy = parseOrderBy(); + + BeanDescriptor desc = request.getBeanDescriptor(); + String orderById = desc.getDefaultOrderBy(); + + if (orderBy == null) { + orderBy = orderById; + } + + // check for default ordering on the many property... + String manyOrderBy = manyProp.getFetchOrderBy(); + if (manyOrderBy != null) { + orderBy = orderBy + ", " + CQueryBuilder.prefixOrderByFields(manyProp.getName(), manyOrderBy); + } + + if (request.isFindById()) { + // only one master bean so should be fine... + return orderBy; + } + + if (orderBy.startsWith(orderById)) { + return orderBy; + } + + // more than one top level row may be returned so + // we need to make sure their is an order by on the + // top level first (to ensure master/detail construction). + + int manyPos = orderBy.indexOf(manyProp.getName()); + int idPos = orderBy.indexOf(" " + orderById); + + if (manyPos == -1) { + // no ordering of the many + if (idPos == -1) { + // append the orderById so that master level objects are ordered + // even if the orderBy is not unique for the master object + return orderBy + ", " + orderById; + } + // orderById is already in the order by clause + return orderBy; + } + + if (idPos > -1 && idPos < manyPos) { + // its all ok, id property appears before a many property + + } else { + if (idPos > manyPos) { + // there was an error with the order by... + String msg = "A Query on [" + desc + "] includes a join to a 'many' association [" + manyProp.getName(); + msg += "] with an incorrect orderBy [" + orderBy + "]. The id property [" + orderById + "]"; + msg += " must come before the many property [" + manyProp.getName() + "] in the orderBy."; + msg += " Ebean has automatically modified the orderBy clause to do this."; + + logger.warn(msg); + } + + // the id needs to come before the manyPropName + orderBy = orderBy.substring(0, manyPos) + orderById + ", " + orderBy.substring(manyPos); + } + + return orderBy; + } + + /** + * Return the bind values for the where expression. + */ + public ArrayList getWhereExprBindValues() { + return whereExprBindValues; + } + + /** + * Return the db column version of the combined where clause. + */ + public String getDbHaving() { + return dbHaving; + } + + /** + * Return the db column version of the combined where clause. + */ + public String getDbWhere() { + return dbWhere; + } + + /** + * Return a db filter for filtering many fetch joins. + */ + public String getDbFilterMany() { + return dbFilterMany; + } + + /** + * Return the db column version of the order by clause. + */ + public String getDbOrderBy() { + return dbOrderBy; + } + + /** + * Return the includes required for the where and order by clause. + */ + public Set getPredicateIncludes() { + return predicateIncludes; + } + + /** + * Return the orderBy includes. + */ + public Set getOrderByIncludes() { + return orderByIncludes; + } + + /** + * The where sql with named bind parameters converted to ?. + */ + public String getWhereRawSql() { + return whereRawSql; + } + + /** + * The where sql from the expression objects. + */ + public String getWhereExpressionSql() { + return whereExprSql; + } + + /** + * The having sql with named bind parameters converted to ?. + */ + public String getHavingRawSql() { + return havingRawSql; + } + + /** + * The having sql from the expression objects. + */ + public String getHavingExpressionSql() { + return havingExprSql; + } + + public String getLogWhereSql() { + if (rawSql) { + return ""; + } + if (dbWhere == null && dbFilterMany == null) { + return ""; + } + StringBuilder sb = new StringBuilder(); + if (dbWhere != null) { + sb.append(dbWhere); + } + if (dbFilterMany != null) { + if (sb.length() > 0) { + sb.append(" and "); + } + sb.append(dbFilterMany); + } + String logPred = sb.toString(); + if (logPred.length() > 400) { + logPred = logPred.substring(0, 400) + " ..."; + } + return logPred; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryRowCount.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryRowCount.java index b976e4c68..6c7cef7d9 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryRowCount.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryRowCount.java @@ -1,173 +1,173 @@ -package com.avaje.ebeaninternal.server.query; - -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; - -import javax.persistence.PersistenceException; - -import com.avaje.ebeaninternal.api.SpiQuery; -import com.avaje.ebeaninternal.api.SpiTransaction; -import com.avaje.ebeaninternal.server.core.OrmQueryRequest; -import com.avaje.ebeaninternal.server.core.SpiOrmQueryRequest; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.type.DataBind; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Executes the select row count query. - */ -public class CQueryRowCount { - - private static final Logger logger = LoggerFactory.getLogger(CQueryRowCount.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; - - /** - * The resultSet that is read and converted to objects. - */ - private ResultSet rset; - - /** - * The statement used to create the resultSet. - */ - private PreparedStatement pstmt; - - private String bindLog; - - private long startNano; - - private int executionTimeMicros; - - private int rowCount; - - /** - * Create the Sql select based on the request. - */ - public CQueryRowCount(OrmQueryRequest request, CQueryPredicates predicates, String sql) { - this.request = request; - this.query = request.getQuery(); - this.sql = sql; - - query.setGeneratedSql(sql); - - this.desc = request.getBeanDescriptor(); - this.predicates = predicates; - - } - - /** - * Return a summary description of this query. - */ - public String getSummary() { - StringBuilder sb = new StringBuilder(); - sb.append("FindRowCount exeMicros[").append(executionTimeMicros) - .append("] rows[").append(rowCount) - .append("] type[").append(desc.getFullName()) - .append("] predicates[").append(predicates.getLogWhereSql()) - .append("] bind[").append(bindLog).append("]"); - - return sb.toString(); - } - - /** - * Return the bind log. - */ - public String getBindLog() { - return bindLog; - } - - /** - * Return the generated sql. - */ - public String getGeneratedSql() { - return sql; - } - - public SpiOrmQueryRequest getQueryRequest() { - return request; - } - - /** - * Execute the query returning the row count. - */ - public int findRowCount() throws SQLException { - - startNano = System.nanoTime(); - try { - - SpiTransaction t = request.getTransaction(); - Connection conn = t.getInternalConnection(); - pstmt = conn.prepareStatement(sql); - - if (query.getTimeout() > 0){ - pstmt.setQueryTimeout(query.getTimeout()); - } - - bindLog = predicates.bind(new DataBind(pstmt)); - - rset = pstmt.executeQuery(); - - if (!rset.next()){ - throw new PersistenceException("Expecting 1 row but got none?"); - } - - rowCount = rset.getInt(1); - - long exeNano = System.nanoTime() - startNano; - executionTimeMicros = (int)exeNano/1000; - - return rowCount; - - } finally { - close(); - } - } - - /** - * Close the resources. - *

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

- */ - private void close() { - try { - if (rset != null) { - rset.close(); - rset = null; - } - } catch (SQLException e) { - logger.error(null, e); - } - try { - if (pstmt != null) { - pstmt.close(); - pstmt = null; - } - } catch (SQLException e) { - logger.error(null, e); - } - } - - -} +package com.avaje.ebeaninternal.server.query; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import javax.persistence.PersistenceException; + +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.core.SpiOrmQueryRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.type.DataBind; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Executes the select row count query. + */ +public class CQueryRowCount { + + private static final Logger logger = LoggerFactory.getLogger(CQueryRowCount.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; + + /** + * The resultSet that is read and converted to objects. + */ + private ResultSet rset; + + /** + * The statement used to create the resultSet. + */ + private PreparedStatement pstmt; + + private String bindLog; + + private long startNano; + + private int executionTimeMicros; + + private int rowCount; + + /** + * Create the Sql select based on the request. + */ + public CQueryRowCount(OrmQueryRequest request, CQueryPredicates predicates, String sql) { + this.request = request; + this.query = request.getQuery(); + this.sql = sql; + + query.setGeneratedSql(sql); + + this.desc = request.getBeanDescriptor(); + this.predicates = predicates; + + } + + /** + * Return a summary description of this query. + */ + public String getSummary() { + StringBuilder sb = new StringBuilder(); + sb.append("FindRowCount exeMicros[").append(executionTimeMicros) + .append("] rows[").append(rowCount) + .append("] type[").append(desc.getFullName()) + .append("] predicates[").append(predicates.getLogWhereSql()) + .append("] bind[").append(bindLog).append("]"); + + return sb.toString(); + } + + /** + * Return the bind log. + */ + public String getBindLog() { + return bindLog; + } + + /** + * Return the generated sql. + */ + public String getGeneratedSql() { + return sql; + } + + public SpiOrmQueryRequest getQueryRequest() { + return request; + } + + /** + * Execute the query returning the row count. + */ + public int findRowCount() throws SQLException { + + startNano = System.nanoTime(); + try { + + SpiTransaction t = request.getTransaction(); + Connection conn = t.getInternalConnection(); + pstmt = conn.prepareStatement(sql); + + if (query.getTimeout() > 0){ + pstmt.setQueryTimeout(query.getTimeout()); + } + + bindLog = predicates.bind(new DataBind(pstmt)); + + rset = pstmt.executeQuery(); + + if (!rset.next()){ + throw new PersistenceException("Expecting 1 row but got none?"); + } + + rowCount = rset.getInt(1); + + long exeNano = System.nanoTime() - startNano; + executionTimeMicros = (int)exeNano/1000; + + return rowCount; + + } finally { + close(); + } + } + + /** + * Close the resources. + *

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

+ */ + private void close() { + try { + if (rset != null) { + rset.close(); + rset = null; + } + } catch (SQLException e) { + logger.error(null, e); + } + try { + if (pstmt != null) { + pstmt.close(); + pstmt = null; + } + } catch (SQLException e) { + logger.error(null, e); + } + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CallableQuery.java b/src/main/java/com/avaje/ebeaninternal/server/query/CallableQuery.java index b15c07dab..448d9a648 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CallableQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CallableQuery.java @@ -1,36 +1,36 @@ -package com.avaje.ebeaninternal.server.query; - -import com.avaje.ebean.Transaction; -import com.avaje.ebeaninternal.api.SpiEbeanServer; -import com.avaje.ebeaninternal.api.SpiQuery; - -/** - * Base object for making query execution into Callable's. - * - * @author rbygrave - * - * @param the entity bean type - */ -public abstract class CallableQuery { - - protected final SpiQuery query; - - protected final SpiEbeanServer server; - - protected final Transaction transaction; - - public CallableQuery(SpiEbeanServer server, SpiQuery query, Transaction t) { - this.server = server; - this.query = query; - this.transaction = t; - } - - public SpiQuery getQuery() { - return query; - } - - public Transaction getTransaction() { - return transaction; - } - -} +package com.avaje.ebeaninternal.server.query; + +import com.avaje.ebean.Transaction; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.SpiQuery; + +/** + * Base object for making query execution into Callable's. + * + * @author rbygrave + * + * @param the entity bean type + */ +public abstract class CallableQuery { + + protected final SpiQuery query; + + protected final SpiEbeanServer server; + + protected final Transaction transaction; + + public CallableQuery(SpiEbeanServer server, SpiQuery query, Transaction t) { + this.server = server; + this.query = query; + this.transaction = t; + } + + public SpiQuery getQuery() { + return query; + } + + public Transaction getTransaction() { + return transaction; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CallableQueryIds.java b/src/main/java/com/avaje/ebeaninternal/server/query/CallableQueryIds.java index 8e58af8a8..8d0e9953a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CallableQueryIds.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CallableQueryIds.java @@ -1,36 +1,36 @@ -package com.avaje.ebeaninternal.server.query; - -import java.util.List; -import java.util.concurrent.Callable; - -import com.avaje.ebean.Transaction; -import com.avaje.ebeaninternal.api.SpiEbeanServer; -import com.avaje.ebeaninternal.api.SpiQuery; - -/** - * Represent the fetch Id's query as a Callable. - * - * @param the entity bean type - */ -public class CallableQueryIds extends CallableQuery implements Callable> { - - - public CallableQueryIds(SpiEbeanServer server, SpiQuery query, Transaction t) { - super(server, query, t); - } - - /** - * Execute the find Id's query returning the list of Id's. - */ - public List call() throws Exception { - // we have already made a copy of the query - // this way the same query instance is available to the - // QueryFutureIds (as so has access to the List before it is done) - try { - return server.findIdsWithCopy(query, transaction); - } finally { - transaction.end(); - } - } - -} +package com.avaje.ebeaninternal.server.query; + +import java.util.List; +import java.util.concurrent.Callable; + +import com.avaje.ebean.Transaction; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.SpiQuery; + +/** + * Represent the fetch Id's query as a Callable. + * + * @param the entity bean type + */ +public class CallableQueryIds extends CallableQuery implements Callable> { + + + public CallableQueryIds(SpiEbeanServer server, SpiQuery query, Transaction t) { + super(server, query, t); + } + + /** + * Execute the find Id's query returning the list of Id's. + */ + public List call() throws Exception { + // we have already made a copy of the query + // this way the same query instance is available to the + // QueryFutureIds (as so has access to the List before it is done) + try { + return server.findIdsWithCopy(query, transaction); + } finally { + transaction.end(); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CallableQueryList.java b/src/main/java/com/avaje/ebeaninternal/server/query/CallableQueryList.java index d73a2b36b..6ef624670 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CallableQueryList.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CallableQueryList.java @@ -1,36 +1,36 @@ -package com.avaje.ebeaninternal.server.query; - -import java.util.List; -import java.util.concurrent.Callable; - -import com.avaje.ebean.Transaction; -import com.avaje.ebeaninternal.api.SpiEbeanServer; -import com.avaje.ebeaninternal.api.SpiQuery; - -/** - * Represent the findList query as a Callable. - * - * @param the entity bean type - */ -public class CallableQueryList extends CallableQuery implements Callable> { - - - public CallableQueryList(SpiEbeanServer server, SpiQuery query, Transaction t) { - super(server, query, t); - } - - /** - * Execute the query returning the resulting List. - */ - public List call() throws Exception { - try { - return server.findList(query, transaction); - } finally { - // cleanup the underlying connection - transaction.end(); - } - } - - - -} +package com.avaje.ebeaninternal.server.query; + +import java.util.List; +import java.util.concurrent.Callable; + +import com.avaje.ebean.Transaction; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.SpiQuery; + +/** + * Represent the findList query as a Callable. + * + * @param the entity bean type + */ +public class CallableQueryList extends CallableQuery implements Callable> { + + + public CallableQueryList(SpiEbeanServer server, SpiQuery query, Transaction t) { + super(server, query, t); + } + + /** + * Execute the query returning the resulting List. + */ + public List call() throws Exception { + try { + return server.findList(query, transaction); + } finally { + // cleanup the underlying connection + transaction.end(); + } + } + + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CallableQueryRowCount.java b/src/main/java/com/avaje/ebeaninternal/server/query/CallableQueryRowCount.java index e64536363..872dff7fd 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CallableQueryRowCount.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CallableQueryRowCount.java @@ -1,37 +1,37 @@ -package com.avaje.ebeaninternal.server.query; - -import java.util.concurrent.Callable; - -import com.avaje.ebean.Transaction; -import com.avaje.ebeaninternal.api.SpiEbeanServer; -import com.avaje.ebeaninternal.api.SpiQuery; - -/** - * Represent the findRowCount query as a Callable. - * - * @param - * the entity bean type - */ -public class CallableQueryRowCount extends CallableQuery implements Callable { - - /** - * Note that the transaction passed in is always a new transaction solely to - * find the row count so it must be cleaned up by this CallableQueryRowCount. - */ - public CallableQueryRowCount(SpiEbeanServer server, SpiQuery query, Transaction t) { - super(server, query, t); - } - - /** - * Execute the query returning the row count. - */ - public Integer call() throws Exception { - try { - return server.findRowCountWithCopy(query, transaction); - } finally { - // cleanup the underlying connection - transaction.end(); - } - } - -} +package com.avaje.ebeaninternal.server.query; + +import java.util.concurrent.Callable; + +import com.avaje.ebean.Transaction; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.SpiQuery; + +/** + * Represent the findRowCount query as a Callable. + * + * @param + * the entity bean type + */ +public class CallableQueryRowCount extends CallableQuery implements Callable { + + /** + * Note that the transaction passed in is always a new transaction solely to + * find the row count so it must be cleaned up by this CallableQueryRowCount. + */ + public CallableQueryRowCount(SpiEbeanServer server, SpiQuery query, Transaction t) { + super(server, query, t); + } + + /** + * Execute the query returning the row count. + */ + public Integer call() throws Exception { + try { + return server.findRowCountWithCopy(query, transaction); + } finally { + // cleanup the underlying connection + transaction.end(); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CallableSqlQueryList.java b/src/main/java/com/avaje/ebeaninternal/server/query/CallableSqlQueryList.java index 1d476ffb9..6b24ac3ef 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CallableSqlQueryList.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CallableSqlQueryList.java @@ -1,41 +1,41 @@ -package com.avaje.ebeaninternal.server.query; - -import java.util.List; -import java.util.concurrent.Callable; - -import com.avaje.ebean.EbeanServer; -import com.avaje.ebean.SqlQuery; -import com.avaje.ebean.SqlRow; -import com.avaje.ebean.Transaction; - -/** - * Represent the SQL query findList as a Callable. - */ -public class CallableSqlQueryList implements Callable> { - - private final SqlQuery query; - - private final EbeanServer server; - - private final Transaction transaction; - - public CallableSqlQueryList(EbeanServer server, SqlQuery query, Transaction t) { - this.server = server; - this.query = query; - this.transaction = t; - } - - /** - * Execute the query returning the resulting list. - */ - public List call() throws Exception { - try { - return server.findList(query, transaction); - } finally { - transaction.end(); - } - } - - - -} +package com.avaje.ebeaninternal.server.query; + +import java.util.List; +import java.util.concurrent.Callable; + +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.SqlQuery; +import com.avaje.ebean.SqlRow; +import com.avaje.ebean.Transaction; + +/** + * Represent the SQL query findList as a Callable. + */ +public class CallableSqlQueryList implements Callable> { + + private final SqlQuery query; + + private final EbeanServer server; + + private final Transaction transaction; + + public CallableSqlQueryList(EbeanServer server, SqlQuery query, Transaction t) { + this.server = server; + this.query = query; + this.transaction = t; + } + + /** + * Execute the query returning the resulting list. + */ + public List call() throws Exception { + try { + return server.findList(query, transaction); + } finally { + transaction.end(); + } + } + + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CancelableQuery.java b/src/main/java/com/avaje/ebeaninternal/server/query/CancelableQuery.java index 38054132e..e54537605 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CancelableQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CancelableQuery.java @@ -1,19 +1,19 @@ -package com.avaje.ebeaninternal.server.query; - -/** - * Defines a cancelable query. - *

- * Typically holds a representation of the PreparedStatement to perform the - * actual cancel. - *

- */ -public interface CancelableQuery { - - /** - * Cancel the query. - *

- * For JDBC this translates to calling cancel on the PreparedStatement. - *

- */ - public void cancel(); -} +package com.avaje.ebeaninternal.server.query; + +/** + * Defines a cancelable query. + *

+ * Typically holds a representation of the PreparedStatement to perform the + * actual cancel. + *

+ */ +public interface CancelableQuery { + + /** + * Cancel the query. + *

+ * For JDBC this translates to calling cancel on the PreparedStatement. + *

+ */ + public void cancel(); +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/Constants.java b/src/main/java/com/avaje/ebeaninternal/server/query/Constants.java index 6c0837cd1..6f3aa5497 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/Constants.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/Constants.java @@ -1,27 +1,27 @@ -package com.avaje.ebeaninternal.server.query; - -/** - * Constants used in find processing. - */ -public interface Constants { - - /** - * literal used for SQL LIMIT in MySql and Postgres. - */ - public static final String LIMIT = "limit"; - - /** - * Literal used for SQL LIMIT OFFSET clause in MySql and Postgres. - */ - public static final String OFFSET = "offset"; - - /** - * ROW_NUMBER() OVER (ORDER BY - */ - public static final String ROW_NUMBER_OVER = "row_number() over (order by "; - - /** - * ) as rn, - */ - public static final String ROW_NUMBER_AS = ") as rn, "; -} +package com.avaje.ebeaninternal.server.query; + +/** + * Constants used in find processing. + */ +public interface Constants { + + /** + * literal used for SQL LIMIT in MySql and Postgres. + */ + public static final String LIMIT = "limit"; + + /** + * Literal used for SQL LIMIT OFFSET clause in MySql and Postgres. + */ + public static final String OFFSET = "offset"; + + /** + * ROW_NUMBER() OVER (ORDER BY + */ + public static final String ROW_NUMBER_OVER = "row_number() over (order by "; + + /** + * ) as rn, + */ + public static final String ROW_NUMBER_AS = ") as rn, "; +} 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 68fd69e6c..5616ac538 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/DefaultOrmQueryEngine.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/DefaultOrmQueryEngine.java @@ -1,125 +1,125 @@ -package com.avaje.ebeaninternal.server.query; - -import java.util.Collection; - -import com.avaje.ebean.QueryIterator; -import com.avaje.ebean.bean.BeanCollection; -import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebean.event.BeanFinder; -import com.avaje.ebeaninternal.api.BeanIdList; -import com.avaje.ebeaninternal.api.SpiQuery; -import com.avaje.ebeaninternal.api.SpiTransaction; -import com.avaje.ebeaninternal.server.core.OrmQueryEngine; -import com.avaje.ebeaninternal.server.core.OrmQueryRequest; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager; - -/** - * Main Finder implementation. - */ -public class DefaultOrmQueryEngine implements OrmQueryEngine { - - /** - * Find using predicates - */ - private final CQueryEngine queryEngine; - - /** - * Create the Finder. - */ - public DefaultOrmQueryEngine(BeanDescriptorManager descMgr, CQueryEngine queryEngine) { - - this.queryEngine = queryEngine; - } - - /** - * Flushes the jdbc batch by default unless explicitly turned off on the transaction. - */ - private void flushJdbcBatchOnQuery(OrmQueryRequest request) { - - SpiTransaction t = request.getTransaction(); - if (t.isBatchFlushOnQuery()) { - // before we perform a query, we need to flush any - // previous persist requests that are queued/batched. - // The query may read data affected by those requests. - t.flushBatch(); - } - } - - public int findRowCount(OrmQueryRequest request) { - - flushJdbcBatchOnQuery(request); - return queryEngine.findRowCount(request); - } - - public BeanIdList findIds(OrmQueryRequest request) { - - flushJdbcBatchOnQuery(request); - return queryEngine.findIds(request); - } - - public QueryIterator findIterate(OrmQueryRequest request) { - - // LIMITATION: You can not use QueryIterator to load bean cache - - flushJdbcBatchOnQuery(request); - return queryEngine.findIterate(request); - } - - public BeanCollection findMany(OrmQueryRequest request) { - - flushJdbcBatchOnQuery(request); - - BeanFinder finder = request.getBeanFinder(); - - BeanCollection result; - if (finder != null) { - // this bean type has its own specific finder - result = finder.findMany(request); - } else { - result = queryEngine.findMany(request); - } - - SpiQuery query = request.getQuery(); - - if (query.isLoadBeanCache()) { - // load the individual beans into the bean cache - BeanDescriptor descriptor = request.getBeanDescriptor(); - Collection c = result.getActualDetails(); - for (T bean : c) { - descriptor.cacheBeanPutData((EntityBean) bean); - } - } - - if (!result.isEmpty() && query.isUseQueryCache()) { - // load the query result into the query cache - request.putToQueryCache(result); - } - - return result; - } - - /** - * Find a single bean using its unique id. - */ - public T findId(OrmQueryRequest request) { - - flushJdbcBatchOnQuery(request); - - BeanFinder finder = request.getBeanFinder(); - - T result; - if (finder != null) { - result = finder.find(request); - } else { - result = queryEngine.find(request); - } - - if (result != null && request.isUseBeanCache()) { - request.getBeanDescriptor().cacheBeanPutData((EntityBean) result); - } - - return result; - } - -} +package com.avaje.ebeaninternal.server.query; + +import java.util.Collection; + +import com.avaje.ebean.QueryIterator; +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.event.BeanFinder; +import com.avaje.ebeaninternal.api.BeanIdList; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.core.OrmQueryEngine; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager; + +/** + * Main Finder implementation. + */ +public class DefaultOrmQueryEngine implements OrmQueryEngine { + + /** + * Find using predicates + */ + private final CQueryEngine queryEngine; + + /** + * Create the Finder. + */ + public DefaultOrmQueryEngine(BeanDescriptorManager descMgr, CQueryEngine queryEngine) { + + this.queryEngine = queryEngine; + } + + /** + * Flushes the jdbc batch by default unless explicitly turned off on the transaction. + */ + private void flushJdbcBatchOnQuery(OrmQueryRequest request) { + + SpiTransaction t = request.getTransaction(); + if (t.isBatchFlushOnQuery()) { + // before we perform a query, we need to flush any + // previous persist requests that are queued/batched. + // The query may read data affected by those requests. + t.flushBatch(); + } + } + + public int findRowCount(OrmQueryRequest request) { + + flushJdbcBatchOnQuery(request); + return queryEngine.findRowCount(request); + } + + public BeanIdList findIds(OrmQueryRequest request) { + + flushJdbcBatchOnQuery(request); + return queryEngine.findIds(request); + } + + public QueryIterator findIterate(OrmQueryRequest request) { + + // LIMITATION: You can not use QueryIterator to load bean cache + + flushJdbcBatchOnQuery(request); + return queryEngine.findIterate(request); + } + + public BeanCollection findMany(OrmQueryRequest request) { + + flushJdbcBatchOnQuery(request); + + BeanFinder finder = request.getBeanFinder(); + + BeanCollection result; + if (finder != null) { + // this bean type has its own specific finder + result = finder.findMany(request); + } else { + result = queryEngine.findMany(request); + } + + SpiQuery query = request.getQuery(); + + if (query.isLoadBeanCache()) { + // load the individual beans into the bean cache + BeanDescriptor descriptor = request.getBeanDescriptor(); + Collection c = result.getActualDetails(); + for (T bean : c) { + descriptor.cacheBeanPutData((EntityBean) bean); + } + } + + if (!result.isEmpty() && query.isUseQueryCache()) { + // load the query result into the query cache + request.putToQueryCache(result); + } + + return result; + } + + /** + * Find a single bean using its unique id. + */ + public T findId(OrmQueryRequest request) { + + flushJdbcBatchOnQuery(request); + + BeanFinder finder = request.getBeanFinder(); + + T result; + if (finder != null) { + result = finder.find(request); + } else { + result = queryEngine.find(request); + } + + if (result != null && request.isUseBeanCache()) { + request.getBeanDescriptor().cacheBeanPutData((EntityBean) result); + } + + return result; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/DefaultRelationalQueryEngine.java b/src/main/java/com/avaje/ebeaninternal/server/query/DefaultRelationalQueryEngine.java index 005fd06cd..14bf8397a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/DefaultRelationalQueryEngine.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/DefaultRelationalQueryEngine.java @@ -1,231 +1,231 @@ -package com.avaje.ebeaninternal.server.query; - -import com.avaje.ebean.SqlQueryListener; -import com.avaje.ebean.SqlRow; -import com.avaje.ebean.bean.BeanCollection; -import com.avaje.ebeaninternal.api.BindParams; -import com.avaje.ebeaninternal.api.SpiSqlQuery; -import com.avaje.ebeaninternal.api.SpiTransaction; -import com.avaje.ebeaninternal.server.core.Message; -import com.avaje.ebeaninternal.server.core.RelationalQueryEngine; -import com.avaje.ebeaninternal.server.core.RelationalQueryRequest; -import com.avaje.ebeaninternal.server.lib.util.Str; -import com.avaje.ebeaninternal.server.persist.Binder; -import com.avaje.ebeaninternal.server.transaction.TransactionManager; -import com.avaje.ebeaninternal.server.type.DataBind; -import com.avaje.ebeaninternal.server.util.BindParamsParser; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.persistence.PersistenceException; -import java.sql.*; -import java.util.ArrayList; - -/** - * Perform native sql fetches. - */ -public class DefaultRelationalQueryEngine implements RelationalQueryEngine { - - private static final Logger logger = LoggerFactory.getLogger(DefaultRelationalQueryEngine.class); - - private static final int GLOBAL_ROW_LIMIT = Integer.valueOf(System.getProperty("ebean.query.globallimit","1000000")); - - private final Binder binder; - - private final String dbTrueValue; - - public DefaultRelationalQueryEngine(Binder binder, String dbTrueValue) { - this.binder = binder; - this.dbTrueValue = dbTrueValue == null ? "true" : dbTrueValue; - } - - public Object findMany(RelationalQueryRequest request) { - - SpiSqlQuery query = request.getQuery(); - - long startTime = System.currentTimeMillis(); - - SpiTransaction t = request.getTransaction(); - Connection conn = t.getInternalConnection(); - ResultSet rset = null; - PreparedStatement pstmt = null; - - String sql = query.getQuery(); - - BindParams bindParams = query.getBindParams(); - - if (!bindParams.isEmpty()) { - // convert any named parameters if required - sql = BindParamsParser.parse(bindParams, sql); - } - - try { - - String bindLog = ""; - String[] propNames; - - synchronized (query) { - if (query.isCancelled()){ - logger.trace("Query already cancelled"); - return null; - } - - // synchronise for query.cancel() support - pstmt = conn.prepareStatement(sql); - - if (query.getTimeout() > 0){ - pstmt.setQueryTimeout(query.getTimeout()); - } - if (query.getBufferFetchSizeHint() > 0){ - pstmt.setFetchSize(query.getBufferFetchSizeHint()); - } - - if (!bindParams.isEmpty()) { - bindLog = binder.bind(bindParams, new DataBind(pstmt)); - } - - if (request.isLogSql()) { - String logSql = sql; - if (TransactionManager.SQL_LOGGER.isTraceEnabled()) { - logSql = Str.add(logSql, "; --bind(", bindLog, ")"); - } - t.logSql(logSql); - } - - rset = pstmt.executeQuery(); - - propNames = getPropertyNames(rset); - } - - // calculate the initialCapacity of the Map to reduce - // rehashing for queries with 12+ columns - float initCap = (propNames.length) / 0.7f; - int estimateCapacity = (int) initCap + 1; - - // determine the maxRows limit - int maxRows = GLOBAL_ROW_LIMIT; - if (query.getMaxRows() >= 1) { - maxRows = query.getMaxRows(); - } - - int loadRowCount = 0; - - SqlQueryListener listener = query.getListener(); - - BeanCollectionWrapper wrapper = new BeanCollectionWrapper(request); - boolean isMap = wrapper.isMap(); - String mapKey = query.getMapKey(); - - SqlRow bean = null; - - while (rset.next()) { - synchronized (query) { - // synchronise for query.cancel() support - if (!query.isCancelled()){ - bean = readRow(rset, propNames, estimateCapacity); - } - } - if (bean != null){ - // bean can be null if query cancelled - if (listener != null) { - listener.process(bean); - - } else { - if (isMap) { - Object keyValue = bean.get(mapKey); - wrapper.addToMap(bean, keyValue); - } else { - wrapper.addToCollection(bean); - } - } - - loadRowCount++; - - if (loadRowCount == maxRows) { - // break, as we have hit the max rows to fetch... - break; - } - } - } - - BeanCollection beanColl = wrapper.getBeanCollection(); - - if (request.isLogSummary()) { - long exeTime = System.currentTimeMillis() - startTime; - String msg = "SqlQuery rows[" + loadRowCount + "] time[" + exeTime + "] bind[" + bindLog + "]"; - t.logSummary(msg); - } - - if (query.isCancelled()){ - logger.debug("Query was cancelled during execution rows:"+loadRowCount); - } - - return beanColl; - - } catch (Exception e) { - String m = Message.msg("fetch.error", e.getMessage(), sql); - throw new PersistenceException(m, e); - - } finally { - try { - if (rset != null) { - rset.close(); - } - } catch (SQLException e) { - logger.error(null, e); - } - try { - if (pstmt != null) { - pstmt.close(); - } - } catch (SQLException e) { - logger.error(null, e); - } - } - } - - /** - * Build the list of property names. - */ - protected String[] getPropertyNames(ResultSet rset) throws SQLException { - - ArrayList propNames = new ArrayList(); - - ResultSetMetaData rsmd = rset.getMetaData(); - - int columnsPlusOne = rsmd.getColumnCount()+1; - - - for (int i = 1; i < columnsPlusOne; i++) { - String columnName = rsmd.getColumnLabel(i); - // will convert columnName to lower case - propNames.add(columnName); - } - - return propNames.toArray(new String[propNames.size()]); - } - - /** - * Read the row from the ResultSet and return as a MapBean. - */ - protected SqlRow readRow(ResultSet rset, String[] propNames, int initialCapacity) throws SQLException { - - // by default a map will rehash on the 12th entry - // it will be pretty common to have 12 or more entries so - // to reduce rehashing I am trying to estimate a good - // initial capacity for the MapBean to use. - SqlRow bean = new DefaultSqlRow(initialCapacity, 0.75f, dbTrueValue); - - int index = 0; - - for (int i = 0; i < propNames.length; i++) { - index++; - Object value = rset.getObject(index); - bean.set(propNames[i], value); - } - - return bean; - - } - -} +package com.avaje.ebeaninternal.server.query; + +import com.avaje.ebean.SqlQueryListener; +import com.avaje.ebean.SqlRow; +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebeaninternal.api.BindParams; +import com.avaje.ebeaninternal.api.SpiSqlQuery; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.core.Message; +import com.avaje.ebeaninternal.server.core.RelationalQueryEngine; +import com.avaje.ebeaninternal.server.core.RelationalQueryRequest; +import com.avaje.ebeaninternal.server.lib.util.Str; +import com.avaje.ebeaninternal.server.persist.Binder; +import com.avaje.ebeaninternal.server.transaction.TransactionManager; +import com.avaje.ebeaninternal.server.type.DataBind; +import com.avaje.ebeaninternal.server.util.BindParamsParser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.persistence.PersistenceException; +import java.sql.*; +import java.util.ArrayList; + +/** + * Perform native sql fetches. + */ +public class DefaultRelationalQueryEngine implements RelationalQueryEngine { + + private static final Logger logger = LoggerFactory.getLogger(DefaultRelationalQueryEngine.class); + + private static final int GLOBAL_ROW_LIMIT = Integer.valueOf(System.getProperty("ebean.query.globallimit","1000000")); + + private final Binder binder; + + private final String dbTrueValue; + + public DefaultRelationalQueryEngine(Binder binder, String dbTrueValue) { + this.binder = binder; + this.dbTrueValue = dbTrueValue == null ? "true" : dbTrueValue; + } + + public Object findMany(RelationalQueryRequest request) { + + SpiSqlQuery query = request.getQuery(); + + long startTime = System.currentTimeMillis(); + + SpiTransaction t = request.getTransaction(); + Connection conn = t.getInternalConnection(); + ResultSet rset = null; + PreparedStatement pstmt = null; + + String sql = query.getQuery(); + + BindParams bindParams = query.getBindParams(); + + if (!bindParams.isEmpty()) { + // convert any named parameters if required + sql = BindParamsParser.parse(bindParams, sql); + } + + try { + + String bindLog = ""; + String[] propNames; + + synchronized (query) { + if (query.isCancelled()){ + logger.trace("Query already cancelled"); + return null; + } + + // synchronise for query.cancel() support + pstmt = conn.prepareStatement(sql); + + if (query.getTimeout() > 0){ + pstmt.setQueryTimeout(query.getTimeout()); + } + if (query.getBufferFetchSizeHint() > 0){ + pstmt.setFetchSize(query.getBufferFetchSizeHint()); + } + + if (!bindParams.isEmpty()) { + bindLog = binder.bind(bindParams, new DataBind(pstmt)); + } + + if (request.isLogSql()) { + String logSql = sql; + if (TransactionManager.SQL_LOGGER.isTraceEnabled()) { + logSql = Str.add(logSql, "; --bind(", bindLog, ")"); + } + t.logSql(logSql); + } + + rset = pstmt.executeQuery(); + + propNames = getPropertyNames(rset); + } + + // calculate the initialCapacity of the Map to reduce + // rehashing for queries with 12+ columns + float initCap = (propNames.length) / 0.7f; + int estimateCapacity = (int) initCap + 1; + + // determine the maxRows limit + int maxRows = GLOBAL_ROW_LIMIT; + if (query.getMaxRows() >= 1) { + maxRows = query.getMaxRows(); + } + + int loadRowCount = 0; + + SqlQueryListener listener = query.getListener(); + + BeanCollectionWrapper wrapper = new BeanCollectionWrapper(request); + boolean isMap = wrapper.isMap(); + String mapKey = query.getMapKey(); + + SqlRow bean = null; + + while (rset.next()) { + synchronized (query) { + // synchronise for query.cancel() support + if (!query.isCancelled()){ + bean = readRow(rset, propNames, estimateCapacity); + } + } + if (bean != null){ + // bean can be null if query cancelled + if (listener != null) { + listener.process(bean); + + } else { + if (isMap) { + Object keyValue = bean.get(mapKey); + wrapper.addToMap(bean, keyValue); + } else { + wrapper.addToCollection(bean); + } + } + + loadRowCount++; + + if (loadRowCount == maxRows) { + // break, as we have hit the max rows to fetch... + break; + } + } + } + + BeanCollection beanColl = wrapper.getBeanCollection(); + + if (request.isLogSummary()) { + long exeTime = System.currentTimeMillis() - startTime; + String msg = "SqlQuery rows[" + loadRowCount + "] time[" + exeTime + "] bind[" + bindLog + "]"; + t.logSummary(msg); + } + + if (query.isCancelled()){ + logger.debug("Query was cancelled during execution rows:"+loadRowCount); + } + + return beanColl; + + } catch (Exception e) { + String m = Message.msg("fetch.error", e.getMessage(), sql); + throw new PersistenceException(m, e); + + } finally { + try { + if (rset != null) { + rset.close(); + } + } catch (SQLException e) { + logger.error(null, e); + } + try { + if (pstmt != null) { + pstmt.close(); + } + } catch (SQLException e) { + logger.error(null, e); + } + } + } + + /** + * Build the list of property names. + */ + protected String[] getPropertyNames(ResultSet rset) throws SQLException { + + ArrayList propNames = new ArrayList(); + + ResultSetMetaData rsmd = rset.getMetaData(); + + int columnsPlusOne = rsmd.getColumnCount()+1; + + + for (int i = 1; i < columnsPlusOne; i++) { + String columnName = rsmd.getColumnLabel(i); + // will convert columnName to lower case + propNames.add(columnName); + } + + return propNames.toArray(new String[propNames.size()]); + } + + /** + * Read the row from the ResultSet and return as a MapBean. + */ + protected SqlRow readRow(ResultSet rset, String[] propNames, int initialCapacity) throws SQLException { + + // by default a map will rehash on the 12th entry + // it will be pretty common to have 12 or more entries so + // to reduce rehashing I am trying to estimate a good + // initial capacity for the MapBean to use. + SqlRow bean = new DefaultSqlRow(initialCapacity, 0.75f, dbTrueValue); + + int index = 0; + + for (int i = 0; i < propNames.length; i++) { + index++; + Object value = rset.getObject(index); + bean.set(propNames[i], value); + } + + return bean; + + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/DefaultSqlRow.java b/src/main/java/com/avaje/ebeaninternal/server/query/DefaultSqlRow.java index a240cb6d3..04c4088e4 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/DefaultSqlRow.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/DefaultSqlRow.java @@ -1,207 +1,207 @@ -package com.avaje.ebeaninternal.server.query; - -import java.math.BigDecimal; -import java.sql.Date; -import java.sql.Timestamp; -import java.util.Collection; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; -import java.util.UUID; - -import com.avaje.ebean.SqlQuery; -import com.avaje.ebean.SqlRow; -import com.avaje.ebeaninternal.server.core.BasicTypeConverter; - -/** - * Used to return raw SQL query results. - *

- * Refer to {@link SqlQuery} for examples. - *

- *

- * There are convenience methods such as getInteger(), getBigDecimal() etc. The - * reason for these methods is that the values put into this map often come - * straight from the JDBC resultSet. Depending on the JDBC driver it may put a - * different type into a given property. For example an Integer, BigDecimal, - * Double could all be put into a property depending on the JDBC driver used. - * These convenience methods automatically convert the value as required - * returning the type you expect. - *

- */ -public class DefaultSqlRow implements SqlRow { - - static final long serialVersionUID = -3120927797041336242L; - - private final String dbTrueValue; - - /** - * The underlying map of property data. - */ - Map map; - - /** - * Create with a specific Map implementation. - *

- * The default Map implementation is LinkedHashMap. - *

- */ - public DefaultSqlRow(Map map, String dbTrueValue) { - this.map = map; - this.dbTrueValue = dbTrueValue; - } - - /** - * Create a new MapBean based on a LinkedHashMap with default - * initialCapacity (of 16). - */ - public DefaultSqlRow(String dbTrueValue) { - this.map = new LinkedHashMap(); - this.dbTrueValue = dbTrueValue; - } - - /** - * Create with an initialCapacity and loadFactor. - *

- * The defaults of these are 16 and 0.75. - *

- *

- * Note that the Map will rehash the contents when the number of keys in - * this map reaches its threshold (initialCapacity * loadFactor). - *

- */ - public DefaultSqlRow(int initialCapacity, float loadFactor, String dbTrueValue) { - this.map = new LinkedHashMap(initialCapacity, loadFactor); - this.dbTrueValue = dbTrueValue; - } - - public Iterator keys() { - return map.keySet().iterator(); - } - - public Object remove(Object name) { - name = ((String) name).toLowerCase(); - return map.remove(name); - } - - public Object get(Object name) { - name = ((String) name).toLowerCase(); - return map.get(name); - } - - public Object put(String name, Object value) { - return setInternal(name, value); - } - - public Object set(String name, Object value) { - return setInternal(name, value); - } - - private Object setInternal(String name, Object newValue) { - // MapBean properties are always lowercase - name = name.toLowerCase(); - - // valueList = null; - return map.put(name, newValue); - } - - public UUID getUUID(String name) { - Object val = get(name); - return BasicTypeConverter.toUUID(val); - } - - public Boolean getBoolean(String name) { - Object val = get(name); - return BasicTypeConverter.toBoolean(val, dbTrueValue); - } - - public Integer getInteger(String name) { - Object val = get(name); - return BasicTypeConverter.toInteger(val); - } - - public BigDecimal getBigDecimal(String name) { - Object val = get(name); - return BasicTypeConverter.toBigDecimal(val); - } - - public Long getLong(String name) { - Object val = get(name); - return BasicTypeConverter.toLong(val); - } - - public Double getDouble(String name) { - Object val = get(name); - return BasicTypeConverter.toDouble(val); - } - - public Float getFloat(String name) { - Object val = get(name); - return BasicTypeConverter.toFloat(val); - } - - public String getString(String name) { - Object val = get(name); - return BasicTypeConverter.toString(val); - } - - public java.util.Date getUtilDate(String name) { - Object val = get(name); - return BasicTypeConverter.toUtilDate(val); - } - - public Date getDate(String name) { - Object val = get(name); - return BasicTypeConverter.toDate(val); - } - - public Timestamp getTimestamp(String name) { - Object val = get(name); - return BasicTypeConverter.toTimestamp(val); - } - - public String toString() { - return map.toString(); - } - - // ------------------------------------ - // Normal map methods... - - public void clear() { - map.clear(); - } - - public boolean containsKey(Object key) { - key = ((String) key).toLowerCase(); - return map.containsKey(key); - } - - public boolean containsValue(Object value) { - return map.containsValue(value); - } - - public Set> entrySet() { - return map.entrySet(); - } - - public boolean isEmpty() { - return map.isEmpty(); - } - - public Set keySet() { - return map.keySet(); - } - - public void putAll(Map t) { - map.putAll(t); - } - - public int size() { - return map.size(); - } - - public Collection values() { - return map.values(); - } - -} +package com.avaje.ebeaninternal.server.query; + +import java.math.BigDecimal; +import java.sql.Date; +import java.sql.Timestamp; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import com.avaje.ebean.SqlQuery; +import com.avaje.ebean.SqlRow; +import com.avaje.ebeaninternal.server.core.BasicTypeConverter; + +/** + * Used to return raw SQL query results. + *

+ * Refer to {@link SqlQuery} for examples. + *

+ *

+ * There are convenience methods such as getInteger(), getBigDecimal() etc. The + * reason for these methods is that the values put into this map often come + * straight from the JDBC resultSet. Depending on the JDBC driver it may put a + * different type into a given property. For example an Integer, BigDecimal, + * Double could all be put into a property depending on the JDBC driver used. + * These convenience methods automatically convert the value as required + * returning the type you expect. + *

+ */ +public class DefaultSqlRow implements SqlRow { + + static final long serialVersionUID = -3120927797041336242L; + + private final String dbTrueValue; + + /** + * The underlying map of property data. + */ + Map map; + + /** + * Create with a specific Map implementation. + *

+ * The default Map implementation is LinkedHashMap. + *

+ */ + public DefaultSqlRow(Map map, String dbTrueValue) { + this.map = map; + this.dbTrueValue = dbTrueValue; + } + + /** + * Create a new MapBean based on a LinkedHashMap with default + * initialCapacity (of 16). + */ + public DefaultSqlRow(String dbTrueValue) { + this.map = new LinkedHashMap(); + this.dbTrueValue = dbTrueValue; + } + + /** + * Create with an initialCapacity and loadFactor. + *

+ * The defaults of these are 16 and 0.75. + *

+ *

+ * Note that the Map will rehash the contents when the number of keys in + * this map reaches its threshold (initialCapacity * loadFactor). + *

+ */ + public DefaultSqlRow(int initialCapacity, float loadFactor, String dbTrueValue) { + this.map = new LinkedHashMap(initialCapacity, loadFactor); + this.dbTrueValue = dbTrueValue; + } + + public Iterator keys() { + return map.keySet().iterator(); + } + + public Object remove(Object name) { + name = ((String) name).toLowerCase(); + return map.remove(name); + } + + public Object get(Object name) { + name = ((String) name).toLowerCase(); + return map.get(name); + } + + public Object put(String name, Object value) { + return setInternal(name, value); + } + + public Object set(String name, Object value) { + return setInternal(name, value); + } + + private Object setInternal(String name, Object newValue) { + // MapBean properties are always lowercase + name = name.toLowerCase(); + + // valueList = null; + return map.put(name, newValue); + } + + public UUID getUUID(String name) { + Object val = get(name); + return BasicTypeConverter.toUUID(val); + } + + public Boolean getBoolean(String name) { + Object val = get(name); + return BasicTypeConverter.toBoolean(val, dbTrueValue); + } + + public Integer getInteger(String name) { + Object val = get(name); + return BasicTypeConverter.toInteger(val); + } + + public BigDecimal getBigDecimal(String name) { + Object val = get(name); + return BasicTypeConverter.toBigDecimal(val); + } + + public Long getLong(String name) { + Object val = get(name); + return BasicTypeConverter.toLong(val); + } + + public Double getDouble(String name) { + Object val = get(name); + return BasicTypeConverter.toDouble(val); + } + + public Float getFloat(String name) { + Object val = get(name); + return BasicTypeConverter.toFloat(val); + } + + public String getString(String name) { + Object val = get(name); + return BasicTypeConverter.toString(val); + } + + public java.util.Date getUtilDate(String name) { + Object val = get(name); + return BasicTypeConverter.toUtilDate(val); + } + + public Date getDate(String name) { + Object val = get(name); + return BasicTypeConverter.toDate(val); + } + + public Timestamp getTimestamp(String name) { + Object val = get(name); + return BasicTypeConverter.toTimestamp(val); + } + + public String toString() { + return map.toString(); + } + + // ------------------------------------ + // Normal map methods... + + public void clear() { + map.clear(); + } + + public boolean containsKey(Object key) { + key = ((String) key).toLowerCase(); + return map.containsKey(key); + } + + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + public Set> entrySet() { + return map.entrySet(); + } + + public boolean isEmpty() { + return map.isEmpty(); + } + + public Set keySet() { + return map.keySet(); + } + + public void putAll(Map t) { + map.putAll(t); + } + + public int size() { + return map.size(); + } + + public Collection values() { + return map.values(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/LimitOffsetPagedList.java b/src/main/java/com/avaje/ebeaninternal/server/query/LimitOffsetPagedList.java index 12e9dddce..bb95e5c40 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/LimitOffsetPagedList.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/LimitOffsetPagedList.java @@ -1,122 +1,122 @@ -package com.avaje.ebeaninternal.server.query; - -import java.util.List; -import java.util.concurrent.Future; - -import javax.persistence.PersistenceException; - -import com.avaje.ebean.EbeanServer; -import com.avaje.ebean.PagedList; -import com.avaje.ebeaninternal.api.Monitor; -import com.avaje.ebeaninternal.api.SpiQuery; - -/** - * PagedList implementation based on limit offset types of queries. - * - * @param - * the entity bean type - */ -public class LimitOffsetPagedList implements PagedList { - - private final transient EbeanServer server; - - private final SpiQuery query; - - private final int pageSize; - - private final int pageIndex; - - private final Monitor monitor = new Monitor(); - - private int foregroundTotalRowCount = -1; - - private Future futureRowCount; - - private List list; - - public LimitOffsetPagedList(EbeanServer server, SpiQuery query, int pageIndex, int pageSize) { - this.server = server; - this.query = query; - this.pageSize = pageSize; - this.pageIndex = pageIndex; - } - - public void loadRowCount() { - getFutureRowCount(); - } - - public Future getFutureRowCount() { - synchronized (monitor) { - if (futureRowCount == null) { - futureRowCount = server.findFutureRowCount(query, null); - } - return futureRowCount; - } - } - - public List getList() { - synchronized (monitor) { - if (list == null) { - query.setFirstRow(pageIndex * pageSize); - query.setMaxRows(pageSize); - list = server.findList(query, null); - } - return list; - } - } - - public int getTotalPageCount() { - - int rowCount = getTotalRowCount(); - if (rowCount == 0) { - return 0; - } else { - return ((rowCount - 1) / pageSize) + 1; - } - } - - public int getTotalRowCount() { - synchronized (monitor) { - if (futureRowCount != null) { - try { - // background query already initiated so get it with a wait - return futureRowCount.get(); - } catch (Exception e) { - throw new PersistenceException(e); - } - } - // already fetched? - if (foregroundTotalRowCount > -1) return foregroundTotalRowCount; - - // just using foreground thread - foregroundTotalRowCount = server.findRowCount(query, null); - return foregroundTotalRowCount; - } - } - - public boolean hasNext() { - return pageIndex < (getTotalPageCount() - 1); - } - - public boolean hasPrev() { - return pageIndex > 0; - } - - public int getPageIndex() { - return pageIndex; - } - - public int getPageSize() { - return pageSize; - } - - public String getDisplayXtoYofZ(String to, String of) { - - int first = pageIndex * pageSize + 1; - int last = first + getList().size() - 1; - int total = getTotalRowCount(); - - return first + to + last + of + total; - } - -} +package com.avaje.ebeaninternal.server.query; + +import java.util.List; +import java.util.concurrent.Future; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.PagedList; +import com.avaje.ebeaninternal.api.Monitor; +import com.avaje.ebeaninternal.api.SpiQuery; + +/** + * PagedList implementation based on limit offset types of queries. + * + * @param + * the entity bean type + */ +public class LimitOffsetPagedList implements PagedList { + + private final transient EbeanServer server; + + private final SpiQuery query; + + private final int pageSize; + + private final int pageIndex; + + private final Monitor monitor = new Monitor(); + + private int foregroundTotalRowCount = -1; + + private Future futureRowCount; + + private List list; + + public LimitOffsetPagedList(EbeanServer server, SpiQuery query, int pageIndex, int pageSize) { + this.server = server; + this.query = query; + this.pageSize = pageSize; + this.pageIndex = pageIndex; + } + + public void loadRowCount() { + getFutureRowCount(); + } + + public Future getFutureRowCount() { + synchronized (monitor) { + if (futureRowCount == null) { + futureRowCount = server.findFutureRowCount(query, null); + } + return futureRowCount; + } + } + + public List getList() { + synchronized (monitor) { + if (list == null) { + query.setFirstRow(pageIndex * pageSize); + query.setMaxRows(pageSize); + list = server.findList(query, null); + } + return list; + } + } + + public int getTotalPageCount() { + + int rowCount = getTotalRowCount(); + if (rowCount == 0) { + return 0; + } else { + return ((rowCount - 1) / pageSize) + 1; + } + } + + public int getTotalRowCount() { + synchronized (monitor) { + if (futureRowCount != null) { + try { + // background query already initiated so get it with a wait + return futureRowCount.get(); + } catch (Exception e) { + throw new PersistenceException(e); + } + } + // already fetched? + if (foregroundTotalRowCount > -1) return foregroundTotalRowCount; + + // just using foreground thread + foregroundTotalRowCount = server.findRowCount(query, null); + return foregroundTotalRowCount; + } + } + + public boolean hasNext() { + return pageIndex < (getTotalPageCount() - 1); + } + + public boolean hasPrev() { + return pageIndex > 0; + } + + public int getPageIndex() { + return pageIndex; + } + + public int getPageSize() { + return pageSize; + } + + public String getDisplayXtoYofZ(String to, String of) { + + int first = pageIndex * pageSize + 1; + int last = first + getList().size() - 1; + int total = getTotalRowCount(); + + return first + to + last + of + total; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/QueryFutureIds.java b/src/main/java/com/avaje/ebeaninternal/server/query/QueryFutureIds.java index 1936c4808..134daec59 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/QueryFutureIds.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/QueryFutureIds.java @@ -1,43 +1,43 @@ -package com.avaje.ebeaninternal.server.query; - -import java.util.List; -import java.util.concurrent.FutureTask; - -import com.avaje.ebean.FutureIds; -import com.avaje.ebean.Query; -import com.avaje.ebean.Transaction; - -/** - * Default implementation of FutureIds. - */ -public class QueryFutureIds extends BaseFuture> implements FutureIds { - - private final CallableQueryIds call; - - public QueryFutureIds(CallableQueryIds call ) { - super(new FutureTask>(call)); - this.call = call; - } - - public FutureTask> getFutureTask() { - return futureTask; - } - - public Transaction getTransaction() { - return call.transaction; - } - - public Query getQuery() { - return call.query; - } - - public List getPartialIds() { - return call.query.getIdList(); - } - - public boolean cancel(boolean mayInterruptIfRunning) { - call.query.cancel(); - return super.cancel(mayInterruptIfRunning); - } - -} +package com.avaje.ebeaninternal.server.query; + +import java.util.List; +import java.util.concurrent.FutureTask; + +import com.avaje.ebean.FutureIds; +import com.avaje.ebean.Query; +import com.avaje.ebean.Transaction; + +/** + * Default implementation of FutureIds. + */ +public class QueryFutureIds extends BaseFuture> implements FutureIds { + + private final CallableQueryIds call; + + public QueryFutureIds(CallableQueryIds call ) { + super(new FutureTask>(call)); + this.call = call; + } + + public FutureTask> getFutureTask() { + return futureTask; + } + + public Transaction getTransaction() { + return call.transaction; + } + + public Query getQuery() { + return call.query; + } + + public List getPartialIds() { + return call.query.getIdList(); + } + + public boolean cancel(boolean mayInterruptIfRunning) { + call.query.cancel(); + return super.cancel(mayInterruptIfRunning); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/QueryFutureList.java b/src/main/java/com/avaje/ebeaninternal/server/query/QueryFutureList.java index 5f7963859..6209df747 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/QueryFutureList.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/QueryFutureList.java @@ -1,68 +1,68 @@ -package com.avaje.ebeaninternal.server.query; - -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.FutureTask; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import com.avaje.ebean.FutureList; -import com.avaje.ebean.Query; -import com.avaje.ebean.Transaction; - -import javax.persistence.PersistenceException; - -/** - * Default implementation for FutureList. - */ -public class QueryFutureList extends BaseFuture> implements FutureList { - - private final CallableQueryList call; - - public QueryFutureList(CallableQueryList call) { - super(new FutureTask>(call)); - this.call = call; - } - - public FutureTask> getFutureTask() { - return futureTask; - } - - public Transaction getTransaction() { - return call.transaction; - } - - @Override - public Query getQuery() { - return call.query; - } - - @Override - public boolean cancel(boolean mayInterruptIfRunning) { - call.query.cancel(); - return super.cancel(mayInterruptIfRunning); - } - - @Override - public List getUnchecked() { - try { - return get(); - } catch (InterruptedException e) { - throw new PersistenceException(e); - } catch (ExecutionException e) { - throw new PersistenceException(e); - } - } - - @Override - public List getUnchecked(long timeout, TimeUnit unit) throws TimeoutException { - try { - return get(timeout, unit); - } catch (InterruptedException e) { - throw new PersistenceException(e); - } catch (ExecutionException e) { - throw new PersistenceException(e); - } - } - -} +package com.avaje.ebeaninternal.server.query; + +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import com.avaje.ebean.FutureList; +import com.avaje.ebean.Query; +import com.avaje.ebean.Transaction; + +import javax.persistence.PersistenceException; + +/** + * Default implementation for FutureList. + */ +public class QueryFutureList extends BaseFuture> implements FutureList { + + private final CallableQueryList call; + + public QueryFutureList(CallableQueryList call) { + super(new FutureTask>(call)); + this.call = call; + } + + public FutureTask> getFutureTask() { + return futureTask; + } + + public Transaction getTransaction() { + return call.transaction; + } + + @Override + public Query getQuery() { + return call.query; + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + call.query.cancel(); + return super.cancel(mayInterruptIfRunning); + } + + @Override + public List getUnchecked() { + try { + return get(); + } catch (InterruptedException e) { + throw new PersistenceException(e); + } catch (ExecutionException e) { + throw new PersistenceException(e); + } + } + + @Override + public List getUnchecked(long timeout, TimeUnit unit) throws TimeoutException { + try { + return get(timeout, unit); + } catch (InterruptedException e) { + throw new PersistenceException(e); + } catch (ExecutionException e) { + throw new PersistenceException(e); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/QueryFutureRowCount.java b/src/main/java/com/avaje/ebeaninternal/server/query/QueryFutureRowCount.java index a98300c1a..cd18fc15f 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/QueryFutureRowCount.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/QueryFutureRowCount.java @@ -1,39 +1,39 @@ -package com.avaje.ebeaninternal.server.query; - -import java.util.concurrent.FutureTask; - -import com.avaje.ebean.FutureRowCount; -import com.avaje.ebean.Query; -import com.avaje.ebean.Transaction; - -/** - * Future implementation for the row count query. - */ -public class QueryFutureRowCount extends BaseFuture implements FutureRowCount { - - private final CallableQueryRowCount call; - - public QueryFutureRowCount(CallableQueryRowCount call ) { - super(new FutureTask(call)); - this.call = call; - } - - public FutureTask getFutureTask() { - return futureTask; - } - - public Transaction getTransaction() { - return call.transaction; - } - - public Query getQuery() { - return call.query; - } - - public boolean cancel(boolean mayInterruptIfRunning) { - call.query.cancel(); - return super.cancel(mayInterruptIfRunning); - } - - -} +package com.avaje.ebeaninternal.server.query; + +import java.util.concurrent.FutureTask; + +import com.avaje.ebean.FutureRowCount; +import com.avaje.ebean.Query; +import com.avaje.ebean.Transaction; + +/** + * Future implementation for the row count query. + */ +public class QueryFutureRowCount extends BaseFuture implements FutureRowCount { + + private final CallableQueryRowCount call; + + public QueryFutureRowCount(CallableQueryRowCount call ) { + super(new FutureTask(call)); + this.call = call; + } + + public FutureTask getFutureTask() { + return futureTask; + } + + public Transaction getTransaction() { + return call.transaction; + } + + public Query getQuery() { + return call.query; + } + + public boolean cancel(boolean mayInterruptIfRunning) { + call.query.cancel(); + return super.cancel(mayInterruptIfRunning); + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/RawSqlSelectClauseBuilder.java b/src/main/java/com/avaje/ebeaninternal/server/query/RawSqlSelectClauseBuilder.java index 4a958f67e..ac1b00945 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/RawSqlSelectClauseBuilder.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/RawSqlSelectClauseBuilder.java @@ -1,99 +1,99 @@ -package com.avaje.ebeaninternal.server.query; - -import javax.persistence.PersistenceException; - -import com.avaje.ebean.config.dbplatform.DatabasePlatform; -import com.avaje.ebean.config.dbplatform.SqlLimitResponse; -import com.avaje.ebean.config.dbplatform.SqlLimiter; -import com.avaje.ebeaninternal.api.SpiQuery; -import com.avaje.ebeaninternal.server.core.OrmQueryRequest; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.deploy.DRawSqlSelect; -import com.avaje.ebeaninternal.server.deploy.DeployNamedQuery; -import com.avaje.ebeaninternal.server.deploy.DeployParser; -import com.avaje.ebeaninternal.server.persist.Binder; -import com.avaje.ebeaninternal.server.querydefn.OrmQueryLimitRequest; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Factory for SqlSelectClause based on raw sql. - *

- * Its job is to execute the sql, read the meta data to determine the columns to - * bean property mapping. - *

- */ -public class RawSqlSelectClauseBuilder { - - private static final Logger logger = LoggerFactory.getLogger(RawSqlSelectClauseBuilder.class); - - private final Binder binder; - - private final SqlLimiter dbQueryLimiter; - private final DatabasePlatform dbPlatform; - - public RawSqlSelectClauseBuilder(DatabasePlatform dbPlatform, Binder binder) { - - this.binder = binder; - this.dbQueryLimiter = dbPlatform.getSqlLimiter(); - this.dbPlatform = dbPlatform; - } - - /** - * Build based on the includes and using the BeanJoinTree. - */ - public CQuery build(OrmQueryRequest request) throws PersistenceException { - - SpiQuery query = request.getQuery(); - BeanDescriptor desc = request.getBeanDescriptor(); - - DeployNamedQuery namedQuery = desc.getNamedQuery(query.getName()); - DRawSqlSelect sqlSelect = namedQuery.getSqlSelect(); - - // create a parser for this specific SqlSelect... has to be really - // as each SqlSelect could have different table alias etc - DeployParser parser = sqlSelect.createDeployPropertyParser(); - - CQueryPredicates predicates = new CQueryPredicates(binder, request); - // prepare and convert logical property names to dbColumns etc - predicates.prepareRawSql(parser); - - SqlTreeAlias alias = new SqlTreeAlias(sqlSelect.getTableAlias()); - predicates.parseTableAlias(alias); - - String sql = null; - try { - - boolean includeRowNumColumn = false; - String orderBy = sqlSelect.getOrderBy(predicates); - - // build the actual sql String - sql = sqlSelect.buildSql(orderBy, predicates, request); - if (query.hasMaxRowsOrFirstRow() && dbQueryLimiter != null) { - // wrap with a limit offset or ROW_NUMBER() etc - SqlLimitResponse limitSql = dbQueryLimiter.limit(new OrmQueryLimitRequest(sql, orderBy, query, dbPlatform)); - includeRowNumColumn = limitSql.isIncludesRowNumberColumn(); - - sql = limitSql.getSql(); - } else { - // add back select keyword - // ... was removed to support dbQueryLimiter - sql = "select " + sql; - } - - SqlTree sqlTree = sqlSelect.getSqlTree(); - - CQueryPlan queryPlan = new CQueryPlan(request, sql, sqlTree, true, includeRowNumColumn, ""); - CQuery compiledQuery = new CQuery(request, predicates, queryPlan); - - return compiledQuery; - - } catch (Exception e) { - - String msg = "Error with " + desc.getFullName() + " query:\r" + sql; - logger.error(msg); - throw new PersistenceException(e); - } - } - -} +package com.avaje.ebeaninternal.server.query; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.config.dbplatform.DatabasePlatform; +import com.avaje.ebean.config.dbplatform.SqlLimitResponse; +import com.avaje.ebean.config.dbplatform.SqlLimiter; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.DRawSqlSelect; +import com.avaje.ebeaninternal.server.deploy.DeployNamedQuery; +import com.avaje.ebeaninternal.server.deploy.DeployParser; +import com.avaje.ebeaninternal.server.persist.Binder; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryLimitRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Factory for SqlSelectClause based on raw sql. + *

+ * Its job is to execute the sql, read the meta data to determine the columns to + * bean property mapping. + *

+ */ +public class RawSqlSelectClauseBuilder { + + private static final Logger logger = LoggerFactory.getLogger(RawSqlSelectClauseBuilder.class); + + private final Binder binder; + + private final SqlLimiter dbQueryLimiter; + private final DatabasePlatform dbPlatform; + + public RawSqlSelectClauseBuilder(DatabasePlatform dbPlatform, Binder binder) { + + this.binder = binder; + this.dbQueryLimiter = dbPlatform.getSqlLimiter(); + this.dbPlatform = dbPlatform; + } + + /** + * Build based on the includes and using the BeanJoinTree. + */ + public CQuery build(OrmQueryRequest request) throws PersistenceException { + + SpiQuery query = request.getQuery(); + BeanDescriptor desc = request.getBeanDescriptor(); + + DeployNamedQuery namedQuery = desc.getNamedQuery(query.getName()); + DRawSqlSelect sqlSelect = namedQuery.getSqlSelect(); + + // create a parser for this specific SqlSelect... has to be really + // as each SqlSelect could have different table alias etc + DeployParser parser = sqlSelect.createDeployPropertyParser(); + + CQueryPredicates predicates = new CQueryPredicates(binder, request); + // prepare and convert logical property names to dbColumns etc + predicates.prepareRawSql(parser); + + SqlTreeAlias alias = new SqlTreeAlias(sqlSelect.getTableAlias()); + predicates.parseTableAlias(alias); + + String sql = null; + try { + + boolean includeRowNumColumn = false; + String orderBy = sqlSelect.getOrderBy(predicates); + + // build the actual sql String + sql = sqlSelect.buildSql(orderBy, predicates, request); + if (query.hasMaxRowsOrFirstRow() && dbQueryLimiter != null) { + // wrap with a limit offset or ROW_NUMBER() etc + SqlLimitResponse limitSql = dbQueryLimiter.limit(new OrmQueryLimitRequest(sql, orderBy, query, dbPlatform)); + includeRowNumColumn = limitSql.isIncludesRowNumberColumn(); + + sql = limitSql.getSql(); + } else { + // add back select keyword + // ... was removed to support dbQueryLimiter + sql = "select " + sql; + } + + SqlTree sqlTree = sqlSelect.getSqlTree(); + + CQueryPlan queryPlan = new CQueryPlan(request, sql, sqlTree, true, includeRowNumColumn, ""); + CQuery compiledQuery = new CQuery(request, predicates, queryPlan); + + return compiledQuery; + + } catch (Exception e) { + + String msg = "Error with " + desc.getFullName() + " query:\r" + sql; + logger.error(msg); + throw new PersistenceException(e); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlBeanLoad.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlBeanLoad.java index e158faaf5..802f740f8 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlBeanLoad.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlBeanLoad.java @@ -1,98 +1,98 @@ -package com.avaje.ebeaninternal.server.query; - -import java.sql.SQLException; - -import javax.persistence.PersistenceException; - -import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebean.bean.EntityBeanIntercept; -import com.avaje.ebeaninternal.api.SpiQuery.Mode; -import com.avaje.ebeaninternal.server.deploy.BeanProperty; -import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; -import com.avaje.ebeaninternal.server.deploy.DbReadContext; - -/** - * Controls the loading of property data into a bean. - *

- * Takes into account the differences of lazy loading and - * partial objects. - *

- */ -public class SqlBeanLoad { - - private final DbReadContext ctx; - private final EntityBean bean; - private final EntityBeanIntercept ebi; - - private final Class type; - private final boolean lazyLoading; - private final boolean refreshLoading; - private final boolean rawSql; - - public SqlBeanLoad(DbReadContext ctx, Class type, EntityBean bean, Mode queryMode) { - - this.ctx = ctx; - this.rawSql = ctx.isRawSql(); - this.type = type; - this.lazyLoading = queryMode.equals(Mode.LAZYLOAD_BEAN); - this.refreshLoading = queryMode.equals(Mode.REFRESH_BEAN); - this.bean = bean; - this.ebi = bean == null ? null : bean._ebean_getIntercept(); - } - - /** - * Return true if this is a lazy loading. - */ - public boolean isLazyLoad() { - return lazyLoading; - } - - /** - * Increment the resultSet index 1. - */ - public void loadIgnore(int increment) { - ctx.getDataReader().incrementPos(increment); - } - - public Object load(BeanProperty prop) throws SQLException { - - if (!rawSql && !prop.isLoadProperty()) { - return null; - } - - if ((bean == null) - || (lazyLoading && ebi.isLoadedProperty(prop.getPropertyIndex())) - || (type != null && !prop.isAssignableFrom(type))) { - - // ignore this property - // ... null: bean already in persistence context - // ... lazyLoading: partial bean that is lazy loading - // ... type: inheritance and not assignable to this instance - - prop.loadIgnore(ctx); - return null; - } - - try { - Object dbVal = prop.read(ctx); - if (!refreshLoading) { - prop.setValue(bean, dbVal); - } else { - prop.setValueIntercept(bean, dbVal); - } - - return dbVal; - - } catch (Exception e) { - String msg = "Error loading on " + prop.getFullBeanName(); - throw new PersistenceException(msg, e); - } - } - - public void loadAssocMany(BeanPropertyAssocMany prop) { - - // do nothing, as a lazy loading BeanCollection 'reference' - // is created and registered with the loading context - // in SqlTreeNodeBean.createListProxies() - } -} +package com.avaje.ebeaninternal.server.query; + +import java.sql.SQLException; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.bean.EntityBeanIntercept; +import com.avaje.ebeaninternal.api.SpiQuery.Mode; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.deploy.DbReadContext; + +/** + * Controls the loading of property data into a bean. + *

+ * Takes into account the differences of lazy loading and + * partial objects. + *

+ */ +public class SqlBeanLoad { + + private final DbReadContext ctx; + private final EntityBean bean; + private final EntityBeanIntercept ebi; + + private final Class type; + private final boolean lazyLoading; + private final boolean refreshLoading; + private final boolean rawSql; + + public SqlBeanLoad(DbReadContext ctx, Class type, EntityBean bean, Mode queryMode) { + + this.ctx = ctx; + this.rawSql = ctx.isRawSql(); + this.type = type; + this.lazyLoading = queryMode.equals(Mode.LAZYLOAD_BEAN); + this.refreshLoading = queryMode.equals(Mode.REFRESH_BEAN); + this.bean = bean; + this.ebi = bean == null ? null : bean._ebean_getIntercept(); + } + + /** + * Return true if this is a lazy loading. + */ + public boolean isLazyLoad() { + return lazyLoading; + } + + /** + * Increment the resultSet index 1. + */ + public void loadIgnore(int increment) { + ctx.getDataReader().incrementPos(increment); + } + + public Object load(BeanProperty prop) throws SQLException { + + if (!rawSql && !prop.isLoadProperty()) { + return null; + } + + if ((bean == null) + || (lazyLoading && ebi.isLoadedProperty(prop.getPropertyIndex())) + || (type != null && !prop.isAssignableFrom(type))) { + + // ignore this property + // ... null: bean already in persistence context + // ... lazyLoading: partial bean that is lazy loading + // ... type: inheritance and not assignable to this instance + + prop.loadIgnore(ctx); + return null; + } + + try { + Object dbVal = prop.read(ctx); + if (!refreshLoading) { + prop.setValue(bean, dbVal); + } else { + prop.setValueIntercept(bean, dbVal); + } + + return dbVal; + + } catch (Exception e) { + String msg = "Error loading on " + prop.getFullBeanName(); + throw new PersistenceException(msg, e); + } + } + + public void loadAssocMany(BeanPropertyAssocMany prop) { + + // do nothing, as a lazy loading BeanCollection 'reference' + // is created and registered with the loading context + // in SqlTreeNodeBean.createListProxies() + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTree.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTree.java index 835b98a6b..8beb31d44 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTree.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTree.java @@ -1,155 +1,155 @@ -package com.avaje.ebeaninternal.server.query; - -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -import com.avaje.ebeaninternal.server.deploy.BeanProperty; -import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; -import com.avaje.ebeaninternal.server.el.ElPropertyValue; - -/** - * Represents the SELECT clause part of the SQL query. - */ -public class SqlTree { - - private final SqlTreeNode rootNode; - - /** - * Property if resultSet contains master and detail rows. - */ - private final BeanPropertyAssocMany manyProperty; - - private final String manyPropertyName; - - private final ElPropertyValue manyPropEl; - - private final Set includes; - - /** - * Summary of the select being generated. - */ - private final String summary; - - private final String selectSql; - - private final String fromSql; - - /** - * Encrypted Properties require additional binding. - */ - private final BeanProperty[] encryptedProps; - - /** - * Where clause for inheritance. - */ - private final String inheritanceWhereSql; - - /** - * Create the SqlSelectClause. - */ - public SqlTree(String summary, SqlTreeNode rootNode, String selectSql, String fromSql, String inheritanceWhereSql, - BeanProperty[] encryptedProps, BeanPropertyAssocMany manyProperty, String manyPropertyName, - ElPropertyValue manyPropEl, Set includes) { - - this.summary = summary; - this.rootNode = rootNode; - this.selectSql = selectSql; - this.fromSql = fromSql; - this.inheritanceWhereSql = inheritanceWhereSql; - this.encryptedProps = encryptedProps; - this.manyProperty = manyProperty; - this.manyPropertyName = manyPropertyName; - this.manyPropEl = manyPropEl; - this.includes = includes; - } - - /** - * Construct for RawSql. - */ - public SqlTree(String summary, SqlTreeNode rootNode) { - this.summary = summary; - this.rootNode = rootNode; - this.selectSql = null; - this.fromSql = null; - this.inheritanceWhereSql = null; - this.encryptedProps = null; - this.manyProperty = null; - this.manyPropertyName = null; - this.manyPropEl = null; - this.includes = null; - } - - /** - * Build a select expression chain for RawSql. - */ - public List buildSelectExpressionChain() { - ArrayList list = new ArrayList(); - rootNode.buildSelectExpressionChain(list); - return list; - } - - /** - * Return the includes. Associated beans lists etc. - */ - public Set getIncludes() { - return includes; - } - - /** - * Return the String for the actual SQL. - */ - public String getSelectSql() { - return selectSql; - } - - public String getFromSql() { - return fromSql; - } - - /** - * Return the where clause for inheritance. - */ - public String getInheritanceWhereSql() { - return inheritanceWhereSql; - } - - /** - * Return a summary of the select clause. - */ - public String getSummary() { - return summary; - } - - public SqlTreeNode getRootNode() { - return rootNode; - } - - /** - * Return the property that is associated with the many. There can only be one - * per SqlSelect. This can be null. - */ - public BeanPropertyAssocMany getManyProperty() { - return manyProperty; - } - - public String getManyPropertyName() { - return manyPropertyName; - } - - public ElPropertyValue getManyPropertyEl() { - return manyPropEl; - } - - /** - * Return true if this query includes a Many association. - */ - public boolean isManyIncluded() { - return (manyProperty != null); - } - - public BeanProperty[] getEncryptedProps() { - return encryptedProps; - } - -} +package com.avaje.ebeaninternal.server.query; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.el.ElPropertyValue; + +/** + * Represents the SELECT clause part of the SQL query. + */ +public class SqlTree { + + private final SqlTreeNode rootNode; + + /** + * Property if resultSet contains master and detail rows. + */ + private final BeanPropertyAssocMany manyProperty; + + private final String manyPropertyName; + + private final ElPropertyValue manyPropEl; + + private final Set includes; + + /** + * Summary of the select being generated. + */ + private final String summary; + + private final String selectSql; + + private final String fromSql; + + /** + * Encrypted Properties require additional binding. + */ + private final BeanProperty[] encryptedProps; + + /** + * Where clause for inheritance. + */ + private final String inheritanceWhereSql; + + /** + * Create the SqlSelectClause. + */ + public SqlTree(String summary, SqlTreeNode rootNode, String selectSql, String fromSql, String inheritanceWhereSql, + BeanProperty[] encryptedProps, BeanPropertyAssocMany manyProperty, String manyPropertyName, + ElPropertyValue manyPropEl, Set includes) { + + this.summary = summary; + this.rootNode = rootNode; + this.selectSql = selectSql; + this.fromSql = fromSql; + this.inheritanceWhereSql = inheritanceWhereSql; + this.encryptedProps = encryptedProps; + this.manyProperty = manyProperty; + this.manyPropertyName = manyPropertyName; + this.manyPropEl = manyPropEl; + this.includes = includes; + } + + /** + * Construct for RawSql. + */ + public SqlTree(String summary, SqlTreeNode rootNode) { + this.summary = summary; + this.rootNode = rootNode; + this.selectSql = null; + this.fromSql = null; + this.inheritanceWhereSql = null; + this.encryptedProps = null; + this.manyProperty = null; + this.manyPropertyName = null; + this.manyPropEl = null; + this.includes = null; + } + + /** + * Build a select expression chain for RawSql. + */ + public List buildSelectExpressionChain() { + ArrayList list = new ArrayList(); + rootNode.buildSelectExpressionChain(list); + return list; + } + + /** + * Return the includes. Associated beans lists etc. + */ + public Set getIncludes() { + return includes; + } + + /** + * Return the String for the actual SQL. + */ + public String getSelectSql() { + return selectSql; + } + + public String getFromSql() { + return fromSql; + } + + /** + * Return the where clause for inheritance. + */ + public String getInheritanceWhereSql() { + return inheritanceWhereSql; + } + + /** + * Return a summary of the select clause. + */ + public String getSummary() { + return summary; + } + + public SqlTreeNode getRootNode() { + return rootNode; + } + + /** + * Return the property that is associated with the many. There can only be one + * per SqlSelect. This can be null. + */ + public BeanPropertyAssocMany getManyProperty() { + return manyProperty; + } + + public String getManyPropertyName() { + return manyPropertyName; + } + + public ElPropertyValue getManyPropertyEl() { + return manyPropEl; + } + + /** + * Return true if this query includes a Many association. + */ + public boolean isManyIncluded() { + return (manyProperty != null); + } + + public BeanProperty[] getEncryptedProps() { + return encryptedProps; + } + +} 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 a9d3cd6fb..0cf0d629a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java @@ -1,666 +1,666 @@ -package com.avaje.ebeaninternal.server.query; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.avaje.ebeaninternal.api.ManyWhereJoins; -import com.avaje.ebeaninternal.api.PropertyJoin; -import com.avaje.ebeaninternal.api.SpiQuery; -import com.avaje.ebeaninternal.api.SpiQuery.Type; -import com.avaje.ebeaninternal.server.core.OrmQueryRequest; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -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; -import com.avaje.ebeaninternal.server.deploy.InheritInfo; -import com.avaje.ebeaninternal.server.deploy.TableJoin; -import com.avaje.ebeaninternal.server.el.ElPropertyValue; -import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail; -import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties; - -/** - * Factory for SqlTree. - */ -public class SqlTreeBuilder { - - private static final Logger logger = LoggerFactory.getLogger(SqlTreeBuilder.class); - - private final SpiQuery query; - - private final BeanDescriptor desc; - - private final OrmQueryDetail queryDetail; - - private final StringBuilder summary = new StringBuilder(); - - private final CQueryPredicates predicates; - - private final boolean subQuery; - - /** - * Property if resultSet contains master and detail rows. - */ - private BeanPropertyAssocMany manyProperty; - - private String manyPropertyName; - - private final SqlTreeAlias alias; - - private final DefaultDbSqlContext ctx; - - private final HashSet selectIncludes = new HashSet(); - - private final ManyWhereJoins manyWhereJoins; - - private final TableJoin includeJoin; - - private final boolean rawSql; - - private SqlTreeNode rootNode; - - /** - * Construct for RawSql query. - */ - public SqlTreeBuilder(OrmQueryRequest request, CQueryPredicates predicates, OrmQueryDetail queryDetail) { - - this.rawSql = true; - this.desc = request.getBeanDescriptor(); - this.query = null; - this.subQuery = false; - this.queryDetail = queryDetail; - this.predicates = predicates; - - this.includeJoin = null; - this.manyWhereJoins = null; - this.alias = null; - this.ctx = null; - } - - /** - * The predicates are used to determine if 'extra' joins are required to - * support the where and/or order by clause. If so these extra joins are added - * to the root node. - */ - public SqlTreeBuilder(String tableAliasPlaceHolder, String columnAliasPrefix, - OrmQueryRequest request, CQueryPredicates predicates) { - - this.rawSql = false; - this.desc = request.getBeanDescriptor(); - this.query = request.getQuery(); - - this.subQuery = Type.SUBQUERY.equals(query.getType()) || Type.ID_LIST.equals(query.getType()); - this.includeJoin = query.getIncludeTableJoin(); - this.manyWhereJoins = query.getManyWhereJoins(); - this.queryDetail = query.getDetail(); - - this.predicates = predicates; - this.alias = new SqlTreeAlias(request.getQuery().getAlias()==null?request.getBeanDescriptor().getBaseTableAlias():request.getQuery().getAlias()); - this.ctx = new DefaultDbSqlContext(alias, tableAliasPlaceHolder, columnAliasPrefix, !subQuery); - } - - /** - * Build based on the includes and using the BeanJoinTree. - */ - public SqlTree build() { - - summary.append(desc.getName()); - - // build the appropriate chain of SelectAdapter's - buildRoot(desc); - - // build the actual String - String selectSql = null; - String fromSql = null; - String inheritanceWhereSql = null; - BeanProperty[] encryptedProps = null; - if (!rawSql) { - selectSql = buildSelectClause(); - fromSql = buildFromClause(); - inheritanceWhereSql = buildWhereClause(); - encryptedProps = ctx.getEncryptedProps(); - } - - ElPropertyValue manyPropEl = null; - if (manyPropertyName != null) { - manyPropEl = desc.getElGetValue(manyPropertyName); - } - - return new SqlTree(summary.toString(), rootNode, selectSql, fromSql, inheritanceWhereSql, encryptedProps, - manyProperty, manyPropertyName, manyPropEl, queryDetail.getIncludes()); - } - - private String buildSelectClause() { - - if (rawSql) { - return "Not Used"; - } - rootNode.appendSelect(ctx, subQuery); - - String selectSql = ctx.getContent(); - - // trim off the first comma - if (selectSql.length() >= SqlTreeNode.COMMA.length()) { - selectSql = selectSql.substring(SqlTreeNode.COMMA.length()); - } - - return selectSql; - } - - private String buildWhereClause() { - - if (rawSql) { - return "Not Used"; - } - rootNode.appendWhere(ctx); - return ctx.getContent(); - } - - private String buildFromClause() { - - if (rawSql) { - return "Not Used"; - } - rootNode.appendFrom(ctx, SqlJoinType.AUTO); - return ctx.getContent(); - } - - private void buildRoot(BeanDescriptor desc) { - - rootNode = buildSelectChain(null, null, desc, null); - - if (!rawSql) { - alias.addJoin(queryDetail.getIncludes(), desc); - alias.addJoin(predicates.getPredicateIncludes(), desc); - alias.addManyWhereJoins(manyWhereJoins.getPropertyNames()); - - // build set of table alias - alias.buildAlias(); - - predicates.parseTableAlias(alias); - } - } - - /** - * Recursively build the query tree depending on what leaves in the tree - * should be included. - */ - private SqlTreeNode buildSelectChain(String prefix, BeanPropertyAssoc prop, - BeanDescriptor desc, List joinList) { - - List myJoinList = new ArrayList(); - - BeanPropertyAssocOne[] ones = desc.propertiesOne(); - for (int i = 0; i < ones.length; i++) { - String propPrefix = SplitName.add(prefix, ones[i].getName()); - if (isIncludeBean(propPrefix, ones[i])) { - selectIncludes.add(propPrefix); - buildSelectChain(propPrefix, ones[i], ones[i].getTargetDescriptor(), myJoinList); - } - } - - BeanPropertyAssocMany[] manys = desc.propertiesMany(); - for (int i = 0; i < manys.length; i++) { - String propPrefix = SplitName.add(prefix, manys[i].getName()); - if (isIncludeMany(prefix, propPrefix, manys[i])) { - selectIncludes.add(propPrefix); - buildSelectChain(propPrefix, manys[i], manys[i].getTargetDescriptor(), myJoinList); - } - } - - if (prefix == null && !rawSql) { - addManyWhereJoins(myJoinList); - } - - SqlTreeNode selectNode = buildNode(prefix, prop, desc, myJoinList); - if (joinList != null) { - joinList.add(selectNode); - } - return selectNode; - } - - /** - * Add joins used to support where clause predicates on 'many' properties. - *

- * These joins are effectively independent of any fetch joins on 'many' - * properties. - *

- */ - private void addManyWhereJoins(List myJoinList) { - - Collection includes = manyWhereJoins.getPropertyJoins(); - for (PropertyJoin joinProp : includes) { - BeanPropertyAssoc beanProperty = (BeanPropertyAssoc) desc.getBeanPropertyFromPath(joinProp.getProperty()); - SqlTreeNodeManyWhereJoin nodeJoin = new SqlTreeNodeManyWhereJoin(joinProp.getProperty(), beanProperty, joinProp.getSqlJoinType()); - myJoinList.add(nodeJoin); - } - } - - private SqlTreeNode buildNode(String prefix, BeanPropertyAssoc prop, BeanDescriptor desc, - List myList) { - - OrmQueryProperties queryProps = queryDetail.getChunk(prefix, false); - - SqlTreeProperties props = getBaseSelect(desc, queryProps); - - if (prefix == null) { - buildExtraJoins(desc, myList); - - // Optional many property for lazy loading query - BeanPropertyAssocMany lazyLoadMany = (query == null) ? null : query.getLazyLoadForParentsProperty(); - boolean withId = !subQuery && (query == null || !query.isDistinct()); - return new SqlTreeNodeRoot(desc, props, myList, withId, includeJoin, lazyLoadMany); - - } else if (prop instanceof BeanPropertyAssocMany) { - return new SqlTreeNodeManyRoot(prefix, (BeanPropertyAssocMany) prop, props, myList); - - } else { - return new SqlTreeNodeBean(prefix, prop, props, myList, true); - } - } - - /** - * Build extra joins to support properties used in where clause but not - * already in select clause. - */ - private void buildExtraJoins(BeanDescriptor desc, List myList) { - - if (rawSql) { - return; - } - - Set predicateIncludes = predicates.getPredicateIncludes(); - - if (predicateIncludes == null) { - return; - } - - // Note includes - basically means joins. - // The selectIncludes is the set of joins that are required to support - // the 'select' part of the query. We may need to add other joins to - // support the predicates or order by clauses. - - // remove ManyWhereJoins from the predicateIncludes - predicateIncludes.removeAll(manyWhereJoins.getPropertyNames()); - predicateIncludes.addAll(predicates.getOrderByIncludes()); - - // look for predicateIncludes that are not in selectIncludes and add - // them as extra joins to the query - IncludesDistiller extraJoinDistill = new IncludesDistiller(desc, selectIncludes, predicateIncludes); - - Collection extraJoins = extraJoinDistill.getExtraJoinRootNodes(); - if (extraJoins.isEmpty()) { - return; - - } else { - // add extra joins required to support predicates - // and/or order by clause - for (SqlTreeNodeExtraJoin extraJoin: extraJoins) { - myList.add(extraJoin); - if (extraJoin.isManyJoin()) { - // as we are now going to join to the many then we need - // to add the distinct to the sql query to stop duplicate - // rows... - query.setSqlDistinct(true); - } - } - } - } - - /** - * A subQuery has slightly different rules in that it just generates SQL (into - * the where clause) and its properties are not required to read the resultSet - * etc. - *

- * This means it can included individual properties of an embedded bean. - *

- */ - private void addPropertyToSubQuery(SqlTreeProperties selectProps, BeanDescriptor desc, - OrmQueryProperties queryProps, String propName) { - - BeanProperty p = desc.findBeanProperty(propName); - if (p == null) { - logger.error("property [" + propName + "]not found on " + desc + " for query - excluding it."); - - } else if (p instanceof BeanPropertyAssoc && p.isEmbedded()) { - // if the property is embedded we need to lookup the real column name - int pos = propName.indexOf("."); - if (pos > -1) { - String name = propName.substring(pos + 1); - p = ((BeanPropertyAssoc) p).getTargetDescriptor().findBeanProperty(name); - } - } - - selectProps.add(p); - } - - private void addProperty(SqlTreeProperties selectProps, BeanDescriptor desc, - OrmQueryProperties queryProps, String propName) { - - if (subQuery) { - addPropertyToSubQuery(selectProps, desc, queryProps, propName); - return; - } - - int basePos = propName.indexOf('.'); - if (basePos > -1) { - // property on an embedded bean. Embedded beans do not yet - // support being partially populated so we include the - // 'base' property and make sure we only do that once - String baseName = propName.substring(0, basePos); - - // make sure we only included the base/embedded bean once - if (!selectProps.containsProperty(baseName)) { - BeanProperty p = desc.findBeanProperty(baseName); - if (p == null) { - logger.error("property [" + propName + "] not found on " + desc + " for query - excluding it."); - - } else if (p.isEmbedded()) { - // add the embedded bean (and effectively - // all its properties) - selectProps.add(p); - - } else { - String m = "property [" + p.getFullBeanName() + "] expected to be an embedded bean for query - excluding it."; - logger.error(m); - } - } - - } else { - // find the property including searching the - // sub class hierarchy if required - BeanProperty p = desc.findBeanProperty(propName); - if (p == null) { - logger.error("property [" + propName + "] not found on " + desc + " for query - excluding it."); - p = desc.findBeanProperty("id"); - selectProps.add(p); - - } else if (p.isId()) { - // do not bother to include id for normal queries as the - // id is always added (except for subQueries) - - } else if (p instanceof BeanPropertyAssoc) { - // need to check if this property should be - // excluded. This occurs when this property is - // included as a bean join. With a bean join - // the property should be excluded as the bean - // join has its own node in the SqlTree. - if (!queryProps.isIncludedBeanJoin(p.getName())) { - // include the property... which basically - // means include the foreign key column(s) - selectProps.add(p); - } - } else { - selectProps.add(p); - } - } - } - - private SqlTreeProperties getBaseSelectPartial(BeanDescriptor desc, OrmQueryProperties queryProps) { - - SqlTreeProperties selectProps = new SqlTreeProperties(); - selectProps.setReadOnly(queryProps.isReadOnly()); - - // add properties in the order in which they appear - // in the query. Gives predictable sql/properties for - // use with SqlSelect type queries. - - // Also note that this can include transient properties. - // This makes sense for transient properties used to - // hold sum() count() type values (with SqlSelect) - for (String propName : queryProps.getSelectProperties()) { - if (propName.length() > 0) { - addProperty(selectProps, desc, queryProps, propName); - } - } - - return selectProps; - } - - private SqlTreeProperties getBaseSelect(BeanDescriptor desc, OrmQueryProperties queryProps) { - - boolean partial = queryProps != null && !queryProps.allProperties(); - if (partial) { - return getBaseSelectPartial(desc, queryProps); - } - - SqlTreeProperties selectProps = new SqlTreeProperties(); - selectProps.setAllProperties(true); - - // normal simple properties of the bean - selectProps.add(desc.propertiesBaseScalar()); - selectProps.add(desc.propertiesBaseCompound()); - selectProps.add(desc.propertiesEmbedded()); - - BeanPropertyAssocOne[] propertiesOne = desc.propertiesOne(); - for (int i = 0; i < propertiesOne.length; i++) { - if (queryProps != null && queryProps.isIncludedBeanJoin(propertiesOne[i].getName())) { - // if it is a joined bean... then don't add the property - // as it will have its own entire Node in the SqlTree - } else { - selectProps.add(propertiesOne[i]); - } - } - - selectProps.setTableJoins(desc.tableJoins()); - - InheritInfo inheritInfo = desc.getInheritInfo(); - if (inheritInfo != null) { - // add sub type properties - inheritInfo.addChildrenProperties(selectProps); - - } - return selectProps; - } - - /** - * Return true if this many node should be included in the query. - */ - private boolean isIncludeMany(String prefix, String propName, BeanPropertyAssocMany manyProp) { - - if (queryDetail.isJoinsEmpty()) { - return false; - } - - if (queryDetail.includes(propName)) { - - if (manyProperty != null) { - // only one many associated allowed to be included in fetch - if (logger.isDebugEnabled()) { - logger.debug("Not joining [" + propName + "] as already joined to a Many[" + manyProperty + "]."); - } - return false; - } - - manyProperty = manyProp; - manyPropertyName = propName; - summary.append(" +many:").append(propName); - return true; - } - return false; - } - - /** - * Test to see if we are including this node into the query. - *

- * Return true if this node is FULLY included resulting in table join. If the - * node is not included but its parent has been included then a "bean proxy" - * is added and false is returned. - *

- */ - private boolean isIncludeBean(String prefix, BeanPropertyAssocOne prop) { - - if (queryDetail.includes(prefix)) { - // explicitly included - summary.append(", ").append(prefix); - String[] splitNames = SplitName.split(prefix); - queryDetail.includeBeanJoin(splitNames[0], splitNames[1]); - return true; - } - - return false; - } - - /** - * Takes the select includes and the predicates includes and determines the - * extra joins required to support the predicates (that are not already - * supported by the select includes). - *

- * This returns ONLY the leaves. The joins for the leaves - *

- */ - private static class IncludesDistiller { - - private final Set selectIncludes; - private final Set predicateIncludes; - - /** - * Contains the 'root' extra joins. We only return the roots back. - */ - private final Map joinRegister = new HashMap(); - - /** - * Register of all the extra join nodes. - */ - private final Map rootRegister = new HashMap(); - - private final BeanDescriptor desc; - - private IncludesDistiller(BeanDescriptor desc, Set selectIncludes, - Set predicateIncludes) { - this.desc = desc; - this.selectIncludes = selectIncludes; - this.predicateIncludes = predicateIncludes; - } - - /** - * Build the collection of extra joins returning just the roots. - *

- * each root returned here could contain a little tree of joins. This - * follows the more natural pattern and allows for forcing outer joins from - * a join to a 'many' down through the rest of its tree. - *

- */ - private Collection getExtraJoinRootNodes() { - - String[] extras = findExtras(); - if (extras.length == 0) { - return rootRegister.values(); - } - - // sort so we process only getting the leaves - // excluding nodes between root and the leaf - Arrays.sort(extras); - - // reverse order so get the leaves first... - for (int i = 0; i < extras.length; i++) { - createExtraJoin(extras[i]); - } - - return rootRegister.values(); - } - - private void createExtraJoin(String includeProp) { - - SqlTreeNodeExtraJoin extraJoin = createJoinLeaf(includeProp); - if (extraJoin != null) { - // add the extra join... - - // find root of this extra join... linking back to the - // parents (creating the tree) as it goes. - SqlTreeNodeExtraJoin root = findExtraJoinRoot(includeProp, extraJoin); - - // register the root because these are the only ones we - // return back. - rootRegister.put(root.getName(), root); - } - } - - /** - * Create a SqlTreeNodeExtraJoin, register and return it. - */ - private SqlTreeNodeExtraJoin createJoinLeaf(String propertyName) { - - ElPropertyValue elGetValue = desc.getElGetValue(propertyName); - - if (elGetValue == null) { - // this can occur for master detail queries - // with concatenated keys (so not an error now) - return null; - } - BeanProperty beanProperty = elGetValue.getBeanProperty(); - if (beanProperty instanceof BeanPropertyAssoc) { - BeanPropertyAssoc assocProp = (BeanPropertyAssoc) beanProperty; - if (assocProp.isEmbedded()) { - // no extra join required for embedded beans - return null; - } - SqlTreeNodeExtraJoin extraJoin = new SqlTreeNodeExtraJoin(propertyName, assocProp); - joinRegister.put(propertyName, extraJoin); - return extraJoin; - } - return null; - } - - /** - * Find the root the this extra join tree. - *

- * This may need to create a parent join implicitly if a predicate join - * 'skips' a level. e.g. where details.user.id = 1 (maybe join to details is - * not specified and is implicitly created. - *

- */ - private SqlTreeNodeExtraJoin findExtraJoinRoot(String includeProp, - SqlTreeNodeExtraJoin childJoin) { - - int dotPos = includeProp.lastIndexOf('.'); - if (dotPos == -1) { - // no parent possible(parent is root) - return childJoin; - - } else { - // look in register ... - String parentPropertyName = includeProp.substring(0, dotPos); - if (selectIncludes.contains(parentPropertyName)) { - // parent already handled by select - return childJoin; - } - - SqlTreeNodeExtraJoin parentJoin = joinRegister.get(parentPropertyName); - if (parentJoin == null) { - // we need to create this the parent implicitly... - parentJoin = createJoinLeaf(parentPropertyName); - } - - parentJoin.addChild(childJoin); - return findExtraJoinRoot(parentPropertyName, parentJoin); - } - } - - /** - * Find the extra joins required by predicates and not already taken care of - * by the select. - */ - private String[] findExtras() { - - List extras = new ArrayList(); - - for (String predProp : predicateIncludes) { - if (!selectIncludes.contains(predProp)) { - extras.add(predProp); - } - } - return extras.toArray(new String[extras.size()]); - } - - } -} +package com.avaje.ebeaninternal.server.query; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.avaje.ebeaninternal.api.ManyWhereJoins; +import com.avaje.ebeaninternal.api.PropertyJoin; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.api.SpiQuery.Type; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +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; +import com.avaje.ebeaninternal.server.deploy.InheritInfo; +import com.avaje.ebeaninternal.server.deploy.TableJoin; +import com.avaje.ebeaninternal.server.el.ElPropertyValue; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties; + +/** + * Factory for SqlTree. + */ +public class SqlTreeBuilder { + + private static final Logger logger = LoggerFactory.getLogger(SqlTreeBuilder.class); + + private final SpiQuery query; + + private final BeanDescriptor desc; + + private final OrmQueryDetail queryDetail; + + private final StringBuilder summary = new StringBuilder(); + + private final CQueryPredicates predicates; + + private final boolean subQuery; + + /** + * Property if resultSet contains master and detail rows. + */ + private BeanPropertyAssocMany manyProperty; + + private String manyPropertyName; + + private final SqlTreeAlias alias; + + private final DefaultDbSqlContext ctx; + + private final HashSet selectIncludes = new HashSet(); + + private final ManyWhereJoins manyWhereJoins; + + private final TableJoin includeJoin; + + private final boolean rawSql; + + private SqlTreeNode rootNode; + + /** + * Construct for RawSql query. + */ + public SqlTreeBuilder(OrmQueryRequest request, CQueryPredicates predicates, OrmQueryDetail queryDetail) { + + this.rawSql = true; + this.desc = request.getBeanDescriptor(); + this.query = null; + this.subQuery = false; + this.queryDetail = queryDetail; + this.predicates = predicates; + + this.includeJoin = null; + this.manyWhereJoins = null; + this.alias = null; + this.ctx = null; + } + + /** + * The predicates are used to determine if 'extra' joins are required to + * support the where and/or order by clause. If so these extra joins are added + * to the root node. + */ + public SqlTreeBuilder(String tableAliasPlaceHolder, String columnAliasPrefix, + OrmQueryRequest request, CQueryPredicates predicates) { + + this.rawSql = false; + this.desc = request.getBeanDescriptor(); + this.query = request.getQuery(); + + this.subQuery = Type.SUBQUERY.equals(query.getType()) || Type.ID_LIST.equals(query.getType()); + this.includeJoin = query.getIncludeTableJoin(); + this.manyWhereJoins = query.getManyWhereJoins(); + this.queryDetail = query.getDetail(); + + this.predicates = predicates; + this.alias = new SqlTreeAlias(request.getQuery().getAlias()==null?request.getBeanDescriptor().getBaseTableAlias():request.getQuery().getAlias()); + this.ctx = new DefaultDbSqlContext(alias, tableAliasPlaceHolder, columnAliasPrefix, !subQuery); + } + + /** + * Build based on the includes and using the BeanJoinTree. + */ + public SqlTree build() { + + summary.append(desc.getName()); + + // build the appropriate chain of SelectAdapter's + buildRoot(desc); + + // build the actual String + String selectSql = null; + String fromSql = null; + String inheritanceWhereSql = null; + BeanProperty[] encryptedProps = null; + if (!rawSql) { + selectSql = buildSelectClause(); + fromSql = buildFromClause(); + inheritanceWhereSql = buildWhereClause(); + encryptedProps = ctx.getEncryptedProps(); + } + + ElPropertyValue manyPropEl = null; + if (manyPropertyName != null) { + manyPropEl = desc.getElGetValue(manyPropertyName); + } + + return new SqlTree(summary.toString(), rootNode, selectSql, fromSql, inheritanceWhereSql, encryptedProps, + manyProperty, manyPropertyName, manyPropEl, queryDetail.getIncludes()); + } + + private String buildSelectClause() { + + if (rawSql) { + return "Not Used"; + } + rootNode.appendSelect(ctx, subQuery); + + String selectSql = ctx.getContent(); + + // trim off the first comma + if (selectSql.length() >= SqlTreeNode.COMMA.length()) { + selectSql = selectSql.substring(SqlTreeNode.COMMA.length()); + } + + return selectSql; + } + + private String buildWhereClause() { + + if (rawSql) { + return "Not Used"; + } + rootNode.appendWhere(ctx); + return ctx.getContent(); + } + + private String buildFromClause() { + + if (rawSql) { + return "Not Used"; + } + rootNode.appendFrom(ctx, SqlJoinType.AUTO); + return ctx.getContent(); + } + + private void buildRoot(BeanDescriptor desc) { + + rootNode = buildSelectChain(null, null, desc, null); + + if (!rawSql) { + alias.addJoin(queryDetail.getIncludes(), desc); + alias.addJoin(predicates.getPredicateIncludes(), desc); + alias.addManyWhereJoins(manyWhereJoins.getPropertyNames()); + + // build set of table alias + alias.buildAlias(); + + predicates.parseTableAlias(alias); + } + } + + /** + * Recursively build the query tree depending on what leaves in the tree + * should be included. + */ + private SqlTreeNode buildSelectChain(String prefix, BeanPropertyAssoc prop, + BeanDescriptor desc, List joinList) { + + List myJoinList = new ArrayList(); + + BeanPropertyAssocOne[] ones = desc.propertiesOne(); + for (int i = 0; i < ones.length; i++) { + String propPrefix = SplitName.add(prefix, ones[i].getName()); + if (isIncludeBean(propPrefix, ones[i])) { + selectIncludes.add(propPrefix); + buildSelectChain(propPrefix, ones[i], ones[i].getTargetDescriptor(), myJoinList); + } + } + + BeanPropertyAssocMany[] manys = desc.propertiesMany(); + for (int i = 0; i < manys.length; i++) { + String propPrefix = SplitName.add(prefix, manys[i].getName()); + if (isIncludeMany(prefix, propPrefix, manys[i])) { + selectIncludes.add(propPrefix); + buildSelectChain(propPrefix, manys[i], manys[i].getTargetDescriptor(), myJoinList); + } + } + + if (prefix == null && !rawSql) { + addManyWhereJoins(myJoinList); + } + + SqlTreeNode selectNode = buildNode(prefix, prop, desc, myJoinList); + if (joinList != null) { + joinList.add(selectNode); + } + return selectNode; + } + + /** + * Add joins used to support where clause predicates on 'many' properties. + *

+ * These joins are effectively independent of any fetch joins on 'many' + * properties. + *

+ */ + private void addManyWhereJoins(List myJoinList) { + + Collection includes = manyWhereJoins.getPropertyJoins(); + for (PropertyJoin joinProp : includes) { + BeanPropertyAssoc beanProperty = (BeanPropertyAssoc) desc.getBeanPropertyFromPath(joinProp.getProperty()); + SqlTreeNodeManyWhereJoin nodeJoin = new SqlTreeNodeManyWhereJoin(joinProp.getProperty(), beanProperty, joinProp.getSqlJoinType()); + myJoinList.add(nodeJoin); + } + } + + private SqlTreeNode buildNode(String prefix, BeanPropertyAssoc prop, BeanDescriptor desc, + List myList) { + + OrmQueryProperties queryProps = queryDetail.getChunk(prefix, false); + + SqlTreeProperties props = getBaseSelect(desc, queryProps); + + if (prefix == null) { + buildExtraJoins(desc, myList); + + // Optional many property for lazy loading query + BeanPropertyAssocMany lazyLoadMany = (query == null) ? null : query.getLazyLoadForParentsProperty(); + boolean withId = !subQuery && (query == null || !query.isDistinct()); + return new SqlTreeNodeRoot(desc, props, myList, withId, includeJoin, lazyLoadMany); + + } else if (prop instanceof BeanPropertyAssocMany) { + return new SqlTreeNodeManyRoot(prefix, (BeanPropertyAssocMany) prop, props, myList); + + } else { + return new SqlTreeNodeBean(prefix, prop, props, myList, true); + } + } + + /** + * Build extra joins to support properties used in where clause but not + * already in select clause. + */ + private void buildExtraJoins(BeanDescriptor desc, List myList) { + + if (rawSql) { + return; + } + + Set predicateIncludes = predicates.getPredicateIncludes(); + + if (predicateIncludes == null) { + return; + } + + // Note includes - basically means joins. + // The selectIncludes is the set of joins that are required to support + // the 'select' part of the query. We may need to add other joins to + // support the predicates or order by clauses. + + // remove ManyWhereJoins from the predicateIncludes + predicateIncludes.removeAll(manyWhereJoins.getPropertyNames()); + predicateIncludes.addAll(predicates.getOrderByIncludes()); + + // look for predicateIncludes that are not in selectIncludes and add + // them as extra joins to the query + IncludesDistiller extraJoinDistill = new IncludesDistiller(desc, selectIncludes, predicateIncludes); + + Collection extraJoins = extraJoinDistill.getExtraJoinRootNodes(); + if (extraJoins.isEmpty()) { + return; + + } else { + // add extra joins required to support predicates + // and/or order by clause + for (SqlTreeNodeExtraJoin extraJoin: extraJoins) { + myList.add(extraJoin); + if (extraJoin.isManyJoin()) { + // as we are now going to join to the many then we need + // to add the distinct to the sql query to stop duplicate + // rows... + query.setSqlDistinct(true); + } + } + } + } + + /** + * A subQuery has slightly different rules in that it just generates SQL (into + * the where clause) and its properties are not required to read the resultSet + * etc. + *

+ * This means it can included individual properties of an embedded bean. + *

+ */ + private void addPropertyToSubQuery(SqlTreeProperties selectProps, BeanDescriptor desc, + OrmQueryProperties queryProps, String propName) { + + BeanProperty p = desc.findBeanProperty(propName); + if (p == null) { + logger.error("property [" + propName + "]not found on " + desc + " for query - excluding it."); + + } else if (p instanceof BeanPropertyAssoc && p.isEmbedded()) { + // if the property is embedded we need to lookup the real column name + int pos = propName.indexOf("."); + if (pos > -1) { + String name = propName.substring(pos + 1); + p = ((BeanPropertyAssoc) p).getTargetDescriptor().findBeanProperty(name); + } + } + + selectProps.add(p); + } + + private void addProperty(SqlTreeProperties selectProps, BeanDescriptor desc, + OrmQueryProperties queryProps, String propName) { + + if (subQuery) { + addPropertyToSubQuery(selectProps, desc, queryProps, propName); + return; + } + + int basePos = propName.indexOf('.'); + if (basePos > -1) { + // property on an embedded bean. Embedded beans do not yet + // support being partially populated so we include the + // 'base' property and make sure we only do that once + String baseName = propName.substring(0, basePos); + + // make sure we only included the base/embedded bean once + if (!selectProps.containsProperty(baseName)) { + BeanProperty p = desc.findBeanProperty(baseName); + if (p == null) { + logger.error("property [" + propName + "] not found on " + desc + " for query - excluding it."); + + } else if (p.isEmbedded()) { + // add the embedded bean (and effectively + // all its properties) + selectProps.add(p); + + } else { + String m = "property [" + p.getFullBeanName() + "] expected to be an embedded bean for query - excluding it."; + logger.error(m); + } + } + + } else { + // find the property including searching the + // sub class hierarchy if required + BeanProperty p = desc.findBeanProperty(propName); + if (p == null) { + logger.error("property [" + propName + "] not found on " + desc + " for query - excluding it."); + p = desc.findBeanProperty("id"); + selectProps.add(p); + + } else if (p.isId()) { + // do not bother to include id for normal queries as the + // id is always added (except for subQueries) + + } else if (p instanceof BeanPropertyAssoc) { + // need to check if this property should be + // excluded. This occurs when this property is + // included as a bean join. With a bean join + // the property should be excluded as the bean + // join has its own node in the SqlTree. + if (!queryProps.isIncludedBeanJoin(p.getName())) { + // include the property... which basically + // means include the foreign key column(s) + selectProps.add(p); + } + } else { + selectProps.add(p); + } + } + } + + private SqlTreeProperties getBaseSelectPartial(BeanDescriptor desc, OrmQueryProperties queryProps) { + + SqlTreeProperties selectProps = new SqlTreeProperties(); + selectProps.setReadOnly(queryProps.isReadOnly()); + + // add properties in the order in which they appear + // in the query. Gives predictable sql/properties for + // use with SqlSelect type queries. + + // Also note that this can include transient properties. + // This makes sense for transient properties used to + // hold sum() count() type values (with SqlSelect) + for (String propName : queryProps.getSelectProperties()) { + if (propName.length() > 0) { + addProperty(selectProps, desc, queryProps, propName); + } + } + + return selectProps; + } + + private SqlTreeProperties getBaseSelect(BeanDescriptor desc, OrmQueryProperties queryProps) { + + boolean partial = queryProps != null && !queryProps.allProperties(); + if (partial) { + return getBaseSelectPartial(desc, queryProps); + } + + SqlTreeProperties selectProps = new SqlTreeProperties(); + selectProps.setAllProperties(true); + + // normal simple properties of the bean + selectProps.add(desc.propertiesBaseScalar()); + selectProps.add(desc.propertiesBaseCompound()); + selectProps.add(desc.propertiesEmbedded()); + + BeanPropertyAssocOne[] propertiesOne = desc.propertiesOne(); + for (int i = 0; i < propertiesOne.length; i++) { + if (queryProps != null && queryProps.isIncludedBeanJoin(propertiesOne[i].getName())) { + // if it is a joined bean... then don't add the property + // as it will have its own entire Node in the SqlTree + } else { + selectProps.add(propertiesOne[i]); + } + } + + selectProps.setTableJoins(desc.tableJoins()); + + InheritInfo inheritInfo = desc.getInheritInfo(); + if (inheritInfo != null) { + // add sub type properties + inheritInfo.addChildrenProperties(selectProps); + + } + return selectProps; + } + + /** + * Return true if this many node should be included in the query. + */ + private boolean isIncludeMany(String prefix, String propName, BeanPropertyAssocMany manyProp) { + + if (queryDetail.isJoinsEmpty()) { + return false; + } + + if (queryDetail.includes(propName)) { + + if (manyProperty != null) { + // only one many associated allowed to be included in fetch + if (logger.isDebugEnabled()) { + logger.debug("Not joining [" + propName + "] as already joined to a Many[" + manyProperty + "]."); + } + return false; + } + + manyProperty = manyProp; + manyPropertyName = propName; + summary.append(" +many:").append(propName); + return true; + } + return false; + } + + /** + * Test to see if we are including this node into the query. + *

+ * Return true if this node is FULLY included resulting in table join. If the + * node is not included but its parent has been included then a "bean proxy" + * is added and false is returned. + *

+ */ + private boolean isIncludeBean(String prefix, BeanPropertyAssocOne prop) { + + if (queryDetail.includes(prefix)) { + // explicitly included + summary.append(", ").append(prefix); + String[] splitNames = SplitName.split(prefix); + queryDetail.includeBeanJoin(splitNames[0], splitNames[1]); + return true; + } + + return false; + } + + /** + * Takes the select includes and the predicates includes and determines the + * extra joins required to support the predicates (that are not already + * supported by the select includes). + *

+ * This returns ONLY the leaves. The joins for the leaves + *

+ */ + private static class IncludesDistiller { + + private final Set selectIncludes; + private final Set predicateIncludes; + + /** + * Contains the 'root' extra joins. We only return the roots back. + */ + private final Map joinRegister = new HashMap(); + + /** + * Register of all the extra join nodes. + */ + private final Map rootRegister = new HashMap(); + + private final BeanDescriptor desc; + + private IncludesDistiller(BeanDescriptor desc, Set selectIncludes, + Set predicateIncludes) { + this.desc = desc; + this.selectIncludes = selectIncludes; + this.predicateIncludes = predicateIncludes; + } + + /** + * Build the collection of extra joins returning just the roots. + *

+ * each root returned here could contain a little tree of joins. This + * follows the more natural pattern and allows for forcing outer joins from + * a join to a 'many' down through the rest of its tree. + *

+ */ + private Collection getExtraJoinRootNodes() { + + String[] extras = findExtras(); + if (extras.length == 0) { + return rootRegister.values(); + } + + // sort so we process only getting the leaves + // excluding nodes between root and the leaf + Arrays.sort(extras); + + // reverse order so get the leaves first... + for (int i = 0; i < extras.length; i++) { + createExtraJoin(extras[i]); + } + + return rootRegister.values(); + } + + private void createExtraJoin(String includeProp) { + + SqlTreeNodeExtraJoin extraJoin = createJoinLeaf(includeProp); + if (extraJoin != null) { + // add the extra join... + + // find root of this extra join... linking back to the + // parents (creating the tree) as it goes. + SqlTreeNodeExtraJoin root = findExtraJoinRoot(includeProp, extraJoin); + + // register the root because these are the only ones we + // return back. + rootRegister.put(root.getName(), root); + } + } + + /** + * Create a SqlTreeNodeExtraJoin, register and return it. + */ + private SqlTreeNodeExtraJoin createJoinLeaf(String propertyName) { + + ElPropertyValue elGetValue = desc.getElGetValue(propertyName); + + if (elGetValue == null) { + // this can occur for master detail queries + // with concatenated keys (so not an error now) + return null; + } + BeanProperty beanProperty = elGetValue.getBeanProperty(); + if (beanProperty instanceof BeanPropertyAssoc) { + BeanPropertyAssoc assocProp = (BeanPropertyAssoc) beanProperty; + if (assocProp.isEmbedded()) { + // no extra join required for embedded beans + return null; + } + SqlTreeNodeExtraJoin extraJoin = new SqlTreeNodeExtraJoin(propertyName, assocProp); + joinRegister.put(propertyName, extraJoin); + return extraJoin; + } + return null; + } + + /** + * Find the root the this extra join tree. + *

+ * This may need to create a parent join implicitly if a predicate join + * 'skips' a level. e.g. where details.user.id = 1 (maybe join to details is + * not specified and is implicitly created. + *

+ */ + private SqlTreeNodeExtraJoin findExtraJoinRoot(String includeProp, + SqlTreeNodeExtraJoin childJoin) { + + int dotPos = includeProp.lastIndexOf('.'); + if (dotPos == -1) { + // no parent possible(parent is root) + return childJoin; + + } else { + // look in register ... + String parentPropertyName = includeProp.substring(0, dotPos); + if (selectIncludes.contains(parentPropertyName)) { + // parent already handled by select + return childJoin; + } + + SqlTreeNodeExtraJoin parentJoin = joinRegister.get(parentPropertyName); + if (parentJoin == null) { + // we need to create this the parent implicitly... + parentJoin = createJoinLeaf(parentPropertyName); + } + + parentJoin.addChild(childJoin); + return findExtraJoinRoot(parentPropertyName, parentJoin); + } + } + + /** + * Find the extra joins required by predicates and not already taken care of + * by the select. + */ + private String[] findExtras() { + + List extras = new ArrayList(); + + for (String predProp : predicateIncludes) { + if (!selectIncludes.contains(predProp)) { + extras.add(predProp); + } + } + return extras.toArray(new String[extras.size()]); + } + + } +} 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 58f1c612f..f81ea6ef2 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java @@ -1,525 +1,525 @@ -package com.avaje.ebeaninternal.server.query; - -import java.sql.SQLException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -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.Mode; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -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; -import com.avaje.ebeaninternal.server.deploy.DbSqlContext; -import com.avaje.ebeaninternal.server.deploy.InheritInfo; -import com.avaje.ebeaninternal.server.deploy.TableJoin; -import com.avaje.ebeaninternal.server.deploy.id.IdBinder; -import com.avaje.ebeaninternal.server.lib.util.StringHelper; - -/** - * Normal bean included in the query. - */ -public class SqlTreeNodeBean implements SqlTreeNode { - - private static final SqlTreeNode[] NO_CHILDREN = new SqlTreeNode[0]; - - protected final BeanDescriptor desc; - - protected final IdBinder idBinder; - - /** - * The children which will be other SelectBean or SelectProxyBean. - */ - protected final SqlTreeNode[] children; - - protected final boolean readOnlyLeaf; - - /** - * Set to true if this is a partial object fetch. - */ - protected final boolean partialObject; - - protected final BeanProperty[] properties; - - /** - * Extra where clause added by Where annotation on associated many. - */ - protected final String extraWhere; - - protected final BeanPropertyAssoc nodeBeanProp; - - protected final TableJoin[] tableJoins; - - /** - * False if report bean and has no id property. - */ - protected final boolean readId; - - protected final boolean disableLazyLoad; - - protected final InheritInfo inheritInfo; - - protected final String prefix; - - protected final Map pathMap; - - protected final BeanPropertyAssocMany lazyLoadParent; - - public SqlTreeNodeBean(String prefix, BeanPropertyAssoc beanProp, SqlTreeProperties props, - List myChildren, boolean withId) { - - this(prefix, beanProp, beanProp.getTargetDescriptor(), props, myChildren, withId, null); - } - - /** - * Create with the appropriate node. - */ - public SqlTreeNodeBean(String prefix, BeanPropertyAssoc beanProp, BeanDescriptor desc, - SqlTreeProperties props, List myChildren, boolean withId, BeanPropertyAssocMany lazyLoadParent) { - - this.lazyLoadParent = lazyLoadParent; - this.prefix = prefix; - this.nodeBeanProp = beanProp; - this.desc = desc; - this.inheritInfo = desc.getInheritInfo(); - this.extraWhere = (beanProp == null) ? null : beanProp.getExtraWhere(); - - this.idBinder = desc.getIdBinder(); - - // the bean has an Id property and we want to use it - this.readId = withId && (desc.getIdProperty() != null); - this.disableLazyLoad = !readId || desc.isSqlSelectBased(); - - this.tableJoins = props.getTableJoins(); - - this.partialObject = props.isPartialObject(); - - this.readOnlyLeaf = props.isReadOnly(); - - this.properties = props.getProps(); - - - if (myChildren == null) { - children = NO_CHILDREN; - } else { - children = myChildren.toArray(new SqlTreeNode[myChildren.size()]); - } - - pathMap = createPathMap(prefix, desc); - } - - private Map createPathMap(String prefix, BeanDescriptor desc) { - - BeanPropertyAssocMany[] manys = desc.propertiesMany(); - - HashMap m = new HashMap(); - for (int i = 0; i < manys.length; i++) { - String name = manys[i].getName(); - m.put(name, getPath(prefix, name)); - } - - return m; - } - - private String getPath(String prefix, String propertyName) { - if (prefix == null) { - return propertyName; - } else { - return prefix + "." + propertyName; - } - } - - protected void postLoad(DbReadContext cquery, EntityBean loadedBean, Object id, Object lazyLoadParentId) { - } - - public void buildSelectExpressionChain(List selectChain) { - if (readId) { - idBinder.buildSelectExpressionChain(prefix, selectChain); - } - for (int i = 0, x = properties.length; i < x; i++) { - properties[i].buildSelectExpressionChain(prefix, selectChain); - } - // recursively continue reading... - for (int i = 0; i < children.length; i++) { - // read each child... and let them set their - // values back to this localBean - children[i].buildSelectExpressionChain(selectChain); - } - } - - /** - * read the properties from the resultSet. - */ - public void load(DbReadContext ctx, EntityBean parentBean) throws SQLException { - - Object lazyLoadParentId = null; - if (lazyLoadParent != null) { - lazyLoadParentId = lazyLoadParent.getBeanDescriptor().getIdBinder().read(ctx); - } - - // bean already existing in the persistence context - EntityBean contextBean = null; - - Class localType; - BeanDescriptor localDesc; - IdBinder localIdBinder; - EntityBean localBean; - - if (inheritInfo != null) { - InheritInfo localInfo = inheritInfo.readType(ctx); - if (localInfo == null) { - // the bean must be null - localIdBinder = idBinder; - localBean = null; - localType = null; - localDesc = desc; - } else { - localBean = localInfo.createEntityBean(); - localType = localInfo.getType(); - localIdBinder = localInfo.getIdBinder(); - localDesc = localInfo.getBeanDescriptor(); - } - - } else { - localType = null; - localDesc = desc; - localBean = desc.createEntityBean(); - localIdBinder = idBinder; - } - - Mode queryMode = ctx.getQueryMode(); - - PersistenceContext persistenceContext = !readId ? null : ctx.getPersistenceContext(); - - Object id = null; - if (!readId) { - // report type bean... or perhaps excluding the id for SqlSelect? - - } else { - id = localIdBinder.readSet(ctx, localBean); - if (id == null) { - // bean must be null... - localBean = null; - } else { - // check the PersistenceContext to see if the bean already exists - contextBean = (EntityBean)persistenceContext.putIfAbsent(id, localBean); - if (contextBean == null) { - // bean just added to the persistenceContext - contextBean = localBean; - } else { - // bean already exists in persistenceContext - if (isLoadContextBeanNeeded(queryMode, contextBean)){ - // refresh it anyway (lazy loading for example) - localBean = contextBean; - } else { - // ignore the DB data... - localBean = null; - } - } - } - } - - ctx.setCurrentPrefix(prefix, pathMap); - - ctx.propagateState(localBean); - - SqlBeanLoad sqlBeanLoad = new SqlBeanLoad(ctx, localType, localBean, queryMode); - - if (inheritInfo == null) { - // normal behavior with no inheritance - for (int i = 0, x = properties.length; i < x; i++) { - properties[i].load(sqlBeanLoad); - } - - } else { - // take account of inheritance and due to subclassing approach - // need to get a 'local' version of the property - for (int i = 0, x = properties.length; i < x; i++) { - // get a local version of the BeanProperty - BeanProperty p = localDesc.getBeanProperty(properties[i].getName()); - if (p != null) { - p.load(sqlBeanLoad); - } else { - properties[i].loadIgnore(ctx); - } - } - } - - for (int i = 0, x = tableJoins.length; i < x; i++) { - tableJoins[i].load(sqlBeanLoad); - } - - boolean lazyLoadMany = false; - if (localBean == null && queryMode.equals(Mode.LAZYLOAD_MANY)) { - // batch lazy load many into existing contextBean - localBean = contextBean; - lazyLoadMany = true; - } - - // recursively continue reading... - for (int i = 0; i < children.length; i++) { - // read each child... and let them set their - // values back to this localBean - children[i].load(ctx, localBean); - } - - if (lazyLoadMany) { - // special case where we load children - - } else if (localBean != null) { - - ctx.setCurrentPrefix(prefix, pathMap); - if (readId) { - createListProxies(localDesc, ctx, localBean); - } - localDesc.postLoad(localBean, null); - - if (localBean instanceof EntityBean) { - EntityBeanIntercept ebi = ((EntityBean) localBean)._ebean_getIntercept(); - ebi.setPersistenceContext(persistenceContext); - if (Mode.LAZYLOAD_BEAN.equals(queryMode)) { - // Lazy Load does not reset the dirty state - ebi.setLoadedLazy(); - } else { - // normal bean loading - ebi.setLoaded(); - } - - if (partialObject) { - if (readId) { - // register for lazy loading - ctx.register(null, ebi); - } - } else { - ebi.setFullyLoadedBean(true); - } - - if (disableLazyLoad) { - // bean does not have an Id or is SqlSelect based - ebi.setDisableLazyLoad(true); - } - if (ctx.isAutoFetchProfiling()) { - // collect autofetch profiling for this bean... - ctx.profileBean(ebi, prefix); - } - } - - } - if (parentBean != null) { - // set this back to the parentBean - nodeBeanProp.setValue(parentBean, contextBean); - } - - if (!readId) { - // a bean with no Id (never found in context) - postLoad(ctx, localBean, id, null); - - } else { - // return the contextBean which is either the localBean - // read from the resultSet and put into the context OR - // the 'matching' bean that already existed in the context - postLoad(ctx, contextBean, id, lazyLoadParentId); - } - } - - /** - * Create lazy loading proxies for the Many's except for the one that is - * included in the actual query. - */ - private void createListProxies(BeanDescriptor localDesc, DbReadContext ctx, EntityBean localBean) { - - BeanPropertyAssocMany fetchedMany = ctx.getManyProperty(); - - // load the List/Set/Map proxy objects (deferred fetching of lists) - BeanPropertyAssocMany[] manys = localDesc.propertiesMany(); - for (int i = 0; i < manys.length; i++) { - - if (fetchedMany != null && fetchedMany.equals(manys[i])) { - // this many property is included in the query... - // it is being loaded with real row data (result[1]) - } else { - // create a proxy for the many (deferred fetching) - BeanCollection ref = manys[i].createReferenceIfNull(localBean); - if (ref != null && !ref.isRegisteredWithLoadContext()) { - ctx.register(manys[i].getName(), ref); - } - } - } - } - - /** - * Append the property columns to the buffer. - */ - public void appendSelect(DbSqlContext ctx, boolean subQuery) { - - ctx.pushJoin(prefix); - ctx.pushTableAlias(prefix); - - if (lazyLoadParent != null) { - lazyLoadParent.addSelectExported(ctx, prefix); - } - - if (!subQuery && inheritInfo != null) { - ctx.appendColumn(inheritInfo.getDiscriminatorColumn()); - } - - if (readId) { - appendSelect(ctx, false, idBinder.getBeanProperty()); - } - appendSelect(ctx, subQuery, properties); - appendSelectTableJoins(ctx); - - for (int i = 0; i < children.length; i++) { - // read each child... and let them set their - // values back to this localBean - children[i].appendSelect(ctx, subQuery); - } - - ctx.popTableAlias(); - ctx.popJoin(); - } - - private void appendSelectTableJoins(DbSqlContext ctx) { - - String baseAlias = ctx.getTableAlias(prefix); - - for (int i = 0; i < tableJoins.length; i++) { - TableJoin join = tableJoins[i]; - - String alias = baseAlias + i; - - ctx.pushSecondaryTableAlias(alias); - join.appendSelect(ctx, false); - ctx.popTableAlias(); - } - } - - /** - * Append the properties to the buffer. - */ - private void appendSelect(DbSqlContext ctx, boolean subQuery, BeanProperty[] props) { - - for (int i = 0; i < props.length; i++) { - props[i].appendSelect(ctx, subQuery); - } - } - - private void appendSelect(DbSqlContext ctx, boolean subQuery, BeanProperty prop) { - - if (prop != null) { - prop.appendSelect(ctx, subQuery); - } - } - - public void appendWhere(DbSqlContext ctx) { - - // Only apply inheritance to root node as any join will alreay have the inheritance join include - see TableJoin - if (inheritInfo != null && nodeBeanProp == null) { - if (inheritInfo.isRoot()) { - // at root of hierarchy so don't bother - // adding a where clause because we want - // all the types... - } else { - // restrict to this type and - // sub types of this type. - if (ctx.length() > 0) { - ctx.append(" and"); - } - ctx.append(" ").append(ctx.getTableAlias(prefix)).append(".");// tableAlias - ctx.append(inheritInfo.getWhere()).append(" "); - } - } - if (extraWhere != null) { - if (ctx.length() > 0) { - ctx.append(" and"); - } - String ta = ctx.getTableAlias(prefix); - String ew = StringHelper.replaceString(extraWhere, "${ta}", ta); - ctx.append(" ").append(ew).append(" "); - } - - for (int i = 0; i < children.length; i++) { - // recursively add to the where clause any - // fixed predicates (extraWhere etc) - children[i].appendWhere(ctx); - } - } - - /** - * Append to the FROM clause for this node. - */ - public void appendFrom(DbSqlContext ctx, SqlJoinType joinType) { - - ctx.pushJoin(prefix); - ctx.pushTableAlias(prefix); - - // join and return SqlJoinType to use for child joins - joinType = appendFromBaseTable(ctx, joinType); - - for (int i = 0; i < properties.length; i++) { - // usually nothing... except for 1-1 Exported - properties[i].appendFrom(ctx, joinType); - } - - for (int i = 0; i < children.length; i++) { - children[i].appendFrom(ctx, joinType); - } - - ctx.popTableAlias(); - ctx.popJoin(); - } - - /** - * Join to base table for this node. This includes a join to the intersection - * table if this is a ManyToMany node. - */ - public SqlJoinType appendFromBaseTable(DbSqlContext ctx, SqlJoinType joinType) { - - if (nodeBeanProp instanceof BeanPropertyAssocMany) { - BeanPropertyAssocMany manyProp = (BeanPropertyAssocMany) nodeBeanProp; - if (manyProp.isManyToMany()) { - - String alias = ctx.getTableAlias(prefix); - String[] split = SplitName.split(prefix); - String parentAlias = ctx.getTableAlias(split[0]); - String alias2 = alias + "z_"; - - TableJoin manyToManyJoin = manyProp.getIntersectionTableJoin(); - manyToManyJoin.addJoin(joinType, parentAlias, alias2, ctx); - - return nodeBeanProp.addJoin(joinType, alias2, alias, ctx); - } - - } - - return nodeBeanProp.addJoin(joinType, prefix, ctx); - } - - /** - * Summary description. - */ - public String toString() { - return "SqlTreeNodeBean: " + desc; - } - - private boolean isLoadContextBeanNeeded(Mode queryMode, EntityBean contextBean) { - // if explicitly set loadContextBean to true, then reload - if (queryMode.isLoadContextBean()) { - return true; - } - - if (contextBean._ebean_getIntercept().isFullyLoadedBean()) { - // reload if contextBean is partial object - return false; - } - - // return true by default - return true; - } - -} +package com.avaje.ebeaninternal.server.query; + +import java.sql.SQLException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +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.Mode; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +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; +import com.avaje.ebeaninternal.server.deploy.DbSqlContext; +import com.avaje.ebeaninternal.server.deploy.InheritInfo; +import com.avaje.ebeaninternal.server.deploy.TableJoin; +import com.avaje.ebeaninternal.server.deploy.id.IdBinder; +import com.avaje.ebeaninternal.server.lib.util.StringHelper; + +/** + * Normal bean included in the query. + */ +public class SqlTreeNodeBean implements SqlTreeNode { + + private static final SqlTreeNode[] NO_CHILDREN = new SqlTreeNode[0]; + + protected final BeanDescriptor desc; + + protected final IdBinder idBinder; + + /** + * The children which will be other SelectBean or SelectProxyBean. + */ + protected final SqlTreeNode[] children; + + protected final boolean readOnlyLeaf; + + /** + * Set to true if this is a partial object fetch. + */ + protected final boolean partialObject; + + protected final BeanProperty[] properties; + + /** + * Extra where clause added by Where annotation on associated many. + */ + protected final String extraWhere; + + protected final BeanPropertyAssoc nodeBeanProp; + + protected final TableJoin[] tableJoins; + + /** + * False if report bean and has no id property. + */ + protected final boolean readId; + + protected final boolean disableLazyLoad; + + protected final InheritInfo inheritInfo; + + protected final String prefix; + + protected final Map pathMap; + + protected final BeanPropertyAssocMany lazyLoadParent; + + public SqlTreeNodeBean(String prefix, BeanPropertyAssoc beanProp, SqlTreeProperties props, + List myChildren, boolean withId) { + + this(prefix, beanProp, beanProp.getTargetDescriptor(), props, myChildren, withId, null); + } + + /** + * Create with the appropriate node. + */ + public SqlTreeNodeBean(String prefix, BeanPropertyAssoc beanProp, BeanDescriptor desc, + SqlTreeProperties props, List myChildren, boolean withId, BeanPropertyAssocMany lazyLoadParent) { + + this.lazyLoadParent = lazyLoadParent; + this.prefix = prefix; + this.nodeBeanProp = beanProp; + this.desc = desc; + this.inheritInfo = desc.getInheritInfo(); + this.extraWhere = (beanProp == null) ? null : beanProp.getExtraWhere(); + + this.idBinder = desc.getIdBinder(); + + // the bean has an Id property and we want to use it + this.readId = withId && (desc.getIdProperty() != null); + this.disableLazyLoad = !readId || desc.isSqlSelectBased(); + + this.tableJoins = props.getTableJoins(); + + this.partialObject = props.isPartialObject(); + + this.readOnlyLeaf = props.isReadOnly(); + + this.properties = props.getProps(); + + + if (myChildren == null) { + children = NO_CHILDREN; + } else { + children = myChildren.toArray(new SqlTreeNode[myChildren.size()]); + } + + pathMap = createPathMap(prefix, desc); + } + + private Map createPathMap(String prefix, BeanDescriptor desc) { + + BeanPropertyAssocMany[] manys = desc.propertiesMany(); + + HashMap m = new HashMap(); + for (int i = 0; i < manys.length; i++) { + String name = manys[i].getName(); + m.put(name, getPath(prefix, name)); + } + + return m; + } + + private String getPath(String prefix, String propertyName) { + if (prefix == null) { + return propertyName; + } else { + return prefix + "." + propertyName; + } + } + + protected void postLoad(DbReadContext cquery, EntityBean loadedBean, Object id, Object lazyLoadParentId) { + } + + public void buildSelectExpressionChain(List selectChain) { + if (readId) { + idBinder.buildSelectExpressionChain(prefix, selectChain); + } + for (int i = 0, x = properties.length; i < x; i++) { + properties[i].buildSelectExpressionChain(prefix, selectChain); + } + // recursively continue reading... + for (int i = 0; i < children.length; i++) { + // read each child... and let them set their + // values back to this localBean + children[i].buildSelectExpressionChain(selectChain); + } + } + + /** + * read the properties from the resultSet. + */ + public void load(DbReadContext ctx, EntityBean parentBean) throws SQLException { + + Object lazyLoadParentId = null; + if (lazyLoadParent != null) { + lazyLoadParentId = lazyLoadParent.getBeanDescriptor().getIdBinder().read(ctx); + } + + // bean already existing in the persistence context + EntityBean contextBean = null; + + Class localType; + BeanDescriptor localDesc; + IdBinder localIdBinder; + EntityBean localBean; + + if (inheritInfo != null) { + InheritInfo localInfo = inheritInfo.readType(ctx); + if (localInfo == null) { + // the bean must be null + localIdBinder = idBinder; + localBean = null; + localType = null; + localDesc = desc; + } else { + localBean = localInfo.createEntityBean(); + localType = localInfo.getType(); + localIdBinder = localInfo.getIdBinder(); + localDesc = localInfo.getBeanDescriptor(); + } + + } else { + localType = null; + localDesc = desc; + localBean = desc.createEntityBean(); + localIdBinder = idBinder; + } + + Mode queryMode = ctx.getQueryMode(); + + PersistenceContext persistenceContext = !readId ? null : ctx.getPersistenceContext(); + + Object id = null; + if (!readId) { + // report type bean... or perhaps excluding the id for SqlSelect? + + } else { + id = localIdBinder.readSet(ctx, localBean); + if (id == null) { + // bean must be null... + localBean = null; + } else { + // check the PersistenceContext to see if the bean already exists + contextBean = (EntityBean)persistenceContext.putIfAbsent(id, localBean); + if (contextBean == null) { + // bean just added to the persistenceContext + contextBean = localBean; + } else { + // bean already exists in persistenceContext + if (isLoadContextBeanNeeded(queryMode, contextBean)){ + // refresh it anyway (lazy loading for example) + localBean = contextBean; + } else { + // ignore the DB data... + localBean = null; + } + } + } + } + + ctx.setCurrentPrefix(prefix, pathMap); + + ctx.propagateState(localBean); + + SqlBeanLoad sqlBeanLoad = new SqlBeanLoad(ctx, localType, localBean, queryMode); + + if (inheritInfo == null) { + // normal behavior with no inheritance + for (int i = 0, x = properties.length; i < x; i++) { + properties[i].load(sqlBeanLoad); + } + + } else { + // take account of inheritance and due to subclassing approach + // need to get a 'local' version of the property + for (int i = 0, x = properties.length; i < x; i++) { + // get a local version of the BeanProperty + BeanProperty p = localDesc.getBeanProperty(properties[i].getName()); + if (p != null) { + p.load(sqlBeanLoad); + } else { + properties[i].loadIgnore(ctx); + } + } + } + + for (int i = 0, x = tableJoins.length; i < x; i++) { + tableJoins[i].load(sqlBeanLoad); + } + + boolean lazyLoadMany = false; + if (localBean == null && queryMode.equals(Mode.LAZYLOAD_MANY)) { + // batch lazy load many into existing contextBean + localBean = contextBean; + lazyLoadMany = true; + } + + // recursively continue reading... + for (int i = 0; i < children.length; i++) { + // read each child... and let them set their + // values back to this localBean + children[i].load(ctx, localBean); + } + + if (lazyLoadMany) { + // special case where we load children + + } else if (localBean != null) { + + ctx.setCurrentPrefix(prefix, pathMap); + if (readId) { + createListProxies(localDesc, ctx, localBean); + } + localDesc.postLoad(localBean, null); + + if (localBean instanceof EntityBean) { + EntityBeanIntercept ebi = ((EntityBean) localBean)._ebean_getIntercept(); + ebi.setPersistenceContext(persistenceContext); + if (Mode.LAZYLOAD_BEAN.equals(queryMode)) { + // Lazy Load does not reset the dirty state + ebi.setLoadedLazy(); + } else { + // normal bean loading + ebi.setLoaded(); + } + + if (partialObject) { + if (readId) { + // register for lazy loading + ctx.register(null, ebi); + } + } else { + ebi.setFullyLoadedBean(true); + } + + if (disableLazyLoad) { + // bean does not have an Id or is SqlSelect based + ebi.setDisableLazyLoad(true); + } + if (ctx.isAutoFetchProfiling()) { + // collect autofetch profiling for this bean... + ctx.profileBean(ebi, prefix); + } + } + + } + if (parentBean != null) { + // set this back to the parentBean + nodeBeanProp.setValue(parentBean, contextBean); + } + + if (!readId) { + // a bean with no Id (never found in context) + postLoad(ctx, localBean, id, null); + + } else { + // return the contextBean which is either the localBean + // read from the resultSet and put into the context OR + // the 'matching' bean that already existed in the context + postLoad(ctx, contextBean, id, lazyLoadParentId); + } + } + + /** + * Create lazy loading proxies for the Many's except for the one that is + * included in the actual query. + */ + private void createListProxies(BeanDescriptor localDesc, DbReadContext ctx, EntityBean localBean) { + + BeanPropertyAssocMany fetchedMany = ctx.getManyProperty(); + + // load the List/Set/Map proxy objects (deferred fetching of lists) + BeanPropertyAssocMany[] manys = localDesc.propertiesMany(); + for (int i = 0; i < manys.length; i++) { + + if (fetchedMany != null && fetchedMany.equals(manys[i])) { + // this many property is included in the query... + // it is being loaded with real row data (result[1]) + } else { + // create a proxy for the many (deferred fetching) + BeanCollection ref = manys[i].createReferenceIfNull(localBean); + if (ref != null && !ref.isRegisteredWithLoadContext()) { + ctx.register(manys[i].getName(), ref); + } + } + } + } + + /** + * Append the property columns to the buffer. + */ + public void appendSelect(DbSqlContext ctx, boolean subQuery) { + + ctx.pushJoin(prefix); + ctx.pushTableAlias(prefix); + + if (lazyLoadParent != null) { + lazyLoadParent.addSelectExported(ctx, prefix); + } + + if (!subQuery && inheritInfo != null) { + ctx.appendColumn(inheritInfo.getDiscriminatorColumn()); + } + + if (readId) { + appendSelect(ctx, false, idBinder.getBeanProperty()); + } + appendSelect(ctx, subQuery, properties); + appendSelectTableJoins(ctx); + + for (int i = 0; i < children.length; i++) { + // read each child... and let them set their + // values back to this localBean + children[i].appendSelect(ctx, subQuery); + } + + ctx.popTableAlias(); + ctx.popJoin(); + } + + private void appendSelectTableJoins(DbSqlContext ctx) { + + String baseAlias = ctx.getTableAlias(prefix); + + for (int i = 0; i < tableJoins.length; i++) { + TableJoin join = tableJoins[i]; + + String alias = baseAlias + i; + + ctx.pushSecondaryTableAlias(alias); + join.appendSelect(ctx, false); + ctx.popTableAlias(); + } + } + + /** + * Append the properties to the buffer. + */ + private void appendSelect(DbSqlContext ctx, boolean subQuery, BeanProperty[] props) { + + for (int i = 0; i < props.length; i++) { + props[i].appendSelect(ctx, subQuery); + } + } + + private void appendSelect(DbSqlContext ctx, boolean subQuery, BeanProperty prop) { + + if (prop != null) { + prop.appendSelect(ctx, subQuery); + } + } + + public void appendWhere(DbSqlContext ctx) { + + // Only apply inheritance to root node as any join will alreay have the inheritance join include - see TableJoin + if (inheritInfo != null && nodeBeanProp == null) { + if (inheritInfo.isRoot()) { + // at root of hierarchy so don't bother + // adding a where clause because we want + // all the types... + } else { + // restrict to this type and + // sub types of this type. + if (ctx.length() > 0) { + ctx.append(" and"); + } + ctx.append(" ").append(ctx.getTableAlias(prefix)).append(".");// tableAlias + ctx.append(inheritInfo.getWhere()).append(" "); + } + } + if (extraWhere != null) { + if (ctx.length() > 0) { + ctx.append(" and"); + } + String ta = ctx.getTableAlias(prefix); + String ew = StringHelper.replaceString(extraWhere, "${ta}", ta); + ctx.append(" ").append(ew).append(" "); + } + + for (int i = 0; i < children.length; i++) { + // recursively add to the where clause any + // fixed predicates (extraWhere etc) + children[i].appendWhere(ctx); + } + } + + /** + * Append to the FROM clause for this node. + */ + public void appendFrom(DbSqlContext ctx, SqlJoinType joinType) { + + ctx.pushJoin(prefix); + ctx.pushTableAlias(prefix); + + // join and return SqlJoinType to use for child joins + joinType = appendFromBaseTable(ctx, joinType); + + for (int i = 0; i < properties.length; i++) { + // usually nothing... except for 1-1 Exported + properties[i].appendFrom(ctx, joinType); + } + + for (int i = 0; i < children.length; i++) { + children[i].appendFrom(ctx, joinType); + } + + ctx.popTableAlias(); + ctx.popJoin(); + } + + /** + * Join to base table for this node. This includes a join to the intersection + * table if this is a ManyToMany node. + */ + public SqlJoinType appendFromBaseTable(DbSqlContext ctx, SqlJoinType joinType) { + + if (nodeBeanProp instanceof BeanPropertyAssocMany) { + BeanPropertyAssocMany manyProp = (BeanPropertyAssocMany) nodeBeanProp; + if (manyProp.isManyToMany()) { + + String alias = ctx.getTableAlias(prefix); + String[] split = SplitName.split(prefix); + String parentAlias = ctx.getTableAlias(split[0]); + String alias2 = alias + "z_"; + + TableJoin manyToManyJoin = manyProp.getIntersectionTableJoin(); + manyToManyJoin.addJoin(joinType, parentAlias, alias2, ctx); + + return nodeBeanProp.addJoin(joinType, alias2, alias, ctx); + } + + } + + return nodeBeanProp.addJoin(joinType, prefix, ctx); + } + + /** + * Summary description. + */ + public String toString() { + return "SqlTreeNodeBean: " + desc; + } + + private boolean isLoadContextBeanNeeded(Mode queryMode, EntityBean contextBean) { + // if explicitly set loadContextBean to true, then reload + if (queryMode.isLoadContextBean()) { + return true; + } + + if (contextBean._ebean_getIntercept().isFullyLoadedBean()) { + // reload if contextBean is partial object + return false; + } + + // return true by default + return true; + } + +} 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 b739c2b9d..4c54619ee 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java @@ -1,107 +1,107 @@ -package com.avaje.ebeaninternal.server.query; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; - -import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc; -import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; -import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; -import com.avaje.ebeaninternal.server.deploy.DbReadContext; -import com.avaje.ebeaninternal.server.deploy.DbSqlContext; -import com.avaje.ebeaninternal.server.deploy.TableJoin; - -/** - * Join to Many (or child of a many) to support where clause predicates on many properties. - */ -public class SqlTreeNodeManyWhereJoin implements SqlTreeNode { - - private final String parentPrefix; - - private final String prefix; - - private final BeanPropertyAssoc nodeBeanProp; - - /** - * Child joins. - */ - private final SqlTreeNode[] children; - - /** - * The many where join which is either INNER or OUTER. - */ - private final SqlJoinType manyJoinType; - - public SqlTreeNodeManyWhereJoin(String prefix, BeanPropertyAssoc prop, SqlJoinType manyJoinType) { - - this.nodeBeanProp = prop; - this.prefix = prefix; - this.manyJoinType = manyJoinType; - - String[] split = SplitName.split(prefix); - this.parentPrefix = split[0]; - - List childrenList = new ArrayList(0); - this.children = childrenList.toArray(new SqlTreeNode[childrenList.size()]); - } - - /** - * Append to the FROM clause for this node. - */ - @Override - public void appendFrom(DbSqlContext ctx, SqlJoinType currentJoinType) { - - // always use the join type as per this many where join - // (OUTER for disjunction and otherwise INNER) - appendFromBaseTable(ctx, manyJoinType); - - for (int i = 0; i < children.length; i++) { - children[i].appendFrom(ctx, manyJoinType); - } - } - - /** - * Join to base table for this node. This includes a join to the - * intersection table if this is a ManyToMany node. - */ - public void appendFromBaseTable(DbSqlContext ctx, SqlJoinType joinType) { - - String alias = ctx.getTableAliasManyWhere(prefix); - String parentAlias = ctx.getTableAliasManyWhere(parentPrefix); - - if (nodeBeanProp instanceof BeanPropertyAssocOne){ - nodeBeanProp.addJoin(joinType, parentAlias, alias, ctx); - - } else { - BeanPropertyAssocMany manyProp = (BeanPropertyAssocMany)nodeBeanProp; - if (!manyProp.isManyToMany()) { - manyProp.addJoin(joinType, parentAlias, alias, ctx); - - } else { - String alias2 = alias + "z_"; - - TableJoin manyToManyJoin = manyProp.getIntersectionTableJoin(); - manyToManyJoin.addJoin(joinType, parentAlias, alias2, ctx); - manyProp.addJoin(joinType, alias2, alias, ctx); - } - } - } - - public void buildSelectExpressionChain(List selectChain) { - // nothing to add - } - - public void appendSelect(DbSqlContext ctx, boolean subQuery) { - // nothing to do here - } - - public void appendWhere(DbSqlContext ctx) { - // nothing to do here - } - - public void load(DbReadContext ctx, EntityBean parentBean) throws SQLException { - // nothing to do here - } - -} +package com.avaje.ebeaninternal.server.query; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.deploy.DbReadContext; +import com.avaje.ebeaninternal.server.deploy.DbSqlContext; +import com.avaje.ebeaninternal.server.deploy.TableJoin; + +/** + * Join to Many (or child of a many) to support where clause predicates on many properties. + */ +public class SqlTreeNodeManyWhereJoin implements SqlTreeNode { + + private final String parentPrefix; + + private final String prefix; + + private final BeanPropertyAssoc nodeBeanProp; + + /** + * Child joins. + */ + private final SqlTreeNode[] children; + + /** + * The many where join which is either INNER or OUTER. + */ + private final SqlJoinType manyJoinType; + + public SqlTreeNodeManyWhereJoin(String prefix, BeanPropertyAssoc prop, SqlJoinType manyJoinType) { + + this.nodeBeanProp = prop; + this.prefix = prefix; + this.manyJoinType = manyJoinType; + + String[] split = SplitName.split(prefix); + this.parentPrefix = split[0]; + + List childrenList = new ArrayList(0); + this.children = childrenList.toArray(new SqlTreeNode[childrenList.size()]); + } + + /** + * Append to the FROM clause for this node. + */ + @Override + public void appendFrom(DbSqlContext ctx, SqlJoinType currentJoinType) { + + // always use the join type as per this many where join + // (OUTER for disjunction and otherwise INNER) + appendFromBaseTable(ctx, manyJoinType); + + for (int i = 0; i < children.length; i++) { + children[i].appendFrom(ctx, manyJoinType); + } + } + + /** + * Join to base table for this node. This includes a join to the + * intersection table if this is a ManyToMany node. + */ + public void appendFromBaseTable(DbSqlContext ctx, SqlJoinType joinType) { + + String alias = ctx.getTableAliasManyWhere(prefix); + String parentAlias = ctx.getTableAliasManyWhere(parentPrefix); + + if (nodeBeanProp instanceof BeanPropertyAssocOne){ + nodeBeanProp.addJoin(joinType, parentAlias, alias, ctx); + + } else { + BeanPropertyAssocMany manyProp = (BeanPropertyAssocMany)nodeBeanProp; + if (!manyProp.isManyToMany()) { + manyProp.addJoin(joinType, parentAlias, alias, ctx); + + } else { + String alias2 = alias + "z_"; + + TableJoin manyToManyJoin = manyProp.getIntersectionTableJoin(); + manyToManyJoin.addJoin(joinType, parentAlias, alias2, ctx); + manyProp.addJoin(joinType, alias2, alias, ctx); + } + } + } + + public void buildSelectExpressionChain(List selectChain) { + // nothing to add + } + + public void appendSelect(DbSqlContext ctx, boolean subQuery) { + // nothing to do here + } + + public void appendWhere(DbSqlContext ctx) { + // nothing to do here + } + + public void load(DbReadContext ctx, EntityBean parentBean) throws SQLException { + // nothing to do here + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeProperties.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeProperties.java index 9065d9b80..b514b4ca1 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeProperties.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeProperties.java @@ -1,99 +1,99 @@ -package com.avaje.ebeaninternal.server.query; - -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; - -import com.avaje.ebeaninternal.server.deploy.BeanProperty; -import com.avaje.ebeaninternal.server.deploy.TableJoin; - -/** - * The select properties for a node in the SqlTree. - */ -public class SqlTreeProperties { - - private static final TableJoin[] EMPTY_TABLE_JOINS = new TableJoin[0]; - - /** - * True if this node of the tree should have read only entity beans. - */ - private boolean readOnly; - - /** - * set to false if the id field is not included. - */ - private boolean includeId = true; - - private TableJoin[] tableJoins = EMPTY_TABLE_JOINS; - - /** - * The bean properties in order. - */ - private List propsList = new ArrayList(); - - /** - * Maintain a list of property names to detect embedded bean additions. - */ - private LinkedHashSet propNames = new LinkedHashSet(); - - private boolean allProperties; - - public SqlTreeProperties() { - } - - public boolean containsProperty(String propName){ - return propNames.contains(propName); - } - - public void add(BeanProperty[] props) { - for (BeanProperty beanProperty : props) { - propsList.add(beanProperty); - } - } - - public void add(BeanProperty prop) { - propsList.add(prop); - propNames.add(prop.getName()); - } - - public BeanProperty[] getProps() { - return propsList.toArray(new BeanProperty[propsList.size()]); - } - - public boolean isIncludeId() { - return includeId; - } - - public void setIncludeId(boolean includeId) { - this.includeId = includeId; - } - - public boolean isPartialObject() { - return !allProperties; - } - - public boolean isReadOnly() { - return readOnly; - } - - public void setReadOnly(boolean readOnly) { - this.readOnly = readOnly; - } - - public TableJoin[] getTableJoins() { - return tableJoins; - } - - public void setTableJoins(TableJoin[] tableJoins) { - this.tableJoins = tableJoins; - } - - public void setAllProperties(boolean allProperties) { - this.allProperties = allProperties; - } - - public boolean isAllProperties() { - return allProperties; - } - +package com.avaje.ebeaninternal.server.query; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; + +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.TableJoin; + +/** + * The select properties for a node in the SqlTree. + */ +public class SqlTreeProperties { + + private static final TableJoin[] EMPTY_TABLE_JOINS = new TableJoin[0]; + + /** + * True if this node of the tree should have read only entity beans. + */ + private boolean readOnly; + + /** + * set to false if the id field is not included. + */ + private boolean includeId = true; + + private TableJoin[] tableJoins = EMPTY_TABLE_JOINS; + + /** + * The bean properties in order. + */ + private List propsList = new ArrayList(); + + /** + * Maintain a list of property names to detect embedded bean additions. + */ + private LinkedHashSet propNames = new LinkedHashSet(); + + private boolean allProperties; + + public SqlTreeProperties() { + } + + public boolean containsProperty(String propName){ + return propNames.contains(propName); + } + + public void add(BeanProperty[] props) { + for (BeanProperty beanProperty : props) { + propsList.add(beanProperty); + } + } + + public void add(BeanProperty prop) { + propsList.add(prop); + propNames.add(prop.getName()); + } + + public BeanProperty[] getProps() { + return propsList.toArray(new BeanProperty[propsList.size()]); + } + + public boolean isIncludeId() { + return includeId; + } + + public void setIncludeId(boolean includeId) { + this.includeId = includeId; + } + + public boolean isPartialObject() { + return !allProperties; + } + + public boolean isReadOnly() { + return readOnly; + } + + public void setReadOnly(boolean readOnly) { + this.readOnly = readOnly; + } + + public TableJoin[] getTableJoins() { + return tableJoins; + } + + public void setTableJoins(TableJoin[] tableJoins) { + this.tableJoins = tableJoins; + } + + public void setAllProperties(boolean allProperties) { + this.allProperties = allProperties; + } + + public boolean isAllProperties() { + return allProperties; + } + } \ No newline at end of file