diff --git a/src/main/java/com/samskivert/depot/QueryBuilder.java b/src/main/java/com/samskivert/depot/QueryBuilder.java index 7e6c121..dfe4e75 100644 --- a/src/main/java/com/samskivert/depot/QueryBuilder.java +++ b/src/main/java/com/samskivert/depot/QueryBuilder.java @@ -364,48 +364,53 @@ public class QueryBuilder } /** - * Returns the value from the first row that matches the configured query clauses. + * Returns the value from the first row that matches the configured query clauses. This value + * may be null if the query clauses match no rows. */ public V load (SQLExpression selexp) { - return select(selexp).get(0); // TODO: revamp FindOneQuery and use that + return getLoaded(select(selexp)); // TODO: revamp FindOneQuery and use that } /** - * Returns the value from the first row that matches the configured query clauses. + * Returns the value from the first row that matches the configured query clauses. This value + * may be null if the query clauses match no rows. */ public Tuple2 load (SQLExpression exp1, SQLExpression exp2) { - return select(exp1, exp2).get(0); // TODO: revamp FindOneQuery and use that + return getLoaded(select(exp1, exp2)); // TODO: revamp FindOneQuery and use that } /** - * Returns the value from the first row that matches the configured query clauses. + * Returns the value from the first row that matches the configured query clauses. This value + * may be null if the query clauses match no rows. */ public Tuple3 load ( SQLExpression exp1, SQLExpression exp2, SQLExpression exp3) { - return select(exp1, exp2, exp3).get(0); // TODO: revamp FindOneQuery and use that + return getLoaded(select(exp1, exp2, exp3)); // TODO: revamp FindOneQuery and use that } /** - * Returns the value from the first row that matches the configured query clauses. + * Returns the value from the first row that matches the configured query clauses. This value + * may be null if the query clauses match no rows. */ public Tuple4 load ( SQLExpression exp1, SQLExpression exp2, SQLExpression exp3, SQLExpression exp4) { - return select(exp1, exp2, exp3, exp4).get(0); // TODO: revamp FindOneQuery and use that + return getLoaded(select(exp1, exp2, exp3, exp4)); // TODO: revamp FindOneQuery and use that } /** - * Returns the value from the first row that matches the configured query clauses. + * Returns the value from the first row that matches the configured query clauses. This value + * may be null if the query clauses match no rows. */ public Tuple5 load ( SQLExpression exp1, SQLExpression exp2, SQLExpression exp3, SQLExpression exp4, SQLExpression exp5) { - return select(exp1, exp2, exp3, exp4, exp5).get(0); // TODO: use revamped FindOneQuery + return getLoaded(select(exp1, exp2, exp3, exp4, exp5)); // TODO: use revamped FindOneQuery } /** @@ -553,6 +558,11 @@ public class QueryBuilder checkState(_forUpdate == null, "ForUpdate clause not supported by delete."); } + protected static T getLoaded (List selections) + { + return selections.isEmpty() ? null : selections.get(0); + } + protected final PersistenceContext _ctx; protected final DepotRepository _repo; protected final Class _pclass; diff --git a/src/test/java/com/samskivert/depot/ProjectionTest.java b/src/test/java/com/samskivert/depot/ProjectionTest.java index e00ae16..c85ef55 100644 --- a/src/test/java/com/samskivert/depot/ProjectionTest.java +++ b/src/test/java/com/samskivert/depot/ProjectionTest.java @@ -96,6 +96,19 @@ public class ProjectionTest extends TestBase assertEquals(20, jdata.size()); } + @Test public void testNoMatches () + { + QueryBuilder empty = + _repo.from(TestRecord.class).where(TestRecord.AGE.greaterThan(100)); + + // test a projection from a query that returns no matches + assertNull(empty.load(TestRecord.RECORD_ID)); + assertEquals(0, empty.select().size()); + + // test an aggregate on a query that matches no rows + assertNull(empty.load(Funcs.max(TestRecord.RECORD_ID))); + } + @Test public void testAggregates () { // test computed expressions on the RHS (the casts are just to cope with JUnit's overloads)