Fully respect the complexities of persistent records whenever we expand field references in a query. For example, a WHERE clause that operates on a field that's been overridden will correctly use the override. This is much more complicated than what we had before (which was fundamentally broken in its own way) so this patch could introduce strange behaviour. Please be on the lookout for SQL errors in your logs and/or data fields showing unexpected values.

This commit is contained in:
Par Winzell
2007-08-20 18:35:43 +00:00
parent 6a608fcd6e
commit 180b663b73
5 changed files with 211 additions and 158 deletions
@@ -21,6 +21,7 @@
package com.samskivert.jdbc.depot;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -83,8 +84,10 @@ public abstract class BuildVisitor implements ExpressionVisitor
throws Exception
{
fieldOverride.getOverride().accept(this);
_builder.append(" as ");
appendField(fieldOverride.getField());
if (_aliasFields) {
_builder.append(" as ");
appendIdentifier(fieldOverride.getField());
}
}
public void visit (WhereCondition<? extends PersistentRecord> whereCondition)
@@ -97,13 +100,7 @@ public abstract class BuildVisitor implements ExpressionVisitor
if (ii > 0) {
_builder.append(" and ");
}
// TODO: This cannot work properly until the Depot architecture has been expanded
// so that every field reference is expanded according to context such as shadowOf
// and FieldOverride. For now, this works out of sheer luck - we have no ambiguous
// references to trip us up... but it must be fixed soon.
// appendTableAbbreviation(pClass);
// _builder.append(".");
appendColumn(pClass, keyFields[ii]);
appendRhsColumn(pClass, keyFields[ii]);
_builder.append(values[ii] == null ? " is null " : " = ? ");
}
}
@@ -126,13 +123,13 @@ public abstract class BuildVisitor implements ExpressionVisitor
} else {
_builder.append(" and ");
}
appendColumn(key.getPersistentClass(), entry.getKey());
appendRhsColumn(key.getPersistentClass(), entry.getKey());
_builder.append(entry.getValue() == null ? " is null " : " = ? ");
}
if (!first) {
_builder.append(" and ");
}
appendColumn(key.getPersistentClass(), key.getMultiField());
appendRhsColumn(key.getPersistentClass(), key.getMultiField());
_builder.append(" in (");
Comparable[] values = key.getMultiValues();
@@ -210,9 +207,7 @@ public abstract class BuildVisitor implements ExpressionVisitor
public void visit (ColumnExp columnExp)
throws Exception
{
appendTableAbbreviation(columnExp.getPersistentClass());
_builder.append(".");
appendColumn(columnExp.getPersistentClass(), columnExp.getField());
appendRhsColumn(columnExp.getPersistentClass(), columnExp.getField());
}
public void visit (Not not)
@@ -317,104 +312,76 @@ public abstract class BuildVisitor implements ExpressionVisitor
}
_builder.append("select ");
Computed entityComputed = pClass.getAnnotation(Computed.class);
// iterate over the fields we're filling in and figure out whence each one comes
boolean skip = true;
for (String field : selectClause.getFields()) {
if (!skip) {
_builder.append(", ");
}
skip = false;
// first, see if there's a field override
FieldOverride override = selectClause.lookupOverride(field);
if (override != null) {
override.accept(this);
continue;
}
// 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;
if (entityComputed == null) {
tableClass = pClass;
} else if (!PersistentRecord.class.equals(entityComputed.shadowOf())) {
tableClass = entityComputed.shadowOf();
} else {
tableClass = null;
}
// handle the field-level @Computed annotation, if there is one
FieldMarshaller fm = _types.getMarshaller(pClass).getFieldMarshaller(field);
Computed fieldComputed = fm.getComputed();
if (fieldComputed != null) {
// check if the computed field has a literal SQL definition
if (fieldComputed.fieldDefinition().length() > 0) {
_builder.append(fieldComputed.fieldDefinition()).append(" as ");
appendField(field);
continue;
}
// or if we can simply ignore the field
if (!fieldComputed.required()) {
skip = true;
continue;
}
// 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) {
appendTableAbbreviation(tableClass);
_builder.append(".");
appendColumn(tableClass, field);
continue;
}
// else owie
if (_overrides.containsKey(pClass)) {
throw new IllegalArgumentException(
"Persistent field has no definition [class=" +
selectClause.getPersistentClass() + ", field=" + field + "]");
"Can not yet nest SELECTs on the same persistent record.");
}
if (selectClause.getFromOverride() != null) {
selectClause.getFromOverride().accept(this);
Map<String, FieldOverride> overrideMap = new HashMap<String, FieldOverride>();
for (FieldOverride override : selectClause.getFieldOverrides()) {
overrideMap.put(override.getField(), override);
}
_overrides.put(pClass, overrideMap);
} else if (_types.getTableName(pClass) != null) {
_builder.append(" from ");
appendTableName(pClass);
_builder.append(" as ");
appendTableAbbreviation(pClass);
try {
// iterate over the fields we're filling in and figure out whence each one comes
boolean skip = true;
} else {
throw new SQLException("Query on @Computed entity with no FromOverrideClause.");
}
// while expanding column names in the SELECT query, do aliasing
_aliasFields = true;
for (Join clause : selectClause.getJoinClauses()) {
clause.accept(this);
}
if (selectClause.getWhereClause() != null) {
selectClause.getWhereClause().accept(this);
}
if (selectClause.getGroupBy() != null) {
selectClause.getGroupBy().accept(this);
}
if (selectClause.getOrderBy() != null) {
selectClause.getOrderBy().accept(this);
}
if (selectClause.getLimit() != null) {
selectClause.getLimit().accept(this);
}
if (selectClause.getForUpdate() != null) {
selectClause.getForUpdate().accept(this);
for (String field : selectClause.getFields()) {
if (!skip) {
_builder.append(", ");
}
skip = false;
int len = _builder.length();
appendRhsColumn(pClass, field);
// if nothing was added, don't add a comma
if (_builder.length() == len) {
skip = true;
}
}
// then stop
_aliasFields = false;
if (selectClause.getFromOverride() != null) {
selectClause.getFromOverride().accept(this);
} else if (_types.getTableName(pClass) != null) {
_builder.append(" from ");
appendTableName(pClass);
_builder.append(" as ");
appendTableAbbreviation(pClass);
} else {
throw new SQLException("Query on @Computed entity with no FromOverrideClause.");
}
for (Join clause : selectClause.getJoinClauses()) {
clause.accept(this);
}
if (selectClause.getWhereClause() != null) {
selectClause.getWhereClause().accept(this);
}
if (selectClause.getGroupBy() != null) {
selectClause.getGroupBy().accept(this);
}
if (selectClause.getOrderBy() != null) {
selectClause.getOrderBy().accept(this);
}
if (selectClause.getLimit() != null) {
selectClause.getLimit().accept(this);
}
if (selectClause.getForUpdate() != null) {
selectClause.getForUpdate().accept(this);
}
} finally {
_overrides.remove(pClass);
}
if (isInner) {
_builder.append(")");
@@ -440,7 +407,7 @@ public abstract class BuildVisitor implements ExpressionVisitor
if (ii > 0) {
_builder.append(", ");
}
appendColumn(pClass, fields[ii]);
appendLhsColumn(pClass, fields[ii]);
_builder.append(" = ");
if (pojo != null) {
@@ -479,7 +446,7 @@ public abstract class BuildVisitor implements ExpressionVisitor
if (ii > 0) {
_builder.append(", ");
}
appendColumn(pClass, fields[ii]);
appendLhsColumn(pClass, fields[ii]);
}
_builder.append(") values(");
@@ -497,23 +464,134 @@ public abstract class BuildVisitor implements ExpressionVisitor
_builder.append(")");
}
protected abstract void appendTableName (Class<? extends PersistentRecord> type);
protected abstract void appendTableAbbreviation (Class<? extends PersistentRecord> type);
protected abstract void appendColumn (Class<? extends PersistentRecord> type, String field);
protected abstract void appendField (String field);
protected abstract void appendIdentifier (String field);
protected void appendTableName (Class<? extends PersistentRecord> type)
{
appendIdentifier(_types.getTableName(type));
}
protected void appendTableAbbreviation (Class<? extends PersistentRecord> type)
{
appendIdentifier(_types.getTableAbbreviation(type));
}
// Constructs a name used for assignment in e.g. INSERT/UPDATE. This is the SQL
// equivalent of an lvalue; something that can appear to the left of an equals sign.
// We do not prepend this identifier with a table abbreviation, nor do we expand
// field overrides, shadowOf declarations, or the like: it is just a column name.
protected void appendLhsColumn (Class<? extends PersistentRecord> type, String field)
throws Exception
{
DepotMarshaller dm = _types.getMarshaller(type);
FieldMarshaller fm = dm.getFieldMarshaller(field);
if (dm == null) {
throw new IllegalArgumentException(
"Unknown field on persistent record [record=" + type + ", field=" + field + "]");
}
appendIdentifier(fm.getColumnName());
}
// Appends an expression for the given field on the given persistent record; this can
// appear in a SELECT list, in WHERE clauses, etc, etc.
protected void appendRhsColumn (Class<? extends PersistentRecord> type, String field)
throws Exception
{
DepotMarshaller dm = _types.getMarshaller(type);
FieldMarshaller fm = dm.getFieldMarshaller(field);
if (dm == null) {
throw new IllegalArgumentException(
"Unknown field on persistent record [record=" + type + ", field=" + field + "]");
}
if (!_ignoreOverrides) {
Map<String, FieldOverride> fieldOverrides = _overrides.get(type);
if (fieldOverrides != null) {
// first, see if there's a field override
FieldOverride override = fieldOverrides.get(field);
if (override != null) {
// overrides point to a raw column even when that column is overridden
// for the purposes of the query itself
_ignoreOverrides = true;
override.accept(this);
_ignoreOverrides = false;
return;
}
}
}
Computed entityComputed = dm.getComputed();
// 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;
if (entityComputed == null) {
tableClass = type;
} else if (!PersistentRecord.class.equals(entityComputed.shadowOf())) {
tableClass = entityComputed.shadowOf();
} else {
tableClass = null;
}
// handle the field-level @Computed annotation, if there is one
Computed fieldComputed = fm.getComputed();
if (fieldComputed != null) {
// check if the computed field has a literal SQL definition
if (fieldComputed.fieldDefinition().length() > 0) {
_builder.append(fieldComputed.fieldDefinition());
if (_aliasFields) {
_builder.append(" as ");
appendIdentifier(field);
}
return;
}
// or if we can simply ignore the field
if (!fieldComputed.required()) {
return;
}
// 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) {
appendTableAbbreviation(tableClass);
_builder.append(".");
appendIdentifier(fm.getColumnName());
return;
}
// else owie
throw new IllegalArgumentException(
"Persistent field has no definition [class=" + type + ", field=" + field + "]");
}
protected BuildVisitor (DepotTypes types)
{
_types = types;
_builder = new StringBuilder();
_innerClause = false;
}
protected DepotTypes _types;
/** A StringBuilder to hold the constructed SQL. */
protected StringBuilder _builder;
protected StringBuilder _builder = new StringBuilder();
/** A mapping of field overrides per persistent record. */
protected Map<Class<? extends PersistentRecord>, Map<String, FieldOverride>> _overrides =
new HashMap<Class<? extends PersistentRecord>, Map<String,FieldOverride>>();
/** A flag that's set to true for inner SELECT's */
protected boolean _innerClause;
protected boolean _innerClause = false;
protected boolean _ignoreOverrides = false;
protected boolean _aliasFields = false;
}
@@ -31,7 +31,6 @@ import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@@ -75,8 +74,8 @@ public class DepotMarshaller<T extends PersistentRecord>
Entity entity = pclass.getAnnotation(Entity.class);
// see if this is a computed entity
Computed computed = pclass.getAnnotation(Computed.class);
if (computed == null) {
_computed = pclass.getAnnotation(Computed.class);
if (_computed == null) {
// if not, this class has a corresponding SQL table
_tableName = _pclass.getName();
_tableName = _tableName.substring(_tableName.lastIndexOf(".")+1);
@@ -212,6 +211,15 @@ public class DepotMarshaller<T extends PersistentRecord>
return _pclass;
}
/**
* Returns the @Computed annotation definition of this entity, or null if none.
*/
public Computed getComputed ()
{
return _computed;
}
/**
* 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.
@@ -958,6 +966,9 @@ public class DepotMarshaller<T extends PersistentRecord>
/** The name of our persistent object table. */
protected String _tableName;
/** The @Computed annotation of this entity, or null. */
protected Computed _computed;
/** A field marshaller for each persistent field in our object. */
protected Map<String, FieldMarshaller> _fields = new HashMap<String, FieldMarshaller>();
@@ -150,20 +150,6 @@ public abstract class DepotRepository
boolean useExplicit =
(marsh.getTableName() == null) || !marsh.hasPrimaryKey() || !_ctx.isUsingCache();
// TODO: This is a very conservative approach where all complicated queries are handled
// TODO: with the traditional single-pass query. The double pass algorithm can handle a
// TODO: much larger class of queries, but only after some engine upgrades: specifically,
// TODO: the field references used in the WHERE clause of the Entity query in WithCache
// TODO: cannot be the trivial expansion currently used in the Key class, but must rather
// TODO: undergo the same treatment SELECT fields currently do in SQLQueryBuilder. This
// TODO: is probably best done with an additional method in QueryBuilderContext, or perhaps
// TODO: a much larger switch to using a visitor pattern for generating the SQL of the
// TODO: clause hierarchies.
for (QueryClause clause : clauses) {
useExplicit |= (clause instanceof FieldOverride ||
clause instanceof Join ||
clause instanceof FromOverride);
}
return _ctx.invoke(useExplicit ?
new FindAllQuery.Explicitly<T>(_ctx, type, clauses) :
new FindAllQuery.WithCache<T>(_ctx, type, clauses));
@@ -29,8 +29,6 @@ import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import com.samskivert.Log;
@@ -106,12 +104,7 @@ public class MySQLBuilder
_builder.append(_types.getTableAbbreviation(type));
}
protected void appendColumn (Class<? extends PersistentRecord> type, String field)
{
_builder.append(_types.getColumnName(type, field));
}
protected void appendField (String field)
protected void appendIdentifier (String field)
{
_builder.append(field);
}
@@ -62,7 +62,7 @@ public class PostgreSQLBuilder
public void visit (FullTextMatch match)
throws Exception
{
appendField("ftsCol_" + match.getName());
appendIdentifier("ftsCol_" + match.getName());
_builder.append(" @@ TO_TSQUERY('default', ?)");
}
@@ -71,22 +71,7 @@ public class PostgreSQLBuilder
super(types);
}
protected void appendTableName (Class<? extends PersistentRecord> type)
{
_builder.append("\"").append(_types.getTableName(type)).append("\"");
}
protected void appendTableAbbreviation (Class<? extends PersistentRecord> type)
{
_builder.append("\"").append(_types.getTableAbbreviation(type)).append("\"");
}
protected void appendColumn (Class<? extends PersistentRecord> type, String field)
{
_builder.append("\"").append(_types.getColumnName(type, field)).append("\"");
}
protected void appendField (String field)
protected void appendIdentifier (String field)
{
_builder.append("\"").append(field).append("\"");
}