From 9f24bd45497db2a07818eb4c0cd81573dc830a19 Mon Sep 17 00:00:00 2001 From: mdb Date: Fri, 16 Mar 2007 00:21:27 +0000 Subject: [PATCH] Improved @Computed support for subset record queries, from Zell. git-svn-id: https://samskivert.googlecode.com/svn/trunk@2072 6335cc39-0255-0410-8fd6-9bcaacd3b74c --- .../jdbc/depot/ConstructedQuery.java | 64 ++++++++++++------- .../jdbc/depot/DepotMarshaller.java | 21 ++++-- .../jdbc/depot/annotation/Computed.java | 22 ++++++- 3 files changed, 79 insertions(+), 28 deletions(-) diff --git a/src/java/com/samskivert/jdbc/depot/ConstructedQuery.java b/src/java/com/samskivert/jdbc/depot/ConstructedQuery.java index 6697da4f..8c5a54d9 100644 --- a/src/java/com/samskivert/jdbc/depot/ConstructedQuery.java +++ b/src/java/com/samskivert/jdbc/depot/ConstructedQuery.java @@ -97,47 +97,65 @@ public abstract class ConstructedQuery 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 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 } _classList = new ArrayList>(classSet); } - + /** The persistent class to instantiate for the results. */ protected Class _mainType; diff --git a/src/java/com/samskivert/jdbc/depot/DepotMarshaller.java b/src/java/com/samskivert/jdbc/depot/DepotMarshaller.java index b6bdedab..d9c4f433 100644 --- a/src/java/com/samskivert/jdbc/depot/DepotMarshaller.java +++ b/src/java/com/samskivert/jdbc/depot/DepotMarshaller.java @@ -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 } /** - * 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 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. */ diff --git a/src/java/com/samskivert/jdbc/depot/annotation/Computed.java b/src/java/com/samskivert/jdbc/depot/annotation/Computed.java index 6cf3e8b7..e7a3fa93 100644 --- a/src/java/com/samskivert/jdbc/depot/annotation/Computed.java +++ b/src/java/com/samskivert/jdbc/depot/annotation/Computed.java @@ -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 shadowOf () default PersistentRecord.class; }