#1831 - Update query with maxRows does not include the limit in the generated SQL

This commit is contained in:
rob bygrave
2019-10-11 17:01:30 +13:00
parent 4e74c68603
commit b665d8375c
10 changed files with 280 additions and 191 deletions
@@ -47,24 +47,19 @@ class CQueryBuilder {
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;
/**
* Create the SqlGenSelect.
*/
CQueryBuilder(DatabasePlatform dbPlatform, Binder binder, CQueryHistorySupport historySupport, CQueryDraftSupport draftSupport) {
this.dbPlatform = dbPlatform;
this.binder = binder;
this.draftSupport = draftSupport;
@@ -102,7 +97,7 @@ class CQueryBuilder {
SpiQuery<T> query = request.getQuery();
String rootTableAlias = query.getAlias();
query.setDelete();
query.setupForDeleteOrUpdate(deleteRequest);
CQueryPredicates predicates = new CQueryPredicates(binder, request);
CQueryPlan queryPlan = request.getQueryPlan();
@@ -135,16 +130,16 @@ class CQueryBuilder {
if (sqlTree.noJoins()) {
if (dbPlatform.isSupportsDeleteTableAlias()) {
// delete from table <alias> ...
return aliasReplace(buildSql("delete", request, predicates, sqlTree).getSql(), alias);
return aliasReplace(buildSqlDelete("delete", request, predicates, sqlTree).getSql(), alias);
} else if (isMySql(dbPlatform.getPlatform())) {
return aliasReplace(buildSql("delete " + alias, request, predicates, sqlTree).getSql(), alias);
return aliasReplace(buildSqlDelete("delete " + alias, request, predicates, sqlTree).getSql(), alias);
} else {
// simple - delete from table ...
return aliasStrip(buildSql("delete", request, predicates, sqlTree).getSql());
return aliasStrip(buildSqlDelete("delete", request, predicates, sqlTree).getSql());
}
}
// wrap as - delete from table where id in (select id ...)
String sql = buildSql(null, request, predicates, sqlTree).getSql();
String sql = buildSqlDelete(null, request, predicates, sqlTree).getSql();
sql = request.getBeanDescriptor().getDeleteByIdInSql() + "in (" + sql + ")";
sql = aliasReplace(sql, alias);
return sql;
@@ -168,7 +163,7 @@ class CQueryBuilder {
sb.append(" set ").append(predicates.getDbUpdateClause());
String updateClause = sb.toString();
if (sqlTree.noJoins()) {
if (sqlTree.noJoins() && request.isInlineSqlUpdateLimit()) {
// simple - update table set ... where ...
return aliasStrip(buildSqlUpdate(updateClause, request, predicates, sqlTree).getSql());
}
@@ -547,137 +542,156 @@ class CQueryBuilder {
* Return the SQL response with row limiting (when not an update statement).
*/
private SqlLimitResponse buildSql(String selectClause, OrmQueryRequest<?> request, CQueryPredicates predicates, SqlTree select) {
return buildSql(selectClause, request, predicates, select, false);
}
/**
* Return the SQL response for update statement (stripping table alias for find by id expression).
*/
private SqlLimitResponse buildSqlUpdate(String selectClause, OrmQueryRequest<?> request, CQueryPredicates predicates, SqlTree select) {
return buildSql(selectClause, request, predicates, select, true);
}
private SqlLimitResponse buildSql(String selectClause, OrmQueryRequest<?> request, CQueryPredicates predicates, SqlTree select, boolean stripAlias) {
SpiQuery<?> query = request.getQuery();
if (query.isNativeSql()) {
return new SqlLimitResponse(query.getGeneratedSql(), false);
}
if (query.isRawSql()) {
return rawSqlHandler.buildSql(request, predicates, query.getRawSql().getSql());
}
return new BuildReq(selectClause, request, predicates, select).buildSql();
}
boolean distinct = query.isDistinct() || select.isSqlDistinct();
boolean useSqlLimiter = false;
StringBuilder sb = new StringBuilder(500);
String dbOrderBy = predicates.getDbOrderBy();
private SqlLimitResponse buildSqlDelete(String selectClause, OrmQueryRequest<?> request, CQueryPredicates predicates, SqlTree select) {
return new BuildReq(selectClause, request, predicates, select).buildSql();
}
if (selectClause != null) {
sb.append(selectClause);
private SqlLimitResponse buildSqlUpdate(String selectClause, OrmQueryRequest<?> request, CQueryPredicates predicates, SqlTree select) {
return new BuildReq(selectClause, request, predicates, select, true).buildSql();
}
} else {
useSqlLimiter = (query.hasMaxRowsOrFirstRow() && select.getManyProperty() == null);
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;
if (!useSqlLimiter) {
sb.append("select ");
if (distinct) {
if (request.isInlineCountDistinct()) {
sb.append("count(");
}
sb.append("distinct ");
String distinctOn = select.getDistinctOn();
if (distinctOn != null) {
sb.append("on (");
sb.append(distinctOn).append(") ");
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));
}
}
}
if (query.isCountDistinct() && query.isSingleAttribute()) {
sb.append("r1.attribute_, count(*) from (select ");
sb.append(select.getSelectSql());
sb.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(") ");
}
}
}
if (selectClause == null || !selectClause.startsWith("update")) {
sb.append(" from ");
sb.append(select.getFromSql());
}
String inheritanceWhere = select.getInheritanceWhereSql();
boolean hasWhere = false;
if (!inheritanceWhere.isEmpty()) {
sb.append(" where");
sb.append(inheritanceWhere);
hasWhere = true;
}
if (query.isAsOfBaseTable() && !historySupport.isStandardsBased()) {
hasWhere = appendWhere(hasWhere, sb);
sb.append(historySupport.getAsOfPredicate(request.getBaseTableAlias()));
}
if (request.isFindById() || query.getId() != null) {
appendWhere(hasWhere, sb);
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??");
private void appendFrom() {
if (selectClause == null || !selectClause.startsWith("update")) {
sb.append(" from ");
sb.append(select.getFromSql());
}
if (stripAlias) {
// strip the table alias for use in update statement
idSql = StringHelper.replaceString(idSql, "t0.", "");
}
sb.append(idSql).append(" ");
hasWhere = true;
}
String dbWhere = predicates.getDbWhere();
if (hasValue(dbWhere)) {
if (!hasWhere) {
hasWhere = true;
sb.append(" where ");
} else {
private void appendAndOrWhere() {
if (hasWhere) {
sb.append(" and ");
}
sb.append(dbWhere);
}
String dbFilterMany = predicates.getDbFilterMany();
if (hasValue(dbFilterMany)) {
if (!hasWhere) {
hasWhere = true;
sb.append(" where ");
} else {
sb.append("and ");
sb.append(" where ");
hasWhere = true;
}
sb.append(dbFilterMany);
}
if (!query.isIncludeSoftDeletes()) {
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<String> softDeletePredicates = query.getSoftDeletePredicates();
if (softDeletePredicates != null) {
if (!hasWhere) {
sb.append(" where ");
} else {
sb.append(" and ");
}
appendAndOrWhere();
for (int i = 0; i < softDeletePredicates.size(); i++) {
if (i > 0) {
sb.append(" and ");
@@ -687,69 +701,75 @@ class CQueryBuilder {
}
}
String groupBy = select.getGroupBy();
if (groupBy != null) {
sb.append(" group by ").append(groupBy);
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), false);
}
}
String dbHaving = predicates.getDbHaving();
if (hasValue(dbHaving)) {
sb.append(" having ").append(dbHaving);
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);
}
}
if (dbOrderBy != null && !query.isCountDistinct()) {
sb.append(" order by ").append(dbOrderBy);
private boolean hasValue(String s) {
return s != null && !s.isEmpty();
}
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 {
return new SqlLimitResponse(dbPlatform.completeSql(sb.toString(), query), false);
}
}
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);
}
}
/**
* Append where or and based on the hasWhere flag.
*/
private boolean appendWhere(boolean hasWhere, StringBuilder sb) {
if (hasWhere) {
sb.append(" and ");
} else {
sb.append(" where ");
}
return true;
}
private boolean hasValue(String s) {
return s != null && !s.isEmpty();
}
boolean isPlatformDistinctOn() {
@@ -759,7 +779,7 @@ class CQueryBuilder {
/**
* Return the 'for update' FROM hint (sql server).
*/
public String fromForUpdate(SpiQuery<?> query) {
String fromForUpdate(SpiQuery<?> query) {
Query.ForUpdate mode = query.getForUpdateMode();
if (mode == null) {
return null;