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:
@@ -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
|
||||
|
||||
+13
-12
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user