Great big revampage: no more strings anywhere. It's all ColumnExps all the way

down (well, all the way to the public API anyway).

Revamped index creation while I was in there because that was one of the big
string users. Now you just put @Index on the field you want indexed, and if you
want a multi-column index you do things with a magical static method just like
we do for complex (function) indices.

@UniqueConstraint may still go away since it's basically exactly the same thing
as @Index(unique=true), so it's kind of pointless to support both.
This commit is contained in:
Michael Bayne
2009-01-08 02:10:54 +00:00
parent a66f15e355
commit a9c992bb44
30 changed files with 260 additions and 356 deletions
+1
View File
@@ -3,6 +3,7 @@
<project name="library-dependencies"> <project name="library-dependencies">
<fileset dir="${libs.dir}" id="depot.libs"> <fileset dir="${libs.dir}" id="depot.libs">
<include name="ant.jar"/> <include name="ant.jar"/>
<include name="commons-collections.jar"/>
<include name="ehcache.jar"/> <include name="ehcache.jar"/>
<include name="google-collect.jar"/> <include name="google-collect.jar"/>
<include name="junit4.jar"/> <include name="junit4.jar"/>
@@ -47,6 +47,7 @@ import com.samskivert.depot.clause.InsertClause;
import com.samskivert.depot.clause.QueryClause; import com.samskivert.depot.clause.QueryClause;
import com.samskivert.depot.clause.UpdateClause; import com.samskivert.depot.clause.UpdateClause;
import com.samskivert.depot.clause.WhereClause; import com.samskivert.depot.clause.WhereClause;
import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.expression.SQLExpression; import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.expression.ValueExp; import com.samskivert.depot.expression.ValueExp;
import com.samskivert.depot.impl.DepotMarshaller; import com.samskivert.depot.impl.DepotMarshaller;
@@ -55,8 +56,8 @@ import com.samskivert.depot.impl.DepotTypes;
import com.samskivert.depot.impl.FindAllKeysQuery; import com.samskivert.depot.impl.FindAllKeysQuery;
import com.samskivert.depot.impl.FindAllQuery; import com.samskivert.depot.impl.FindAllQuery;
import com.samskivert.depot.impl.FindOneQuery; import com.samskivert.depot.impl.FindOneQuery;
import com.samskivert.depot.impl.Modifier;
import com.samskivert.depot.impl.Modifier.*; import com.samskivert.depot.impl.Modifier.*;
import com.samskivert.depot.impl.Modifier;
import com.samskivert.depot.impl.SQLBuilder; import com.samskivert.depot.impl.SQLBuilder;
import static com.samskivert.depot.Log.log; import static com.samskivert.depot.Log.log;
@@ -208,7 +209,7 @@ public abstract class DepotRepository
* *
* @throws DatabaseException if any problem is encountered communicating with the database. * @throws DatabaseException if any problem is encountered communicating with the database.
*/ */
protected <T extends PersistentRecord> T load (Class<T> type, String ix, Comparable<?> val, protected <T extends PersistentRecord> T load (Class<T> type, ColumnExp ix, Comparable<?> val,
QueryClause... clauses) QueryClause... clauses)
throws DatabaseException throws DatabaseException
{ {
@@ -221,8 +222,8 @@ public abstract class DepotRepository
* *
* @throws DatabaseException if any problem is encountered communicating with the database. * @throws DatabaseException if any problem is encountered communicating with the database.
*/ */
protected <T extends PersistentRecord> T load (Class<T> type, String ix1, Comparable<?> val1, protected <T extends PersistentRecord> T load (Class<T> type, ColumnExp ix1, Comparable<?> val1,
String ix2, Comparable<?> val2, ColumnExp ix2, Comparable<?> val2,
QueryClause... clauses) QueryClause... clauses)
throws DatabaseException throws DatabaseException
{ {
@@ -235,8 +236,8 @@ public abstract class DepotRepository
* *
* @throws DatabaseException if any problem is encountered communicating with the database. * @throws DatabaseException if any problem is encountered communicating with the database.
*/ */
protected <T extends PersistentRecord> T load (Class<T> type, String ix1, Comparable<?> val1, protected <T extends PersistentRecord> T load (Class<T> type, ColumnExp ix1, Comparable<?> val1,
String ix2, Comparable<?> val2, String ix3, ColumnExp ix2, Comparable<?> val2, ColumnExp ix3,
Comparable<?> val3, QueryClause... clauses) Comparable<?> val3, QueryClause... clauses)
throws DatabaseException throws DatabaseException
{ {
@@ -448,7 +449,7 @@ public abstract class DepotRepository
updateKey(marsh.getPrimaryKey(_result, false)); updateKey(marsh.getPrimaryKey(_result, false));
} }
builder.newQuery(new InsertClause<T>(pClass, _result, identityFields)); builder.newQuery(new InsertClause(pClass, _result, identityFields));
PreparedStatement stmt = builder.prepare(conn); PreparedStatement stmt = builder.prepare(conn);
try { try {
@@ -515,7 +516,7 @@ public abstract class DepotRepository
* *
* @throws DatabaseException if any problem is encountered communicating with the database. * @throws DatabaseException if any problem is encountered communicating with the database.
*/ */
protected <T extends PersistentRecord> int update (T record, final String... modifiedFields) protected <T extends PersistentRecord> int update (T record, final ColumnExp... modifiedFields)
throws DatabaseException throws DatabaseException
{ {
@SuppressWarnings("unchecked") Class<T> pClass = (Class<T>) record.getClass(); @SuppressWarnings("unchecked") Class<T> pClass = (Class<T>) record.getClass();
@@ -527,7 +528,8 @@ public abstract class DepotRepository
throw new IllegalArgumentException("Can't update record with null primary key."); throw new IllegalArgumentException("Can't update record with null primary key.");
} }
UpdateClause<T> update = new UpdateClause<T>(pClass, key, modifiedFields, record); UpdateClause<T> update = new UpdateClause<T>(
pClass, key, ColumnExp.toNames(modifiedFields), record);
final SQLBuilder builder = _ctx.getSQLBuilder(DepotTypes.getDepotTypes(_ctx, update)); final SQLBuilder builder = _ctx.getSQLBuilder(DepotTypes.getDepotTypes(_ctx, update));
builder.newQuery(update); builder.newQuery(update);
@@ -551,7 +553,7 @@ public abstract class DepotRepository
* *
* @param type the type of the persistent object to be modified. * @param type the type of the persistent object to be modified.
* @param primaryKey the primary key to match in the update. * @param primaryKey the primary key to match in the update.
* @param updates an mapping from the names of the fields/columns ti the values to be assigned. * @param updates an mapping from the columns to the values to be assigned.
* *
* @return the number of rows modified by this action. * @return the number of rows modified by this action.
* *
@@ -560,12 +562,12 @@ public abstract class DepotRepository
* @throws DatabaseException if any problem is encountered communicating with the database. * @throws DatabaseException if any problem is encountered communicating with the database.
*/ */
protected <T extends PersistentRecord> int updatePartial ( protected <T extends PersistentRecord> int updatePartial (
Class<T> type, Comparable<?> primaryKey, Map<String,Object> updates) Class<T> type, Comparable<?> primaryKey, Map<ColumnExp,Object> updates)
throws DatabaseException throws DatabaseException
{ {
Object[] fieldsValues = new Object[updates.size()*2]; Object[] fieldsValues = new Object[updates.size()*2];
int idx = 0; int idx = 0;
for (Map.Entry<String,Object> entry : updates.entrySet()) { for (Map.Entry<ColumnExp,Object> entry : updates.entrySet()) {
fieldsValues[idx++] = entry.getKey(); fieldsValues[idx++] = entry.getKey();
fieldsValues[idx++] = entry.getValue(); fieldsValues[idx++] = entry.getValue();
} }
@@ -577,7 +579,7 @@ public abstract class DepotRepository
* *
* @param type the type of the persistent object to be modified. * @param type the type of the persistent object to be modified.
* @param primaryKey the primary key to match in the update. * @param primaryKey the primary key to match in the update.
* @param fieldsValues an array containing the names of the fields/columns and the values to be * @param fieldsValues an array containing the columns (as ColumnExp) and the values to be
* assigned, in key, value, key, value, etc. order. * assigned, in key, value, key, value, etc. order.
* *
* @return the number of rows modified by this action. * @return the number of rows modified by this action.
@@ -598,7 +600,7 @@ public abstract class DepotRepository
* primary key. * primary key.
* *
* @param type the type of the persistent object to be modified. * @param type the type of the persistent object to be modified.
* @param fieldsValues an array containing the names of the fields/columns and the values to be * @param fieldsValues an array containing the columns (as ColumnExp) and the values to be
* assigned, in key, value, key, value, etc. order. * assigned, in key, value, key, value, etc. order.
* *
* @return the number of rows modified by this action. * @return the number of rows modified by this action.
@@ -608,7 +610,7 @@ public abstract class DepotRepository
* @throws DatabaseException if any problem is encountered communicating with the database. * @throws DatabaseException if any problem is encountered communicating with the database.
*/ */
protected <T extends PersistentRecord> int updatePartial ( protected <T extends PersistentRecord> int updatePartial (
Class<T> type, String ix1, Comparable<?> val1, String ix2, Comparable<?> val2, Class<T> type, ColumnExp ix1, Comparable<?> val1, ColumnExp ix2, Comparable<?> val2,
Object... fieldsValues) Object... fieldsValues)
throws DatabaseException throws DatabaseException
{ {
@@ -620,7 +622,7 @@ public abstract class DepotRepository
* primary key. * primary key.
* *
* @param type the type of the persistent object to be modified. * @param type the type of the persistent object to be modified.
* @param fieldsValues an array containing the names of the fields/columns and the values to be * @param fieldsValues an array containing the columns (as ColumnExp) and the values to be
* assigned, in key, value, key, value, etc. order. * assigned, in key, value, key, value, etc. order.
* *
* @return the number of rows modified by this action. * @return the number of rows modified by this action.
@@ -630,8 +632,8 @@ public abstract class DepotRepository
* @throws DatabaseException if any problem is encountered communicating with the database. * @throws DatabaseException if any problem is encountered communicating with the database.
*/ */
protected <T extends PersistentRecord> int updatePartial ( protected <T extends PersistentRecord> int updatePartial (
Class<T> type, String ix1, Comparable<?> val1, String ix2, Comparable<?> val2, Class<T> type, ColumnExp ix1, Comparable<?> val1, ColumnExp ix2, Comparable<?> val2,
String ix3, Comparable<?> val3, Object... fieldsValues) ColumnExp ix3, Comparable<?> val3, Object... fieldsValues)
throws DatabaseException throws DatabaseException
{ {
return updatePartial(new Key<T>(type, ix1, val1, ix2, val2, ix3, val3), fieldsValues); return updatePartial(new Key<T>(type, ix1, val1, ix2, val2, ix3, val3), fieldsValues);
@@ -641,7 +643,7 @@ public abstract class DepotRepository
* Updates the specified columns for all persistent objects matching the supplied key. * Updates the specified columns for all persistent objects matching the supplied key.
* *
* @param key the key for the persistent objects to be modified. * @param key the key for the persistent objects to be modified.
* @param fieldsValues an array containing the names of the fields/columns and the values to be * @param fieldsValues an array containing the columns (as ColumnExp) and the values to be
* assigned, in key, value, key, value, etc. order. * assigned, in key, value, key, value, etc. order.
* *
* @return the number of rows modified by this action. * @return the number of rows modified by this action.
@@ -666,7 +668,7 @@ public abstract class DepotRepository
* @param key the key to match in the update. * @param key the key to match in the update.
* @param invalidator a cache invalidator that will be run prior to the update to flush the * @param invalidator a cache invalidator that will be run prior to the update to flush the
* relevant persistent objects from the cache, or null if no invalidation is needed. * relevant persistent objects from the cache, or null if no invalidation is needed.
* @param fieldsValues an array containing the names of the fields/columns and the values to be * @param fieldsValues an array containing the columns (as ColumnExp) and the values to be
* assigned, in key, value, key, value, etc. order. * assigned, in key, value, key, value, etc. order.
* *
* @return the number of rows modified by this action. * @return the number of rows modified by this action.
@@ -688,7 +690,7 @@ public abstract class DepotRepository
final String[] fields = new String[fieldsValues.length/2]; final String[] fields = new String[fieldsValues.length/2];
final SQLExpression[] values = new SQLExpression[fields.length]; final SQLExpression[] values = new SQLExpression[fields.length];
for (int ii = 0, idx = 0; ii < fields.length; ii++) { for (int ii = 0, idx = 0; ii < fields.length; ii++) {
fields[ii] = (String)fieldsValues[idx++]; fields[ii] = ((ColumnExp)fieldsValues[idx++]).name;
values[ii] = new ValueExp(fieldsValues[idx++]); values[ii] = new ValueExp(fieldsValues[idx++]);
} }
UpdateClause<T> update = new UpdateClause<T>(type, key, fields, values); UpdateClause<T> update = new UpdateClause<T>(type, key, fields, values);
@@ -720,8 +722,7 @@ public abstract class DepotRepository
* *
* @param type the type of the persistent object to be modified. * @param type the type of the persistent object to be modified.
* @param primaryKey the key to match in the update. * @param primaryKey the key to match in the update.
* @param fieldsValues an array containing the names of the fields/columns and the values to be * @param fieldsValues a map containing the columns and the values to be assigned.
* assigned, in key, literal value, key, literal value, etc. order.
* *
* @return the number of rows modified by this action. * @return the number of rows modified by this action.
* *
@@ -730,7 +731,7 @@ public abstract class DepotRepository
* @throws DatabaseException if any problem is encountered communicating with the database. * @throws DatabaseException if any problem is encountered communicating with the database.
*/ */
protected <T extends PersistentRecord> int updateLiteral ( protected <T extends PersistentRecord> int updateLiteral (
Class<T> type, Comparable<?> primaryKey, Map<String, SQLExpression> fieldsValues) Class<T> type, Comparable<?> primaryKey, Map<ColumnExp, SQLExpression> fieldsValues)
throws DatabaseException throws DatabaseException
{ {
Key<T> key = _ctx.getMarshaller(type).makePrimaryKey(primaryKey); Key<T> key = _ctx.getMarshaller(type).makePrimaryKey(primaryKey);
@@ -748,8 +749,7 @@ public abstract class DepotRepository
* </pre> * </pre>
* *
* @param type the type of the persistent object to be modified. * @param type the type of the persistent object to be modified.
* @param fieldsValues an array containing the names of the fields/columns and the values to be * @param fieldsValues a map containing the columns and the values to be assigned.
* assigned, in key, literal value, key, literal value, etc. order.
* *
* @return the number of rows modified by this action. * @return the number of rows modified by this action.
* *
@@ -758,8 +758,8 @@ public abstract class DepotRepository
* @throws DatabaseException if any problem is encountered communicating with the database. * @throws DatabaseException if any problem is encountered communicating with the database.
*/ */
protected <T extends PersistentRecord> int updateLiteral ( protected <T extends PersistentRecord> int updateLiteral (
Class<T> type, String ix1, Comparable<?> val1, String ix2, Comparable<?> val2, Class<T> type, ColumnExp ix1, Comparable<?> val1, ColumnExp ix2, Comparable<?> val2,
Map<String, SQLExpression> fieldsValues) Map<ColumnExp, SQLExpression> fieldsValues)
throws DatabaseException throws DatabaseException
{ {
Key<T> key = new Key<T>(type, ix1, val1, ix2, val2); Key<T> key = new Key<T>(type, ix1, val1, ix2, val2);
@@ -787,8 +787,8 @@ public abstract class DepotRepository
* @throws DatabaseException if any problem is encountered communicating with the database. * @throws DatabaseException if any problem is encountered communicating with the database.
*/ */
protected <T extends PersistentRecord> int updateLiteral ( protected <T extends PersistentRecord> int updateLiteral (
Class<T> type, String ix1, Comparable<?> val1, String ix2, Comparable<?> val2, Class<T> type, ColumnExp ix1, Comparable<?> val1, ColumnExp ix2, Comparable<?> val2,
String ix3, Comparable<?> val3, Map<String, SQLExpression> fieldsValues) ColumnExp ix3, Comparable<?> val3, Map<ColumnExp, SQLExpression> fieldsValues)
throws DatabaseException throws DatabaseException
{ {
Key<T> key = new Key<T>(type, ix1, val1, ix2, val2, ix3, val3); Key<T> key = new Key<T>(type, ix1, val1, ix2, val2, ix3, val3);
@@ -818,7 +818,7 @@ public abstract class DepotRepository
*/ */
protected <T extends PersistentRecord> int updateLiteral ( protected <T extends PersistentRecord> int updateLiteral (
Class<T> type, final WhereClause key, CacheInvalidator invalidator, Class<T> type, final WhereClause key, CacheInvalidator invalidator,
Map<String, SQLExpression> fieldsValues) Map<ColumnExp, SQLExpression> fieldsValues)
throws DatabaseException throws DatabaseException
{ {
requireNotComputed(type, "updateLiteral"); requireNotComputed(type, "updateLiteral");
@@ -832,8 +832,8 @@ public abstract class DepotRepository
final String[] fields = new String[fieldsValues.size()]; final String[] fields = new String[fieldsValues.size()];
final SQLExpression[] values = new SQLExpression[fields.length]; final SQLExpression[] values = new SQLExpression[fields.length];
int ii = 0; int ii = 0;
for (Map.Entry<String, SQLExpression> entry : fieldsValues.entrySet()) { for (Map.Entry<ColumnExp, SQLExpression> entry : fieldsValues.entrySet()) {
fields[ii] = entry.getKey(); fields[ii] = entry.getKey().name;
values[ii] = entry.getValue(); values[ii] = entry.getValue();
ii ++; ii ++;
} }
@@ -907,7 +907,7 @@ public abstract class DepotRepository
updateKey(marsh.getPrimaryKey(_result, false)); updateKey(marsh.getPrimaryKey(_result, false));
} }
builder.newQuery(new InsertClause<T>(pClass, _result, identityFields)); builder.newQuery(new InsertClause(pClass, _result, identityFields));
stmt = builder.prepare(conn); stmt = builder.prepare(conn);
int mods = stmt.executeUpdate(); int mods = stmt.executeUpdate();
@@ -1012,7 +1012,7 @@ public abstract class DepotRepository
} }
where.validateQueryType(type); // and another where.validateQueryType(type); // and another
DeleteClause<T> delete = new DeleteClause<T>(type, where); DeleteClause delete = new DeleteClause(type, where);
final SQLBuilder builder = _ctx.getSQLBuilder(DepotTypes.getDepotTypes(_ctx, delete)); final SQLBuilder builder = _ctx.getSQLBuilder(DepotTypes.getDepotTypes(_ctx, delete));
builder.newQuery(delete); builder.newQuery(delete);
+23 -12
View File
@@ -28,6 +28,7 @@ import java.util.Map;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.samskivert.depot.clause.WhereClause; import com.samskivert.depot.clause.WhereClause;
import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.expression.SQLExpression; import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.DepotMarshaller; import com.samskivert.depot.impl.DepotMarshaller;
import com.samskivert.depot.impl.DepotUtil; import com.samskivert.depot.impl.DepotUtil;
@@ -71,33 +72,42 @@ public class Key<T extends PersistentRecord> extends WhereClause
/** /**
* Constructs a new single-column {@code Key} with the given value. * Constructs a new single-column {@code Key} with the given value.
*/ */
public Key (Class<T> pClass, String ix, Comparable<?> val) public Key (Class<T> pClass, ColumnExp ix, Comparable<?> val)
{ {
this(pClass, new String[] { ix }, new Comparable[] { val }); this(pClass, new ColumnExp[] { ix }, new Comparable[] { val });
} }
/** /**
* Constructs a new two-column {@code Key} with the given values. * Constructs a new two-column {@code Key} with the given values.
*/ */
public Key (Class<T> pClass, String ix1, Comparable<?> val1, public Key (Class<T> pClass, ColumnExp ix1, Comparable<?> val1,
String ix2, Comparable<?> val2) ColumnExp ix2, Comparable<?> val2)
{ {
this(pClass, new String[] { ix1, ix2 }, new Comparable[] { val1, val2 }); this(pClass, new ColumnExp[] { ix1, ix2 }, new Comparable[] { val1, val2 });
} }
/** /**
* Constructs a new three-column {@code Key} with the given values. * Constructs a new three-column {@code Key} with the given values.
*/ */
public Key (Class<T> pClass, String ix1, Comparable<?> val1, public Key (Class<T> pClass, ColumnExp ix1, Comparable<?> val1,
String ix2, Comparable<?> val2, String ix3, Comparable<?> val3) ColumnExp ix2, Comparable<?> val2, ColumnExp ix3, Comparable<?> val3)
{ {
this(pClass, new String[] { ix1, ix2, ix3 }, new Comparable[] { val1, val2, val3 }); this(pClass, new ColumnExp[] { ix1, ix2, ix3 }, new Comparable[] { val1, val2, val3 });
}
/**
* TEMP: legacy foo bar!
*/
public Key (Class<T> pClass, String[] fields, Comparable<?>[] values)
{
_pClass = pClass;
_values = values;
} }
/** /**
* Constructs a new multi-column {@code Key} with the given values. * Constructs a new multi-column {@code Key} with the given values.
*/ */
public Key (Class<T> pClass, String[] fields, Comparable<?>[] values) public Key (Class<T> pClass, ColumnExp[] fields, Comparable<?>[] values)
{ {
if (fields.length != values.length) { if (fields.length != values.length) {
throw new IllegalArgumentException("Field and Value arrays must be of equal length."); throw new IllegalArgumentException("Field and Value arrays must be of equal length.");
@@ -109,7 +119,7 @@ public class Key<T extends PersistentRecord> extends WhereClause
// build a local map of field name -> field value // build a local map of field name -> field value
Map<String, Comparable<?>> map = Maps.newHashMap(); Map<String, Comparable<?>> map = Maps.newHashMap();
for (int i = 0; i < fields.length; i ++) { for (int i = 0; i < fields.length; i ++) {
map.put(fields[i], values[i]); map.put(fields[i].name, values[i]);
} }
// look up the cached primary key fields for this object // look up the cached primary key fields for this object
@@ -138,9 +148,10 @@ public class Key<T extends PersistentRecord> extends WhereClause
} }
/** /**
* Used to create a key when you know you have the canonical values array. * Used to create a key when you know you have the canonical values array. Don't call this
* unless you know what you're doing!
*/ */
protected Key (Class<T> pClass, Comparable<?>[] values) public Key (Class<T> pClass, Comparable<?>[] values)
{ {
_pClass = pClass; _pClass = pClass;
_values = values; _values = values;
+3 -1
View File
@@ -33,6 +33,7 @@ import com.google.common.base.Function;
import com.samskivert.util.StringUtil; import com.samskivert.util.StringUtil;
import com.samskivert.depot.clause.WhereClause; import com.samskivert.depot.clause.WhereClause;
import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.expression.LiteralExp; import com.samskivert.depot.expression.LiteralExp;
import com.samskivert.depot.expression.SQLExpression; import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.DepotUtil; import com.samskivert.depot.impl.DepotUtil;
@@ -171,7 +172,8 @@ public abstract class KeySet<T extends PersistentRecord> extends WhereClause
// from WhereClause // from WhereClause
public SQLExpression getWhereExpression () { public SQLExpression getWhereExpression () {
// Single-column keys result in the compact IN(keyVal1, keyVal2, ...) // Single-column keys result in the compact IN(keyVal1, keyVal2, ...)
return new Conditionals.In(_pClass, DepotUtil.getKeyFields(_pClass)[0], _keys); ColumnExp column = new ColumnExp(_pClass, DepotUtil.getKeyFields(_pClass)[0]);
return new Conditionals.In(column, _keys);
} }
// from Iterable<Key<T>> // from Iterable<Key<T>>
@@ -22,6 +22,8 @@ package com.samskivert.depot;
import java.io.Serializable; import java.io.Serializable;
import com.samskivert.depot.expression.ColumnExp;
/** /**
* The base class for all persistent records used in Depot. Persistent records must be cloneable * The base class for all persistent records used in Depot. Persistent records must be cloneable
* and serializable; this class is used to enforce those requirements. * and serializable; this class is used to enforce those requirements.
@@ -29,6 +31,10 @@ import java.io.Serializable;
public class PersistentRecord public class PersistentRecord
implements Cloneable, Serializable implements Cloneable, Serializable
{ {
// AUTO-GENERATED: FIELDS START
public static final Class<PersistentRecord> _R = PersistentRecord.class;
// AUTO-GENERATED: FIELDS END
@Override // from Object @Override // from Object
public PersistentRecord clone () public PersistentRecord clone ()
{ {
@@ -38,4 +44,13 @@ public class PersistentRecord
throw new RuntimeException(cnse); // this should never happen throw new RuntimeException(cnse); // this should never happen
} }
} }
/**
* Creates a column expression for this class with the specified field name. Used by the
* autogenerated column expression constants.
*/
protected static ColumnExp colexp (Class<? extends PersistentRecord> clazz, String fieldName)
{
return new ColumnExp(clazz, fieldName);
}
} }
@@ -26,7 +26,9 @@ import java.util.Map;
import com.samskivert.jdbc.ColumnDefinition; import com.samskivert.jdbc.ColumnDefinition;
import com.samskivert.jdbc.DatabaseLiaison; import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.depot.annotation.Column; import com.samskivert.depot.annotation.Column;
import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.impl.FieldMarshaller; import com.samskivert.depot.impl.FieldMarshaller;
import com.samskivert.depot.impl.Modifier; import com.samskivert.depot.impl.Modifier;
@@ -74,10 +76,10 @@ public abstract class SchemaMigration extends Modifier
*/ */
public static class Rename extends SchemaMigration public static class Rename extends SchemaMigration
{ {
public Rename (int targetVersion, String oldColumnName, String newColumnName) { public Rename (int targetVersion, String oldColumnName, ColumnExp newColumn) {
super(targetVersion); super(targetVersion);
_oldColumnName = oldColumnName; _oldColumnName = oldColumnName;
_newColumnName = newColumnName; _newColumnName = newColumn.name;
} }
@Override public boolean runBeforeDefault () { @Override public boolean runBeforeDefault () {
@@ -132,9 +134,9 @@ public abstract class SchemaMigration extends Modifier
*/ */
public static class Retype extends SchemaMigration public static class Retype extends SchemaMigration
{ {
public Retype (int targetVersion, String fieldName) { public Retype (int targetVersion, ColumnExp column) {
super(targetVersion); super(targetVersion);
_fieldName = fieldName; _fieldName = column.name;
} }
@Override public boolean runBeforeDefault () { @Override public boolean runBeforeDefault () {
@@ -170,9 +172,9 @@ public abstract class SchemaMigration extends Modifier
*/ */
public static class Add extends SchemaMigration public static class Add extends SchemaMigration
{ {
public Add (int targetVersion, String fieldName, String defaultValue) { public Add (int targetVersion, ColumnExp column, String defaultValue) {
super(targetVersion); super(targetVersion);
_fieldName = fieldName; _fieldName = column.name;
_defaultValue = defaultValue; _defaultValue = defaultValue;
} }
@@ -20,6 +20,7 @@
package com.samskivert.depot.annotation; package com.samskivert.depot.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
@@ -27,23 +28,22 @@ import java.lang.annotation.Target;
/** /**
* Defines an index on an entity table. * Defines an index on an entity table.
*/ */
@Target(value={}) @Target(value=ElementType.FIELD)
@Retention(value=RetentionPolicy.RUNTIME) @Retention(value=RetentionPolicy.RUNTIME)
public @interface Index public @interface Index
{ {
/** Defines the name of the index. */ /** Defines the name of the index. If this is defined, a static method must be defined on the
String name (); * record that provides the index configuration. The method must match one of the following
* two signatures:
* <pre>
* public static ColumnExp[] indexName ()
* public static List<Tuple<SQLExpression, OrderBy.Order>> indexName ()
* </pre>
* The first form will result in a simple multicolum index being created with the supplied
* columns. The second will create a function index using the supplied SQLExpressions.
*/
String name () default "";
/** Does this index enforce a uniqueness constraint? */ /** Does this index enforce a uniqueness constraint? */
boolean unique () default false; boolean unique () default false;
/** Defines the fields on which the index operates. */
String[] fields () default {};
/** Whether or not this is a complex index. If true, a static method
* must be defined on the record that declares this index of the signature:
* <pre>public static List<Tuple<SQLExpression, OrderBy.Order>> indexNameExpression ()</pre>
* which should return the index's defining expressions and whether each one is ascending
* or descending. */
boolean complex () default false;
} }
@@ -25,15 +25,16 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
/** /**
* This annotation is used to specify that a unique constraint is to be included in the * Used to specify a uniqueness constraint on a set of columns. If you want a single column to be
* generated DDL for a table. * unique, simply use {@link Column#unique}.
*/ */
@Target(value={}) @Target(value={})
@Retention(value=RetentionPolicy.RUNTIME) @Retention(value=RetentionPolicy.RUNTIME)
public @interface UniqueConstraint public @interface UniqueConstraint
{ {
/** /** The name of the index that will be created to enforce this constraint. */
* An array of the field names that make up the constraint String name ();
*/
public String[] fieldNames () default {}; /** An array of the field names that make up the constraint */
String[] fields ();
} }
@@ -32,17 +32,15 @@ import com.samskivert.util.Tuple;
/** /**
* Represents an CREATE INDEX instruction to the database. * Represents an CREATE INDEX instruction to the database.
*/ */
public class CreateIndexClause<T extends PersistentRecord> public class CreateIndexClause
implements QueryClause implements QueryClause
{ {
/** /**
* Create a new {@link CreateIndexClause} clause. The name must be unique within the * Create a new {@link CreateIndexClause} clause. The name must be unique within the relevant
* relevant database. * database.
*/ */
public CreateIndexClause ( public CreateIndexClause (Class<? extends PersistentRecord> pClass, String name, boolean unique,
Class<? extends PersistentRecord> pClass, String name, boolean unique,
List<Tuple<SQLExpression, Order>> fields) List<Tuple<SQLExpression, Order>> fields)
{ {
_pClass = pClass; _pClass = pClass;
_name = name; _name = name;
@@ -83,9 +81,7 @@ public class CreateIndexClause<T extends PersistentRecord>
} }
protected Class<? extends PersistentRecord> _pClass; protected Class<? extends PersistentRecord> _pClass;
protected String _name; protected String _name;
protected boolean _unique; protected boolean _unique;
/** The components of the index, e.g. columns or functions of columns. */ /** The components of the index, e.g. columns or functions of columns. */
@@ -28,7 +28,7 @@ import com.samskivert.depot.impl.ExpressionVisitor;
/** /**
* Builds actual SQL given a main persistent type and some {@link QueryClause} objects. * Builds actual SQL given a main persistent type and some {@link QueryClause} objects.
*/ */
public class DeleteClause<T extends PersistentRecord> implements QueryClause public class DeleteClause implements QueryClause
{ {
public DeleteClause (Class<? extends PersistentRecord> pClass, WhereClause where) public DeleteClause (Class<? extends PersistentRecord> pClass, WhereClause where)
{ {
@@ -55,6 +55,12 @@ public class FieldDefinition implements QueryClause
_definition = override; _definition = override;
} }
public FieldDefinition (ColumnExp field, SQLExpression override)
{
_field = field.name;
_definition = override;
}
/** /**
* The field we're defining. The Query object uses this for indexing. * The field we're defining. The Query object uses this for indexing.
*/ */
@@ -29,10 +29,10 @@ import com.samskivert.depot.impl.ExpressionVisitor;
/** /**
* Builds actual SQL given a main persistent type and some {@link QueryClause} objects. * Builds actual SQL given a main persistent type and some {@link QueryClause} objects.
*/ */
public class InsertClause<T extends PersistentRecord> implements QueryClause public class InsertClause implements QueryClause
{ {
public InsertClause ( public InsertClause (Class<? extends PersistentRecord> pClass, Object pojo,
Class<? extends PersistentRecord> pClass, Object pojo, Set<String> identityFields) Set<String> identityFields)
{ {
_pClass = pClass; _pClass = pClass;
_pojo = pojo; _pojo = pojo;
@@ -37,13 +37,6 @@ public class Join implements QueryClause
/** Indicates the join type to be used. The default is INNER. */ /** Indicates the join type to be used. The default is INNER. */
public static enum Type { INNER, LEFT_OUTER, RIGHT_OUTER }; public static enum Type { INNER, LEFT_OUTER, RIGHT_OUTER };
public Join (Class<? extends PersistentRecord> pClass, String pCol,
Class<? extends PersistentRecord> joinClass, String jCol)
{
_joinClass = joinClass;
_joinCondition = new Equals(new ColumnExp(joinClass, jCol), new ColumnExp(pClass, pCol));
}
public Join (ColumnExp primary, ColumnExp join) public Join (ColumnExp primary, ColumnExp join)
{ {
_joinClass = join.getPersistentClass(); _joinClass = join.getPersistentClass();
@@ -23,6 +23,7 @@ package com.samskivert.depot.clause;
import java.util.Collection; import java.util.Collection;
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.expression.SQLExpression;
import com.samskivert.depot.impl.ExpressionVisitor; import com.samskivert.depot.impl.ExpressionVisitor;
@@ -31,11 +31,24 @@ import com.samskivert.depot.impl.ExpressionVisitor;
public class ColumnExp public class ColumnExp
implements SQLExpression implements SQLExpression
{ {
/** The name of the column we reference. */
public final String name;
/** Converts an array of column expressions to an array of just the bare names. */
public static String[] toNames (ColumnExp[] columns)
{
String[] names = new String[columns.length];
for (int ii = 0; ii < names.length; ii++) {
names[ii] = columns[ii].name;
}
return names;
}
public ColumnExp (Class<? extends PersistentRecord> pClass, String field) public ColumnExp (Class<? extends PersistentRecord> pClass, String field)
{ {
super(); super();
_pClass = pClass; _pClass = pClass;
_pField = field; this.name = field;
} }
// from SQLExpression // from SQLExpression
@@ -57,18 +70,15 @@ public class ColumnExp
public String getField () public String getField ()
{ {
return _pField; return name;
} }
@Override // from Object @Override // from Object
public String toString () public String toString ()
{ {
return "\"" + _pField + "\""; // TODO: qualify with record name and be uber verbose? return "\"" + name + "\""; // TODO: qualify with record name and be uber verbose?
} }
/** The table that hosts the column we reference, or null. */ /** The table that hosts the column we reference, or null. */
protected final Class<? extends PersistentRecord> _pClass; protected final Class<? extends PersistentRecord> _pClass;
/** The name of the column we reference. */
protected final String _pField;
} }
@@ -248,7 +248,7 @@ public class BindVisitor implements ExpressionVisitor
updateClause.getWhereClause().accept(this); updateClause.getWhereClause().accept(this);
} }
public void visit (InsertClause<? extends PersistentRecord> insertClause) public void visit (InsertClause insertClause)
{ {
DepotMarshaller<?> marsh = _types.getMarshaller(insertClause.getPersistentClass()); DepotMarshaller<?> marsh = _types.getMarshaller(insertClause.getPersistentClass());
Object pojo = insertClause.getPojo(); Object pojo = insertClause.getPojo();
@@ -266,12 +266,12 @@ public class BindVisitor implements ExpressionVisitor
} }
} }
public void visit (DeleteClause<? extends PersistentRecord> deleteClause) public void visit (DeleteClause deleteClause)
{ {
deleteClause.getWhereClause().accept(this); deleteClause.getWhereClause().accept(this);
} }
public void visit (CreateIndexClause<? extends PersistentRecord> createIndexClause) public void visit (CreateIndexClause createIndexClause)
{ {
for (Tuple<SQLExpression, Order> field : createIndexClause.getFields()) { for (Tuple<SQLExpression, Order> field : createIndexClause.getFields()) {
field.left.accept(this); field.left.accept(this);
@@ -430,7 +430,7 @@ public abstract class BuildVisitor implements ExpressionVisitor
updateClause.getWhereClause().accept(this); updateClause.getWhereClause().accept(this);
} }
public void visit (DeleteClause<? extends PersistentRecord> deleteClause) public void visit (DeleteClause deleteClause)
{ {
_builder.append("delete from "); _builder.append("delete from ");
appendTableName(deleteClause.getPersistentClass()); appendTableName(deleteClause.getPersistentClass());
@@ -440,7 +440,7 @@ public abstract class BuildVisitor implements ExpressionVisitor
deleteClause.getWhereClause().accept(this); deleteClause.getWhereClause().accept(this);
} }
public void visit (InsertClause<? extends PersistentRecord> insertClause) public void visit (InsertClause insertClause)
{ {
Class<? extends PersistentRecord> pClass = insertClause.getPersistentClass(); Class<? extends PersistentRecord> pClass = insertClause.getPersistentClass();
DepotMarshaller<?> marsh = _types.getMarshaller(pClass); DepotMarshaller<?> marsh = _types.getMarshaller(pClass);
@@ -480,7 +480,7 @@ public abstract class BuildVisitor implements ExpressionVisitor
_builder.append(")"); _builder.append(")");
} }
public void visit (CreateIndexClause<? extends PersistentRecord> createIndexClause) public void visit (CreateIndexClause createIndexClause)
{ {
_builder.append("create "); _builder.append("create ");
if (createIndexClause.isUnique()) { if (createIndexClause.isUnique()) {
@@ -31,7 +31,8 @@ import java.sql.ResultSetMetaData;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.ArrayList; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@@ -46,6 +47,7 @@ import com.samskivert.depot.PersistenceContext;
import com.samskivert.depot.PersistentRecord; import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.SchemaMigration; import com.samskivert.depot.SchemaMigration;
import com.samskivert.depot.Stats; import com.samskivert.depot.Stats;
import com.samskivert.depot.annotation.Column;
import com.samskivert.depot.annotation.Computed; import com.samskivert.depot.annotation.Computed;
import com.samskivert.depot.annotation.Entity; import com.samskivert.depot.annotation.Entity;
import com.samskivert.depot.annotation.FullTextIndex; import com.samskivert.depot.annotation.FullTextIndex;
@@ -194,6 +196,20 @@ public class DepotMarshaller<T extends PersistentRecord>
} }
} }
// check whether this field is indexed
Index index = field.getAnnotation(Index.class);
if (index != null) {
String ixName = index.name().equals("") ? field.getName() + "Index" : index.name();
_indexes.add(buildIndex(ixName, index.unique(),
new ColumnExp(_pClass, field.getName())));
}
// if this column is marked as unique, that also means we create an index
Column column = field.getAnnotation(Column.class);
if (column != null && column.unique()) {
_indexes.add(buildIndex(field.getName() + "Index", true,
new ColumnExp(_pClass, field.getName())));
}
} }
// if we did not find a schema version field, freak out (but not for computed records, for // if we did not find a schema version field, freak out (but not for computed records, for
@@ -207,32 +223,30 @@ public class DepotMarshaller<T extends PersistentRecord>
_allFields = fields.toArray(new String[fields.size()]); _allFields = fields.toArray(new String[fields.size()]);
// now check for @Entity annotations on the entire superclass chain // now check for @Entity annotations on the entire superclass chain
Class<?> iterClass = pClass; Class<? extends PersistentRecord> iterClass = pClass.asSubclass(PersistentRecord.class);
do { do {
entity = iterClass.getAnnotation(Entity.class); entity = iterClass.getAnnotation(Entity.class);
if (entity != null) { if (entity != null) {
// add any indices needed for uniqueness constraints
for (UniqueConstraint constraint : entity.uniqueConstraints()) { for (UniqueConstraint constraint : entity.uniqueConstraints()) {
String[] conFields = constraint.fieldNames(); List<ColumnExp> colExps = Lists.newArrayList();
Set<String> colSet = Sets.newHashSet(); for (String field : constraint.fields()) {
for (int ii = 0; ii < conFields.length; ii ++) { FieldMarshaller<?> fm = _fields.get(field);
FieldMarshaller<?> fm = _fields.get(conFields[ii]);
if (fm == null) { if (fm == null) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Unknown unique constraint field: " + conFields[ii]); "Unknown unique constraint field: " + field);
} }
colSet.add(fm.getColumnName()); colExps.add(new ColumnExp(_pClass, field));
} }
_uniqueConstraints.add(colSet); _indexes.add(buildIndex(constraint.name(), true, colExps));
} }
// add any explicit multicolumn or complex indices
for (Index index : entity.indices()) { for (Index index : entity.indices()) {
if (_indexes.containsKey(index.name())) { _indexes.add(buildIndex(index.name(), index.unique()));
continue;
}
_indexes.put(index.name(), index);
} }
// if there are FTS indexes in the Table, map those out here for future use // note any FTS indices
for (FullTextIndex fti : entity.fullTextIndices()) { for (FullTextIndex fti : entity.fullTextIndices()) {
if (_fullTextIndexes.containsKey(fti.name())) { if (_fullTextIndexes.containsKey(fti.name())) {
continue; continue;
@@ -241,7 +255,7 @@ public class DepotMarshaller<T extends PersistentRecord>
} }
} }
iterClass = iterClass.getSuperclass(); iterClass = iterClass.getSuperclass().asSubclass(PersistentRecord.class);
} while (PersistentRecord.class.isAssignableFrom(iterClass) && } while (PersistentRecord.class.isAssignableFrom(iterClass) &&
!PersistentRecord.class.equals(iterClass)); !PersistentRecord.class.equals(iterClass));
@@ -385,7 +399,7 @@ public class DepotMarshaller<T extends PersistentRecord>
// make sure the keys are all null or all non-null // make sure the keys are all null or all non-null
if (nulls == 0) { if (nulls == 0) {
return makePrimaryKey(values); return new Key<T>(_pClass, values);
} else if (nulls == values.length) { } else if (nulls == values.length) {
return null; return null;
} else if (nulls == zeros) { } else if (nulls == zeros) {
@@ -394,7 +408,7 @@ public class DepotMarshaller<T extends PersistentRecord>
// this is a compromise that allows sensible things like (id=99, type=0) but // this is a compromise that allows sensible things like (id=99, type=0) but
// unfortunately also allows less sensible things like (id=0, type=5) while // unfortunately also allows less sensible things like (id=0, type=5) while
// continuing to disallow the dangerous (id=0) // continuing to disallow the dangerous (id=0)
return makePrimaryKey(values); return new Key<T>(_pClass, values);
} }
// throw an informative error message // throw an informative error message
@@ -413,19 +427,15 @@ public class DepotMarshaller<T extends PersistentRecord>
/** /**
* Creates a primary key record for the type of object handled by this marshaller, using the * Creates a primary key record for the type of object handled by this marshaller, using the
* supplied primary key value. * supplied primary key value. This is only allowed for records with single column keys.
*/ */
public Key<T> makePrimaryKey (Comparable<?>... values) public Key<T> makePrimaryKey (Comparable<?> value)
{ {
if (!hasPrimaryKey()) { if (!hasPrimaryKey()) {
throw new UnsupportedOperationException( throw new UnsupportedOperationException(
getClass().getName() + " does not define a primary key"); getClass().getName() + " does not define a primary key");
} }
String[] fields = new String[_pkColumns.size()]; return new Key<T>(_pClass, new Comparable<?>[] { value });
for (int ii = 0; ii < _pkColumns.size(); ii++) {
fields[ii] = _pkColumns.get(ii).getField().getName();
}
return new Key<T>(_pClass, fields, values);
} }
/** /**
@@ -448,7 +458,7 @@ public class DepotMarshaller<T extends PersistentRecord>
} }
values[ii] = (Comparable<?>) keyValue; values[ii] = (Comparable<?>) keyValue;
} }
return makePrimaryKey(values); return new Key<T>(_pClass, values);
} }
/** /**
@@ -703,12 +713,6 @@ public class DepotMarshaller<T extends PersistentRecord>
{ {
log.info("Creating initial table '" + getTableName() + "'."); log.info("Creating initial table '" + getTableName() + "'.");
final String[][] uniqueConCols = new String[_uniqueConstraints.size()][];
int kk = 0;
for (Set<String> colSet : _uniqueConstraints) {
uniqueConCols[kk++] = colSet.toArray(new String[colSet.size()]);
}
final Iterable<Index> indexen = _indexes.values();
ctx.invoke(new Modifier() { ctx.invoke(new Modifier() {
@Override @Override
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException { protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
@@ -722,14 +726,11 @@ public class DepotMarshaller<T extends PersistentRecord>
} }
liaison.createTableIfMissing( liaison.createTableIfMissing(
conn, getTableName(), fieldsToColumns(_columnFields), conn, getTableName(), fieldsToColumns(_columnFields),
declarations, uniqueConCols, primaryKeyColumns); declarations, null, primaryKeyColumns);
// add its indexen // add its indexen
for (Index index : indexen) { for (CreateIndexClause iclause : _indexes) {
String ixName = getTableName() + "_" + index.name(); execute(conn, builder, iclause);
execute(conn, builder, index.complex() ?
buildComplexIndex(ixName, index.unique(), index.name()) :
buildSimpleIndex(ixName, index.unique(), index.fields()));
} }
// create our value generators // create our value generators
@@ -752,7 +753,6 @@ public class DepotMarshaller<T extends PersistentRecord>
{ {
if (builder.newQuery(clause)) { if (builder.newQuery(clause)) {
PreparedStatement stmt = builder.prepare(conn); PreparedStatement stmt = builder.prepare(conn);
try { try {
return stmt.executeUpdate(); return stmt.executeUpdate();
} finally { } finally {
@@ -783,9 +783,6 @@ public class DepotMarshaller<T extends PersistentRecord>
metaData = TableMetaData.load(ctx, getTableName()); metaData = TableMetaData.load(ctx, getTableName());
} }
// this is a little silly, but we need a copy for name disambiguation later
Set<String> indicesCopy = Sets.newHashSet(metaData.indexColumns.keySet());
// figure out which columns we have in the table now, so that when all is said and done we // figure out which columns we have in the table now, so that when all is said and done we
// can see what new columns we have in the table and run the creation code for any value // can see what new columns we have in the table and run the creation code for any value
// generators that are defined on those columns (we can't just track the columns we add in // generators that are defined on those columns (we can't just track the columns we add in
@@ -854,57 +851,17 @@ public class DepotMarshaller<T extends PersistentRecord>
} }
// add any named indices that exist on the record but not yet on the table // add any named indices that exist on the record but not yet on the table
for (final Index index : _indexes.values()) { for (final CreateIndexClause iclause : _indexes) {
final String ixName = getTableName() + "_" + index.name(); if (metaData.indexColumns.containsKey(iclause.getName())) {
if (metaData.indexColumns.containsKey(ixName)) { metaData.indexColumns.remove(iclause.getName()); // this index already exists
// this index already exists
metaData.indexColumns.remove(ixName);
continue; continue;
} }
// but this is a new, named index, so we create it // but this is a new, named index, so we create it
log.info("Creating new index: " + ixName); log.info("Creating new index: " + iclause.getName());
ctx.invoke(new Modifier() { ctx.invoke(new Modifier() {
@Override @Override
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException { protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
execute(conn, builder, index.complex() ? execute(conn, builder, iclause);
buildComplexIndex(ixName, index.unique(), index.name()) :
buildSimpleIndex(ixName, index.unique(), index.fields()));
return 0;
}
});
}
// now check if there are any @Entity(uniqueConstraints) that need to be created
Set<Set<String>> uniqueIndices = Sets.newHashSet(metaData.indexColumns.values());
// unique constraints are unordered and may be unnamed, so we view them only as column sets
for (Set<String> colSet : _uniqueConstraints) {
if (uniqueIndices.contains(colSet)) {
// the table already contains precisely this column set
continue;
}
// else build the new constraint; we'll name it after one of its columns, adding _N
// to resolve any possible ambiguities, because using all the column names in the
// index name may exceed the maximum length of an SQL identifier
String indexName = colSet.iterator().next();
if (indicesCopy.contains(indexName)) {
int num = 1;
indexName += "_";
while (indicesCopy.contains(indexName + num)) {
num ++;
}
indexName += num;
}
final String[] colArr = colSet.toArray(new String[colSet.size()]);
final String fName = indexName;
ctx.invoke(new Modifier() {
@Override
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
if (!liaison.tableContainsIndex(conn, getTableName(), fName)) {
execute(conn, builder, buildSimpleIndex(fName, true, colArr));
}
return 0; return 0;
} }
}); });
@@ -981,57 +938,49 @@ public class DepotMarshaller<T extends PersistentRecord>
return metaData; return metaData;
} }
private CreateIndexClause<T> buildComplexIndex (String ixName, boolean unique, String localIxName) protected CreateIndexClause buildIndex (String name, boolean unique)
throws SQLException
{ {
List<Tuple<SQLExpression, Order>> definition;
String methName = localIxName + "Definition";
Method method; Method method;
try { try {
method = _pClass.getMethod(methName); method = _pClass.getMethod(name);
} catch (NoSuchMethodException nsme) { } catch (NoSuchMethodException nsme) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Index flagged as complex, but no defining method '" + methName "Index flagged as complex, but no defining method '" + name + "' found.", nsme);
+ "' found.", nsme);
} }
Object result; Object result;
try { try {
result = method.invoke(null); return buildIndex(name, unique, method.invoke(null));
} catch (Exception e) { } catch (Exception e) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Error calling index definition method '" + methName + "'", e); "Error calling index definition method '" + name + "'", e);
}
} }
if (result instanceof SQLExpression) { protected CreateIndexClause buildIndex (String name, boolean unique, Object config)
definition = new ArrayList<Tuple<SQLExpression,Order>>();
definition.add(new Tuple<SQLExpression, Order>((SQLExpression)result, Order.ASC));
} else if (result instanceof List) {
@SuppressWarnings("unchecked")
List<Tuple<SQLExpression, Order>> def = (List<Tuple<SQLExpression, Order>>)result;
definition = def;
} else {
throw new IllegalArgumentException("Method '" + methName
+ "' must return SQLExpression or " + "List<Tuple<SQLExpression, Order>>");
}
return new CreateIndexClause<T>(_pClass, ixName, unique, definition);
}
private CreateIndexClause<T> buildSimpleIndex (String ixName, boolean unique, String[] fields)
throws SQLException
{ {
List<Tuple<SQLExpression, Order>> definition = new ArrayList<Tuple<SQLExpression,Order>>(); List<Tuple<SQLExpression, Order>> definition = Lists.newArrayList();
if (config instanceof ColumnExp) {
for (String field : fields) { definition.add(new Tuple<SQLExpression, Order>((ColumnExp)config, Order.ASC));
ColumnExp column = new ColumnExp(_pClass, field); } else if (config instanceof ColumnExp[]) {
for (ColumnExp column : (ColumnExp[])config) {
definition.add(new Tuple<SQLExpression, Order>(column, Order.ASC)); definition.add(new Tuple<SQLExpression, Order>(column, Order.ASC));
} }
} else if (config instanceof SQLExpression) {
return new CreateIndexClause<T>(_pClass, ixName, unique, definition); definition.add(new Tuple<SQLExpression, Order>((SQLExpression)config, Order.ASC));
} else if (config instanceof Tuple) {
@SuppressWarnings("unchecked") Tuple<SQLExpression, Order> tuple =
(Tuple<SQLExpression, Order>)config;
definition.add(tuple);
} else if (config instanceof List) {
@SuppressWarnings("unchecked") List<Tuple<SQLExpression, Order>> defs =
(List<Tuple<SQLExpression, Order>>)config;
definition.addAll(defs);
} else {
throw new IllegalArgumentException(
"Method '" + name + "' must return ColumnExp[], SQLExpression or " +
"List<Tuple<SQLExpression, Order>>");
}
return new CreateIndexClause(_pClass, getTableName() + "_" + name, unique, definition);
} }
// translate an array of field names to an array of column names // translate an array of field names to an array of column names
@@ -1197,12 +1146,10 @@ public class DepotMarshaller<T extends PersistentRecord>
/** The fields of our object with directly corresponding table columns. */ /** The fields of our object with directly corresponding table columns. */
protected String[] _columnFields; protected String[] _columnFields;
/** The indexes defined in @Entity annotations for this record. */ /** The indexes defined for this record. */
protected Map<String, Index> _indexes = Maps.newHashMap(); protected List<CreateIndexClause> _indexes = Lists.newArrayList();
/** The unique constraints defined in @Entity annotations for this record. */
protected Set<Set<String>> _uniqueConstraints = Sets.newHashSet();
/** Any full text indices defined on this entity. */
protected Map<String, FullTextIndex> _fullTextIndexes = Maps.newHashMap(); protected Map<String, FullTextIndex> _fullTextIndexes = Maps.newHashMap();
/** The version of our persistent object schema as specified in the class definition. */ /** The version of our persistent object schema as specified in the class definition. */
@@ -34,19 +34,9 @@ import com.samskivert.depot.expression.ColumnExp;
public class DepotMigrationHistoryRecord extends PersistentRecord public class DepotMigrationHistoryRecord extends PersistentRecord
{ {
// AUTO-GENERATED: FIELDS START // AUTO-GENERATED: FIELDS START
/** The column identifier for the {@link #ident} field. */ public static final Class<DepotMigrationHistoryRecord> _R = DepotMigrationHistoryRecord.class;
public static final String IDENT = "ident"; public static final ColumnExp IDENT = colexp(_R, "ident");
public static final ColumnExp WHEN_COMPLETED = colexp(_R, "whenCompleted");
/** The qualified column identifier for the {@link #ident} field. */
public static final ColumnExp IDENT_C =
new ColumnExp(DepotMigrationHistoryRecord.class, IDENT);
/** The column identifier for the {@link #whenCompleted} field. */
public static final String WHEN_COMPLETED = "whenCompleted";
/** The qualified column identifier for the {@link #whenCompleted} field. */
public static final ColumnExp WHEN_COMPLETED_C =
new ColumnExp(DepotMigrationHistoryRecord.class, WHEN_COMPLETED);
// AUTO-GENERATED: FIELDS END // AUTO-GENERATED: FIELDS END
/** Our schema version. Probably not likely to change. */ /** Our schema version. Probably not likely to change. */
@@ -68,7 +58,7 @@ public class DepotMigrationHistoryRecord extends PersistentRecord
{ {
return new Key<DepotMigrationHistoryRecord>( return new Key<DepotMigrationHistoryRecord>(
DepotMigrationHistoryRecord.class, DepotMigrationHistoryRecord.class,
new String[] { IDENT }, new ColumnExp[] { IDENT },
new Comparable[] { ident }); new Comparable[] { ident });
} }
// AUTO-GENERATED: METHODS END // AUTO-GENERATED: METHODS END
@@ -81,8 +81,8 @@ public interface ExpressionVisitor
public void visit (Exists<? extends PersistentRecord> exists); public void visit (Exists<? extends PersistentRecord> exists);
public void visit (SelectClause<? extends PersistentRecord> selectClause); public void visit (SelectClause<? extends PersistentRecord> selectClause);
public void visit (UpdateClause<? extends PersistentRecord> updateClause); public void visit (UpdateClause<? extends PersistentRecord> updateClause);
public void visit (DeleteClause<? extends PersistentRecord> deleteClause); public void visit (DeleteClause deleteClause);
public void visit (InsertClause<? extends PersistentRecord> insertClause); public void visit (InsertClause insertClause);
public void visit (CreateIndexClause<? extends PersistentRecord> createIndexClause); public void visit (CreateIndexClause createIndexClause);
public void visit (DropIndexClause<? extends PersistentRecord> dropIndexClause); public void visit (DropIndexClause<? extends PersistentRecord> dropIndexClause);
} }
@@ -67,7 +67,7 @@ public class HSQLBuilder
// (lower(COL1) like '%foo%') OR (lower(COL1) like '%bar%') OR ... // (lower(COL1) like '%foo%') OR (lower(COL1) like '%bar%') OR ...
// (lower(COL2) like '%foo%') OR (lower(COL2) like '%bar%') OR ... // (lower(COL2) like '%foo%') OR (lower(COL2) like '%bar%') OR ...
// ... and so on. Not efficient, but basically functional. // ... and so on. Not efficient, but basically functional.
Class<? extends PersistentRecord> pClass = match.getPersistentRecord(); Class<? extends PersistentRecord> pClass = match.getPersistentClass();
// find the fields involved // find the fields involved
String[] fields = _types.getMarshaller(pClass). String[] fields = _types.getMarshaller(pClass).
@@ -117,7 +117,7 @@ public class HSQLBuilder
} }
@Override @Override
public void visit (CreateIndexClause<? extends PersistentRecord> createIndexClause) public void visit (CreateIndexClause createIndexClause)
{ {
for (Tuple<SQLExpression, Order> field : createIndexClause.getFields()) { for (Tuple<SQLExpression, Order> field : createIndexClause.getFields()) {
if (!(field.left instanceof ColumnExp)) { if (!(field.left instanceof ColumnExp)) {
@@ -67,7 +67,7 @@ public class MySQLBuilder
@Override public void visit (FullTextMatch match) @Override public void visit (FullTextMatch match)
{ {
_builder.append("match("); _builder.append("match(");
Class<? extends PersistentRecord> pClass = match.getPersistentRecord(); Class<? extends PersistentRecord> pClass = match.getPersistentClass();
String[] fields = String[] fields =
_types.getMarshaller(pClass).getFullTextIndex(match.getName()).fields(); _types.getMarshaller(pClass).getFullTextIndex(match.getName()).fields();
for (int ii = 0; ii < fields.length; ii ++) { for (int ii = 0; ii < fields.length; ii ++) {
@@ -79,7 +79,7 @@ public class MySQLBuilder
_builder.append(") against (? in boolean mode)"); _builder.append(") against (? in boolean mode)");
} }
@Override public void visit (DeleteClause<? extends PersistentRecord> deleteClause) @Override public void visit (DeleteClause deleteClause)
{ {
_builder.append("delete from "); _builder.append("delete from ");
appendTableName(deleteClause.getPersistentClass()); appendTableName(deleteClause.getPersistentClass());
@@ -103,7 +103,7 @@ public class MySQLBuilder
} }
@Override @Override
public void visit (CreateIndexClause<? extends PersistentRecord> createIndexClause) public void visit (CreateIndexClause createIndexClause)
{ {
for (Tuple<SQLExpression, Order> field : createIndexClause.getFields()) { for (Tuple<SQLExpression, Order> field : createIndexClause.getFields()) {
if (!(field.left instanceof ColumnExp)) { if (!(field.left instanceof ColumnExp)) {
@@ -38,16 +38,6 @@ public abstract class Conditionals
public static class IsNull public static class IsNull
implements SQLOperator implements SQLOperator
{ {
public IsNull (String pColumn)
{
this(new ColumnExp(null, pColumn));
}
public IsNull (Class<? extends PersistentRecord> pClass, String pColumn)
{
this(new ColumnExp(pClass, pColumn));
}
public IsNull (ColumnExp column) public IsNull (ColumnExp column)
{ {
_column = column; _column = column;
@@ -199,17 +189,6 @@ public abstract class Conditionals
/** The maximum number of keys allowed in an IN() clause. */ /** The maximum number of keys allowed in an IN() clause. */
public static final int MAX_KEYS = Short.MAX_VALUE; public static final int MAX_KEYS = Short.MAX_VALUE;
public In (Class<? extends PersistentRecord> pClass, String pColumn, Comparable<?>... values)
{
this(new ColumnExp(pClass, pColumn), values);
}
public In (Class<? extends PersistentRecord> pClass, String pColumn,
Collection<? extends Comparable<?>> values)
{
this(new ColumnExp(pClass, pColumn), values.toArray(new Comparable<?>[values.size()]));
}
public In (ColumnExp column, Comparable<?>... values) public In (ColumnExp column, Comparable<?>... values)
{ {
if (values.length == 0) { if (values.length == 0) {
@@ -329,7 +308,7 @@ public abstract class Conditionals
_query = query; _query = query;
} }
public Class<? extends PersistentRecord> getPersistentRecord () public Class<? extends PersistentRecord> getPersistentClass ()
{ {
return _pClass; return _pClass;
} }
@@ -35,58 +35,18 @@ import com.samskivert.util.StringUtil;
/** /**
* A test persistent object. * A test persistent object.
*/ */
@Entity(indices={ @Index(name="createdIndex", fields={"created"}) }) @Entity
public class TestRecord extends PersistentRecord public class TestRecord extends PersistentRecord
{ {
// AUTO-GENERATED: FIELDS START // AUTO-GENERATED: FIELDS START
/** The column identifier for the {@link #recordId} field. */ public static final Class<TestRecord> _R = TestRecord.class;
public static final String RECORD_ID = "recordId"; public static final ColumnExp RECORD_ID = colexp(_R, "recordId");
public static final ColumnExp NAME = colexp(_R, "name");
/** The qualified column identifier for the {@link #recordId} field. */ public static final ColumnExp AGE = colexp(_R, "age");
public static final ColumnExp RECORD_ID_C = public static final ColumnExp HOME_TOWN = colexp(_R, "homeTown");
new ColumnExp(TestRecord.class, RECORD_ID); public static final ColumnExp CREATED = colexp(_R, "created");
public static final ColumnExp LAST_MODIFIED = colexp(_R, "lastModified");
/** The column identifier for the {@link #name} field. */ public static final ColumnExp NUMBERS = colexp(_R, "numbers");
public static final String NAME = "name";
/** The qualified column identifier for the {@link #name} field. */
public static final ColumnExp NAME_C =
new ColumnExp(TestRecord.class, NAME);
/** The column identifier for the {@link #age} field. */
public static final String AGE = "age";
/** The qualified column identifier for the {@link #age} field. */
public static final ColumnExp AGE_C =
new ColumnExp(TestRecord.class, AGE);
/** The column identifier for the {@link #homeTown} field. */
public static final String HOME_TOWN = "homeTown";
/** The qualified column identifier for the {@link #homeTown} field. */
public static final ColumnExp HOME_TOWN_C =
new ColumnExp(TestRecord.class, HOME_TOWN);
/** The column identifier for the {@link #created} field. */
public static final String CREATED = "created";
/** The qualified column identifier for the {@link #created} field. */
public static final ColumnExp CREATED_C =
new ColumnExp(TestRecord.class, CREATED);
/** The column identifier for the {@link #lastModified} field. */
public static final String LAST_MODIFIED = "lastModified";
/** The qualified column identifier for the {@link #lastModified} field. */
public static final ColumnExp LAST_MODIFIED_C =
new ColumnExp(TestRecord.class, LAST_MODIFIED);
/** The column identifier for the {@link #numbers} field. */
public static final String NUMBERS = "numbers";
/** The qualified column identifier for the {@link #numbers} field. */
public static final ColumnExp NUMBERS_C =
new ColumnExp(TestRecord.class, NUMBERS);
// AUTO-GENERATED: FIELDS END // AUTO-GENERATED: FIELDS END
public static final int SCHEMA_VERSION = 3; public static final int SCHEMA_VERSION = 3;
@@ -100,6 +60,7 @@ public class TestRecord extends PersistentRecord
public String homeTown; public String homeTown;
@Index
public Date created; public Date created;
public Timestamp lastModified; public Timestamp lastModified;
@@ -121,7 +82,7 @@ public class TestRecord extends PersistentRecord
{ {
return new Key<TestRecord>( return new Key<TestRecord>(
TestRecord.class, TestRecord.class,
new String[] { RECORD_ID }, new ColumnExp[] { RECORD_ID },
new Comparable[] { recordId }); new Comparable[] { recordId });
} }
// AUTO-GENERATED: METHODS END // AUTO-GENERATED: METHODS END
@@ -119,7 +119,7 @@ public class TestRepository extends DepotRepository
System.out.println("Delete none " + repo.deleteAll(TestRecord.class, none) + "."); System.out.println("Delete none " + repo.deleteAll(TestRecord.class, none) + ".");
// test collection caching // test collection caching
Where where = new Where(new Conditionals.GreaterThan(TestRecord.RECORD_ID_C, 100)); Where where = new Where(new Conditionals.GreaterThan(TestRecord.RECORD_ID, 100));
System.out.println("100 and up: " + repo.findAll(TestRecord.class, where).size()); System.out.println("100 and up: " + repo.findAll(TestRecord.class, where).size());
System.out.println("100 and up again: " + repo.findAll(TestRecord.class, where).size()); System.out.println("100 and up again: " + repo.findAll(TestRecord.class, where).size());
@@ -131,7 +131,7 @@ public class TestRepository extends DepotRepository
System.out.println("Names " + repo.findAll(TestNameRecord.class) + "."); System.out.println("Names " + repo.findAll(TestNameRecord.class) + ".");
System.out.println("Have " + repo.findAll(TestRecord.class).size() + " records."); System.out.println("Have " + repo.findAll(TestRecord.class).size() + " records.");
repo.deleteAll(TestRecord.class, new Where(new Conditionals.LessThan( repo.deleteAll(TestRecord.class, new Where(new Conditionals.LessThan(
TestRecord.RECORD_ID_C, CREATE_RECORDS/2))); TestRecord.RECORD_ID, CREATE_RECORDS/2)));
System.out.println("Now have " + repo.findAll(TestRecord.class).size() + " records."); System.out.println("Now have " + repo.findAll(TestRecord.class).size() + " records.");
repo.deleteAll(TestRecord.class, new Where(new LiteralExp("true"))); repo.deleteAll(TestRecord.class, new Where(new LiteralExp("true")));
// // TODO: try to break our In() clause // // TODO: try to break our In() clause
@@ -37,6 +37,7 @@ import java.util.Set;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
@@ -254,29 +255,24 @@ public class GenRecordTask extends Task
// generate our fields section // generate our fields section
StringBuilder fsection = new StringBuilder(); StringBuilder fsection = new StringBuilder();
// add our prototype declaration
VelocityContext ctx = new VelocityContext();
ctx.put("record", rname);
fsection.append(mergeTemplate(PROTO_TMPL, ctx));
// add our ColumnExp constants
for (int ii = 0; ii < flist.size(); ii++) { for (int ii = 0; ii < flist.size(); ii++) {
Field f = flist.get(ii); Field f = flist.get(ii);
String fname = f.getName(); String fname = f.getName();
// create our velocity context // create our velocity context
VelocityContext ctx = new VelocityContext(); VelocityContext fctx = (VelocityContext)ctx.clone();
ctx.put("record", rname); fctx.put("field", fname);
ctx.put("field", fname); fctx.put("capfield", StringUtil.unStudlyName(fname).toUpperCase());
ctx.put("capfield", StringUtil.unStudlyName(fname).toUpperCase());
// now generate our bits // now generate our bits
if (declared.contains(f)) { fsection.append(mergeTemplate(COL_TMPL, fctx));
if (fsection.length() > 0) {
fsection.append("\n");
}
fsection.append(mergeTemplate(NAME_TMPL, ctx));
}
if (!isAbstract) {
if (fsection.length() > 0) {
fsection.append("\n");
}
fsection.append(mergeTemplate(COL_TMPL, ctx));
}
} }
// generate our methods section // generate our methods section
@@ -284,10 +280,6 @@ public class GenRecordTask extends Task
// add a getKey() method, if applicable // add a getKey() method, if applicable
if (kflist.size() > 0) { if (kflist.size() > 0) {
// create our velocity context
VelocityContext ctx = new VelocityContext();
ctx.put("record", rname);
StringBuilder argList = new StringBuilder(); StringBuilder argList = new StringBuilder();
StringBuilder argNameList = new StringBuilder(); StringBuilder argNameList = new StringBuilder();
StringBuilder fieldNameList = new StringBuilder(); StringBuilder fieldNameList = new StringBuilder();
@@ -470,7 +462,7 @@ public class GenRecordTask extends Task
protected Class<?> _prclass; protected Class<?> _prclass;
/** Specifies the path to the name code template. */ /** Specifies the path to the name code template. */
protected static final String NAME_TMPL = "com/samskivert/depot/tools/record_name.tmpl"; protected static final String PROTO_TMPL = "com/samskivert/depot/tools/record_proto.tmpl";
/** Specifies the path to the column code template. */ /** Specifies the path to the column code template. */
protected static final String COL_TMPL = "com/samskivert/depot/tools/record_column.tmpl"; protected static final String COL_TMPL = "com/samskivert/depot/tools/record_column.tmpl";
@@ -1,3 +1 @@
/** The qualified column identifier for the {@link #$field} field. */ public static final ColumnExp ${capfield} = colexp(_R, "$field");
public static final ColumnExp ${capfield}_C =
new ColumnExp(${record}.class, $capfield);
@@ -6,6 +6,6 @@
{ {
return new Key<${record}>( return new Key<${record}>(
${record}.class, ${record}.class,
new String[] { $fieldNameList }, new ColumnExp[] { $fieldNameList },
new Comparable[] { $argNameList }); new Comparable[] { $argNameList });
} }
@@ -1,2 +0,0 @@
/** The column identifier for the {@link #$field} field. */
public static final String $capfield = "$field";
@@ -0,0 +1 @@
public static final Class<${record}> _R = ${record}.class;