#1404 - Query.findCount() with query.setDistinct(true) ... doesn't give count distinct SQL query

This commit is contained in:
rob bygrave
2018-06-07 00:32:39 +12:00
parent eca6383dba
commit eba1003c19
9 changed files with 130 additions and 25 deletions
@@ -1294,8 +1294,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> int findCount(Query<T> query, Transaction t) {
SpiQuery<T> copy = ((SpiQuery<T>) query).copy();
return findCountWithCopy(copy, t);
SpiQuery<T> spiQuery = ((SpiQuery<T>) query);
if (!spiQuery.isDistinct()) {
spiQuery = spiQuery.copy();
}
return findCountWithCopy(spiQuery, t);
}
@Override
@@ -81,6 +81,8 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
private BeanPropertyAssocMany<?> manyProperty;
private boolean inlineCountDistinct;
/**
* Create the InternalQueryRequest.
*/
@@ -742,4 +744,12 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
public void slowQueryCheck(long executionTimeMicros, int rowCount) {
ebeanServer.slowQueryCheck(executionTimeMicros, rowCount, query);
}
public void setInlineCountDistinct() {
inlineCountDistinct = true;
}
public boolean isInlineCountDistinct() {
return inlineCountDistinct;
}
}
@@ -241,10 +241,14 @@ class CQueryBuilder {
ManyWhereJoins manyWhereJoins = query.getManyWhereJoins();
if (manyWhereJoins.isFormulaWithJoin()) {
query.select(manyWhereJoins.getFormulaProperties());
} else {
query.setSelectId();
boolean countDistinct = query.isDistinct();
if (!countDistinct) {
// minimise select clause for standard count
if (manyWhereJoins.isFormulaWithJoin()) {
query.select(manyWhereJoins.getFormulaProperties());
} else {
query.setSelectId();
}
}
CQueryPredicates predicates = new CQueryPredicates(binder, request);
@@ -263,23 +267,35 @@ class CQueryBuilder {
}
boolean hasMany = sqlTree.hasMany();
String sqlSelect = "select count(*)";
if (hasMany) {
// need to count distinct id's ...
query.setSqlDistinct(true);
sqlSelect = null;
String sqlSelect = null;
if (countDistinct) {
if (sqlTree.isSingleProperty()) {
request.setInlineCountDistinct();
}
} else {
if (hasMany) {
// need to count distinct id's ...
query.setSqlDistinct(true);
} else {
sqlSelect = "select count(*)";
}
}
SqlLimitResponse s = buildSql(sqlSelect, request, predicates, sqlTree);
String sql = s.getSql();
if (hasMany || query.isRawSql()) {
int pos = sql.lastIndexOf(" order by "); // remove order by - mssql does not accept order by in subqueries
if (pos != -1) {
sql = sql.substring(0, pos);
}
sql = "select count(*) from ( " + sql + ")";
if (selectCountWithAlias) {
sql += " as c";
if (!request.isInlineCountDistinct()) {
if (countDistinct) {
sql = wrapSelectCount(sql);
} else if (hasMany || 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);
}
}
@@ -290,6 +306,14 @@ class CQueryBuilder {
return new CQueryRowCount(queryPlan, request, predicates);
}
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.
@@ -536,6 +560,9 @@ class CQueryBuilder {
if (!useSqlLimiter) {
sb.append("select ");
if (query.isDistinctQuery()) {
if (request.isInlineCountDistinct()) {
sb.append("count(");
}
sb.append("distinct ");
String distinctOn = select.getDistinctOn();
if (distinctOn != null) {
@@ -551,6 +578,9 @@ class CQueryBuilder {
} else {
sb.append(select.getSelectSql());
}
if (request.isInlineCountDistinct()) {
sb.append(")");
}
if (query.isDistinctQuery() && dbOrderBy != null && !query.isSingleAttribute()) {
// add the orderBy columns to the select clause (due to distinct)
sb.append(", ").append(DbOrderByTrim.trim(dbOrderBy));
@@ -159,4 +159,8 @@ class SqlTree {
boolean hasMany() {
return manyProperty != null || rootNode.hasMany();
}
boolean isSingleProperty() {
return rootNode.isSingleProperty();
}
}
@@ -83,4 +83,8 @@ interface SqlTreeNode {
*/
ScalarType<?> getSingleAttributeScalarType();
/**
* Return true if the query is known to only have a single property selected.
*/
boolean isSingleProperty();
}
@@ -138,6 +138,11 @@ class SqlTreeNodeBean implements SqlTreeNode {
return false;
}
@Override
public boolean isSingleProperty() {
return properties != null && properties.length == 1 && children.length == 0;
}
@Override
public ScalarType<?> getSingleAttributeScalarType() {
if (properties == null || properties.length == 0) {
@@ -39,6 +39,11 @@ class SqlTreeNodeExtraJoin implements SqlTreeNode {
this.manyJoin = assocBeanProperty instanceof STreePropertyAssocMany;
}
@Override
public boolean isSingleProperty() {
return false;
}
@Override
public void buildRawSqlSelectChain(List<String> selectChain) {
// nothing to add
@@ -36,6 +36,11 @@ class SqlTreeNodeManyWhereJoin implements SqlTreeNode {
this.parentPrefix = split[0];
}
@Override
public boolean isSingleProperty() {
return false;
}
@Override
public ScalarType<?> getSingleAttributeScalarType() {
throw new IllegalStateException("No expected");
@@ -3,13 +3,16 @@ package org.tests.query;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import io.ebean.Query;
import org.junit.Test;
import org.tests.model.basic.Customer;
import org.tests.model.basic.Order;
import org.tests.model.basic.ResetBasicData;
import org.junit.Assert;
import org.junit.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
public class TestRowCount extends BaseTestCase {
@Test
@@ -17,8 +20,12 @@ public class TestRowCount extends BaseTestCase {
ResetBasicData.reset();
Query<Order> query = Ebean.find(Order.class).fetch("details").where().gt("id", 1)
.gt("details.id", 1).order("id desc");
Query<Order> query = Ebean.find(Order.class)
.fetch("details")
.where()
.gt("id", 1)
.gt("details.id", 1)
.order("id desc");
int rc = query.findCount();
@@ -30,8 +37,40 @@ public class TestRowCount extends BaseTestCase {
order.getStatus();
}
Assert.assertEquals("same rc to ids.size() ", rc, ids.size());
Assert.assertEquals("same rc to list.size()", rc, list.size());
assertEquals("same rc to ids.size() ", rc, ids.size());
assertEquals("same rc to list.size()", rc, list.size());
}
@Test
public void find_count_distinct_singleProperty() {
ResetBasicData.reset();
Query<Customer> query = Ebean.find(Customer.class)
.setDistinct(true)
.select("anniversary")
.where().eq("status", Customer.Status.NEW)
.query();
int count = query.findCount();
assertThat(sqlOf(query)).contains("select count(distinct t0.anniversary) from o_customer t0 where t0.status = ?");
assertThat(count).isGreaterThan(0);
}
@Test
public void find_count_distinct_multipleProperties() {
ResetBasicData.reset();
Query<Customer> query = Ebean.find(Customer.class)
.setDistinct(true)
.select("anniversary, status");
int count = query.findCount();
assertThat(sqlOf(query)).contains("select count(*) from ( select distinct t0.anniversary, t0.status from o_customer t0)");
assertThat(count).isGreaterThan(0);
}
}