From 780d25866c98ba71181ce964e192ed0efd73be67 Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Thu, 9 Dec 2010 01:14:21 +0000 Subject: [PATCH] Holy plans coming together Batman, selecting computed expressions is working. Some examples: int max = _repo.from(TestRecord.class).load(Funcs.max(TestRecord.RECORD_ID)); List> data = _repo.from(EnumKeyRecord.class). groupBy(EnumKeyRecord.NAME).ascending(EnumKeyRecord.NAME). select(EnumKeyRecord.NAME, Funcs.count(EnumKeyRecord.TYPE)); Still no caching integration, but the ratio of utility over boilerplate is high. --- .../com/samskivert/depot/DepotRepository.java | 7 ---- .../com/samskivert/depot/QueryBuilder.java | 34 +++++++++++----- .../java/com/samskivert/depot/Tuple2.java | 2 +- .../samskivert/depot/clause/SelectClause.java | 26 ++++++------ .../samskivert/depot/impl/BuildVisitor.java | 4 +- .../depot/impl/DepotMarshaller.java | 7 ++-- .../samskivert/depot/impl/FindAllQuery.java | 40 ++++++++++++------- .../samskivert/depot/impl/FindOneQuery.java | 2 +- .../impl/{ColumnSet.java => Projector.java} | 25 ++++++------ .../depot/impl/QueryMarshaller.java | 6 +-- .../samskivert/depot/SelectFieldsTest.java | 21 ++++++++-- 11 files changed, 104 insertions(+), 70 deletions(-) rename src/main/java/com/samskivert/depot/impl/{ColumnSet.java => Projector.java} (65%) diff --git a/src/main/java/com/samskivert/depot/DepotRepository.java b/src/main/java/com/samskivert/depot/DepotRepository.java index d124b77..789b373 100644 --- a/src/main/java/com/samskivert/depot/DepotRepository.java +++ b/src/main/java/com/samskivert/depot/DepotRepository.java @@ -337,13 +337,6 @@ public abstract class DepotRepository return _ctx.invoke(new FindAllKeysQuery(_ctx, type, forUpdate, clauses)); } - // public List findAll ( - // Class type, Iterable clauses) - // throws DatabaseException - // { - // return _ctx.invoke(new FindAllQuery.ForColumns(_ctx, type, clauses, column)); - // } - /** * Returns a builder that can be used to construct a query in a fluent style. For example: * {@code from(FooRecord.class).where(ID.greaterThan(25)).ascending(SIZE).select()} diff --git a/src/main/java/com/samskivert/depot/QueryBuilder.java b/src/main/java/com/samskivert/depot/QueryBuilder.java index b3fd6ae..f7e0ceb 100644 --- a/src/main/java/com/samskivert/depot/QueryBuilder.java +++ b/src/main/java/com/samskivert/depot/QueryBuilder.java @@ -29,7 +29,7 @@ import com.google.common.collect.Lists; import com.samskivert.depot.clause.*; import com.samskivert.depot.expression.ColumnExp; import com.samskivert.depot.expression.SQLExpression; -import com.samskivert.depot.impl.ColumnSet; +import com.samskivert.depot.impl.Projector; import com.samskivert.depot.impl.FindAllQuery; import static com.google.common.base.Preconditions.checkArgument; @@ -363,21 +363,37 @@ public class QueryBuilder } /** - * Returns just the supplied column from the rows matching the query. + * Returns the value from the first row that matches the configured query clauses. */ - public List select (ColumnExp column) + public V load (SQLExpression selexp) { - return _ctx.invoke(new FindAllQuery.ForColumns( - _ctx, ColumnSet.create(_pclass, column), getClauses())); + return select(selexp).get(0); // TODO: revamp FindOneQuery and use that } /** - * Returns just the supplied columns from the rows matching the query. + * Returns the value from the first row that matches the configured query clauses. */ - public List> select (ColumnExp col1, ColumnExp col2) + public Tuple2 load (SQLExpression exp1, SQLExpression exp2) { - return _ctx.invoke(new FindAllQuery.ForColumns>( - _ctx, ColumnSet.create(_pclass, col1, col2), getClauses())); + return select(exp1, exp2).get(0); // TODO: revamp FindOneQuery and use that + } + + /** + * Returns just the supplied expression from the rows matching the query. + */ + public List select (SQLExpression selexp) + { + return _ctx.invoke(new FindAllQuery.Projection( + _ctx, Projector.create(_pclass, selexp), getClauses())); + } + + /** + * Returns just the supplied expressions from the rows matching the query. + */ + public List> select (SQLExpression exp1, SQLExpression exp2) + { + return _ctx.invoke(new FindAllQuery.Projection>( + _ctx, Projector.create(_pclass, exp1, exp2), getClauses())); } /** diff --git a/src/main/java/com/samskivert/depot/Tuple2.java b/src/main/java/com/samskivert/depot/Tuple2.java index 124eb5f..a61ebf1 100644 --- a/src/main/java/com/samskivert/depot/Tuple2.java +++ b/src/main/java/com/samskivert/depot/Tuple2.java @@ -38,7 +38,7 @@ public class Tuple2 implements Serializable public final B b; /** Constructs an initialized two tuple. */ - public static Tuple2 newTuple (A a, B b) + public static Tuple2 create (A a, B b) { return new Tuple2(a, b); } diff --git a/src/main/java/com/samskivert/depot/clause/SelectClause.java b/src/main/java/com/samskivert/depot/clause/SelectClause.java index c2c730d..5df9cbc 100644 --- a/src/main/java/com/samskivert/depot/clause/SelectClause.java +++ b/src/main/java/com/samskivert/depot/clause/SelectClause.java @@ -29,7 +29,7 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.samskivert.depot.PersistentRecord; -import com.samskivert.depot.expression.ColumnExp; +import com.samskivert.depot.expression.SQLExpression; import com.samskivert.depot.impl.FragmentVisitor; import static com.google.common.base.Preconditions.checkArgument; @@ -41,24 +41,24 @@ public class SelectClause implements QueryClause { /** - * Creates a new select clause, selecting the supplied columns from the specified persistent - * class for rows that match the supplied clauses. + * Creates a new select clause, selecting the supplied expressions from the specified + * persistent class (and potentially joined classes) for rows that match the supplied clauses. */ public SelectClause (Class pClass, - ColumnExp[] columns, QueryClause... clauses) + SQLExpression[] selexps, QueryClause... clauses) { - this(pClass, columns, Arrays.asList(clauses)); + this(pClass, selexps, Arrays.asList(clauses)); } /** - * Creates a new select clause, selecting the supplied columns from the specified persistent - * class for rows that match the supplied clauses. + * Creates a new select clause, selecting the supplied expressions from the specified + * persistent class (and potentially joined classes) for rows that match the supplied clauses. */ - public SelectClause (Class pClass, ColumnExp[] columns, + public SelectClause (Class pClass, SQLExpression[] selexps, Iterable clauses) { _pClass = pClass; - _fields = columns; + _selexps = selexps; // iterate over the clauses and sort them into the different types we understand for (QueryClause clause : clauses) { @@ -119,9 +119,9 @@ public class SelectClause return _pClass; } - public ColumnExp[] getFields () + public SQLExpression[] getSelections () { - return _fields; + return _selexps; } public FromOverride getFromOverride () @@ -216,8 +216,8 @@ public class SelectClause /** The persistent class this select defines. */ protected Class _pClass; - /** The persistent fields to select. */ - protected ColumnExp[] _fields; + /** The expressions being selected. */ + protected SQLExpression[] _selexps; /** The from override clause, if any. */ protected FromOverride _fromOverride; diff --git a/src/main/java/com/samskivert/depot/impl/BuildVisitor.java b/src/main/java/com/samskivert/depot/impl/BuildVisitor.java index b5f0b3e..c3151bc 100644 --- a/src/main/java/com/samskivert/depot/impl/BuildVisitor.java +++ b/src/main/java/com/samskivert/depot/impl/BuildVisitor.java @@ -373,11 +373,11 @@ public abstract class BuildVisitor implements FragmentVisitor // while expanding column names in the SELECT query, do aliasing and expansion _enableAliasing = _enableOverrides = true; - for (ColumnExp field : selectClause.getFields()) { + for (SQLExpression selexp : selectClause.getSelections()) { // write column to a temporary buffer StringBuilder saved = _builder; _builder = new StringBuilder(); - appendRhsColumn(field); + selexp.accept(this); String column = _builder.toString(); _builder = saved; diff --git a/src/main/java/com/samskivert/depot/impl/DepotMarshaller.java b/src/main/java/com/samskivert/depot/impl/DepotMarshaller.java index ad5d885..07aee26 100644 --- a/src/main/java/com/samskivert/depot/impl/DepotMarshaller.java +++ b/src/main/java/com/samskivert/depot/impl/DepotMarshaller.java @@ -302,11 +302,10 @@ public class DepotMarshaller implements QueryMarshal return _tableName; } - /** - * Returns all the persistent fields of our class, in definition order. - */ - public ColumnExp[] getFieldNames () + // from QueryMarshaller + public SQLExpression[] getSelections () { + // when we're used in a query, we select all of our fields, in order return _allFields; } diff --git a/src/main/java/com/samskivert/depot/impl/FindAllQuery.java b/src/main/java/com/samskivert/depot/impl/FindAllQuery.java index 98a8aa1..d645858 100644 --- a/src/main/java/com/samskivert/depot/impl/FindAllQuery.java +++ b/src/main/java/com/samskivert/depot/impl/FindAllQuery.java @@ -48,6 +48,7 @@ import com.samskivert.depot.clause.FieldOverride; import com.samskivert.depot.clause.QueryClause; import com.samskivert.depot.clause.SelectClause; import com.samskivert.depot.expression.ColumnExp; +import com.samskivert.depot.expression.SQLExpression; import com.samskivert.depot.impl.operator.In; import com.samskivert.jdbc.DatabaseLiaison; @@ -218,7 +219,7 @@ public abstract class FindAllQuery { super(ctx, type); - _select = new SelectClause(type, _dmarsh.getFieldNames(), clauses); + _select = new SelectClause(type, _dmarsh.getSelections(), clauses); if (cachedContents) { _qkey = new SimpleCacheKey(_dmarsh.getTableName() + "Contents", _select.toString()); @@ -264,17 +265,17 @@ public abstract class FindAllQuery protected SimpleCacheKey _qkey; } - public static class ForColumns + public static class Projection extends FindAllQuery { - public ForColumns (PersistenceContext ctx, ColumnSet cset, + public Projection (PersistenceContext ctx, Projector cset, Iterable clauses) throws DatabaseException { super(cset.ptype, null, new NonCloningCloner()); - _select = new SelectClause(cset.ptype, cset.columns, clauses); + _select = new SelectClause(cset.ptype, cset.selexps, clauses); _types = DepotTypes.getDepotTypes(ctx, _select); - _marsh = new ColumnSetQueryMarshaller(cset, _types); + _marsh = new ProjectionQueryMarshaller(cset, _types); } @Override // from Query @@ -301,10 +302,10 @@ public abstract class FindAllQuery protected DepotTypes _types; } - protected static class ColumnSetQueryMarshaller + protected static class ProjectionQueryMarshaller implements QueryMarshaller { - public ColumnSetQueryMarshaller (ColumnSet cset, DepotTypes types) { + public ProjectionQueryMarshaller (Projector cset, DepotTypes types) { _cset = cset; _types = types; } @@ -313,8 +314,8 @@ public abstract class FindAllQuery return _types.getTableName(_cset.ptype); } - public ColumnExp[] getFieldNames () { - return _cset.columns; + public SQLExpression[] getSelections () { + return _cset.selexps; } public Key getPrimaryKey (Object object) { @@ -322,16 +323,25 @@ public abstract class FindAllQuery } public R createObject (ResultSet rs) throws SQLException { - Object[] data = new Object[_cset.columns.length]; + Object[] data = new Object[_cset.selexps.length]; for (int ii = 0; ii < data.length; ii++) { - ColumnExp col = _cset.columns[ii]; - data[ii] = _types.getMarshaller(col.getPersistentClass()). - getFieldMarshaller(col.name).getFromSet(rs, ii+1); + SQLExpression exp = _cset.selexps[ii]; + if (exp instanceof ColumnExp) { + ColumnExp col = (ColumnExp)exp; + data[ii] = _types.getMarshaller(col.getPersistentClass()). + getFieldMarshaller(col.name).getFromSet(rs, ii+1); + } else { + // TEMP: in the case of selecting computed expressions, we rely on the types to + // be correct by construction; TODO: this will probably bite us when JDBC + // drivers choose Long instead of Integer or whatnot, so we'll need to set up + // more complex machinery + data[ii] = rs.getObject(ii+1); + } } return _cset.createObject(data); } - protected ColumnSet _cset; + protected Projector _cset; protected DepotTypes _types; } @@ -419,7 +429,7 @@ public abstract class FindAllQuery throws SQLException { SelectClause select = new SelectClause( - _type, _marsh.getFieldNames(), (QueryClause) KeySet.newKeySet(_type, keys)); + _type, _marsh.getSelections(), (QueryClause) KeySet.newKeySet(_type, keys)); SQLBuilder builder = ctx.getSQLBuilder(DepotTypes.getDepotTypes(ctx, select)); builder.newQuery(select); Set> got = Sets.newHashSet(); diff --git a/src/main/java/com/samskivert/depot/impl/FindOneQuery.java b/src/main/java/com/samskivert/depot/impl/FindOneQuery.java index f6210f4..3ac4a94 100644 --- a/src/main/java/com/samskivert/depot/impl/FindOneQuery.java +++ b/src/main/java/com/samskivert/depot/impl/FindOneQuery.java @@ -49,7 +49,7 @@ public class FindOneQuery extends Query { _strategy = strategy; _marsh = ctx.getMarshaller(type); - _select = new SelectClause(type, _marsh.getFieldNames(), clauses); + _select = new SelectClause(type, _marsh.getSelections(), clauses); WhereClause where = _select.getWhereClause(); if (where != null) { _select.getWhereClause().validateQueryType(type); // sanity check diff --git a/src/main/java/com/samskivert/depot/impl/ColumnSet.java b/src/main/java/com/samskivert/depot/impl/Projector.java similarity index 65% rename from src/main/java/com/samskivert/depot/impl/ColumnSet.java rename to src/main/java/com/samskivert/depot/impl/Projector.java index 4a77420..1fe9903 100644 --- a/src/main/java/com/samskivert/depot/impl/ColumnSet.java +++ b/src/main/java/com/samskivert/depot/impl/Projector.java @@ -22,17 +22,18 @@ package com.samskivert.depot.impl; import com.samskivert.depot.PersistentRecord; import com.samskivert.depot.Tuple2; -import com.samskivert.depot.expression.ColumnExp; +import com.samskivert.depot.expression.SQLExpression; /** - * Contains a set of columns which are to be selected from a query result. + * Contains a set of selection expressions which are to be projected (selected) from a set of + * tables. Handles the necessary type conversions to turn the results into typed tuples. */ -public abstract class ColumnSet +public abstract class Projector { - public static ColumnSet create ( - Class ptype, ColumnExp column) + public static Projector create ( + Class ptype, SQLExpression column) { - return new ColumnSet(ptype, new ColumnExp[] { column }) { + return new Projector(ptype, new SQLExpression[] { column }) { public V createObject (Object[] results) { @SuppressWarnings("unchecked") V result = (V)results[0]; return result; @@ -40,10 +41,10 @@ public abstract class ColumnSet }; } - public static ColumnSet> create ( - Class ptype, ColumnExp col1, ColumnExp col2) + public static Projector> create ( + Class ptype, SQLExpression col1, SQLExpression col2) { - return new ColumnSet>(ptype, new ColumnExp[] { col1, col2 }) { + return new Projector>(ptype, new SQLExpression[] { col1, col2 }) { public Tuple2 createObject (Object[] results) { @SuppressWarnings("unchecked") V1 r1 = (V1)results[0]; @SuppressWarnings("unchecked") V2 r2 = (V2)results[1]; @@ -53,13 +54,13 @@ public abstract class ColumnSet } public final Class ptype; - public final ColumnExp[] columns; + public final SQLExpression[] selexps; public abstract R createObject (Object[] results); - protected ColumnSet (Class ptype, ColumnExp[] columns) + protected Projector (Class ptype, SQLExpression[] selexps) { this.ptype = ptype; - this.columns = columns; + this.selexps = selexps; } } diff --git a/src/main/java/com/samskivert/depot/impl/QueryMarshaller.java b/src/main/java/com/samskivert/depot/impl/QueryMarshaller.java index 70ee56e..40643bf 100644 --- a/src/main/java/com/samskivert/depot/impl/QueryMarshaller.java +++ b/src/main/java/com/samskivert/depot/impl/QueryMarshaller.java @@ -25,7 +25,7 @@ import java.sql.SQLException; import com.samskivert.depot.Key; import com.samskivert.depot.PersistentRecord; -import com.samskivert.depot.expression.ColumnExp; +import com.samskivert.depot.expression.SQLExpression; /** * Marshalls the results for a query. @@ -39,9 +39,9 @@ public interface QueryMarshaller String getTableName (); /** - * Returns the field names being selected for this query. + * Returns the expressions being selected for this query. */ - ColumnExp[] getFieldNames (); + SQLExpression[] getSelections (); /** * Extracts the primary key from the supplied object. diff --git a/src/test/java/com/samskivert/depot/SelectFieldsTest.java b/src/test/java/com/samskivert/depot/SelectFieldsTest.java index 8f0717a..c2ff9a8 100644 --- a/src/test/java/com/samskivert/depot/SelectFieldsTest.java +++ b/src/test/java/com/samskivert/depot/SelectFieldsTest.java @@ -54,14 +54,29 @@ public class SelectFieldsTest extends TestBase List> data = _repo.from(TestRecord.class).where( TestRecord.RECORD_ID.greaterThan(7)).select(TestRecord.RECORD_ID, TestRecord.NAME); List> want = Lists.newArrayList(); - want.add(Tuple2.newTuple(8, "Elvis")); - want.add(Tuple2.newTuple(9, "Elvis")); + want.add(Tuple2.create(8, "Elvis")); + want.add(Tuple2.create(9, "Elvis")); assertEquals(data, want); // test a basic join List> jdata = _repo.from(TestRecord.class).join( TestRecord.NAME, EnumKeyRecord.NAME).select(TestRecord.RECORD_ID, EnumKeyRecord.TYPE); - System.out.println(jdata); + assertEquals(20, jdata.size()); + + // test computed expressions on the RHS (the casts are just to cope with JUnit's overloads) + assertEquals(9, (int)_repo.from(TestRecord.class).load(Funcs.max(TestRecord.RECORD_ID))); + assertEquals(10, (int)_repo.from(TestRecord.class).load(Funcs.count(TestRecord.RECORD_ID))); + assertEquals(0, (int)_repo.from(TestRecord.class).load(Funcs.min(TestRecord.RECORD_ID))); + assertEquals(Tuple2.create(9, 0), _repo.from(TestRecord.class).load( + Funcs.max(TestRecord.RECORD_ID), Funcs.min(TestRecord.RECORD_ID))); + + List> ewant = Lists.newArrayList(); + ewant.add(Tuple2.create("Abraham", 1)); + ewant.add(Tuple2.create("Elvis", 2)); + ewant.add(Tuple2.create("Moses", 1)); + assertEquals(ewant, _repo.from(EnumKeyRecord.class). + groupBy(EnumKeyRecord.NAME).ascending(EnumKeyRecord.NAME). + select(EnumKeyRecord.NAME, Funcs.count(EnumKeyRecord.TYPE))); // finally clean up after ourselves _repo.from(TestRecord.class).whereTrue().delete();