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

io.ebeaninternal.server.query.CQueryBuilder Maven / Gradle / Ivy

package io.ebeaninternal.server.query;

import io.ebean.CountDistinctOrder;
import io.ebean.OrderBy;
import io.ebean.Query;
import io.ebean.RawSql;
import io.ebean.RawSqlBuilder;
import io.ebean.annotation.Platform;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.SqlLimitRequest;
import io.ebean.config.dbplatform.SqlLimitResponse;
import io.ebean.config.dbplatform.SqlLimiter;
import io.ebean.event.readaudit.ReadAuditQueryPlan;
import io.ebean.text.PathProperties;
import io.ebean.util.SplitName;
import io.ebean.util.StringHelper;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.core.OrmQueryRequest;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebeaninternal.server.persist.Binder;
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
import io.ebeaninternal.server.querydefn.OrmQueryLimitRequest;
import io.ebeaninternal.server.rawsql.SpiRawSql;
import io.ebeaninternal.server.rawsql.SpiRawSql.ColumnMapping;
import io.ebeaninternal.server.rawsql.SpiRawSql.ColumnMapping.Column;

import javax.persistence.PersistenceException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Generates the SQL SELECT statements taking into account the physical
 * deployment properties.
 */
class CQueryBuilder {

  private final String columnAliasPrefix;
  private final SqlLimiter sqlLimiter;
  private final CQueryBuilderRawSql rawSqlHandler;
  private final Binder binder;

  private final boolean selectCountWithAlias;

  private final CQueryHistorySupport historySupport;
  private final CQueryDraftSupport draftSupport;
  private final DatabasePlatform dbPlatform;
  private final boolean selectCountWithColumnAlias;

  /**
   * Create the SqlGenSelect.
   */
  CQueryBuilder(DatabasePlatform dbPlatform, Binder binder, CQueryHistorySupport historySupport, CQueryDraftSupport draftSupport) {
    this.dbPlatform = dbPlatform;
    this.binder = binder;
    this.draftSupport = draftSupport;
    this.historySupport = historySupport;
    this.columnAliasPrefix = dbPlatform.getColumnAliasPrefix();
    this.sqlLimiter = dbPlatform.getSqlLimiter();
    this.rawSqlHandler = new CQueryBuilderRawSql(sqlLimiter, dbPlatform);
    this.selectCountWithAlias = dbPlatform.isSelectCountWithAlias();
    this.selectCountWithColumnAlias = dbPlatform.isSelectCountWithColumnAlias();
  }

  /**
   * split the order by claus on the field delimiter and prefix each field with
   * the relation name
   */
  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 delete query.
   */
   CQueryUpdate buildUpdateQuery(boolean deleteRequest, OrmQueryRequest request) {

    SpiQuery query = request.getQuery();
    String rootTableAlias = query.getAlias();
    query.setupForDeleteOrUpdate();

    CQueryPredicates predicates = new CQueryPredicates(binder, request);
    CQueryPlan queryPlan = request.getQueryPlan();
    if (queryPlan != null) {
      // skip building the SqlTree and Sql string
      predicates.prepare(false);
      return new CQueryUpdate(request, predicates, queryPlan);
    }

    predicates.prepare(true);

    SqlTree sqlTree = createSqlTree(request, predicates);

    String sql;
    if (deleteRequest) {
      sql = buildDeleteSql(request, rootTableAlias, predicates, sqlTree);
    } else {
      sql = buildUpdateSql(request, rootTableAlias, predicates, sqlTree);
    }

    // cache the query plan
    queryPlan = new CQueryPlan(request, sql, sqlTree, predicates.getLogWhereSql());
    request.putQueryPlan(queryPlan);
    return new CQueryUpdate(request, predicates, queryPlan);
  }

  private  String buildDeleteSql(OrmQueryRequest request, String rootTableAlias, CQueryPredicates predicates, SqlTree sqlTree) {

    String alias = alias(rootTableAlias);
    if (sqlTree.noJoins() && !request.getQuery().hasMaxRowsOrFirstRow()) {
      if (dbPlatform.isSupportsDeleteTableAlias()) {
        // delete from table  ...
        return aliasReplace(buildSqlDelete("delete", request, predicates, sqlTree).getSql(), alias);
      } else if (isMySql(dbPlatform.getPlatform())) {
        return aliasReplace(buildSqlDelete("delete " + alias, request, predicates, sqlTree).getSql(), alias);
      } else {
        // simple - delete from table ...
        return aliasStrip(buildSqlDelete("delete", request, predicates, sqlTree).getSql());
      }
    }
    // wrap as - delete from table where id in (select id ...)
    String sql = buildSqlDelete(null, request, predicates, sqlTree).getSql();
    sql = request.getBeanDescriptor().getDeleteByIdInSql() + "in (" + sql + ")";
    sql = aliasReplace(sql, alias);
    return sql;
  }

  private boolean isMySql(Platform platform) {
    return platform.base() == Platform.MYSQL;
  }

  private String alias(String rootTableAlias) {
    return (rootTableAlias == null) ? "t0" : rootTableAlias;
  }

  private  String buildUpdateSql(OrmQueryRequest request, String rootTableAlias, CQueryPredicates predicates, SqlTree sqlTree) {

    StringBuilder sb = new StringBuilder(200);
    sb.append("update ").append(request.getBeanDescriptor().getBaseTable());
    if (rootTableAlias != null) {
      sb.append(" ").append(rootTableAlias);
    }
    sb.append(" set ").append(predicates.getDbUpdateClause());
    String updateClause = sb.toString();

    if (sqlTree.noJoins() && request.isInlineSqlUpdateLimit()) {
      // simple - update table set ... where ...
      return aliasStrip(buildSqlUpdate(updateClause, request, predicates, sqlTree).getSql());
    }
    // wrap as - update table set ... where id in (select id ...)
    String sql = buildSqlUpdate(null, request, predicates, sqlTree).getSql();
    sql = updateClause + " " + request.getBeanDescriptor().getWhereIdInSql() + "in (" + sql + ")";
    sql = aliasReplace(sql, alias(rootTableAlias));
    return sql;
  }

  /**
   * Strip the root table alias.
   */
  private String aliasStrip(String sql) {
    sql = StringHelper.replaceString(sql, "${RTA}.", "");
    return StringHelper.replaceString(sql, " ${RTA}", "");
  }

  /**
   * Replace the root table alias.
   */
  private String aliasReplace(String sql, String replaceWith) {
    sql = StringHelper.replaceString(sql, "${RTA}.", replaceWith + ".");
    return StringHelper.replaceString(sql, "${RTA}", replaceWith);
  }

  CQueryFetchSingleAttribute buildFetchAttributeQuery(OrmQueryRequest request) {

    SpiQuery query = request.getQuery();
    query.setSingleAttribute();

    CQueryPredicates predicates = new CQueryPredicates(binder, request);
    CQueryPlan queryPlan = request.getQueryPlan();
    if (queryPlan != null) {
      predicates.prepare(false);
      return new CQueryFetchSingleAttribute(request, predicates, queryPlan, query.isCountDistinct());
    }

    // use RawSql or generated Sql
    predicates.prepare(true);

    SqlTree sqlTree = createSqlTree(request, predicates);
    SqlLimitResponse s = buildSql(null, request, predicates, sqlTree);

    queryPlan = new CQueryPlan(request, s.getSql(), sqlTree, predicates.getLogWhereSql());
    request.putQueryPlan(queryPlan);
    return new CQueryFetchSingleAttribute(request, predicates, queryPlan, query.isCountDistinct());
  }

  /**
   * Build the find ids query.
   */
   CQueryFetchSingleAttribute buildFetchIdsQuery(OrmQueryRequest request) {

    SpiQuery query = request.getQuery();
    query.setSelectId();
    BeanDescriptor desc = request.getBeanDescriptor();
    if (!query.isIncludeSoftDeletes() && desc.isSoftDelete()) {
      query.addSoftDeletePredicate(desc.getSoftDeletePredicate(alias(query.getAlias())));
    }
    return buildFetchAttributeQuery(request);
  }

  /**
   * Return the history support if this query needs it (is a 'as of' type query).
   */
   CQueryHistorySupport getHistorySupport(SpiQuery query) {
    return query.getTemporalMode().isHistory() ? historySupport : null;
  }

  /**
   * Return the draft support (or null) for a 'asDraft' query.
   */
   CQueryDraftSupport getDraftSupport(SpiQuery query) {
    return query.getTemporalMode() == SpiQuery.TemporalMode.DRAFT ? draftSupport : null;
  }

  /**
   * Build the row count query.
   */
   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);

    boolean countDistinct = query.isDistinct();
    boolean withAgg = false;
    if (!countDistinct) {
      withAgg = includesAggregation(request, query);
      if (!withAgg) {
        // minimise select clause for standard count
        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);
      return new CQueryRowCount(queryPlan, request, predicates);
    }

    predicates.prepare(true);

    SqlTree sqlTree = createSqlTree(request, predicates, selectCountWithColumnAlias && withAgg);
    if (SpiQuery.TemporalMode.CURRENT == query.getTemporalMode()) {
      sqlTree.addSoftDeletePredicate(query);
    }

    boolean wrap = sqlTree.hasMany() || withAgg;

    String sqlSelect = null;
    if (countDistinct) {
      if (sqlTree.isSingleProperty()) {
        request.setInlineCountDistinct();
      }
    } else if (!wrap) {
      sqlSelect = "select count(*)";
    }

    SqlLimitResponse s = buildSql(sqlSelect, request, predicates, sqlTree);
    String sql = s.getSql();

    if (!request.isInlineCountDistinct()) {
      if (countDistinct) {
        sql = wrapSelectCount(sql);

      } else if (wrap || query.isRawSql()) {
        // remove order by - mssql does not accept order by in subqueries
        int pos = sql.lastIndexOf(" order by ");
        if (pos != -1) {
          sql = sql.substring(0, pos);
        }
        sql = wrapSelectCount(sql);
      }
    }

    // cache the query plan
    queryPlan = new CQueryPlan(request, sql, sqlTree, predicates.getLogWhereSql());
    request.putQueryPlan(queryPlan);

    return new CQueryRowCount(queryPlan, request, predicates);
  }

  /**
   * Return true if the query includes an aggregation property.
   */
  private  boolean includesAggregation(OrmQueryRequest request, SpiQuery query) {
    return request.getBeanDescriptor().includesAggregation(query.getDetail());
  }

  private String wrapSelectCount(String sql) {
    sql = "select count(*) from ( " + sql + ")";
    if (selectCountWithAlias) {
      sql += " as c";
    }
    return sql;
  }

  /**
   * Return the SQL Select statement as a String. Converts logical property
   * names to physical deployment column names.
   */
   CQuery buildQuery(OrmQueryRequest 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.
    SpiQuery query = request.getQuery();

    SqlTree sqlTree = createSqlTree(request, predicates);
    if (query.isAsOfQuery()) {
      sqlTree.addAsOfTableAlias(query);
    } else if (SpiQuery.TemporalMode.CURRENT == query.getTemporalMode()) {
      sqlTree.addSoftDeletePredicate(query);
    }

    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, false, predicates.getLogWhereSql());
    }

    BeanDescriptor desc = request.getBeanDescriptor();
    if (desc.isReadAuditing()) {
      // log the query plan based bean type (i.e. ignoring query disabling for logging the sql/plan)
      desc.getReadAuditLogger().queryPlan(new ReadAuditQueryPlan(desc.getFullName(), queryPlan.getAuditQueryKey(), queryPlan.getSql()));
    }

    // 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) { return createSqlTree(request, predicates, false); } private SqlTree createSqlTree(OrmQueryRequest request, CQueryPredicates predicates, boolean forceColumnAlias) { if (request.isNativeSql()) { return createNativeSqlTree(request, predicates); } if (request.isRawSql()) { return createRawSqlSqlTree(request, predicates); } String colAliasPrefix = forceColumnAlias ? "c" : columnAliasPrefix; return new SqlTreeBuilder(colAliasPrefix, this, request, predicates).build(); } private String nativeQueryPaging(SpiQuery query, String sql) { return dbPlatform.getBasicSqlLimiter().limit(sql, query.getFirstRow(), query.getMaxRows()); } /** * Create the SqlTree by reading the ResultSetMetaData and mapping table/columns to bean property paths. */ private SqlTree createNativeSqlTree(OrmQueryRequest request, CQueryPredicates predicates) { SpiQuery query = request.getQuery(); // parse named parameters returning the final sql to execute String sql = predicates.parseBindParams(query.getNativeSql()); if (query.hasMaxRowsOrFirstRow()) { sql = nativeQueryPaging(query, sql); } query.setGeneratedSql(sql); Connection connection = request.getTransaction().getConnection(); BeanDescriptor desc = request.getBeanDescriptor(); try { // For SqlServer we need either "selectMethod=cursor" in the connection string or fetch explicitly a cursorable // statement here by specifying ResultSet.CONCUR_UPDATABLE PreparedStatement statement = connection.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, dbPlatform.isSupportsResultSetConcurrencyModeUpdatable() ? ResultSet.CONCUR_UPDATABLE : ResultSet.CONCUR_READ_ONLY); predicates.bind(statement, connection); ResultSet resultSet = statement.executeQuery(); ResultSetMetaData metaData = resultSet.getMetaData(); int cols = 1 + metaData.getColumnCount(); List propertyNames = new ArrayList<>(cols - 1); for (int i = 1; i < cols; i++) { String tableName = metaData.getTableName(i).toLowerCase(); String columnName = metaData.getColumnName(i).toLowerCase(); String path = desc.findBeanPath(tableName, columnName); if (path != null) { propertyNames.add(path); } else { propertyNames.add(SpiRawSql.IGNORE_COLUMN); } } RawSql rawSql = RawSqlBuilder.resultSet(resultSet, propertyNames.toArray(new String[0])); query.setRawSql(rawSql); return createRawSqlSqlTree(request, predicates); } catch (SQLException e) { throw new RuntimeException(e); } } 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()) { SpiRawSql.ColumnMapping.Column column = it.next(); String propertyName = column.getPropertyName(); if (!SpiRawSql.IGNORE_COLUMN.equals(propertyName)) { ElPropertyValue el = descriptor.getElGetValue(propertyName); if (el == null && propertyName.endsWith("Id")) { // try default naming convention for foreign key columns String foreignIdPath = assocOneIdPath(propertyName); el = descriptor.getElGetValue(foreignIdPath); if (el != null) { propertyName = foreignIdPath; } } if (el == null) { throw new PersistenceException("Property [" + propertyName + "] not found on " + descriptor.getFullName()); } addRawColumnMapping(pathProps, column, propertyName, el); } } OrmQueryDetail detail = new OrmQueryDetail(); // transfer PathProperties into OrmQueryDetail for (PathProperties.Props props : pathProps.getPathProps()) { detail.fetch(props.getPath(), props.getProperties()); } // check if @Id property included in RawSql boolean rawNoId = true; BeanProperty idProperty = descriptor.getIdProperty(); if (idProperty != null && columnMapping.contains(idProperty.getName())) { // contains the @Id property for the root level bean rawNoId = false; } // build SqlTree based on OrmQueryDetail of the RawSql return new SqlTreeBuilder(request, predicates, detail, rawNoId).build(); } private void addRawColumnMapping(PathProperties pathProps, Column column, String propertyName, ElPropertyValue el) { BeanProperty beanProperty = el.getBeanProperty(); if (beanProperty.isId()) { if (propertyName.contains(".")) { // For @Id properties we chop off the last part of the path propertyName = SplitName.parent(propertyName); } } else if (beanProperty.isDiscriminator()) { 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 (probably the Id property). "; throw new PersistenceException(msg); } if (propertyName != null) { boolean assocProperty = el.isAssocProperty(); if (!assocProperty) { pathProps.addToPath(null, propertyName); } else { String[] pathProp = SplitName.split(propertyName); pathProps.addToPath(pathProp[0], pathProp[1]); } } } /** * Return a path for a foreign key property using the default naming convention. */ private String assocOneIdPath(String propertyName) { return propertyName.substring(0, propertyName.length() - 2) + ".id"; } /** * Return the SQL response with row limiting (when not an update statement). */ private SqlLimitResponse buildSql(String selectClause, OrmQueryRequest request, CQueryPredicates predicates, SqlTree select) { SpiQuery query = request.getQuery(); if (query.isNativeSql()) { return new SqlLimitResponse(query.getGeneratedSql()); } if (query.isRawSql()) { return rawSqlHandler.buildSql(request, predicates, query.getRawSql().getSql()); } return new BuildReq(selectClause, request, predicates, select).buildSql(); } private SqlLimitResponse buildSqlDelete(String selectClause, OrmQueryRequest request, CQueryPredicates predicates, SqlTree select) { return new BuildReq(selectClause, request, predicates, select).buildSql(); } private SqlLimitResponse buildSqlUpdate(String selectClause, OrmQueryRequest request, CQueryPredicates predicates, SqlTree select) { return new BuildReq(selectClause, request, predicates, select, true).buildSql(); } private class BuildReq { private final StringBuilder sb = new StringBuilder(500); private final String selectClause; private final OrmQueryRequest request; private final SpiQuery query; private final CQueryPredicates predicates; private final SqlTree select; private final boolean updateStatement; private final boolean distinct; private final String dbOrderBy; private boolean useSqlLimiter; private boolean hasWhere; private BuildReq(String selectClause, OrmQueryRequest request, CQueryPredicates predicates, SqlTree select) { this(selectClause, request, predicates, select, false); } private BuildReq(String selectClause, OrmQueryRequest request, CQueryPredicates predicates, SqlTree select, boolean updateStatement) { this.selectClause = selectClause; this.request = request; this.query = request.getQuery(); this.predicates = predicates; this.select = select; this.updateStatement = updateStatement; this.distinct = query.isDistinct() || select.isSqlDistinct(); this.dbOrderBy = predicates.getDbOrderBy(); } private void appendSelect() { if (selectClause != null) { sb.append(selectClause); } else { useSqlLimiter = (query.hasMaxRowsOrFirstRow() && select.getManyProperty() == null); if (!useSqlLimiter) { appendSelectDistinct(); } if (query.isCountDistinct() && query.isSingleAttribute()) { sb.append("r1.attribute_, count(*) from (select ").append(select.getSelectSql()).append(" as attribute_"); } else { sb.append(select.getSelectSql()); } if (request.isInlineCountDistinct()) { sb.append(")"); } if (distinct && dbOrderBy != null && !query.isSingleAttribute()) { // add the orderBy columns to the select clause (due to distinct) final OrderBy orderBy = query.getOrderBy(); if (orderBy != null && orderBy.supportsSelect()) { sb.append(", ").append(DbOrderByTrim.trim(dbOrderBy)); } } } } private void appendSelectDistinct() { sb.append("select "); if (distinct) { if (request.isInlineCountDistinct()) { sb.append("count("); } sb.append("distinct "); String distinctOn = select.getDistinctOn(); if (distinctOn != null) { sb.append("on (").append(distinctOn).append(") "); } } } private void appendFrom() { if (selectClause == null || !selectClause.startsWith("update")) { sb.append(" from "); sb.append(select.getFromSql()); } } private void appendAndOrWhere() { if (hasWhere) { sb.append(" and "); } else { sb.append(" where "); hasWhere = true; } } private void appendInheritanceWhere() { String inheritanceWhere = select.getInheritanceWhereSql(); if (!inheritanceWhere.isEmpty()) { sb.append(" where"); sb.append(inheritanceWhere); hasWhere = true; } } private void appendHistoryAsOfPredicate() { if (query.isAsOfBaseTable() && !historySupport.isStandardsBased()) { appendAndOrWhere(); sb.append(historySupport.getAsOfPredicate(request.getBaseTableAlias())); } } private void appendFindId() { if (request.isFindById() || query.getId() != null) { appendAndOrWhere(); BeanDescriptor desc = request.getBeanDescriptor(); String idSql = desc.getIdBinderIdSql(query.getAlias()); if (idSql.isEmpty()) { throw new IllegalStateException("Executing FindById query on entity bean " + desc.getName() + " that doesn't have an @Id property??"); } if (updateStatement) { // strip the table alias for use in update statement idSql = StringHelper.replaceString(idSql, "t0.", ""); } sb.append(idSql).append(" "); hasWhere = true; } } private void appendToWhere(String predicate) { if (hasValue(predicate)) { appendAndOrWhere(); sb.append(predicate); } } private void appendSoftDelete() { List softDeletePredicates = query.getSoftDeletePredicates(); if (softDeletePredicates != null) { appendAndOrWhere(); for (int i = 0; i < softDeletePredicates.size(); i++) { if (i > 0) { sb.append(" and "); } sb.append(softDeletePredicates.get(i)); } } } private SqlLimitResponse buildSql() { appendSelect(); appendFrom(); appendInheritanceWhere(); appendHistoryAsOfPredicate(); appendFindId(); appendToWhere(predicates.getDbWhere()); appendToWhere(predicates.getDbFilterMany()); if (!query.isIncludeSoftDeletes()) { appendSoftDelete(); } String groupBy = select.getGroupBy(); if (groupBy != null) { sb.append(" group by ").append(groupBy); } String dbHaving = predicates.getDbHaving(); if (hasValue(dbHaving)) { sb.append(" having ").append(dbHaving); } if (dbOrderBy != null && !query.isCountDistinct()) { sb.append(" order by ").append(dbOrderBy); } if (query.isCountDistinct() && query.isSingleAttribute()) { sb.append(") r1 group by r1.attribute_"); sb.append(toSql(query.getCountDistinctOrder())); } if (useSqlLimiter) { // use LIMIT/OFFSET, ROW_NUMBER() or rownum type SQL query limitation SqlLimitRequest r = new OrmQueryLimitRequest(sb.toString(), dbOrderBy, query, dbPlatform, distinct); return sqlLimiter.limit(r); } else { if (updateStatement) { final int maxRows = query.getMaxRows(); if (maxRows > 0) { // limit on update statement only support on platforms with supportsMaxRowsOnUpdate sb.append(" limit ").append(maxRows); } } return new SqlLimitResponse(dbPlatform.completeSql(sb.toString(), query)); } } private String toSql(CountDistinctOrder orderBy) { switch (orderBy) { case ATTR_ASC: return " order by r1.attribute_"; case ATTR_DESC: return " order by r1.attribute_ desc"; case COUNT_ASC_ATTR_ASC: return " order by count(*), r1.attribute_"; case COUNT_ASC_ATTR_DESC: return " order by count(*), r1.attribute_ desc"; case COUNT_DESC_ATTR_ASC: return " order by count(*) desc, r1.attribute_"; case COUNT_DESC_ATTR_DESC: return " order by count(*) desc, r1.attribute_ desc"; default: throw new IllegalArgumentException("Illegal enum: " + orderBy); } } private boolean hasValue(String s) { return s != null && !s.isEmpty(); } } boolean isPlatformDistinctOn() { return dbPlatform.isPlatform(Platform.POSTGRES); } /** * Return the 'for update' FROM hint (sql server). */ String fromForUpdate(SpiQuery query) { Query.ForUpdate mode = query.getForUpdateMode(); if (mode == null) { return null; } else { return dbPlatform.fromForUpdate(mode); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy