#717563 : unified Projectable.uniqueResult() semantics

This commit is contained in:
Timo Westkämper 2011-02-17 20:01:41 +00:00
parent 6d2a1efddd
commit c0bdc03969
19 changed files with 158 additions and 30 deletions

View File

@ -0,0 +1,14 @@
package com.mysema.query.collections;
import org.junit.Test;
import com.mysema.query.NonUniqueResultException;
public class UniqueResultContract extends AbstractQueryTest{
@Test(expected=NonUniqueResultException.class)
public void Unique_Result_Throws_Exception_On_Multiple_Results(){
MiniApi.from(cat, cats).where(cat.name.isNotNull()).uniqueResult(cat);
}
}

View File

@ -0,0 +1,19 @@
package com.mysema.query;
/**
* @author tiwe
*
*/
public class NonUniqueResultException extends QueryException{
private static final long serialVersionUID = -1757423191400510323L;
public NonUniqueResultException() {
super("Only one result is allowed for uniqueResult calls");
}
public NonUniqueResultException(String message) {
super(message);
}
}

View File

@ -196,6 +196,7 @@ public interface Projectable {
* @param first
* @param second
* @param rest
* @throws NonUniqueResultException if there is more than one matching result
* @return
*/
@Nullable
@ -203,10 +204,9 @@ public interface Projectable {
/**
* return a unique result for the given projection or null if not result is found
*
* <p>for multiple results only the first one is returned</p>
*
* @param args
* @throws NonUniqueResultException if there is more than one matching result
* @return
*/
@Nullable
@ -214,12 +214,11 @@ public interface Projectable {
/**
* return a unique result for the given projection or null if not result is found
*
* <p>for multiple results only the first one is returned</p>
*
* @param <RT>
* return type
* @param projection
* @throws NonUniqueResultException if there is more than one matching result
* @return the result or null for an empty result
*/
@Nullable

View File

@ -49,7 +49,8 @@ public interface SimpleProjectable<T> {
/**
* Get the projection as a unique result
*
*
* @throws NonUniqueResultException if there is more than one matching result
* @return
*/
@Nullable

View File

@ -13,6 +13,7 @@ import java.util.Map;
import org.apache.commons.collections15.IteratorUtils;
import com.mysema.commons.lang.CloseableIterator;
import com.mysema.query.NonUniqueResultException;
import com.mysema.query.Projectable;
import com.mysema.query.SearchResults;
import com.mysema.query.types.Expression;
@ -128,15 +129,29 @@ public abstract class ProjectableQuery<Q extends ProjectableQuery<Q>>
public Object[] uniqueResult(Expression<?>[] args) {
queryMixin.setUnique(true);
Iterator<Object[]> it = iterate(args);
return it.hasNext() ? it.next() : null;
return getUniqueResult(iterate(args));
}
@Override
public <RT> RT uniqueResult(Expression<RT> expr) {
queryMixin.setUnique(true);
limit(1l);
Iterator<RT> it = iterate(expr);
return it.hasNext() ? it.next() : null;
if (queryMixin.getMetadata().getModifiers().getLimit() == null){
limit(2l);
}
return getUniqueResult(iterate(expr));
}
protected <T> T getUniqueResult(Iterator<T> it) {
if (it.hasNext()){
T rv = it.next();
if (it.hasNext()){
throw new NonUniqueResultException();
}
return rv;
}else{
return null;
}
}
}

View File

@ -15,6 +15,7 @@ import org.hibernate.search.Search;
import com.mysema.commons.lang.Assert;
import com.mysema.commons.lang.CloseableIterator;
import com.mysema.commons.lang.IteratorAdapter;
import com.mysema.query.NonUniqueResultException;
import com.mysema.query.QueryMetadata;
import com.mysema.query.QueryModifiers;
import com.mysema.query.SearchResults;
@ -159,7 +160,11 @@ public class SearchQuery<T> implements SimpleQuery<SearchQuery<T>>, SimpleProjec
@SuppressWarnings("unchecked")
@Override
public T uniqueResult() {
return (T) createQuery(false).uniqueResult();
try{
return (T) createQuery(false).uniqueResult();
}catch (org.hibernate.NonUniqueResultException e){
throw new NonUniqueResultException();
}
}
@Override

View File

@ -15,6 +15,7 @@ import java.util.List;
import org.hibernate.Session;
import org.junit.Test;
import com.mysema.query.NonUniqueResultException;
import com.mysema.query.SearchResults;
import com.mysema.query.types.OrderSpecifier;
import com.mysema.query.types.expr.BooleanExpression;
@ -57,6 +58,11 @@ public class SearchQueryTest extends AbstractQueryTest{
assertEquals(u, list.get(0));
}
@Test(expected=NonUniqueResultException.class)
public void Unique_Result_Throws_Exception_On_Multiple_Results(){
query().where(user.middleName.eq("X")).uniqueResult();
}
@Test
public void Ordering(){
BooleanExpression filter = user.middleName.eq("X");

View File

@ -24,6 +24,7 @@ import org.slf4j.LoggerFactory;
import com.mysema.commons.lang.CloseableIterator;
import com.mysema.commons.lang.IteratorAdapter;
import com.mysema.query.DefaultQueryMetadata;
import com.mysema.query.NonUniqueResultException;
import com.mysema.query.QueryException;
import com.mysema.query.QueryMetadata;
import com.mysema.query.QueryModifiers;
@ -270,9 +271,24 @@ public abstract class AbstractJDOQLQuery<Q extends AbstractJDOQLQuery<Q>> extend
@Nullable
public <RT> RT uniqueResult(Expression<RT> expr) {
queryMixin.addToProjection(expr);
Query query = createQuery(false);
query.setUnique(true);
if (getMetadata().getModifiers().getLimit() == null){
limit(2);
}
Query query = createQuery(false);
reset();
return (RT) execute(query);
Object rv = execute(query);
if (rv instanceof List){
List<RT> list = (List)rv;
if (!list.isEmpty()){
if (list.size() > 1){
throw new NonUniqueResultException();
}
return list.get(0);
}else{
return null;
}
}else{
return (RT)rv;
}
}
}

View File

@ -17,6 +17,7 @@ import org.junit.Ignore;
import org.junit.Test;
import com.mysema.query.BooleanBuilder;
import com.mysema.query.NonUniqueResultException;
import com.mysema.query.jdo.test.domain.Book;
import com.mysema.query.jdo.test.domain.Product;
import com.mysema.query.jdo.test.domain.QBook;
@ -67,6 +68,11 @@ public class BasicsTest extends AbstractJDOTest {
public void CountTests() {
assertEquals("count", 2, query().from(product).count());
}
@Test(expected=NonUniqueResultException.class)
public void Unique_Result_Throws_Exception_On_Multiple_Results(){
query().from(product).uniqueResult(product);
}
@Test
public void SimpleTest() throws IOException{

View File

@ -22,6 +22,7 @@ import org.slf4j.LoggerFactory;
import com.mysema.commons.lang.CloseableIterator;
import com.mysema.commons.lang.IteratorAdapter;
import com.mysema.query.DefaultQueryMetadata;
import com.mysema.query.NonUniqueResultException;
import com.mysema.query.QueryException;
import com.mysema.query.QueryMetadata;
import com.mysema.query.QueryModifiers;
@ -351,8 +352,12 @@ public abstract class AbstractHibernateQuery<Q extends AbstractHibernateQuery<Q>
String queryString = toQueryString();
logQuery(queryString);
Query query = createQuery(queryString, modifiers);
reset();
return (RT) query.uniqueResult();
reset();
try{
return (RT) query.uniqueResult();
}catch (org.hibernate.NonUniqueResultException e){
throw new NonUniqueResultException();
}
}
}

View File

@ -19,6 +19,7 @@ import org.slf4j.LoggerFactory;
import com.mysema.commons.lang.CloseableIterator;
import com.mysema.commons.lang.IteratorAdapter;
import com.mysema.query.DefaultQueryMetadata;
import com.mysema.query.NonUniqueResultException;
import com.mysema.query.QueryMetadata;
import com.mysema.query.QueryModifiers;
import com.mysema.query.SearchResults;
@ -201,8 +202,12 @@ public final class HibernateSQLQuery extends AbstractSQLQuery<HibernateSQLQuery>
@SuppressWarnings("unchecked")
public <RT> RT uniqueResult(Expression<RT> expr) {
Query query = createQuery(expr);
reset();
return (RT) query.uniqueResult();
reset();
try{
return (RT) query.uniqueResult();
}catch (org.hibernate.NonUniqueResultException e){
throw new NonUniqueResultException();
}
}
/**

View File

@ -13,7 +13,6 @@ import java.util.Map;
import javax.annotation.Nullable;
import javax.persistence.EntityManager;
import javax.persistence.LockModeType;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import org.slf4j.Logger;
@ -22,6 +21,7 @@ import org.slf4j.LoggerFactory;
import com.mysema.commons.lang.CloseableIterator;
import com.mysema.commons.lang.IteratorAdapter;
import com.mysema.query.DefaultQueryMetadata;
import com.mysema.query.NonUniqueResultException;
import com.mysema.query.QueryException;
import com.mysema.query.QueryMetadata;
import com.mysema.query.QueryModifiers;
@ -216,11 +216,12 @@ public abstract class AbstractJPAQuery<Q extends AbstractJPAQuery<Q>> extends JP
reset();
try{
return (RT) query.getSingleResult();
}catch(NoResultException e){
}catch(javax.persistence.NoResultException e){
logger.debug(e.getMessage(),e);
return null;
}catch(javax.persistence.NonUniqueResultException e){
throw new NonUniqueResultException();
}
}
@SuppressWarnings("unchecked")

View File

@ -19,6 +19,7 @@ import org.slf4j.LoggerFactory;
import com.mysema.commons.lang.CloseableIterator;
import com.mysema.commons.lang.IteratorAdapter;
import com.mysema.query.DefaultQueryMetadata;
import com.mysema.query.NonUniqueResultException;
import com.mysema.query.QueryMetadata;
import com.mysema.query.QueryModifiers;
import com.mysema.query.SearchResults;
@ -176,9 +177,11 @@ public final class JPASQLQuery extends AbstractSQLQuery<JPASQLQuery> implements
reset();
try{
return (RT) query.getSingleResult();
}catch(NoResultException e){
}catch(javax.persistence.NoResultException e){
logger.debug(e.getMessage(),e);
return null;
}catch(javax.persistence.NonUniqueResultException e){
throw new NonUniqueResultException();
}
}

View File

@ -14,6 +14,7 @@ import org.apache.lucene.search.Sort;
import com.mysema.commons.lang.CloseableIterator;
import com.mysema.commons.lang.EmptyCloseableIterator;
import com.mysema.commons.lang.IteratorAdapter;
import com.mysema.query.NonUniqueResultException;
import com.mysema.query.QueryException;
import com.mysema.query.QueryMetadata;
import com.mysema.query.QueryModifiers;
@ -202,7 +203,7 @@ SimpleProjectable<T> {
}
final ScoreDoc[] scoreDocs = searcher.search(createQuery(), maxDoc).scoreDocs;
if (scoreDocs.length > 1) {
throw new QueryException("More than one result found!");
throw new NonUniqueResultException();
} else if (scoreDocs.length == 1) {
return transformer.transform(searcher.doc(scoreDocs[0].doc));
} else {

View File

@ -15,6 +15,7 @@ import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mysema.commons.lang.CloseableIterator;
import com.mysema.query.NonUniqueResultException;
import com.mysema.query.QueryMetadata;
import com.mysema.query.QueryModifiers;
import com.mysema.query.SearchResults;
@ -148,7 +149,15 @@ public class MongodbQuery<K> implements SimpleQuery<MongodbQuery<K>>, SimpleProj
@Override
public K uniqueResult() {
DBCursor c = createCursor().limit(1);
return c.hasNext() ? transformer.transform(c.next()) : null;
if (c.hasNext()){
K rv = transformer.transform(c.next());
if (c.hasNext()){
throw new NonUniqueResultException();
}
return rv;
}else{
return null;
}
}
@Override

View File

@ -20,6 +20,7 @@ import org.junit.Test;
import com.google.code.morphia.Datastore;
import com.google.code.morphia.Morphia;
import com.mysema.query.NonUniqueResultException;
import com.mysema.query.SearchResults;
import com.mysema.query.mongodb.domain.Address;
import com.mysema.query.mongodb.domain.City;
@ -59,6 +60,11 @@ public class MongodbQueryTest {
public void UniqueResult(){
assertEquals("Jantunen", where(user.firstName.eq("Jaakko")).uniqueResult().getLastName());
}
@Test(expected=NonUniqueResultException.class)
public void UniqueResultContract(){
where(user.firstName.isNotNull()).uniqueResult();
}
@Test
public void LongPath(){

View File

@ -27,6 +27,7 @@ import com.mysema.commons.lang.IteratorAdapter;
import com.mysema.query.DefaultQueryMetadata;
import com.mysema.query.JoinExpression;
import com.mysema.query.JoinFlag;
import com.mysema.query.NonUniqueResultException;
import com.mysema.query.QueryException;
import com.mysema.query.QueryFlag;
import com.mysema.query.QueryMetadata;
@ -548,10 +549,18 @@ public abstract class AbstractSQLQuery<Q extends AbstractSQLQuery<Q>> extends
@Override
public <RT> RT uniqueResult(Expression<RT> expr) {
if (getMetadata().getModifiers().getLimit() == null
&& !expr.toString().contains("count(")){
limit(2);
}
CloseableIterator<RT> iterator = iterate(expr);
try{
if (iterator.hasNext()){
return iterator.next();
RT rv = iterator.next();
if (iterator.hasNext()){
throw new NonUniqueResultException();
}
return rv;
}else{
return null;
}

View File

@ -29,7 +29,7 @@ public abstract class BeanPopulationBaseTest extends AbstractBaseTest{
assertEquals(1l, update(e).populate(employee).where(e.id.eq(employee.getId())).execute());
// Query
Employee smith = query().from(e).where(e.lastname.eq("Smith")).uniqueResult(e);
Employee smith = query().from(e).where(e.lastname.eq("Smith")).limit(1).uniqueResult(e);
assertEquals("John", smith.getFirstname());
// Delete (no changes needed)
@ -50,18 +50,21 @@ public abstract class BeanPopulationBaseTest extends AbstractBaseTest{
// Query
Employee smith = extQuery().from(e).where(e.lastname.eq("Smith"))
.limit(1)
.uniqueResult(Employee.class, e.lastname, e.firstname);
assertEquals("John", smith.getFirstname());
assertEquals("Smith", smith.getLastname());
// Query with alias
smith = extQuery().from(e).where(e.lastname.eq("Smith"))
.limit(1)
.uniqueResult(Employee.class, e.lastname.as("lastname"), e.firstname.as("firstname"));
assertEquals("John", smith.getFirstname());
assertEquals("Smith", smith.getLastname());
// Query into custom type
OtherEmployee other = extQuery().from(e).where(e.lastname.eq("Smith"))
.limit(1)
.uniqueResult(OtherEmployee.class, e.lastname, e.firstname);
assertEquals("John", other.getFirstname());
assertEquals("Smith", other.getLastname());

View File

@ -807,7 +807,7 @@ public abstract class SelectBaseTest extends AbstractBaseTest{
@Test
public void Unique_Constructor_Projection(){
// unique constructor projection
IdName idAndName = query().from(survey).uniqueResult(new QIdName(survey.id, survey.name));
IdName idAndName = query().from(survey).limit(1).uniqueResult(new QIdName(survey.id, survey.name));
assertNotNull(idAndName);
assertNotNull(idAndName.getId());
assertNotNull(idAndName.getName());
@ -817,7 +817,7 @@ public abstract class SelectBaseTest extends AbstractBaseTest{
@Test
public void Unique_Single(){
// unique single
String s = query().from(survey).uniqueResult(survey.name);
String s = query().from(survey).limit(1).uniqueResult(survey.name);
assertNotNull(s);
}
@ -825,13 +825,18 @@ public abstract class SelectBaseTest extends AbstractBaseTest{
@Test
public void Unique_Wildcard(){
// unique wildcard
Object[] row = query().from(survey).uniqueResult(survey.all());
Object[] row = query().from(survey).limit(1).uniqueResult(survey.all());
assertNotNull(row);
assertEquals(2, row.length);
assertNotNull(row[0]);
assertNotNull(row[1]);
}
@Test(expected=NonUniqueResultException.class)
public void UniqueResultContract(){
query().from(employee).uniqueResult(employee.all());
}
@Test
public void Various() throws SQLException {
@ -872,7 +877,7 @@ public abstract class SelectBaseTest extends AbstractBaseTest{
@SkipForQuoted
public void Wildcard_All() {
expectedQuery = "select * from EMPLOYEE2 e";
query().from(employee).uniqueResult(Wildcard.all);
query().from(employee).list(Wildcard.all);
}
@Test