Improved @Computed support for subset record queries, from Zell.

git-svn-id: https://samskivert.googlecode.com/svn/trunk@2072 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
mdb
2007-03-16 00:21:27 +00:00
parent 0d496c5ad9
commit 9f24bd4549
3 changed files with 79 additions and 28 deletions
@@ -97,47 +97,65 @@ public abstract class ConstructedQuery<T>
throw new RuntimeException("createQuery() called with _mainClass == null");
}
// we need to know if there's a entity-level @Computed annotation
Computed entityComputed = _mainType.getAnnotation(Computed.class);
// and we need to look up per-field information on our marshaller
DepotMarshaller<?> mainMarshaller = _classMap.get(_mainType);
String[] fields = mainMarshaller._allFields;
StringBuilder query = new StringBuilder("select ");
boolean skip = true;
for (int ii = 0; ii < fields.length; ii ++) {
for (String field : mainMarshaller.getFieldNames()) {
if (!skip) {
query.append(", ");
}
skip = false;
FieldOverride clause = _disMap.get(fields[ii]);
// see if there's a field override
FieldOverride clause = _disMap.get(field);
if (clause != null) {
clause.appendClause(this, query);
continue;
}
Computed computed = mainMarshaller._fields.get(fields[ii]).getComputed();
if (computed == null) {
// make sure the object corresponds to a table, otherwise the whole thing is
// computed
if (mainMarshaller.getTableName() != null) {
// if it's neither overridden nor computed, it's a standard field
query.append(getTableAbbreviation(_mainType)).append(".").append(fields[ii]);
// figure out the class we're selecting from unless we're otherwise overriden:
// for a concrete record, simply use the corresponding table; for a computed one,
// default to the shadowed concrete record, or null if there isn't one
Class<? extends PersistentRecord> tableClass =
entityComputed == null ? _mainType : entityComputed.shadowOf();
// handle the field-level @Computed annotation, if there is one
Computed fieldComputed = mainMarshaller.getFieldMarshaller(field).getComputed();
if (fieldComputed != null) {
// check if the computed field has a literal SQL definition
if (fieldComputed.fieldDefinition().length() > 0) {
query.append(fieldComputed.fieldDefinition()).append(" as ").append(field);
continue;
}
throw new SQLException(
"@Computed entity field without definition [field=" + fields[ii] + "]");
}
// check if the computed field has a literal SQL definition
if (computed.fieldDefinition().length() > 0) {
query.append(computed.fieldDefinition() + " as " + fields[ii]);
} else if (!computed.required()) {
// or if we can simply ignore the field
skip = true;
if (!fieldComputed.required()) {
skip = true;
continue;
}
} else {
throw new SQLException(
"@Computed(required) field without definition [field=" + fields[ii] + "]");
// else see if there's an overriding shadowOf definition
if (fieldComputed.shadowOf() != null) {
tableClass = fieldComputed.shadowOf();
}
}
// if we get this far we hopefully have a table to select from
if (tableClass != null) {
String tableName = getTableAbbreviation(tableClass);
query.append(tableName).append(".").append(field);
continue;
}
// else owie
throw new SQLException(
"Persistent field has no definition [class=" + _mainType +
", field=" + field + "]");
}
if (_fromOverride != null) {
@@ -268,7 +286,7 @@ public abstract class ConstructedQuery<T>
}
_classList = new ArrayList<Class<? extends PersistentRecord>>(classSet);
}
/** The persistent class to instantiate for the results. */
protected Class<? extends PersistentRecord> _mainType;
@@ -31,9 +31,6 @@ import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@@ -253,7 +250,7 @@ public class DepotMarshaller<T extends PersistentRecord>
}
/**
* Returns the name of the table in which persistence instances of this class are stored. By
* Returns the name of the table in which persistent instances of our class are stored. By
* default this is the classname of the persistent object without the package.
*/
public String getTableName ()
@@ -261,6 +258,22 @@ public class DepotMarshaller<T extends PersistentRecord>
return _tableName;
}
/**
* Returns all the persistent fields of our class, in definition order.
*/
public String[] getFieldNames ()
{
return _allFields;
}
/**
* Returns the {@link FieldMarshaller} for a named field on our persistent class.
*/
public FieldMarshaller getFieldMarshaller (String fieldName)
{
return _fields.get(fieldName);
}
/**
* Returns true if our persistent object defines a primary key.
*/
@@ -26,10 +26,12 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.samskivert.jdbc.depot.ConstructedQuery;
import com.samskivert.jdbc.depot.PersistentRecord;
/**
* Marks a field as computed, meaning it is ignored for schema purposes and it does not directly
* correspond to a column in a table, thus its value must be overridden in the {@link ConstructedQuery}.
* correspond to a column in a table, thus its value must be overridden in the {@link
* ConstructedQuery}.
*/
@Retention(value=RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.TYPE })
@@ -40,4 +42,22 @@ public @interface Computed
/** A non-empty value here is taken as literal SQL and used to populate the computed field. */
String fieldDefinition () default "";
/**
* A computed record can shadow a concrete record, which causes any field the former has in
* common with the latter and which is not otherwise overriden to inherit its definition. The
* shadowed class may also be given at the field level.
*
* The purpose of shadowing is largely to avoid having to supply a {@link FieldOverride} when
* querying for objects that that contain large subsets of some other persistent object's
* fields -- in other words, when you use a computed entity to query only some of the columns
* from a table.
*
* The shadowed class must have been brought into the query using e.g. {@link FromOverride}
* or {@link Join} clauses. The referenced fields must be simple concrete columns in a table;
* they must themselves be computed or overridden or shadowing.
*
* TODO: Do in fact let the shadowed field be computed, overriden or shadowing.
*/
Class<? extends PersistentRecord> shadowOf () default PersistentRecord.class;
}