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<Tuple2<String,Integer>> 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.
This commit is contained in:
Michael Bayne
2010-12-09 01:14:21 +00:00
parent 800c566fc6
commit 780d25866c
11 changed files with 104 additions and 70 deletions
@@ -337,13 +337,6 @@ public abstract class DepotRepository
return _ctx.invoke(new FindAllKeysQuery<T>(_ctx, type, forUpdate, clauses)); return _ctx.invoke(new FindAllKeysQuery<T>(_ctx, type, forUpdate, clauses));
} }
// public <T extends PersistentRecord> List<Object> findAll (
// Class<T> type, Iterable<? extends QueryClause> clauses)
// throws DatabaseException
// {
// return _ctx.invoke(new FindAllQuery.ForColumns<T>(_ctx, type, clauses, column));
// }
/** /**
* Returns a builder that can be used to construct a query in a fluent style. For example: * 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()} * {@code from(FooRecord.class).where(ID.greaterThan(25)).ascending(SIZE).select()}
@@ -29,7 +29,7 @@ import com.google.common.collect.Lists;
import com.samskivert.depot.clause.*; import com.samskivert.depot.clause.*;
import com.samskivert.depot.expression.ColumnExp; import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.expression.SQLExpression; 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 com.samskivert.depot.impl.FindAllQuery;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
@@ -363,21 +363,37 @@ public class QueryBuilder<T extends PersistentRecord>
} }
/** /**
* 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 <V> List<V> select (ColumnExp<V> column) public <V> V load (SQLExpression<V> selexp)
{ {
return _ctx.invoke(new FindAllQuery.ForColumns<T,V>( return select(selexp).get(0); // TODO: revamp FindOneQuery and use that
_ctx, ColumnSet.create(_pclass, column), getClauses()));
} }
/** /**
* 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 <V1, V2> List<Tuple2<V1,V2>> select (ColumnExp<V1> col1, ColumnExp<V2> col2) public <V1, V2> Tuple2<V1, V2> load (SQLExpression<V1> exp1, SQLExpression<V2> exp2)
{ {
return _ctx.invoke(new FindAllQuery.ForColumns<T,Tuple2<V1,V2>>( return select(exp1, exp2).get(0); // TODO: revamp FindOneQuery and use that
_ctx, ColumnSet.create(_pclass, col1, col2), getClauses())); }
/**
* Returns just the supplied expression from the rows matching the query.
*/
public <V> List<V> select (SQLExpression<V> selexp)
{
return _ctx.invoke(new FindAllQuery.Projection<T,V>(
_ctx, Projector.create(_pclass, selexp), getClauses()));
}
/**
* Returns just the supplied expressions from the rows matching the query.
*/
public <V1, V2> List<Tuple2<V1,V2>> select (SQLExpression<V1> exp1, SQLExpression<V2> exp2)
{
return _ctx.invoke(new FindAllQuery.Projection<T,Tuple2<V1,V2>>(
_ctx, Projector.create(_pclass, exp1, exp2), getClauses()));
} }
/** /**
@@ -38,7 +38,7 @@ public class Tuple2<A,B> implements Serializable
public final B b; public final B b;
/** Constructs an initialized two tuple. */ /** Constructs an initialized two tuple. */
public static <A, B> Tuple2<A, B> newTuple (A a, B b) public static <A, B> Tuple2<A, B> create (A a, B b)
{ {
return new Tuple2<A, B>(a, b); return new Tuple2<A, B>(a, b);
} }
@@ -29,7 +29,7 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.samskivert.depot.PersistentRecord; import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.expression.ColumnExp; import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.FragmentVisitor; import com.samskivert.depot.impl.FragmentVisitor;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
@@ -41,24 +41,24 @@ public class SelectClause
implements QueryClause implements QueryClause
{ {
/** /**
* Creates a new select clause, selecting the supplied columns from the specified persistent * Creates a new select clause, selecting the supplied expressions from the specified
* class for rows that match the supplied clauses. * persistent class (and potentially joined classes) for rows that match the supplied clauses.
*/ */
public SelectClause (Class<? extends PersistentRecord> pClass, public SelectClause (Class<? extends PersistentRecord> 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 * Creates a new select clause, selecting the supplied expressions from the specified
* class for rows that match the supplied clauses. * persistent class (and potentially joined classes) for rows that match the supplied clauses.
*/ */
public SelectClause (Class<? extends PersistentRecord> pClass, ColumnExp<?>[] columns, public SelectClause (Class<? extends PersistentRecord> pClass, SQLExpression<?>[] selexps,
Iterable<? extends QueryClause> clauses) Iterable<? extends QueryClause> clauses)
{ {
_pClass = pClass; _pClass = pClass;
_fields = columns; _selexps = selexps;
// iterate over the clauses and sort them into the different types we understand // iterate over the clauses and sort them into the different types we understand
for (QueryClause clause : clauses) { for (QueryClause clause : clauses) {
@@ -119,9 +119,9 @@ public class SelectClause
return _pClass; return _pClass;
} }
public ColumnExp<?>[] getFields () public SQLExpression<?>[] getSelections ()
{ {
return _fields; return _selexps;
} }
public FromOverride getFromOverride () public FromOverride getFromOverride ()
@@ -216,8 +216,8 @@ public class SelectClause
/** The persistent class this select defines. */ /** The persistent class this select defines. */
protected Class<? extends PersistentRecord> _pClass; protected Class<? extends PersistentRecord> _pClass;
/** The persistent fields to select. */ /** The expressions being selected. */
protected ColumnExp<?>[] _fields; protected SQLExpression<?>[] _selexps;
/** The from override clause, if any. */ /** The from override clause, if any. */
protected FromOverride _fromOverride; protected FromOverride _fromOverride;
@@ -373,11 +373,11 @@ public abstract class BuildVisitor implements FragmentVisitor<Void>
// while expanding column names in the SELECT query, do aliasing and expansion // while expanding column names in the SELECT query, do aliasing and expansion
_enableAliasing = _enableOverrides = true; _enableAliasing = _enableOverrides = true;
for (ColumnExp<?> field : selectClause.getFields()) { for (SQLExpression<?> selexp : selectClause.getSelections()) {
// write column to a temporary buffer // write column to a temporary buffer
StringBuilder saved = _builder; StringBuilder saved = _builder;
_builder = new StringBuilder(); _builder = new StringBuilder();
appendRhsColumn(field); selexp.accept(this);
String column = _builder.toString(); String column = _builder.toString();
_builder = saved; _builder = saved;
@@ -302,11 +302,10 @@ public class DepotMarshaller<T extends PersistentRecord> implements QueryMarshal
return _tableName; return _tableName;
} }
/** // from QueryMarshaller
* Returns all the persistent fields of our class, in definition order. public SQLExpression<?>[] getSelections ()
*/
public ColumnExp<?>[] getFieldNames ()
{ {
// when we're used in a query, we select all of our fields, in order
return _allFields; return _allFields;
} }
@@ -48,6 +48,7 @@ import com.samskivert.depot.clause.FieldOverride;
import com.samskivert.depot.clause.QueryClause; import com.samskivert.depot.clause.QueryClause;
import com.samskivert.depot.clause.SelectClause; import com.samskivert.depot.clause.SelectClause;
import com.samskivert.depot.expression.ColumnExp; import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.operator.In; import com.samskivert.depot.impl.operator.In;
import com.samskivert.jdbc.DatabaseLiaison; import com.samskivert.jdbc.DatabaseLiaison;
@@ -218,7 +219,7 @@ public abstract class FindAllQuery<T extends PersistentRecord,R>
{ {
super(ctx, type); super(ctx, type);
_select = new SelectClause(type, _dmarsh.getFieldNames(), clauses); _select = new SelectClause(type, _dmarsh.getSelections(), clauses);
if (cachedContents) { if (cachedContents) {
_qkey = new SimpleCacheKey(_dmarsh.getTableName() + "Contents", _select.toString()); _qkey = new SimpleCacheKey(_dmarsh.getTableName() + "Contents", _select.toString());
@@ -264,17 +265,17 @@ public abstract class FindAllQuery<T extends PersistentRecord,R>
protected SimpleCacheKey _qkey; protected SimpleCacheKey _qkey;
} }
public static class ForColumns<T extends PersistentRecord,R> public static class Projection<T extends PersistentRecord,R>
extends FindAllQuery<T,R> extends FindAllQuery<T,R>
{ {
public ForColumns (PersistenceContext ctx, ColumnSet<T,R> cset, public Projection (PersistenceContext ctx, Projector<T,R> cset,
Iterable<? extends QueryClause> clauses) Iterable<? extends QueryClause> clauses)
throws DatabaseException throws DatabaseException
{ {
super(cset.ptype, null, new NonCloningCloner<R>()); super(cset.ptype, null, new NonCloningCloner<R>());
_select = new SelectClause(cset.ptype, cset.columns, clauses); _select = new SelectClause(cset.ptype, cset.selexps, clauses);
_types = DepotTypes.getDepotTypes(ctx, _select); _types = DepotTypes.getDepotTypes(ctx, _select);
_marsh = new ColumnSetQueryMarshaller<T,R>(cset, _types); _marsh = new ProjectionQueryMarshaller<T,R>(cset, _types);
} }
@Override // from Query @Override // from Query
@@ -301,10 +302,10 @@ public abstract class FindAllQuery<T extends PersistentRecord,R>
protected DepotTypes _types; protected DepotTypes _types;
} }
protected static class ColumnSetQueryMarshaller<T extends PersistentRecord,R> protected static class ProjectionQueryMarshaller<T extends PersistentRecord,R>
implements QueryMarshaller<T,R> implements QueryMarshaller<T,R>
{ {
public ColumnSetQueryMarshaller (ColumnSet<T,R> cset, DepotTypes types) { public ProjectionQueryMarshaller (Projector<T,R> cset, DepotTypes types) {
_cset = cset; _cset = cset;
_types = types; _types = types;
} }
@@ -313,8 +314,8 @@ public abstract class FindAllQuery<T extends PersistentRecord,R>
return _types.getTableName(_cset.ptype); return _types.getTableName(_cset.ptype);
} }
public ColumnExp<?>[] getFieldNames () { public SQLExpression<?>[] getSelections () {
return _cset.columns; return _cset.selexps;
} }
public Key<T> getPrimaryKey (Object object) { public Key<T> getPrimaryKey (Object object) {
@@ -322,16 +323,25 @@ public abstract class FindAllQuery<T extends PersistentRecord,R>
} }
public R createObject (ResultSet rs) throws SQLException { 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++) { for (int ii = 0; ii < data.length; ii++) {
ColumnExp<?> col = _cset.columns[ii]; SQLExpression<?> exp = _cset.selexps[ii];
data[ii] = _types.getMarshaller(col.getPersistentClass()). if (exp instanceof ColumnExp<?>) {
getFieldMarshaller(col.name).getFromSet(rs, ii+1); 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); return _cset.createObject(data);
} }
protected ColumnSet<T,R> _cset; protected Projector<T,R> _cset;
protected DepotTypes _types; protected DepotTypes _types;
} }
@@ -419,7 +429,7 @@ public abstract class FindAllQuery<T extends PersistentRecord,R>
throws SQLException throws SQLException
{ {
SelectClause select = new SelectClause( 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)); SQLBuilder builder = ctx.getSQLBuilder(DepotTypes.getDepotTypes(ctx, select));
builder.newQuery(select); builder.newQuery(select);
Set<Key<T>> got = Sets.newHashSet(); Set<Key<T>> got = Sets.newHashSet();
@@ -49,7 +49,7 @@ public class FindOneQuery<T extends PersistentRecord> extends Query<T>
{ {
_strategy = strategy; _strategy = strategy;
_marsh = ctx.getMarshaller(type); _marsh = ctx.getMarshaller(type);
_select = new SelectClause(type, _marsh.getFieldNames(), clauses); _select = new SelectClause(type, _marsh.getSelections(), clauses);
WhereClause where = _select.getWhereClause(); WhereClause where = _select.getWhereClause();
if (where != null) { if (where != null) {
_select.getWhereClause().validateQueryType(type); // sanity check _select.getWhereClause().validateQueryType(type); // sanity check
@@ -22,17 +22,18 @@ package com.samskivert.depot.impl;
import com.samskivert.depot.PersistentRecord; import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.Tuple2; 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<T extends PersistentRecord,R> public abstract class Projector<T extends PersistentRecord,R>
{ {
public static <T extends PersistentRecord, V> ColumnSet<T,V> create ( public static <T extends PersistentRecord, V> Projector<T,V> create (
Class<T> ptype, ColumnExp<V> column) Class<T> ptype, SQLExpression<V> column)
{ {
return new ColumnSet<T, V>(ptype, new ColumnExp[] { column }) { return new Projector<T, V>(ptype, new SQLExpression[] { column }) {
public V createObject (Object[] results) { public V createObject (Object[] results) {
@SuppressWarnings("unchecked") V result = (V)results[0]; @SuppressWarnings("unchecked") V result = (V)results[0];
return result; return result;
@@ -40,10 +41,10 @@ public abstract class ColumnSet<T extends PersistentRecord,R>
}; };
} }
public static <T extends PersistentRecord, V1, V2> ColumnSet<T,Tuple2<V1,V2>> create ( public static <T extends PersistentRecord, V1, V2> Projector<T,Tuple2<V1,V2>> create (
Class<T> ptype, ColumnExp<V1> col1, ColumnExp<V2> col2) Class<T> ptype, SQLExpression<V1> col1, SQLExpression<V2> col2)
{ {
return new ColumnSet<T, Tuple2<V1,V2>>(ptype, new ColumnExp[] { col1, col2 }) { return new Projector<T, Tuple2<V1,V2>>(ptype, new SQLExpression[] { col1, col2 }) {
public Tuple2<V1,V2> createObject (Object[] results) { public Tuple2<V1,V2> createObject (Object[] results) {
@SuppressWarnings("unchecked") V1 r1 = (V1)results[0]; @SuppressWarnings("unchecked") V1 r1 = (V1)results[0];
@SuppressWarnings("unchecked") V2 r2 = (V2)results[1]; @SuppressWarnings("unchecked") V2 r2 = (V2)results[1];
@@ -53,13 +54,13 @@ public abstract class ColumnSet<T extends PersistentRecord,R>
} }
public final Class<T> ptype; public final Class<T> ptype;
public final ColumnExp<?>[] columns; public final SQLExpression<?>[] selexps;
public abstract R createObject (Object[] results); public abstract R createObject (Object[] results);
protected ColumnSet (Class<T> ptype, ColumnExp<?>[] columns) protected Projector (Class<T> ptype, SQLExpression<?>[] selexps)
{ {
this.ptype = ptype; this.ptype = ptype;
this.columns = columns; this.selexps = selexps;
} }
} }
@@ -25,7 +25,7 @@ import java.sql.SQLException;
import com.samskivert.depot.Key; import com.samskivert.depot.Key;
import com.samskivert.depot.PersistentRecord; import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.expression.ColumnExp; import com.samskivert.depot.expression.SQLExpression;
/** /**
* Marshalls the results for a query. * Marshalls the results for a query.
@@ -39,9 +39,9 @@ public interface QueryMarshaller<T extends PersistentRecord,R>
String getTableName (); 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. * Extracts the primary key from the supplied object.
@@ -54,14 +54,29 @@ public class SelectFieldsTest extends TestBase
List<Tuple2<Integer,String>> data = _repo.from(TestRecord.class).where( List<Tuple2<Integer,String>> data = _repo.from(TestRecord.class).where(
TestRecord.RECORD_ID.greaterThan(7)).select(TestRecord.RECORD_ID, TestRecord.NAME); TestRecord.RECORD_ID.greaterThan(7)).select(TestRecord.RECORD_ID, TestRecord.NAME);
List<Tuple2<Integer,String>> want = Lists.newArrayList(); List<Tuple2<Integer,String>> want = Lists.newArrayList();
want.add(Tuple2.newTuple(8, "Elvis")); want.add(Tuple2.create(8, "Elvis"));
want.add(Tuple2.newTuple(9, "Elvis")); want.add(Tuple2.create(9, "Elvis"));
assertEquals(data, want); assertEquals(data, want);
// test a basic join // test a basic join
List<Tuple2<Integer,EnumKeyRecord.Type>> jdata = _repo.from(TestRecord.class).join( List<Tuple2<Integer,EnumKeyRecord.Type>> jdata = _repo.from(TestRecord.class).join(
TestRecord.NAME, EnumKeyRecord.NAME).select(TestRecord.RECORD_ID, EnumKeyRecord.TYPE); 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<Tuple2<String, Integer>> 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 // finally clean up after ourselves
_repo.from(TestRecord.class).whereTrue().delete(); _repo.from(TestRecord.class).whereTrue().delete();