com.avaje.ebeaninternal.server.query.CQueryBuilder Maven / Gradle / Ivy
/**
* Copyright (C) 2006 Robin Bygrave
*
* This file is part of Ebean.
*
* Ebean is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* Ebean is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Ebean; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package com.avaje.ebeaninternal.server.query;
import java.util.Iterator;
import java.util.Set;
import javax.persistence.PersistenceException;
import com.avaje.ebean.BackgroundExecutor;
import com.avaje.ebean.RawSql;
import com.avaje.ebean.RawSqlBuilder;
import com.avaje.ebean.Query.UseIndex;
import com.avaje.ebean.RawSql.ColumnMapping;
import com.avaje.ebean.RawSql.ColumnMapping.Column;
import com.avaje.ebean.config.GlobalProperties;
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.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.lucene.LIndex;
import com.avaje.ebeaninternal.server.lucene.LuceneIndexManager;
import com.avaje.ebeaninternal.server.persist.Binder;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryLimitRequest;
/**
* 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 BackgroundExecutor backgroundExecutor;
private final boolean selectCountWithAlias;
private final boolean luceneAvailable;
private final UseIndex defaultUseIndex;
/**
* Create the SqlGenSelect.
*/
public CQueryBuilder(BackgroundExecutor backgroundExecutor, DatabasePlatform dbPlatform, Binder binder,
LuceneIndexManager luceneIndexManager) {
this.luceneAvailable = luceneIndexManager.isLuceneAvailable();
this.defaultUseIndex = luceneIndexManager.getDefaultUseIndex();
this.backgroundExecutor = backgroundExecutor;
this.binder = binder;
this.tableAliasPlaceHolder = GlobalProperties.get("ebean.tableAliasPlaceHolder","${ta}");
this.columnAliasPrefix = GlobalProperties.get("ebean.columnAliasPrefix", "c");
this.sqlSelectBuilder = new RawSqlSelectClauseBuilder(dbPlatform, binder);
this.sqlLimiter = dbPlatform.getSqlLimiter();
this.rawSqlHandler = new CQueryBuilderRawSql(sqlLimiter);
this.selectCountWithAlias = dbPlatform.isSelectCountWithAlias();
}
protected String getOrderBy(String orderBy, BeanPropertyAssocMany> many, BeanDescriptor> desc,
boolean hasListener) {
String manyOrderBy = null;
if (many != null) {
manyOrderBy = many.getFetchOrderBy();
if (manyOrderBy != null) {
manyOrderBy = prefixOrderByFields(many.getName(), manyOrderBy);
}
}
if (orderBy == null && (hasListener || manyOrderBy != null)) {
// build orderBy to be the list of primary key columns
StringBuffer sb = new StringBuffer();
BeanProperty[] uids = desc.propertiesId();
for (int i = 0; i < uids.length; i++) {
if (i > 0) {
sb.append(", ");
}
sb.append(uids[i].getName());
}
orderBy = sb.toString();
}
if (manyOrderBy != null) {
// add first orderBy to manyOrderby
orderBy = orderBy + " , " + manyOrderBy;
}
return orderBy;
}
/**
* 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, backgroundExecutor);
}
String sql;
if (isLuceneSupported(request) && predicates.isLuceneResolvable()){
//FIXME: CQueryFetchIds via Lucene
SqlTree sqlTree = createLuceneSqlTree(request, predicates);
queryPlan = new CQueryPlanLucene(request, sqlTree);
sql = "Lucene Index";
} else {
// use RawSql or generated Sql
predicates.prepare(true);
SqlTree sqlTree = createSqlTree(request, predicates);
SqlLimitResponse s = buildSql(null, request, predicates, sqlTree);
sql = s.getSql();
// cache the query plan
queryPlan = new CQueryPlan(sql, sqlTree, false, s.isIncludesRowNumberColumn(), predicates.getLogWhereSql());
}
request.putQueryPlan(queryPlan);
return new CQueryFetchIds(request, predicates, sql, backgroundExecutor);
}
/**
* 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);
boolean hasMany = !query.getManyWhereJoins().isEmpty();
query.setSelectId();
String sqlSelect = "select count(*)";
if (hasMany){
// need to count distinct id's ...
query.setDistinct(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){
sql = "select count(*) from ( "+sql+")";
if (selectCountWithAlias){
sql += " as c";
}
}
// cache the query plan
queryPlan = new CQueryPlan(sql, sqlTree, false, s.isIncludesRowNumberColumn(), predicates.getLogWhereSql());
request.putQueryPlan(queryPlan);
return new CQueryRowCount(request, predicates, sql);
}
private boolean isLuceneSupported(OrmQueryRequest> request) {
if (!luceneAvailable){
return false;
}
UseIndex useIndex = request.getQuery().getUseIndex();
if (useIndex == null){
// get the default strategy for this bean type
useIndex = request.getBeanDescriptor().getUseIndex();
if (useIndex == null){
// get the default global strategy
useIndex = defaultUseIndex;
}
}
if (UseIndex.NO.equals(useIndex)){
return false;
}
return true;
}
/**
* 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
if (queryPlan.isLucene()){
predicates.isLuceneResolvable();
} else {
predicates.prepare(false);
}
return new CQuery(request, predicates, queryPlan);
}
if (isLuceneSupported(request) && predicates.isLuceneResolvable()){
// Use Lucene Index to resolve query
SqlTree sqlTree = createLuceneSqlTree(request, predicates);
queryPlan = new CQueryPlanLucene(request, sqlTree);
} else {
// 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(), null);
}
}
// 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 createLuceneSqlTree(OrmQueryRequest> request, CQueryPredicates predicates) {
LIndex luceneIndex = request.getLuceneIndex();
OrmQueryDetail ormQueryDetail = luceneIndex.getOrmQueryDetail();
// build SqlTree based on OrmQueryDetail of the LuceneIndex
return new SqlTreeBuilder(request, predicates, ormQueryDetail).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){
String msg = "Property ["+propertyName+"] not found on "+descriptor.getFullName();
throw new PersistenceException(msg);
}
BeanProperty beanProperty = el.getBeanProperty();
if (beanProperty.isId()){
// 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
Iterator pathIt = pathProps.getPaths().iterator();
while (pathIt.hasNext()) {
String path = pathIt.next();
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);
if (selectClause != null){
sb.append(selectClause);
} else {
useSqlLimiter = (query.hasMaxRowsOrFirstRow() && manyProp == null);
if (!useSqlLimiter){
sb.append("select ");
if (query.isDistinct()) {
sb.append("distinct ");
}
}
sb.append(select.getSelectSql());
}
sb.append(" ").append(NEW_LINE);
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(" ").append(NEW_LINE).append("where");
sb.append(inheritanceWhere);
hasWhere = true;
}
if (request.isFindById() || query.getId() != null){
if (hasWhere){
sb.append(" and ");
} else {
sb.append(NEW_LINE).append("where ");
}
BeanDescriptor> desc = request.getBeanDescriptor();
String idSql = desc.getIdBinderIdSql();
sb.append(idSql).append(" ");
hasWhere = true;
}
String dbWhere = predicates.getDbWhere();
if (!isEmpty(dbWhere)) {
if (!hasWhere) {
hasWhere = true;
sb.append(" ").append(NEW_LINE).append("where ");
} else {
sb.append("and ");
}
sb.append(dbWhere);
}
String dbFilterMany = predicates.getDbFilterMany();
if (!isEmpty(dbFilterMany)) {
if (!hasWhere) {
sb.append(" ").append(NEW_LINE).append("where ");
} else {
sb.append("and ");
}
sb.append(dbFilterMany);
}
String dbOrderBy = predicates.getDbOrderBy();
if (dbOrderBy != null) {
sb.append(" ").append(NEW_LINE);
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);
return sqlLimiter.limit(r);
} else {
return new SqlLimitResponse(sb.toString(), false);
}
}
private boolean isEmpty(String s){
return s == null || s.length() == 0;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy