From 5809a5e8c615c8cae3c5ddffe9899e8edcf16ed8 Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Sat, 18 Dec 2010 17:32:31 +0000 Subject: [PATCH] New deal: every time you call a builder method, a copy of the Query is created, so that partially constructed queries can be reused without any special care being taken by the caller. I also switched to the use of a functional list internally to make this whole process more pleasant and efficient. --- src/main/java/com/samskivert/depot/Query.java | 146 +++++++++++------- 1 file changed, 86 insertions(+), 60 deletions(-) diff --git a/src/main/java/com/samskivert/depot/Query.java b/src/main/java/com/samskivert/depot/Query.java index 035db65..38d76dc 100644 --- a/src/main/java/com/samskivert/depot/Query.java +++ b/src/main/java/com/samskivert/depot/Query.java @@ -38,52 +38,61 @@ import static com.google.common.base.Preconditions.checkState; /** * The root of a fluent mechanism for constructing queries. Obtain an instance via {@link - * DepotRepository#from}. + * DepotRepository#from}. For example: + *
{@code
+ * List> results = _repo.from(FooRecord.class).
+ *     where(FooRecord.NAME.like("%foo%")).
+ *     descending(FooRecord.NAME).
+ *     limit(25).
+ *     select(FooRecord.ID, FooRecord.NAME);
+ * }
+ * + *

Note that each builder method returns a new builder instance so that partial queries can be + * constructed and reused without interfering with one another: + *

{@code
+ * Query query = from(FooRecord.class).where(FooRecord.NAME.eq("foo")).limit(10);
+ * List> top = query.ascending().select(FooRecord.ID, FooRecord.NAME);
+ * List> bot = query.descending().select(FooRecord.ID, FooRecord.NAME);
+ * }
*/ public class Query implements Cloneable { - public Query (PersistenceContext ctx, DepotRepository repo, Class pclass) - { - _ctx = ctx; - _repo = repo; - _pclass = pclass; - } - /** Disables the use of the cache for this query. */ public Query noCache () { return cache(DepotRepository.CacheStrategy.BEST); } - /** Configures the use of {@link CacheStrategy#BEST} for this query. */ + /** Configures the use of {@link DepotRepository.CacheStrategy#BEST} for this query. */ public Query cacheBest () { return cache(DepotRepository.CacheStrategy.BEST); } - /** Configures the use of {@link CacheStrategy#RECORDS} for this query. */ + /** Configures the use of {@link DepotRepository.CacheStrategy#RECORDS} for this query. */ public Query cacheRecords () { return cache(DepotRepository.CacheStrategy.RECORDS); } - /** Configures the use of {@link CacheStrategy#SHORT_KEYS} for this query. */ + /** Configures the use of {@link DepotRepository.CacheStrategy#SHORT_KEYS} for this query. */ public Query cacheShortKeys () { return cache(DepotRepository.CacheStrategy.SHORT_KEYS); } - /** Configures the use of {@link CacheStrategy#LONG_KEYS} for this query. */ + /** Configures the use of {@link DepotRepository.CacheStrategy#LONG_KEYS} for this query. */ public Query cacheLongKeys () { return cache(DepotRepository.CacheStrategy.LONG_KEYS); } - /** Configures the use of {@link CacheStrategy#CONTENTS} for this query. */ + /** Configures the use of {@link DepotRepository.CacheStrategy#CONTENTS} for this query. */ public Query cacheContents () { return cache(DepotRepository.CacheStrategy.CONTENTS); } /** Configures the specified caching policy this query. */ public Query cache (DepotRepository.CacheStrategy cache) { - _cache = cache; - return this; + Query query = clone(); + query._cache = cache; + return query; } /** @@ -141,8 +150,9 @@ public class Query public Query where (WhereClause where) { checkState(_where == null, "Where clause is already configured."); - _where = where; - return this; + Query query = clone(); + query._where = where; + return query; } /** @@ -177,11 +187,9 @@ public class Query */ public Query join (Join join) { - if (_joins == null) { - _joins = Lists.newArrayList(); - } - _joins.add(join); - return this; + Query query = clone(); + query._joins = cons(join, query._joins); + return query; } /** @@ -190,8 +198,9 @@ public class Query public Query groupBy (SQLExpression... exprs) { checkState(_groupBy == null, "GroupBy clause is already configured."); - _groupBy = new GroupBy(exprs); - return this; + Query query = clone(); + query._groupBy = new GroupBy(exprs); + return query; } /** @@ -224,8 +233,9 @@ public class Query public Query orderBy (OrderBy orderBy) { checkState(_orderBy == null, "OrderBy clause is already configured."); - _orderBy = orderBy; - return this; + Query query = clone(); + query._orderBy = orderBy; + return query; } /** @@ -234,8 +244,9 @@ public class Query public Query limit (int count) { checkState(_limit == null, "Limit clause is already configured."); - _limit = new Limit(0, count); - return this; + Query query = clone(); + query._limit = new Limit(0, count); + return query; } /** @@ -244,8 +255,9 @@ public class Query public Query limit (int offset, int count) { checkState(_limit == null, "Limit clause is already configured."); - _limit = new Limit(offset, count); - return this; + Query query = clone(); + query._limit = new Limit(offset, count); + return query; } /** @@ -271,8 +283,9 @@ public class Query public Query override (FromOverride fromOverride) { checkState(_fromOverride == null, "FromOverride clause is already configured."); - _fromOverride = fromOverride; - return this; + Query query = clone(); + query._fromOverride = fromOverride; + return query; } /** @@ -304,11 +317,9 @@ public class Query */ public Query fieldDef (FieldDefinition fieldDef) { - if (_fieldDefs == null) { - _fieldDefs = Lists.newArrayList(); - } - _fieldDefs.add(fieldDef); - return this; + Query query = clone(); + query._fieldDefs = cons(fieldDef, query._fieldDefs); + return query; } /** @@ -317,8 +328,9 @@ public class Query public Query forUpdate () { checkState(_forUpdate == null, "ForUpdate clause is already configured."); - _forUpdate = new ForUpdate(); - return this; + Query query = clone(); + query._forUpdate = new ForUpdate(); + return query; } /** @@ -554,21 +566,17 @@ public class Query return _repo.deleteAll(_pclass, _where, invalidator); } - /** - * Returns a clone of this query builder, including all partially configured state. Useful for - * constructing partially configured queries and then executing variants. - */ - public Query clone () + protected Query (PersistenceContext ctx, DepotRepository repo, Class pclass) + { + _ctx = ctx; + _repo = repo; + _pclass = pclass; + } + + protected Query clone () { try { @SuppressWarnings("unchecked") Query qb = (Query)super.clone(); - // deep copy the list fields, if we have any - if (qb._joins != null) { - qb._joins = Lists.newArrayList(qb._joins); - } - if (qb._fieldDefs != null) { - qb._fieldDefs = Lists.newArrayList(qb._fieldDefs); - } return qb; } catch (Throwable t) { throw new AssertionError(t); @@ -579,16 +587,12 @@ public class Query { List clauses = Lists.newArrayList(); addIfNotNull(clauses, _where); - if (_joins != null) { - clauses.addAll(_joins); - } + addAll(clauses, _joins); addIfNotNull(clauses, _orderBy); addIfNotNull(clauses, _groupBy); addIfNotNull(clauses, _limit); addIfNotNull(clauses, _fromOverride); - if (_fieldDefs != null) { - clauses.addAll(_fieldDefs); - } + addAll(clauses, _fieldDefs); addIfNotNull(clauses, _forUpdate); return clauses; } @@ -623,6 +627,29 @@ public class Query return selections.isEmpty() ? null : selections.get(0); } + protected static final class Cons + { + public T head; + public Cons tail; + public Cons (T head, Cons tail) { + this.head = head; + this.tail = tail; + } + } + + protected static Cons cons (T head, Cons tail) + { + return new Cons(head, tail); + } + + protected static void addAll (List toList, Cons cons) + { + if (cons != null) { + toList.add(cons.head); + addAll(toList, cons.tail); + } + } + protected final PersistenceContext _ctx; protected final DepotRepository _repo; protected final Class _pclass; @@ -635,7 +662,6 @@ public class Query protected Limit _limit; protected FromOverride _fromOverride; protected ForUpdate _forUpdate; - - protected List _joins; - protected List _fieldDefs; + protected Cons _joins; + protected Cons _fieldDefs; }