fix several bugs in findSingleAttibute (#980)

* add test case for distinct on id property

* suggested fix for "select distinct id"

* add test case for distinct with fetch

* FIX: .setDistinct(true) can be used in conjunction with fetch() now

* add test case for distinct with beans that have a disriminator column

* FIX: discriminator column is only read if also Id is read.

* fine tuned the test case

* added test cases for findSingleAttributeList without distinct

* improved fix to support also findSingleAttribute without distinct

* ADD: Bonus test case - assertion not yet verified
This commit is contained in:
Roland Praml
2017-02-26 12:37:36 +13:00
committed by Rob Bygrave
parent 40ca179509
commit a46e086dc4
6 changed files with 171 additions and 13 deletions
@@ -322,7 +322,9 @@ public class SqlTreeBuilder {
return new SqlTreeNodeManyRoot(prefix, (BeanPropertyAssocMany<?>) prop, props, myList, temporalMode, disableLazyLoad);
} else {
return new SqlTreeNodeBean(prefix, prop, props, myList, temporalMode, disableLazyLoad);
// do not read Id on child beans (e.g. when used with fetch())
boolean withId = (query == null || !query.isSingleAttribute());
return new SqlTreeNodeBean(prefix, prop, props, myList, withId, temporalMode, disableLazyLoad);
}
}
@@ -438,7 +440,7 @@ public class SqlTreeBuilder {
p = desc.findBeanProperty("id");
selectProps.add(p);
} else if (p.isId()) {
} else if (p.isId() && (query == null || !query.isSingleAttribute())) {
// do not bother to include id for normal queries as the
// id is always added (except for subQueries)
@@ -58,7 +58,7 @@ class SqlTreeNodeBean implements SqlTreeNode {
* False if report bean and has no id property.
*/
protected final boolean readId;
private final boolean disableLazyLoad;
protected final InheritInfo inheritInfo;
@@ -89,9 +89,9 @@ class SqlTreeNodeBean implements SqlTreeNode {
* Construct for leaf node.
*/
SqlTreeNodeBean(String prefix, BeanPropertyAssoc<?> beanProp, SqlTreeProperties props,
List<SqlTreeNode> myChildren, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) {
List<SqlTreeNode> myChildren, boolean withId, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) {
this(prefix, beanProp, beanProp.getTargetDescriptor(), props, myChildren, true, null, temporalMode, disableLazyLoad);
this(prefix, beanProp, beanProp.getTargetDescriptor(), props, myChildren, withId, null, temporalMode, disableLazyLoad);
}
/**
@@ -135,6 +135,11 @@ class SqlTreeNodeBean implements SqlTreeNode {
@Override
public BeanProperty getSingleProperty() {
if (properties == null || properties.length == 0) {
// if we have no property ask first children (in a distinct select with join)
// if we have also no children, NPE happens anyway.
return children[0].getSingleProperty();
}
return properties[0];
}
@@ -162,9 +167,9 @@ class SqlTreeNodeBean implements SqlTreeNode {
@Override
public void buildRawSqlSelectChain(List<String> selectChain) {
if (readId) {
if (desc.hasInheritance()) {
if (inheritInfo != null) {
// discriminator column always proceeds id column
selectChain.add(getPath(prefix, desc.getInheritInfo().getDiscriminatorColumn()));
selectChain.add(getPath(prefix, inheritInfo.getDiscriminatorColumn()));
}
idBinder.buildRawSqlSelectChain(prefix, selectChain);
}
@@ -434,11 +439,11 @@ class SqlTreeNodeBean implements SqlTreeNode {
lazyLoadParent.addSelectExported(ctx, prefix);
}
if (!subQuery && inheritInfo != null) {
ctx.appendColumn(inheritInfo.getDiscriminatorColumn());
}
if (readId) {
if (!subQuery && inheritInfo != null) {
ctx.appendColumn(inheritInfo.getDiscriminatorColumn());
}
appendSelectId(ctx, idBinder.getBeanProperty());
}
appendSelect(ctx, subQuery, properties);
@@ -15,7 +15,7 @@ final class SqlTreeNodeManyRoot extends SqlTreeNodeBean {
SqlTreeNodeManyRoot(String prefix, BeanPropertyAssocMany<?> prop, SqlTreeProperties props, List<SqlTreeNode> myList,
SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) {
super(prefix, prop, props, myList, temporalMode, disableLazyLoad);
super(prefix, prop, props, myList, true, temporalMode, disableLazyLoad);
this.manyProp = prop;
}
@@ -48,6 +48,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -1220,6 +1221,10 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
@Override
@SuppressWarnings("unchecked")
public <A> List<A> findSingleAttributeList() {
if (!detail.hasSelectClause()) {
// (no explicit select set - clear all properties)
detail.setBase(new OrmQueryProperties(null, new LinkedHashSet<>()));
}
return (List<A>) server.findSingleAttributeList(this, null);
}
@@ -440,7 +440,7 @@ public class OrmQueryDetail implements Serializable {
}
}
private boolean hasSelectClause() {
public boolean hasSelectClause() {
return baseProps.hasSelectClause();
}
@@ -5,6 +5,10 @@ import io.ebean.Ebean;
import io.ebean.Query;
import org.tests.model.basic.Customer;
import org.tests.model.basic.ResetBasicData;
import org.avaje.test.model.rawsql.inherit.ChildA;
import org.avaje.test.model.rawsql.inherit.Data;
import org.avaje.test.model.rawsql.inherit.EUncle;
import org.junit.Ignore;
import org.junit.Test;
import java.sql.Date;
@@ -125,4 +129,146 @@ public class TestQuerySingleAttribute extends BaseTestCase {
query2.findList();
assertThat(sqlOf(query2, 1)).contains("select t0.id, t0.name from o_customer t0");
}
@Test
public void distinctOnIdProperty(){
Query<Customer> query = Ebean.find(Customer.class)
.setDistinct(true)
.select("id")
.setMaxRows(100);
List<String> ids = query.findSingleAttributeList();
if (isSqlServer()) {
assertThat(sqlOf(query)).contains("select distinct top 100 t0.id from o_customer t0");
} else {
assertThat(sqlOf(query)).contains("select distinct t0.id from o_customer t0 limit 100");
}
assertThat(ids).isNotEmpty();
}
@Test
public void distinctWithFetch() {
ResetBasicData.reset();
Query<Customer> query = Ebean.find(Customer.class)
.setDistinct(true)
.fetch("billingAddress","city")
.setMaxRows(100);
List<String> cities = query.findSingleAttributeList();
assertThat(sqlOf(query)).contains("select distinct t1.city from o_customer t0 left join o_address t1 on t1.id = t0.billing_address_id");
assertThat(cities).contains("Auckland").containsNull();
}
@Test
public void distinctSelectOnInheritedBean() {
ResetBasicData.reset();
Query<ChildA> query = Ebean.find(ChildA.class)
.setDistinct(true)
.select("more")
.setMaxRows(100);
query.findSingleAttributeList();
assertThat(sqlOf(query)).contains("select distinct t0.more from rawinherit_parent t0 where t0.type = 'A' limit 100");
}
@Test
public void distinctFetchManyToOneInheritedBean() {
ResetBasicData.reset();
Query<EUncle> query = Ebean.find(EUncle.class)
.setDistinct(true)
.fetch("parent","more")
.setMaxRows(100);
query.findSingleAttributeList();
assertThat(sqlOf(query)).contains("select distinct t1.more from rawinherit_uncle t0 join rawinherit_parent t1 on t1.id = t0.parent_id and t1.type in ('A','B')");
}
// hmm - same problem when not using distinct
@Test
public void findSingleOnIdProperty(){
Query<Customer> query = Ebean.find(Customer.class)
.select("id")
.setMaxRows(100);
List<String> ids = query.findSingleAttributeList();
if (isSqlServer()) {
assertThat(sqlOf(query)).contains("select top 100 t0.id from o_customer t0");
} else {
assertThat(sqlOf(query)).contains("select t0.id from o_customer t0 limit 100");
}
assertThat(ids).isNotEmpty();
}
@Test
public void findSingleWithFetch() {
ResetBasicData.reset();
Query<Customer> query = Ebean.find(Customer.class)
.fetch("billingAddress","city")
.setMaxRows(100);
List<String> cities = query.findSingleAttributeList();
assertThat(cities).contains("Auckland").containsNull();
assertThat(sqlOf(query)).contains("select t1.city from o_customer t0 left join o_address t1 on t1.id = t0.billing_address_id");
}
@Test
public void findSingleSelectOnInheritedBean() {
ResetBasicData.reset();
Query<ChildA> query = Ebean.find(ChildA.class)
.select("more")
.setMaxRows(100);
query.findSingleAttributeList();
assertThat(sqlOf(query)).contains("select t0.more from rawinherit_parent t0 where t0.type = 'A' limit 100");
}
@Test
public void findSingleFetchManyToOneInheritedBean() {
ResetBasicData.reset();
Query<EUncle> query = Ebean.find(EUncle.class)
.fetch("parent","more")
.setMaxRows(100);
query.findSingleAttributeList();
assertThat(sqlOf(query)).contains("select t1.more from rawinherit_uncle t0 join rawinherit_parent t1 on t1.id = t0.parent_id and t1.type in ('A','B')");
}
@Test
@Ignore //don't know if ebean can handle this on many to many, as this means that the cartesian product is generated
public void distinctFetchManyToManyInheritedBean() {
ResetBasicData.reset();
Query<Data> query = Ebean.find(Data.class)
.setDistinct(true)
.fetch("parents","more")
.setMaxRows(100);
query.findSingleAttributeList();
assertThat(sqlOf(query)).contains("select distinct t0.more from rawinherit_data t0 "
+ "join rawinherit_parent_rawinherit_data t1 on t0.id = t1.rawinherit_data_id "
+ "join parent t2 on t1.rawinherit_parent_id = t2.id");
}
}