auomatically append "order by id" when paging and query.orderById = true (#1275)

Add ServerConfig.setDefaultOrderById() and query.orderById(boolean) ... to control automatically append "order by id" with paging query
This commit is contained in:
Roland Praml
2018-02-27 10:09:52 +13:00
committed by Rob Bygrave
parent 0cba0664f8
commit a712be2917
11 changed files with 235 additions and 37 deletions
+8
View File
@@ -1507,4 +1507,12 @@ public interface Query<T> {
*/
Set<String> validate();
/**
* Controls, if paginated queries should always append an 'order by id' statement at the end to
* guarantee a deterministic sort result. This may affect performance.
* If this is not enabled, and an orderBy is set on the query, it's up to the programmer that
* this query provides a deterministic result.
*/
Query<T> orderById(boolean orderById);
}
@@ -488,6 +488,11 @@ public class ServerConfig {
private ProfilingConfig profilingConfig = new ProfilingConfig();
/**
* Controls the default order by id setting of queries. See {@link Query#orderById(boolean)}
*/
private boolean defaultOrderById = false;
/**
* Construct a Server Configuration for programmatically creating an EbeanServer.
*/
@@ -523,6 +528,21 @@ public class ServerConfig {
this.slowQueryListener = slowQueryListener;
}
/**
* Sets the default orderById setting for queries.
*/
public void setDefaultOrderById(boolean defaultOrderById) {
this.defaultOrderById = defaultOrderById;
}
/**
* Returns the default orderById setting for queries.
*/
public boolean isDefaultOrderById() {
return defaultOrderById;
}
/**
* Put a service object into configuration such that it can be passed to a plugin.
* <p>
@@ -2767,6 +2787,7 @@ public class ServerConfig {
databaseBooleanTrue = p.get("databaseBooleanTrue", databaseBooleanTrue);
databaseBooleanFalse = p.get("databaseBooleanFalse", databaseBooleanFalse);
databasePlatformName = p.get("databasePlatformName", databasePlatformName);
defaultOrderById = p.getBoolean("defaultOrderById", defaultOrderById);
DbUuid dbUuid = p.getEnum(DbUuid.class, "dbuuid", null);
if (dbUuid != null) {
@@ -1,7 +1,6 @@
package io.ebeaninternal.api;
import io.ebean.CacheMode;
import io.ebean.EbeanServer;
import io.ebean.ExpressionList;
import io.ebean.OrderBy;
import io.ebean.PersistenceContextScope;
@@ -338,7 +337,7 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
/**
* Return a copy of the query attaching to a different EbeanServer.
*/
SpiQuery<T> copy(EbeanServer server);
SpiQuery<T> copy(SpiEbeanServer server);
/**
* Return the type of query (List, Set, Map, Bean, rowCount etc).
@@ -593,6 +592,11 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
*/
boolean checkPagingOrderBy();
/**
* Return true if there is no Order By clause.
*/
boolean orderByIsEmpty();
/**
* Return the Order By clause or null if there is none defined.
*/
@@ -3053,13 +3053,8 @@ public class BeanDescriptor<T> implements BeanType<T> {
*/
public void appendOrderById(SpiQuery<T> query) {
if (idProperty != null && !idProperty.isEmbedded()) {
SpiRawSql rawSql = query.getRawSql();
if (rawSql != null) {
query.order(rawSql.getSql().getOrderBy());
} else {
query.order().asc(idProperty.getName());
}
if (idProperty != null && !idProperty.isEmbedded() && !query.order().containsProperty(idProperty.getName())) {
query.order().asc(idProperty.getName());
}
}
@@ -190,7 +190,6 @@ public class CQueryEngine {
*/
public <T> QueryIterator<T> findIterate(OrmQueryRequest<T> request) {
prepareForPaging(request);
CQuery<T> cquery = queryBuilder.buildQuery(request);
request.setCancelableQuery(cquery);
@@ -326,24 +325,12 @@ public class CQueryEngine {
return historySupport.getSysPeriodLower(rootTableAlias);
}
/**
* deemed to be a be a paging query - check that the order by contains the id
* property to ensure unique row ordering for predicable paging but only in
* case, this is not a distinct query
*/
private <T> void prepareForPaging(OrmQueryRequest<T> request) {
SpiQuery<T> query = request.getQuery();
if (query.checkPagingOrderBy()) {
request.getBeanDescriptor().appendOrderById(query);
}
}
/**
* Find a list/map/set of beans.
*/
<T> BeanCollection<T> findMany(OrmQueryRequest<T> request) {
prepareForPaging(request);
CQuery<T> cquery = queryBuilder.buildQuery(request);
request.setCancelableQuery(cquery);
@@ -1,7 +1,6 @@
package io.ebeaninternal.server.querydefn;
import io.ebean.CacheMode;
import io.ebean.EbeanServer;
import io.ebean.Expression;
import io.ebean.ExpressionFactory;
import io.ebean.ExpressionList;
@@ -32,6 +31,7 @@ import io.ebeaninternal.api.CQueryPlanKey;
import io.ebeaninternal.api.HashQuery;
import io.ebeaninternal.api.ManyWhereJoins;
import io.ebeaninternal.api.NaturalKeyQueryData;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionList;
import io.ebeaninternal.api.SpiExpressionValidation;
@@ -39,6 +39,7 @@ import io.ebeaninternal.api.SpiNamedParam;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.SpiQuerySecondary;
import io.ebeaninternal.server.autotune.ProfilingListener;
import io.ebeaninternal.server.core.OrmQueryRequest;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.TableJoin;
@@ -75,7 +76,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
private final BeanDescriptor<T> beanDescriptor;
private final EbeanServer server;
private final SpiEbeanServer server;
private final ExpressionFactory expressionFactory;
@@ -254,6 +255,8 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
private String nativeSql;
private boolean orderById;
/**
* Identity the query for profiling purposes (expected to be unique for a bean type).
*/
@@ -261,10 +264,11 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
private ProfileLocation profileLocation;
public DefaultOrmQuery(BeanDescriptor<T> desc, EbeanServer server, ExpressionFactory expressionFactory) {
public DefaultOrmQuery(BeanDescriptor<T> desc, SpiEbeanServer server, ExpressionFactory expressionFactory) {
this.beanDescriptor = desc;
this.beanType = desc.getBeanType();
this.server = server;
this.orderById = server.getServerConfig().isDefaultOrderById();
this.expressionFactory = expressionFactory;
this.detail = new OrmQueryDetail();
}
@@ -279,6 +283,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
}
@Override
public boolean isFindAll() {
return whereExpressions == null && nativeSql == null && rawSql == null;
}
@@ -304,6 +309,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
}
}
@Override
public short getProfileId() {
return profileId;
}
@@ -717,8 +723,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
}
@Override
public DefaultOrmQuery<T> copy(EbeanServer server) {
public DefaultOrmQuery<T> copy(SpiEbeanServer server) {
DefaultOrmQuery<T> copy = new DefaultOrmQuery<>(beanDescriptor, server, expressionFactory);
copy.m2mIncludeJoin = m2mIncludeJoin;
copy.profilingListener = profilingListener;
@@ -742,6 +747,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
if (orderBy != null) {
copy.orderBy = orderBy.copy();
}
copy.orderById = orderById;
if (bindParams != null) {
copy.bindParams = bindParams.copy();
}
@@ -1037,6 +1043,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
public CQueryPlanKey prepare(BeanQueryRequest<?> request) {
prepareExpressions(request);
prepareForPaging((OrmQueryRequest) request);
queryPlanKey = createQueryPlanKey();
return queryPlanKey;
}
@@ -1054,6 +1061,24 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
}
}
/**
* deemed to be a be a paging query - check that the order by contains the id
* property to ensure unique row ordering for predicable paging but only in
* case, this is not a distinct query
*/
private void prepareForPaging(OrmQueryRequest<T> request) {
// add the rawSql statement - if any
if (orderByIsEmpty()) {
SpiRawSql rawSql = getRawSql();
if (rawSql != null && rawSql.getSql() != null) {
order(rawSql.getSql().getOrderBy());
}
}
if (checkPagingOrderBy()) {
request.getBeanDescriptor().appendOrderById(this);
}
}
/**
* Calculate a hash based on the bind values used in the query.
* <p>
@@ -1388,10 +1413,11 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
@Override
public boolean checkPagingOrderBy() {
return (maxRows > 1 || firstRow > 0) && !distinct && orderByIsEmpty();
return (maxRows > 1 || firstRow > 0) && !distinct && (orderByIsEmpty() || isOrderById());
}
private boolean orderByIsEmpty() {
@Override
public boolean orderByIsEmpty() {
return orderBy == null || orderBy.isEmpty();
}
@@ -1776,4 +1802,14 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
public ProfileLocation getProfileLocation() {
return profileLocation;
}
@Override
public Query<T> orderById(boolean orderById) {
this.orderById = orderById;
return this;
}
public boolean isOrderById() {
return orderById;
}
}