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));
}
// 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:
* {@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.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<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>(
_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 <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>>(
_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 <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;
/** 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);
}
@@ -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<? 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
* 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<? extends PersistentRecord> pClass, ColumnExp<?>[] columns,
public SelectClause (Class<? extends PersistentRecord> pClass, SQLExpression<?>[] selexps,
Iterable<? extends QueryClause> 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<? extends PersistentRecord> _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;
@@ -373,11 +373,11 @@ public abstract class BuildVisitor implements FragmentVisitor<Void>
// 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;
@@ -302,11 +302,10 @@ public class DepotMarshaller<T extends PersistentRecord> 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;
}
@@ -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<T extends PersistentRecord,R>
{
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<T extends PersistentRecord,R>
protected SimpleCacheKey _qkey;
}
public static class ForColumns<T extends PersistentRecord,R>
public static class Projection<T extends PersistentRecord,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)
throws DatabaseException
{
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);
_marsh = new ColumnSetQueryMarshaller<T,R>(cset, _types);
_marsh = new ProjectionQueryMarshaller<T,R>(cset, _types);
}
@Override // from Query
@@ -301,10 +302,10 @@ public abstract class FindAllQuery<T extends PersistentRecord,R>
protected DepotTypes _types;
}
protected static class ColumnSetQueryMarshaller<T extends PersistentRecord,R>
protected static class ProjectionQueryMarshaller<T extends PersistentRecord,R>
implements QueryMarshaller<T,R>
{
public ColumnSetQueryMarshaller (ColumnSet<T,R> cset, DepotTypes types) {
public ProjectionQueryMarshaller (Projector<T,R> cset, DepotTypes types) {
_cset = cset;
_types = types;
}
@@ -313,8 +314,8 @@ public abstract class FindAllQuery<T extends PersistentRecord,R>
return _types.getTableName(_cset.ptype);
}
public ColumnExp<?>[] getFieldNames () {
return _cset.columns;
public SQLExpression<?>[] getSelections () {
return _cset.selexps;
}
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 {
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<T,R> _cset;
protected Projector<T,R> _cset;
protected DepotTypes _types;
}
@@ -419,7 +429,7 @@ public abstract class FindAllQuery<T extends PersistentRecord,R>
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<Key<T>> got = Sets.newHashSet();
@@ -49,7 +49,7 @@ public class FindOneQuery<T extends PersistentRecord> extends Query<T>
{
_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
@@ -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<T extends PersistentRecord,R>
public abstract class Projector<T extends PersistentRecord,R>
{
public static <T extends PersistentRecord, V> ColumnSet<T,V> create (
Class<T> ptype, ColumnExp<V> column)
public static <T extends PersistentRecord, V> Projector<T,V> create (
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) {
@SuppressWarnings("unchecked") V result = (V)results[0];
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 (
Class<T> ptype, ColumnExp<V1> col1, ColumnExp<V2> col2)
public static <T extends PersistentRecord, V1, V2> Projector<T,Tuple2<V1,V2>> create (
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) {
@SuppressWarnings("unchecked") V1 r1 = (V1)results[0];
@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 ColumnExp<?>[] columns;
public final SQLExpression<?>[] selexps;
public abstract R createObject (Object[] results);
protected ColumnSet (Class<T> ptype, ColumnExp<?>[] columns)
protected Projector (Class<T> ptype, SQLExpression<?>[] selexps)
{
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.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<T extends PersistentRecord,R>
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.
@@ -54,14 +54,29 @@ public class SelectFieldsTest extends TestBase
List<Tuple2<Integer,String>> data = _repo.from(TestRecord.class).where(
TestRecord.RECORD_ID.greaterThan(7)).select(TestRecord.RECORD_ID, TestRecord.NAME);
List<Tuple2<Integer,String>> 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<Tuple2<Integer,EnumKeyRecord.Type>> 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<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
_repo.from(TestRecord.class).whereTrue().delete();