All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.avaje.ebeaninternal.server.query.CQueryBuilder Maven / Gradle / Ivy

The newest version!
/**
 * 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.RawSql.ColumnMapping;
import com.avaje.ebean.RawSql.ColumnMapping.Column;
import com.avaje.ebean.RawSqlBuilder;
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.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 DatabasePlatform dbPlatform;

  /**
   * Create the SqlGenSelect.
   */
  public CQueryBuilder(BackgroundExecutor backgroundExecutor, DatabasePlatform dbPlatform, Binder binder) {

    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, 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, backgroundExecutor);

    }

    // 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(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);
  }

  /**
   * 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(), 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 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(); 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(" ").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, dbPlatform); return sqlLimiter.limit(r); } else { return new SqlLimitResponse(dbPlatform.completeSql(sb.toString(), query), false); } } private boolean isEmpty(String s) { return s == null || s.length() == 0; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy