From 083cb788d80842b734ecfab982c27be103f7f71c Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Thu, 12 Jul 2007 20:02:54 +0000 Subject: [PATCH] Another Zell Depot patch: - refactoring query construction; - modified collection lookup to first look up the matching primary keys, then check the cache for hits then look up the remainder from the database; - other small renamery and cleanup. --- .../jdbc/depot/ConstructedQuery.java | 322 ----------------- .../jdbc/depot/DepotMarshaller.java | 64 +++- .../jdbc/depot/DepotRepository.java | 129 ++----- .../com/samskivert/jdbc/depot/DepotTypes.java | 106 ++++++ .../jdbc/depot/FieldMarshaller.java | 333 ++++++++++++------ .../samskivert/jdbc/depot/FindAllQuery.java | 219 ++++++++++++ .../samskivert/jdbc/depot/FindOneQuery.java | 100 ++++++ src/java/com/samskivert/jdbc/depot/Key.java | 43 ++- .../com/samskivert/jdbc/depot/MultiKey.java | 11 +- .../jdbc/depot/PersistenceContext.java | 3 +- src/java/com/samskivert/jdbc/depot/Query.java | 11 +- .../jdbc/depot/QueryBuilderContext.java | 43 +++ .../jdbc/depot/SQLQueryBuilder.java | 288 +++++++++++++++ .../jdbc/depot/annotation/Computed.java | 4 +- .../jdbc/depot/annotation/Index.java | 1 - .../jdbc/depot/clause/FieldOverride.java | 8 +- .../jdbc/depot/clause/ForUpdate.java | 9 +- .../jdbc/depot/clause/FromOverride.java | 11 +- .../samskivert/jdbc/depot/clause/GroupBy.java | 10 +- .../samskivert/jdbc/depot/clause/Join.java | 22 +- .../samskivert/jdbc/depot/clause/Limit.java | 8 +- .../samskivert/jdbc/depot/clause/OrderBy.java | 10 +- .../jdbc/depot/clause/QueryClause.java | 26 +- .../samskivert/jdbc/depot/clause/Where.java | 19 +- .../jdbc/depot/expression/ColumnExp.java | 6 +- .../jdbc/depot/expression/FunctionExp.java | 8 +- .../jdbc/depot/expression/LiteralExp.java | 6 +- .../jdbc/depot/expression/SQLExpression.java | 6 +- .../jdbc/depot/expression/ValueExp.java | 6 +- .../jdbc/depot/operator/Conditionals.java | 14 +- .../samskivert/jdbc/depot/operator/Logic.java | 17 +- .../jdbc/depot/operator/SQLOperator.java | 20 +- 32 files changed, 1219 insertions(+), 664 deletions(-) create mode 100644 src/java/com/samskivert/jdbc/depot/DepotTypes.java create mode 100644 src/java/com/samskivert/jdbc/depot/FindAllQuery.java create mode 100644 src/java/com/samskivert/jdbc/depot/FindOneQuery.java create mode 100644 src/java/com/samskivert/jdbc/depot/QueryBuilderContext.java create mode 100644 src/java/com/samskivert/jdbc/depot/SQLQueryBuilder.java diff --git a/src/java/com/samskivert/jdbc/depot/ConstructedQuery.java b/src/java/com/samskivert/jdbc/depot/ConstructedQuery.java index 8c5a54d..e69de29 100644 --- a/src/java/com/samskivert/jdbc/depot/ConstructedQuery.java +++ b/src/java/com/samskivert/jdbc/depot/ConstructedQuery.java @@ -1,322 +0,0 @@ -// -// $Id$ -// -// samskivert library - useful routines for java programs -// Copyright (C) 2006 Michael Bayne -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published -// by the Free Software Foundation; either version 2.1 of the License, or -// (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -package com.samskivert.jdbc.depot; - -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.SQLException; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import com.samskivert.io.PersistenceException; -import com.samskivert.jdbc.depot.annotation.Computed; -import com.samskivert.jdbc.depot.clause.FieldOverride; -import com.samskivert.jdbc.depot.clause.ForUpdate; -import com.samskivert.jdbc.depot.clause.FromOverride; -import com.samskivert.jdbc.depot.clause.GroupBy; -import com.samskivert.jdbc.depot.clause.Join; -import com.samskivert.jdbc.depot.clause.Limit; -import com.samskivert.jdbc.depot.clause.OrderBy; -import com.samskivert.jdbc.depot.clause.QueryClause; -import com.samskivert.jdbc.depot.clause.Where; - -/** - * Encapsulates a non-modifying query of persistent objects. - */ -public abstract class ConstructedQuery - implements Query -{ - /** - * Maps a class referenced by this query to its associated table. - * - * This method is called by individual clauses. - */ - public String getTableName (Class cl) - { - return _classMap.get(cl).getTableName(); - } - - /** - * Maps a class referenced by this query to the abbreviation used for its associated table. The - * abbreviation is transientand only has meaning within the scope of this particular query. - * - * This method is called by individual clauses. - */ - public String getTableAbbreviation (Class cl) - { - int ix = _classList.indexOf(cl); - if (ix < 0) { - throw new IllegalArgumentException("Unknown persistence class: " + cl); - } - return "T" + (ix+1); - } - - // from Query - public CacheKey getCacheKey () - { - if (_where != null && _where instanceof CacheKey) { - return (CacheKey) _where; - } - return null; - } - - /** - * Translates the data in this Query object into a SQL query suited to instantiate a persistent - * object given the supplied key and clauses. These clauses are assumed to contain no - * conflicts. - * - * If no key is supplied all instances will be loaded. - */ - public PreparedStatement createQuery (Connection conn) - throws SQLException - { - if (_mainType == null) { // internal error - 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); - - StringBuilder query = new StringBuilder("select "); - boolean skip = true; - for (String field : mainMarshaller.getFieldNames()) { - if (!skip) { - query.append(", "); - } - skip = false; - - // see if there's a field override - FieldOverride clause = _disMap.get(field); - if (clause != null) { - clause.appendClause(this, query); - 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 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; - } - - // 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) { - 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) { - _fromOverride.appendClause(this, query); - } else if (mainMarshaller.getTableName() != null) { - query.append(" from ").append(mainMarshaller.getTableName()); - query.append(" as ").append(getTableAbbreviation(_mainType)); - } else { - throw new SQLException("Query on @Computed entity with no FromOverrideClause."); - } - - for (Join clause : _joinClauses) { - clause.appendClause(this, query); - } - if (_where != null) { - _where.appendClause(this, query); - } - if (_groupBy != null) { - _groupBy.appendClause(this, query); - } - if (_orderBy != null) { - _orderBy.appendClause(this, query); - } - if (_limit != null) { - _limit.appendClause(this, query); - } - if (_forUpdate != null) { - _forUpdate.appendClause(this, query); - } - - PreparedStatement pstmt = conn.prepareStatement(query.toString()); - int argIdx = 1; - for (Join clause : _joinClauses) { - argIdx = clause.bindArguments(pstmt, argIdx); - } - if (_where != null) { - argIdx = _where.bindArguments(pstmt, argIdx); - } - if (_groupBy != null) { - argIdx = _groupBy.bindArguments(pstmt, argIdx); - } - if (_orderBy != null) { - argIdx = _orderBy.bindArguments(pstmt, argIdx); - } - if (_limit != null) { - argIdx = _limit.bindArguments(pstmt, argIdx); - } - if (_forUpdate != null) { - argIdx = _forUpdate.bindArguments(pstmt, argIdx); - } - - return pstmt; - } - - /** - * Creates a new Query object to generate one or more instances of the specified persistent - * class, as dictated by the key and query clauses. A persistence context is supplied for - * instantiation of marshallers, which may trigger table creations and schema migrations. - */ - protected ConstructedQuery (PersistenceContext ctx, Class type, - QueryClause... clauses) - throws PersistenceException - { - _mainType = type; - Set> classSet = - new HashSet>(); - if (type != null) { - classSet.add(type); - } - - for (QueryClause clause : clauses) { - if (clause instanceof Where) { - if (_where != null) { - throw new IllegalArgumentException( - "Query can't contain multiple Where clauses."); - } - _where = (Where) clause; - - } else if (clause instanceof FromOverride) { - if (_fromOverride != null) { - throw new IllegalArgumentException( - "Query can't contain multiple FromOverride clauses."); - } - _fromOverride = (FromOverride) clause; - classSet.addAll(_fromOverride.getClassSet()); - - } else if (clause instanceof Join) { - _joinClauses.add((Join) clause); - classSet.addAll(((Join) clause).getClassSet()); - - } else if (clause instanceof FieldOverride) { - _disMap.put(((FieldOverride) clause).getField(), - ((FieldOverride) clause)); - - } else if (clause instanceof OrderBy) { - if (_orderBy != null) { - throw new IllegalArgumentException( - "Query can't contain multiple OrderBy clauses."); - } - _orderBy = (OrderBy) clause; - - } else if (clause instanceof GroupBy) { - if (_groupBy != null) { - throw new IllegalArgumentException( - "Query can't contain multiple GroupBy clauses."); - } - _groupBy = (GroupBy) clause; - - } else if (clause instanceof Limit) { - if (_limit != null) { - throw new IllegalArgumentException( - "Query can't contain multiple Limit clauses."); - } - _limit = (Limit) clause; - - } else if (clause instanceof ForUpdate) { - if (_forUpdate != null) { - throw new IllegalArgumentException( - "Query can't contain multiple For Update clauses."); - } - _forUpdate = (ForUpdate) clause; - } - } - _classMap = new HashMap(); - - for (Class c : classSet) { - _classMap.put(c, ctx.getMarshaller(c)); - } - _classList = new ArrayList>(classSet); - } - - /** The persistent class to instantiate for the results. */ - protected Class _mainType; - - /** A list of referenced classes, used to generate table abbreviations. */ - protected List> _classList; - - /** Classes mapped to marshallers, used for table names and field lists. */ - protected Map _classMap; - - /** Persistent class fields mapped to field override clauses. */ - protected Map _disMap = new HashMap(); - - /** The from override clause, if any. */ - protected FromOverride _fromOverride; - - /** The where clause. */ - protected Where _where; - - /** A list of join clauses, each potentially referencing a new class. */ - protected List _joinClauses = new ArrayList(); - - /** The order by clause, if any. */ - protected OrderBy _orderBy; - - /** The group by clause, if any. */ - protected GroupBy _groupBy; - - /** The limit clause, if any. */ - protected Limit _limit; - - /** The For Update clause, if any. */ - protected ForUpdate _forUpdate; -} diff --git a/src/java/com/samskivert/jdbc/depot/DepotMarshaller.java b/src/java/com/samskivert/jdbc/depot/DepotMarshaller.java index 3c753bf..b305ae0 100644 --- a/src/java/com/samskivert/jdbc/depot/DepotMarshaller.java +++ b/src/java/com/samskivert/jdbc/depot/DepotMarshaller.java @@ -2,7 +2,7 @@ // $Id$ // // samskivert library - useful routines for java programs -// Copyright (C) 2006 Michael Bayne, Pär Winzell +// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell // // This library is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -239,7 +239,7 @@ public class DepotMarshaller // add the primary key, if we have one if (hasPrimaryKey()) { - _declarations.add("PRIMARY KEY (" + getPrimaryKeyColumns() + ")"); + _declarations.add("PRIMARY KEY (" + getJoinedKeyColumns() + ")"); } // if we did not find a schema version field, complain @@ -282,6 +282,19 @@ public class DepotMarshaller return (_pkColumns != null); } + /** + * Return the names of the columns that constitute the primary key of our associated + * persistent record. + */ + public String[] getPrimaryKeyColumns () + { + String[] pkcols = new String[_pkColumns.size()]; + for (int ii = 0; ii < pkcols.length; ii ++) { + pkcols[ii] = _pkColumns.get(ii).getColumnName(); + } + return pkcols; + } + /** * Returns a key configured with the primary key of the supplied object. If all the fields are * null, this method returns null. An exception is thrown if some of the fields are null and @@ -353,6 +366,29 @@ public class DepotMarshaller return new Key(_pclass, columns, values); } + /** + * Creates a primary key record for the type of object handled by this marshaller, using the + * supplied result set. + */ + public Key makePrimaryKey (ResultSet rs) + throws SQLException + { + if (!hasPrimaryKey()) { + throw new UnsupportedOperationException( + getClass().getName() + " does not define a primary key"); + } + Comparable[] values = new Comparable[_pkColumns.size()]; + for (int ii = 0; ii < _pkColumns.size(); ii++) { + Object keyValue = _pkColumns.get(ii).getFromSet(rs); + if (!(keyValue instanceof Comparable)) { + throw new IllegalArgumentException("Key field must be Comparable [field=" + + _pkColumns.get(ii).getColumnName() + "]"); + } + values[ii] = (Comparable) keyValue; + } + return makePrimaryKey(values); + } + /** * Returns true if this marshaller has been initialized ({@link #init} has been called), its * migrations run and it is ready for operation. False otherwise. @@ -387,7 +423,7 @@ public class DepotMarshaller } throw new SQLException("ResultSet missing field: " + fm.getField().getName()); } - fm.getValue(rs, po); + fm.writeToObject(rs, po); } return po; @@ -427,7 +463,7 @@ public class DepotMarshaller PreparedStatement pstmt = conn.prepareStatement(insert.toString()); int idx = 0; for (String field : _columnFields) { - _fields.get(field).setValue(po, pstmt, ++idx); + _fields.get(field).readFromObject(po, pstmt, ++idx); } return pstmt; @@ -507,10 +543,10 @@ public class DepotMarshaller idx = 0; // bind the update arguments for (String field : modifiedFields) { - _fields.get(field).setValue(po, pstmt, ++idx); + _fields.get(field).readFromObject(po, pstmt, ++idx); } // now bind the key arguments - key.bindArguments(pstmt, ++idx); + key.bindClauseArguments(pstmt, ++idx); return pstmt; } catch (SQLException sqe) { @@ -553,7 +589,7 @@ public class DepotMarshaller pstmt.setObject(++idx, value); } // now bind the key arguments - key.bindArguments(pstmt, ++idx); + key.bindClauseArguments(pstmt, ++idx); return pstmt; } @@ -568,7 +604,7 @@ public class DepotMarshaller StringBuilder query = new StringBuilder("delete from " + getTableName()); key.appendClause(null, query); PreparedStatement pstmt = conn.prepareStatement(query.toString()); - key.bindArguments(pstmt, 1); + key.bindClauseArguments(pstmt, 1); return pstmt; } @@ -594,7 +630,7 @@ public class DepotMarshaller key.appendClause(null, update); PreparedStatement pstmt = conn.prepareStatement(update.toString()); - key.bindArguments(pstmt, 1); + key.bindClauseArguments(pstmt, 1); return pstmt; } @@ -745,7 +781,7 @@ public class DepotMarshaller // add or remove the primary key as needed if (hasPrimaryKey() && !indexColumns.containsKey("PRIMARY")) { - String pkdef = "primary key (" + getPrimaryKeyColumns() + ")"; + String pkdef = "primary key (" + getJoinedKeyColumns() + ")"; log.info("Adding primary key to " + getTableName() + ": " + pkdef); ctx.invoke(new Modifier.Simple("alter table " + getTableName() + " add " + pkdef)); @@ -888,13 +924,9 @@ public class DepotMarshaller } } - protected String getPrimaryKeyColumns () + protected String getJoinedKeyColumns () { - String[] pkcols = new String[_pkColumns.size()]; - for (int ii = 0; ii < pkcols.length; ii ++) { - pkcols[ii] = _pkColumns.get(ii).getColumnName(); - } - return StringUtil.join(pkcols, ", "); + return StringUtil.join(getPrimaryKeyColumns(), ", "); } protected void updateVersion (Connection conn, int version) diff --git a/src/java/com/samskivert/jdbc/depot/DepotRepository.java b/src/java/com/samskivert/jdbc/depot/DepotRepository.java index 501dbfe..1742914 100644 --- a/src/java/com/samskivert/jdbc/depot/DepotRepository.java +++ b/src/java/com/samskivert/jdbc/depot/DepotRepository.java @@ -2,7 +2,7 @@ // $Id$ // // samskivert library - useful routines for java programs -// Copyright (C) 2006 Michael Bayne, Pär Winzell +// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell // // This library is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -22,10 +22,8 @@ package com.samskivert.jdbc.depot; import java.sql.Connection; import java.sql.PreparedStatement; -import java.sql.ResultSet; import java.sql.SQLException; -import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; @@ -34,6 +32,9 @@ import com.samskivert.io.PersistenceException; import com.samskivert.jdbc.ConnectionProvider; import com.samskivert.jdbc.JDBCUtil; import com.samskivert.jdbc.depot.Modifier.*; +import com.samskivert.jdbc.depot.clause.FieldOverride; +import com.samskivert.jdbc.depot.clause.FromOverride; +import com.samskivert.jdbc.depot.clause.Join; import com.samskivert.jdbc.depot.clause.QueryClause; import com.samskivert.jdbc.depot.clause.Where; import com.samskivert.util.ArrayUtil; @@ -113,104 +114,44 @@ public class DepotRepository protected T load (Class type, QueryClause... clauses) throws PersistenceException { - final DepotMarshaller marsh = _ctx.getMarshaller(type); - return _ctx.invoke(new ConstructedQuery(_ctx, type, clauses) { - public T invoke (Connection conn) throws SQLException { - PreparedStatement stmt = createQuery(conn); - try { - T result = null; - ResultSet rs = stmt.executeQuery(); - if (rs.next()) { - result = marsh.createObject(rs); - } - // TODO: if (rs.next()) issue warning? - rs.close(); - return result; - - } finally { - JDBCUtil.close(stmt); - } - } - - // from Query - public void updateCache (PersistenceContext ctx, T result) { - CacheKey key = getCacheKey(); - if (key == null) { - // no row-specific cache key was given - if (result == null || !marsh.hasPrimaryKey()) { - return; - } - // if we can, create a key from what was actually returned - key = marsh.getPrimaryKey(result); - } - ctx.cacheStore(key, result != null ? result.clone() : null); - } - - // from Query - public T transformCacheHit (CacheKey key, T value) - { - // we do not want to return a reference to the actual cached entity - if (value == null) { - return null; - } - @SuppressWarnings("unchecked") T cvalue = (T) value.clone(); - return cvalue; - } - }); + return _ctx.invoke(new FindOneQuery(_ctx, type, clauses)); } /** - * Loads all persistent objects that match the specified key. + * Loads all persistent objects that match the specified clauses. + * + * We have two strategies for doing this: one performs the query as-is, the second executes + * two passes: first fetching only key columns and consulting the cache for each such key; + * then, in the second pass, fetching the full entity only for keys that were not found in + * the cache. + * + * The more complex strategy could save a lot of data shuffling. On the other hand, its + * complexity is an inherent drawback, and it does execute two separate database queries + * for what the simple method does in one. */ - protected List findAll ( - Class type, QueryClause... clauses) + protected List findAll (Class type, QueryClause... clauses) throws PersistenceException { - final DepotMarshaller marsh = _ctx.getMarshaller(type); - return _ctx.invoke(new ConstructedQuery>(_ctx, type, clauses) { - public ArrayList invoke (Connection conn) throws SQLException { - PreparedStatement stmt = createQuery(conn); - try { - ArrayList results = new ArrayList(); - ResultSet rs = stmt.executeQuery(); - while (rs.next()) { - results.add(marsh.createObject(rs)); - } - return results; + DepotMarshaller marsh = _ctx.getMarshaller(type); + boolean useExplicit = (marsh.getTableName() == null) || !marsh.hasPrimaryKey(); - } finally { - JDBCUtil.close(stmt); - } - } - - // from Query - public void updateCache (PersistenceContext ctx, ArrayList result) { - if (marsh.hasPrimaryKey()) { - for (T bit : result) { - ctx.cacheStore(marsh.getPrimaryKey(bit), bit.clone()); - } - } - } - - // from Query - public ArrayList transformCacheHit (CacheKey key, ArrayList bits) - { - if (bits == null) { - return bits; - } - - ArrayList result = new ArrayList(); - for (T bit : bits) { - if (bit != null) { - @SuppressWarnings("unchecked") T cbit = (T) bit.clone(); - result.add(cbit); - } else { - result.add(null); - } - } - return result; - } - }); + // 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(_ctx, type, clauses) : + new FindAllQuery.WithCache(_ctx, type, clauses)); } /** diff --git a/src/java/com/samskivert/jdbc/depot/DepotTypes.java b/src/java/com/samskivert/jdbc/depot/DepotTypes.java new file mode 100644 index 0000000..b41f753 --- /dev/null +++ b/src/java/com/samskivert/jdbc/depot/DepotTypes.java @@ -0,0 +1,106 @@ +// +// $Id$ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.samskivert.jdbc.depot; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.samskivert.io.PersistenceException; + +/** + * Primarily an implementation of {@link QueryBuilderContext}, this class holds the persistent + * classes used in the context of a single query and maps them to {@link DepotMarshaller} + * instances as well as table names and table abbreviations. + * + * The main motivation for breaking this functionality out into its own class is to encapsulate + * the operation that throws {@link PersistenceException} as separate from the operations that + * throw {@link SQLException}. + */ +public class DepotTypes + implements QueryBuilderContext +{ + public DepotTypes (PersistenceContext ctx, + Class main, + Set> others) + throws PersistenceException + { + _mainType = main; + _classMap = new HashMap(); + + for (Class c : others) { + _classMap.put(c, ctx.getMarshaller(c)); + } + _classList = new ArrayList>(others); + } + + public Class getMainType () + { + return _mainType; + } + + // from ConstructedQuery + public String getTableName (Class cl) + { + return _classMap.get(cl).getTableName(); + } + + // from ConstructedQuery + public String getTableAbbreviation (Class cl) + { + int ix = _classList.indexOf(cl); + if (ix < 0) { + throw new IllegalArgumentException("Unknown persistence class: " + cl); + } + return "T" + (ix+1); + } + + public DepotMarshaller getMarshaller (Class cl) + { + return _classMap.get(cl); + } + + public String getMainTableName () + { + return getTableName(_mainType); + } + + public DepotMarshaller getMainMarshaller () + { + return getMarshaller(_mainType); + } + + public String getMainTableAbbreviation () + { + return getTableAbbreviation(_mainType); + } + + /** The persistent class to instantiate for the results. */ + protected Class _mainType; + + /** A list of referenced classes, used to generate table abbreviations. */ + protected List> _classList; + + /** Classes mapped to marshallers, used for table names and field lists. */ + protected Map _classMap; +} diff --git a/src/java/com/samskivert/jdbc/depot/FieldMarshaller.java b/src/java/com/samskivert/jdbc/depot/FieldMarshaller.java index cc5343d..e27ede0 100644 --- a/src/java/com/samskivert/jdbc/depot/FieldMarshaller.java +++ b/src/java/com/samskivert/jdbc/depot/FieldMarshaller.java @@ -2,7 +2,7 @@ // $Id$ // // samskivert library - useful routines for java programs -// Copyright (C) 2006 Michael Bayne, Pär Winzell +// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell // // This library is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -21,7 +21,6 @@ package com.samskivert.jdbc.depot; import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -37,7 +36,6 @@ import java.sql.Timestamp; import com.samskivert.jdbc.depot.annotation.Column; import com.samskivert.jdbc.depot.annotation.Computed; import com.samskivert.jdbc.depot.annotation.GeneratedValue; -import com.samskivert.jdbc.depot.annotation.GenerationType; import com.samskivert.jdbc.depot.annotation.Id; import com.samskivert.util.StringUtil; @@ -47,7 +45,7 @@ import com.samskivert.util.StringUtil; * * @see DepotMarshaller */ -public abstract class FieldMarshaller +public abstract class FieldMarshaller { /** * Creates and returns a field marshaller for the specified field. Throws an exception if the @@ -152,19 +150,49 @@ public abstract class FieldMarshaller return _columnDefinition; } + /** + * Reads and returns this field from the result set. + */ + public abstract T getFromSet (ResultSet rs) + throws SQLException; + + /** + * Sets the specified column of the given prepared statement to the given value. + */ + public abstract void writeToStatement (PreparedStatement ps, int column, T value) + throws SQLException; + + /** + * Reads this field from the given persistent object. + */ + public abstract T getFromObject (Object po) + throws IllegalArgumentException, IllegalAccessException; + + /** + * Writes the given value to the given persistent value. + */ + public abstract void writeToObject (Object po, T value) + throws IllegalArgumentException, IllegalAccessException; + /** * Reads the value of our field from the persistent object and sets that * value into the specified column of the supplied prepared statement. */ - public abstract void setValue (Object po, PreparedStatement ps, int column) - throws SQLException, IllegalAccessException; + public void readFromObject (Object po, PreparedStatement ps, int column) + throws SQLException, IllegalAccessException + { + writeToStatement(ps, column, getFromObject(po)); + } /** * Reads the specified column from the supplied result set and writes it to * the appropriate field of the persistent object. */ - public abstract void getValue (ResultSet rset, Object po) - throws SQLException, IllegalAccessException; + public void writeToObject (ResultSet rset, Object po) + throws SQLException, IllegalAccessException + { + writeToObject(po, getFromSet(rset)); + } /** * Returns the type used in the SQL column definition for this field. @@ -254,113 +282,161 @@ public abstract class FieldMarshaller _columnDefinition = builder.toString(); } - protected static class BooleanMarshaller extends FieldMarshaller { - public void setValue (Object po, PreparedStatement ps, int column) - throws SQLException, IllegalAccessException { - ps.setBoolean(column, _field.getBoolean(po)); - } - public void getValue (ResultSet rs, Object po) - throws SQLException, IllegalAccessException { - _field.setBoolean(po, rs.getBoolean(getColumnName())); - } + protected static class BooleanMarshaller extends FieldMarshaller { public String getColumnType () { return "TINYINT"; } + public Boolean getFromObject (Object po) + throws IllegalArgumentException, IllegalAccessException { + return _field.getBoolean(po); + } + public Boolean getFromSet (ResultSet rs) + throws SQLException { + return rs.getBoolean(getColumnName()); + } + public void writeToObject (Object po, Boolean value) + throws IllegalArgumentException, IllegalAccessException { + _field.setBoolean(po, value); + } + public void writeToStatement (PreparedStatement ps, int column, Boolean value) + throws SQLException { + ps.setBoolean(column, value); + } } - protected static class ByteMarshaller extends FieldMarshaller { - public void setValue (Object po, PreparedStatement ps, int column) - throws SQLException, IllegalAccessException { - ps.setByte(column, _field.getByte(po)); - } - public void getValue (ResultSet rs, Object po) - throws SQLException, IllegalAccessException { - _field.setByte(po, rs.getByte(getColumnName())); - } + protected static class ByteMarshaller extends FieldMarshaller { public String getColumnType () { return "TINYINT"; } + public Byte getFromObject (Object po) + throws IllegalArgumentException, IllegalAccessException { + return _field.getByte(po); + } + public Byte getFromSet (ResultSet rs) + throws SQLException { + return rs.getByte(getColumnName()); + } + public void writeToObject (Object po, Byte value) + throws IllegalArgumentException, IllegalAccessException { + _field.setByte(po, value); + } + public void writeToStatement (PreparedStatement ps, int column, Byte value) + throws SQLException { + ps.setByte(column, value); + } } - protected static class ShortMarshaller extends FieldMarshaller { - public void setValue (Object po, PreparedStatement ps, int column) - throws SQLException, IllegalAccessException { - ps.setShort(column, _field.getShort(po)); - } - public void getValue (ResultSet rs, Object po) - throws SQLException, IllegalAccessException { - _field.setShort(po, rs.getShort(getColumnName())); - } + protected static class ShortMarshaller extends FieldMarshaller { public String getColumnType () { return "SMALLINT"; } + public Short getFromObject (Object po) + throws IllegalArgumentException, IllegalAccessException { + return _field.getShort(po); + } + public Short getFromSet (ResultSet rs) + throws SQLException { + return rs.getShort(getColumnName()); + } + public void writeToObject (Object po, Short value) + throws IllegalArgumentException, IllegalAccessException { + _field.setShort(po, value); + } + public void writeToStatement (PreparedStatement ps, int column, Short value) + throws SQLException { + ps.setShort(column, value); + } } - protected static class IntMarshaller extends FieldMarshaller { - public void setValue (Object po, PreparedStatement ps, int column) - throws SQLException, IllegalAccessException { - ps.setInt(column, _field.getInt(po)); - } - public void getValue (ResultSet rs, Object po) - throws SQLException, IllegalAccessException { - _field.setInt(po, rs.getInt(getColumnName())); - } + protected static class IntMarshaller extends FieldMarshaller { public String getColumnType () { return "INTEGER"; } + public Integer getFromObject (Object po) + throws IllegalArgumentException, IllegalAccessException { + return _field.getInt(po); + } + public Integer getFromSet (ResultSet rs) + throws SQLException { + return rs.getInt(getColumnName()); + } + public void writeToObject (Object po, Integer value) + throws IllegalArgumentException, IllegalAccessException { + _field.setInt(po, value); + } + public void writeToStatement (PreparedStatement ps, int column, Integer value) + throws SQLException { + ps.setInt(column, value); + } } - protected static class LongMarshaller extends FieldMarshaller { - public void setValue (Object po, PreparedStatement ps, int column) - throws SQLException, IllegalAccessException { - ps.setLong(column, _field.getLong(po)); - } - public void getValue (ResultSet rs, Object po) - throws SQLException, IllegalAccessException { - _field.setLong(po, rs.getLong(getColumnName())); - } + protected static class LongMarshaller extends FieldMarshaller { public String getColumnType () { return "BIGINT"; } + public Long getFromObject (Object po) + throws IllegalArgumentException, IllegalAccessException { + return _field.getLong(po); + } + public Long getFromSet (ResultSet rs) + throws SQLException { + return rs.getLong(getColumnName()); + } + public void writeToObject (Object po, Long value) + throws IllegalArgumentException, IllegalAccessException { + _field.setLong(po, value); + } + public void writeToStatement (PreparedStatement ps, int column, Long value) + throws SQLException { + ps.setLong(column, value); + } } - protected static class FloatMarshaller extends FieldMarshaller { - public void setValue (Object po, PreparedStatement ps, int column) - throws SQLException, IllegalAccessException { - ps.setFloat(column, _field.getFloat(po)); - } - public void getValue (ResultSet rs, Object po) - throws SQLException, IllegalAccessException { - _field.setFloat(po, rs.getFloat(getColumnName())); - } + protected static class FloatMarshaller extends FieldMarshaller { public String getColumnType () { return "FLOAT"; } + public Float getFromObject (Object po) + throws IllegalArgumentException, IllegalAccessException { + return _field.getFloat(po); + } + public Float getFromSet (ResultSet rs) + throws SQLException { + return rs.getFloat(getColumnName()); + } + public void writeToObject (Object po, Float value) + throws IllegalArgumentException, IllegalAccessException { + _field.setFloat(po, value); + } + public void writeToStatement (PreparedStatement ps, int column, Float value) + throws SQLException { + ps.setFloat(column, value); + } } - protected static class DoubleMarshaller extends FieldMarshaller { - public void setValue (Object po, PreparedStatement ps, int column) - throws SQLException, IllegalAccessException { - ps.setDouble(column, _field.getDouble(po)); - } - public void getValue (ResultSet rs, Object po) - throws SQLException, IllegalAccessException { - _field.setDouble(po, rs.getDouble(getColumnName())); - } + protected static class DoubleMarshaller extends FieldMarshaller { public String getColumnType () { return "DOUBLE"; } + public Double getFromObject (Object po) + throws IllegalArgumentException, IllegalAccessException { + return _field.getDouble(po); + } + public Double getFromSet (ResultSet rs) + throws SQLException { + return rs.getDouble(getColumnName()); + } + public void writeToObject (Object po, Double value) + throws IllegalArgumentException, IllegalAccessException { + _field.setDouble(po, value); + } + public void writeToStatement (PreparedStatement ps, int column, Double value) + throws SQLException { + ps.setDouble(column, value); + } } - protected static class ObjectMarshaller extends FieldMarshaller { - public void setValue (Object po, PreparedStatement ps, int column) - throws SQLException, IllegalAccessException { - ps.setObject(column, _field.get(po)); - } - public void getValue (ResultSet rs, Object po) - throws SQLException, IllegalAccessException { - _field.set(po, rs.getObject(getColumnName())); - } + protected static class ObjectMarshaller extends FieldMarshaller { public String getColumnType () { Class ftype = _field.getType(); if (ftype.equals(Byte.class)) { @@ -392,37 +468,69 @@ public abstract class FieldMarshaller "Don't know how to create SQL for " + ftype + "."); } } + public Object getFromObject (Object po) + throws IllegalArgumentException, IllegalAccessException { + return _field.get(po); + } + public Object getFromSet (ResultSet rs) + throws SQLException { + return rs.getObject(getColumnName()); + } + public void writeToObject (Object po, Object value) + throws IllegalArgumentException, IllegalAccessException { + _field.set(po, value); + } + public void writeToStatement (PreparedStatement ps, int column, Object value) + throws SQLException { + ps.setObject(column, value); + } } - protected static class ByteArrayMarshaller extends FieldMarshaller { - public void setValue (Object po, PreparedStatement ps, int column) - throws SQLException, IllegalAccessException { - ps.setBytes(column, (byte[])_field.get(po)); + protected static class ByteArrayMarshaller extends FieldMarshaller { + public byte[] getFromObject (Object po) + throws IllegalArgumentException, IllegalAccessException { + return (byte[]) _field.get(po); } - public void getValue (ResultSet rs, Object po) - throws SQLException, IllegalAccessException { - _field.set(po, rs.getBytes(getColumnName())); + public byte[] getFromSet (ResultSet rs) + throws SQLException { + return rs.getBytes(getColumnName()); + } + public void writeToObject (Object po, byte[] value) + throws IllegalArgumentException, IllegalAccessException { + _field.set(po, value); + } + public void writeToStatement (PreparedStatement ps, int column, byte[] value) + throws SQLException { + ps.setBytes(column, value); } public String getColumnType () { return "VARBINARY"; } } - protected static class IntArrayMarshaller extends FieldMarshaller { - public void setValue (Object po, PreparedStatement ps, int column) - throws SQLException, IllegalAccessException { - ps.setObject(column, _field.get(po)); + protected static class IntArrayMarshaller extends FieldMarshaller { + public int[] getFromObject (Object po) + throws IllegalArgumentException, IllegalAccessException { + return (int[]) _field.get(po); } - public void getValue (ResultSet rs, Object po) - throws SQLException, IllegalAccessException { - _field.set(po, rs.getObject(getColumnName())); + public int[] getFromSet (ResultSet rs) + throws SQLException { + return (int[]) rs.getObject(getColumnName()); + } + public void writeToObject (Object po, int[] value) + throws IllegalArgumentException, IllegalAccessException { + _field.set(po, value); + } + public void writeToStatement (PreparedStatement ps, int column, int[] value) + throws SQLException { + ps.setObject(column, value); } public String getColumnType () { return "BLOB"; } } - protected static class ByteEnumMarshaller extends FieldMarshaller { + protected static class ByteEnumMarshaller extends FieldMarshaller { public ByteEnumMarshaller (Class clazz) { try { _factmeth = clazz.getMethod("fromByte", new Class[] { Byte.TYPE }); @@ -437,22 +545,33 @@ public abstract class FieldMarshaller } } - public void setValue (Object po, PreparedStatement ps, int column) - throws SQLException, IllegalAccessException { - ps.setInt(column, ((ByteEnum)_field.get(po)).toByte()); - } - public void getValue (ResultSet rs, Object po) - throws SQLException, IllegalAccessException { - try { - _field.set(po, _factmeth.invoke(null, rs.getByte(getColumnName()))); - } catch (InvocationTargetException ite) { - throw new RuntimeException(ite); - } - } public String getColumnType () { return "TINYINT"; } + public ByteEnum getFromObject (Object po) + throws IllegalArgumentException, IllegalAccessException { + return (ByteEnum) _field.get(po); + } + public ByteEnum getFromSet (ResultSet rs) + throws SQLException { + try { + return (ByteEnum) _factmeth.invoke(null, rs.getByte(getColumnName())); + } catch (SQLException se) { + throw se; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + public void writeToObject (Object po, ByteEnum value) + throws IllegalArgumentException, IllegalAccessException { + _field.set(po, value); + } + public void writeToStatement (PreparedStatement ps, int column, ByteEnum value) + throws SQLException { + ps.setByte(column, value.toByte()); + } + protected Method _factmeth; } diff --git a/src/java/com/samskivert/jdbc/depot/FindAllQuery.java b/src/java/com/samskivert/jdbc/depot/FindAllQuery.java new file mode 100644 index 0000000..7d22590 --- /dev/null +++ b/src/java/com/samskivert/jdbc/depot/FindAllQuery.java @@ -0,0 +1,219 @@ +// +// $Id$ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +package com.samskivert.jdbc.depot; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import net.sf.ehcache.Cache; +import net.sf.ehcache.Element; + +import com.samskivert.io.PersistenceException; +import com.samskivert.jdbc.JDBCUtil; +import com.samskivert.jdbc.depot.operator.Logic.*; +import com.samskivert.jdbc.depot.clause.FieldOverride; +import com.samskivert.jdbc.depot.clause.Join; +import com.samskivert.jdbc.depot.clause.QueryClause; +import com.samskivert.jdbc.depot.clause.Where; +import com.samskivert.jdbc.depot.expression.SQLExpression; +import com.samskivert.util.ArrayUtil; + +/** + * This class implements the functionality required by {@link DepotRepository#findAll): fetch + * a collection of persistent objects using one of two included strategies. + */ +public abstract class FindAllQuery + implements Query> +{ + /** + * The two-pass collection query implementation. {@see DepotRepository#findAll} for details. + */ + public static class WithCache extends FindAllQuery + { + public WithCache (PersistenceContext ctx, Class type, QueryClause[] clauses) + throws PersistenceException + { + super(ctx, type); + _types = SQLQueryBuilder.getDepotTypes(ctx, type, clauses); + _clauses = clauses; + } + + public List invoke (Connection conn) throws SQLException { + SQLQueryBuilder keyFieldQuery = new SQLQueryBuilder( + _ctx, _types, _marsh.getPrimaryKeyColumns(), _clauses); + PreparedStatement stmt = keyFieldQuery.prepare(conn); + + Map, T> entities = new HashMap, T>(); + List> allKeys = new ArrayList>(); + List> fetchKeys = new ArrayList>(); + try { + ResultSet rs = stmt.executeQuery(); + while (rs.next()) { + Key key = _marsh.makePrimaryKey(rs); + allKeys.add(key); + + // TODO: All this cache fiddling needs to move to PersistenceContext? + Cache cache = _ctx.getCache(key.getCacheId()); + Element hit = cache.get(key.getCacheKey()); + if (hit != null) { + @SuppressWarnings("unchecked") T value = (T) hit.getValue(); + if (value != null) { + @SuppressWarnings("unchecked") T newValue = (T) value.clone(); + entities.put(key, newValue); + continue; + } + } + + fetchKeys.add(key); + } + + } finally { + JDBCUtil.close(stmt); + } + + if (fetchKeys.size() > 0) { + int jj = 0; + QueryClause[] newClauses = new QueryClause[_clauses.length + 1]; + // a select subset of query clauses are preserved for the entity query + for (int ii = 0; ii < _clauses.length; ii ++) { + if (_clauses[ii] instanceof Join || _clauses[ii] instanceof FieldOverride) { + newClauses[jj ++] = _clauses[ii]; + } + } + SQLExpression[] keyArray = fetchKeys.toArray(new SQLExpression[0]); + + // add our special key-matching where clause + newClauses[jj ++] = new Where(new Or(keyArray)); + newClauses = ArrayUtil.splice(newClauses, jj); + + // build the new query + SQLQueryBuilder entityQuery = new SQLQueryBuilder( + _ctx, _types, _marsh.getFieldNames(), newClauses); + + // and execute it + stmt = entityQuery.prepare(conn); + + try { + ResultSet rs = stmt.executeQuery(); + for (Key key : fetchKeys) { + if (!rs.next()) { + throw new SQLException("Expecting more rows in result set."); + } + entities.put(key, _marsh.createObject(rs)); + } + + } finally { + JDBCUtil.close(stmt); + } + } + + List result = new ArrayList(); + for (Key key : allKeys) { + result.add(entities.get(key)); + } + return result; + } + + protected DepotTypes _types; + protected QueryClause[] _clauses; + } + + /** + * The single-pass collection query implementation. {@see DepotRepository#findAll} for details. + */ + public static class Explicitly extends FindAllQuery + { + public Explicitly (PersistenceContext ctx, Class type, QueryClause[] clauses) + throws PersistenceException + { + super(ctx, type); + DepotTypes> types = SQLQueryBuilder.getDepotTypes(ctx, type, clauses); + _builder = new SQLQueryBuilder>(ctx, types, _marsh.getFieldNames(), clauses); + } + + public List invoke (Connection conn) throws SQLException { + PreparedStatement stmt = _builder.prepare(conn); + + List result = new ArrayList(); + try { + ResultSet rs = stmt.executeQuery(); + while (rs.next()) { + result.add(_marsh.createObject(rs)); + } + + } finally { + JDBCUtil.close(stmt); + } + return result; + } + + protected SQLQueryBuilder> _builder; + } + + public FindAllQuery (PersistenceContext ctx, Class type) + throws PersistenceException + { + _ctx = ctx; + _marsh = _ctx.getMarshaller(type); + } + + // from Query + public CacheKey getCacheKey () + { + return null; + } + + // from Query + public void updateCache (PersistenceContext ctx, List result) { + if (_marsh.hasPrimaryKey()) { + for (T bit : result) { + ctx.cacheStore(_marsh.getPrimaryKey(bit), bit.clone()); + } + } + } + + // from Query + public List transformCacheHit (CacheKey key, List bits) + { + if (bits == null) { + return bits; + } + + List result = new ArrayList(); + for (T bit : bits) { + if (bit != null) { + @SuppressWarnings("unchecked") T cbit = (T) bit.clone(); + result.add(cbit); + } else { + result.add(null); + } + } + return result; + } + + protected PersistenceContext _ctx; + protected DepotMarshaller _marsh; +} diff --git a/src/java/com/samskivert/jdbc/depot/FindOneQuery.java b/src/java/com/samskivert/jdbc/depot/FindOneQuery.java new file mode 100644 index 0000000..9cbe52d --- /dev/null +++ b/src/java/com/samskivert/jdbc/depot/FindOneQuery.java @@ -0,0 +1,100 @@ +// +// $Id$ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.samskivert.jdbc.depot; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import com.samskivert.io.PersistenceException; +import com.samskivert.jdbc.JDBCUtil; +import com.samskivert.jdbc.depot.clause.QueryClause; + +/** + * The implementation of {@link DepotRepository#find) functionality. + */ +public class FindOneQuery + implements Query +{ + public FindOneQuery (PersistenceContext ctx, Class type, QueryClause[] clauses) + throws PersistenceException + { + _marsh = ctx.getMarshaller(type); + DepotTypes types = SQLQueryBuilder.getDepotTypes(ctx, type, clauses); + _builder = new SQLQueryBuilder(ctx, types, _marsh.getFieldNames(), clauses); + } + + // from Query + public CacheKey getCacheKey () + { + if (_builder.where != null && _builder.where instanceof CacheKey) { + return (CacheKey) _builder.where; + } + return null; + } + + // from Query + public T invoke (Connection conn) throws SQLException { + PreparedStatement stmt = _builder.prepare(conn); + try { + T result = null; + ResultSet rs = stmt.executeQuery(); + if (rs.next()) { + result = _marsh.createObject(rs); + } + // TODO: if (rs.next()) issue warning? + rs.close(); + return result; + + } finally { + JDBCUtil.close(stmt); + } + } + + // from Query + public void updateCache (PersistenceContext ctx, T result) { + CacheKey key = getCacheKey(); + if (key == null) { + // no row-specific cache key was given + if (result == null || !_marsh.hasPrimaryKey()) { + return; + } + // if we can, create a key from what was actually returned + key = _marsh.getPrimaryKey(result); + } + ctx.cacheStore(key, (result != null) ? result.clone() : null); + } + + // from Query + public T transformCacheHit (CacheKey key, T value) + { + if (value == null) { + return null; + } + // we do not want to return a reference to the actual cached entity so we clone it + @SuppressWarnings("unchecked") T cvalue = (T) value.clone(); + return cvalue; + } + + protected DepotMarshaller _marsh; + protected SQLQueryBuilder _builder; +} diff --git a/src/java/com/samskivert/jdbc/depot/Key.java b/src/java/com/samskivert/jdbc/depot/Key.java index 4e62e3a..55f2617 100644 --- a/src/java/com/samskivert/jdbc/depot/Key.java +++ b/src/java/com/samskivert/jdbc/depot/Key.java @@ -32,17 +32,18 @@ import java.util.Map; import com.samskivert.jdbc.depot.annotation.Id; import com.samskivert.jdbc.depot.clause.Where; +import com.samskivert.jdbc.depot.expression.SQLExpression; import com.samskivert.util.StringUtil; /** - * A special form of {@link Where} clause that uniquely specifices a single database row and + * A special form of {@link Where} clause that uniquely specifies a single database row and * thus also a single persistent object. Because it implements both {@link CacheKey} and * {@link CacheInvalidator} it also uniquely indexes into the cache and knows how to invalidate * itself upon modification. This class is created by many {@link DepotMarshaller} methods as * a convenience, and may also be instantiated explicitly. */ public class Key extends Where - implements CacheKey, CacheInvalidator, Serializable + implements SQLExpression, CacheKey, CacheInvalidator, Serializable { /** * Constructs a new single-column {@code Key} with the given value. @@ -109,18 +110,33 @@ public class Key extends Where } // from QueryClause - public Collection> getClassSet () + public void addClasses (Collection> classSet) { - ArrayList> set = - new ArrayList>(); - set.add(_pClass); - return set; + classSet.add(_pClass); } // from QueryClause - public void appendClause (ConstructedQuery query, StringBuilder builder) + public void appendClause (QueryBuilderContext query, StringBuilder builder) { builder.append(" where "); + appendExpression(query, builder); + } + + // from QueryClause + public int bindClauseArguments (PreparedStatement pstmt, int argIdx) + throws SQLException + { + for (int ii = 0; ii < _values.length; ii ++) { + if (_values[ii] != null) { + pstmt.setObject(argIdx ++, _values[ii]); + } + } + return argIdx; + } + + // from SQLExpression + public void appendExpression (QueryBuilderContext query, StringBuilder builder) + { String[] keyFields = getKeyFields(_pClass); for (int ii = 0; ii < keyFields.length; ii ++) { if (ii > 0) { @@ -130,16 +146,11 @@ public class Key extends Where } } - // from QueryClause - public int bindArguments (PreparedStatement pstmt, int argIdx) + // from SQLExpression + public int bindExpressionArguments (PreparedStatement pstmt, int argIdx) throws SQLException { - for (int ii = 0; ii < _values.length; ii ++) { - if (_values[ii] != null) { - pstmt.setObject(argIdx ++, _values[ii]); - } - } - return argIdx; + return bindClauseArguments(pstmt, argIdx); } // from CacheKey diff --git a/src/java/com/samskivert/jdbc/depot/MultiKey.java b/src/java/com/samskivert/jdbc/depot/MultiKey.java index 8f226be..838a821 100644 --- a/src/java/com/samskivert/jdbc/depot/MultiKey.java +++ b/src/java/com/samskivert/jdbc/depot/MultiKey.java @@ -85,16 +85,13 @@ public class MultiKey extends Where } // from QueryClause - public Collection> getClassSet () + public void addClasses (Collection> classSet) { - ArrayList> set = - new ArrayList>(); - set.add(_pClass); - return set; + classSet.add(_pClass); } // from QueryClause - public void appendClause (ConstructedQuery query, StringBuilder builder) + public void appendClause (QueryBuilderContext query, StringBuilder builder) { builder.append(" where "); boolean first = true; @@ -121,7 +118,7 @@ public class MultiKey extends Where } // from QueryClause - public int bindArguments (PreparedStatement pstmt, int argIdx) + public int bindExpressionArguments (PreparedStatement pstmt, int argIdx) throws SQLException { for (Map.Entry entry : _map.entrySet()) { diff --git a/src/java/com/samskivert/jdbc/depot/PersistenceContext.java b/src/java/com/samskivert/jdbc/depot/PersistenceContext.java index eed1416..4dedf88 100644 --- a/src/java/com/samskivert/jdbc/depot/PersistenceContext.java +++ b/src/java/com/samskivert/jdbc/depot/PersistenceContext.java @@ -257,7 +257,8 @@ public class PersistenceContext if (listeners != null && listeners.size() > 0) { for (CacheListener listener : listeners) { Log.debug("cacheInvalidate: cascading [listener=" + listener + "]"); - @SuppressWarnings("unchecked") CacheListener casted = (CacheListener)listener; + @SuppressWarnings("unchecked") + CacheListener casted = (CacheListener)listener; casted.entryCached(key, entry, oldEntry); } } diff --git a/src/java/com/samskivert/jdbc/depot/Query.java b/src/java/com/samskivert/jdbc/depot/Query.java index 83b95d3..9ea85f8 100644 --- a/src/java/com/samskivert/jdbc/depot/Query.java +++ b/src/java/com/samskivert/jdbc/depot/Query.java @@ -29,12 +29,19 @@ import java.sql.SQLException; public interface Query { /** - * Returns the {@link CacheKey} associated with this query, if relevant, or null. + * Any query may elect to utilize the built-in cache by returning a non-null {@link CacheKey} + * in this method. This is done automatically by the {@link DepotRepository} when looking up + * single entities by primary key, but even entire collections can be cached under a single + * key. + * + * Great care must be taken to invalidate such cached collections when their constituent + * entities are invalidated. This is generally done using {@link CacheListener} and + * {@link CacheInvalidator}. */ public CacheKey getCacheKey (); /** - * Performs the actual JDBC operations associated with this query. + * Performs the actual JDBC operations associated with this query. */ public T invoke (Connection conn) throws SQLException; diff --git a/src/java/com/samskivert/jdbc/depot/QueryBuilderContext.java b/src/java/com/samskivert/jdbc/depot/QueryBuilderContext.java new file mode 100644 index 0000000..966d7d0 --- /dev/null +++ b/src/java/com/samskivert/jdbc/depot/QueryBuilderContext.java @@ -0,0 +1,43 @@ +// +// $Id$ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.samskivert.jdbc.depot; + +/** + * This type is used during the construction of SQL queries to map persistent classes + * to table names and table abbreviations. Its primary implementation is {@link DepotTypes}. + */ +public interface QueryBuilderContext +{ + /** + * Maps a class referenced by this query to its associated table. + * + * This method is called by individual clauses. + */ + public String getTableName (Class cl); + + /** + * Maps a class referenced by this query to the abbreviation used for its associated table. The + * abbreviation is meaningful only within the scope of this particular query. + * + * This method is called by individual clauses. + */ + public String getTableAbbreviation (Class cl); +} diff --git a/src/java/com/samskivert/jdbc/depot/SQLQueryBuilder.java b/src/java/com/samskivert/jdbc/depot/SQLQueryBuilder.java new file mode 100644 index 0000000..2134747 --- /dev/null +++ b/src/java/com/samskivert/jdbc/depot/SQLQueryBuilder.java @@ -0,0 +1,288 @@ +// +// $Id$ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2006 Michael Bayne +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.samskivert.jdbc.depot; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.samskivert.io.PersistenceException; +import com.samskivert.jdbc.depot.annotation.Computed; +import com.samskivert.jdbc.depot.clause.FieldOverride; +import com.samskivert.jdbc.depot.clause.ForUpdate; +import com.samskivert.jdbc.depot.clause.FromOverride; +import com.samskivert.jdbc.depot.clause.GroupBy; +import com.samskivert.jdbc.depot.clause.Join; +import com.samskivert.jdbc.depot.clause.Limit; +import com.samskivert.jdbc.depot.clause.OrderBy; +import com.samskivert.jdbc.depot.clause.QueryClause; +import com.samskivert.jdbc.depot.clause.Where; + +/** + * Builds actual SQL given a main persistent type and some {@link QueryClause} objects. + */ +public class SQLQueryBuilder +{ + /** The from override clause, if any. */ + public FromOverride fromOverride; + + /** The where clause. */ + public Where where; + + /** A list of join clauses, each potentially referencing a new class. */ + public List joinClauses = new ArrayList(); + + /** The order by clause, if any. */ + public OrderBy orderBy; + + /** The group by clause, if any. */ + public GroupBy groupBy; + + /** The limit clause, if any. */ + public Limit limit; + + /** The For Update clause, if any. */ + public ForUpdate forUpdate; + + /** + * Constructs a {@link DepotTypes} object for your convenience given similar arguments used to + * construct a {@link SQLQueryBuilder}. This method mainly offloads the task of interrogating + * the various clauses for what persistent classes they introduce into the query. + */ + public static DepotTypes getDepotTypes ( + PersistenceContext ctx, Class type, QueryClause... clauses) + throws PersistenceException + { + Set> classSet = + new HashSet>(); + if (type != null) { + classSet.add(type); + } + + for (QueryClause clause : clauses) { + if (clause != null) { + clause.addClasses(classSet); + } + } + + return new DepotTypes(ctx, type, classSet); + } + + /** + * Creates a new Query object to generate one or more instances of the specified persistent + * class, as dictated by the key and query clauses. A persistence context is supplied for + * instantiation of marshallers, which may trigger table creations and schema migrations. + */ + public SQLQueryBuilder (PersistenceContext ctx, DepotTypes types, String[] fields, + QueryClause... clauses) + { + _builder = new StringBuilder("select "); + _types = types; + + // iterate over the clauses and sort them into the different types we understand + for (QueryClause clause : clauses) { + if (clause == null) { + continue; + } + if (clause instanceof Where) { + if (where != null) { + throw new IllegalArgumentException( + "Query can't contain multiple Where clauses."); + } + where = (Where) clause; + + } else if (clause instanceof FromOverride) { + if (fromOverride != null) { + throw new IllegalArgumentException( + "Query can't contain multiple FromOverride clauses."); + } + fromOverride = (FromOverride) clause; + + } else if (clause instanceof Join) { + joinClauses.add((Join) clause); + + } else if (clause instanceof FieldOverride) { + _disMap.put(((FieldOverride) clause).getField(), + ((FieldOverride) clause)); + + } else if (clause instanceof OrderBy) { + if (orderBy != null) { + throw new IllegalArgumentException( + "Query can't contain multiple OrderBy clauses."); + } + orderBy = (OrderBy) clause; + + } else if (clause instanceof GroupBy) { + if (groupBy != null) { + throw new IllegalArgumentException( + "Query can't contain multiple GroupBy clauses."); + } + groupBy = (GroupBy) clause; + + } else if (clause instanceof Limit) { + if (limit != null) { + throw new IllegalArgumentException( + "Query can't contain multiple Limit clauses."); + } + limit = (Limit) clause; + + } else if (clause instanceof ForUpdate) { + if (forUpdate != null) { + throw new IllegalArgumentException( + "Query can't contain multiple For Update clauses."); + } + forUpdate = (ForUpdate) clause; + } + } + + Computed entityComputed = _types.getMainType().getAnnotation(Computed.class); + + // iterate over the fields we're filling in and figure out whence each one comes + boolean skip = true; + for (String field : fields) { + if (!skip) { + _builder.append(", "); + } + skip = false; + + // first, see if there's a field override + FieldOverride clause1 = _disMap.get(field); + if (clause1 != null) { + clause1.appendClause(_types, _builder); + 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 tableClass = + entityComputed == null ? _types.getMainType() : entityComputed.shadowOf(); + + // handle the field-level @Computed annotation, if there is one + Computed fieldComputed = + _types.getMainMarshaller().getFieldMarshaller(field).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 ").append(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) { + String tableName = _types.getTableAbbreviation(tableClass); + _builder.append(tableName).append(".").append(field); + continue; + } + + // else owie + throw new IllegalArgumentException( + "Persistent field has no definition [class=" + _types.getMainType() + + ", field=" + field + "]"); + } + } + + public PreparedStatement prepare (Connection conn) + throws SQLException + { + if (fromOverride != null) { + fromOverride.appendClause(_types, _builder); + + } else if (_types.getMainTableName() != null) { + _builder.append(" from ").append(_types.getMainTableName()). + append(" as ").append(_types.getMainTableAbbreviation()); + + } else { + throw new SQLException("Query on @Computed entity with no FromOverrideClause."); + } + + for (Join clause : joinClauses) { + clause.appendClause(_types, _builder); + } + if (where != null) { + where.appendClause(_types, _builder); + } + if (groupBy != null) { + groupBy.appendClause(_types, _builder); + } + if (orderBy != null) { + orderBy.appendClause(_types, _builder); + } + if (limit != null) { + limit.appendClause(_types, _builder); + } + if (forUpdate != null) { + forUpdate.appendClause(_types, _builder); + } + + PreparedStatement pstmt = conn.prepareStatement(_builder.toString()); + int argIdx = 1; + for (Join clause : joinClauses) { + argIdx = clause.bindClauseArguments(pstmt, argIdx); + } + if (where != null) { + argIdx = where.bindClauseArguments(pstmt, argIdx); + } + if (groupBy != null) { + argIdx = groupBy.bindClauseArguments(pstmt, argIdx); + } + if (orderBy != null) { + argIdx = orderBy.bindClauseArguments(pstmt, argIdx); + } + if (limit != null) { + argIdx = limit.bindClauseArguments(pstmt, argIdx); + } + if (forUpdate != null) { + argIdx = forUpdate.bindClauseArguments(pstmt, argIdx); + } + + return pstmt; + } + + /** A StringBuilder to hold the constructed SQL. */ + protected StringBuilder _builder; + + /** The class that maps persistent classes to marshallers. */ + protected DepotTypes _types; + + /** Persistent class fields mapped to field override clauses. */ + protected Map _disMap = new HashMap(); +} diff --git a/src/java/com/samskivert/jdbc/depot/annotation/Computed.java b/src/java/com/samskivert/jdbc/depot/annotation/Computed.java index e7a3fa9..83e676e 100644 --- a/src/java/com/samskivert/jdbc/depot/annotation/Computed.java +++ b/src/java/com/samskivert/jdbc/depot/annotation/Computed.java @@ -25,13 +25,13 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import com.samskivert.jdbc.depot.ConstructedQuery; +import com.samskivert.jdbc.depot.QueryBuilderContext; 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}. + * QueryBuilderContext}. */ @Retention(value=RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD, ElementType.TYPE }) diff --git a/src/java/com/samskivert/jdbc/depot/annotation/Index.java b/src/java/com/samskivert/jdbc/depot/annotation/Index.java index cc46a7e..208a62f 100644 --- a/src/java/com/samskivert/jdbc/depot/annotation/Index.java +++ b/src/java/com/samskivert/jdbc/depot/annotation/Index.java @@ -20,7 +20,6 @@ package com.samskivert.jdbc.depot.annotation; -import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; diff --git a/src/java/com/samskivert/jdbc/depot/clause/FieldOverride.java b/src/java/com/samskivert/jdbc/depot/clause/FieldOverride.java index 565c14a..9259964 100644 --- a/src/java/com/samskivert/jdbc/depot/clause/FieldOverride.java +++ b/src/java/com/samskivert/jdbc/depot/clause/FieldOverride.java @@ -20,8 +20,12 @@ package com.samskivert.jdbc.depot.clause; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Collection; + import com.samskivert.io.PersistenceException; -import com.samskivert.jdbc.depot.ConstructedQuery; +import com.samskivert.jdbc.depot.QueryBuilderContext; import com.samskivert.jdbc.depot.PersistentRecord; import com.samskivert.jdbc.depot.expression.ColumnExp; import com.samskivert.jdbc.depot.expression.LiteralExp; @@ -65,7 +69,7 @@ public class FieldOverride extends QueryClause } // from QueryClause - public void appendClause (ConstructedQuery query, StringBuilder builder) + public void appendClause (QueryBuilderContext query, StringBuilder builder) { _override.appendExpression(query, builder); builder.append(" as ").append(_field); diff --git a/src/java/com/samskivert/jdbc/depot/clause/ForUpdate.java b/src/java/com/samskivert/jdbc/depot/clause/ForUpdate.java index d8289f0..e57d8c3 100644 --- a/src/java/com/samskivert/jdbc/depot/clause/ForUpdate.java +++ b/src/java/com/samskivert/jdbc/depot/clause/ForUpdate.java @@ -20,7 +20,12 @@ package com.samskivert.jdbc.depot.clause; -import com.samskivert.jdbc.depot.ConstructedQuery; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Collection; + +import com.samskivert.jdbc.depot.QueryBuilderContext; +import com.samskivert.jdbc.depot.PersistentRecord; /** * Represents a FOR UPDATE clause. @@ -28,7 +33,7 @@ import com.samskivert.jdbc.depot.ConstructedQuery; public class ForUpdate extends QueryClause { // from QueryClause - public void appendClause (ConstructedQuery query, StringBuilder builder) + public void appendClause (QueryBuilderContext query, StringBuilder builder) { builder.append(" for update "); } diff --git a/src/java/com/samskivert/jdbc/depot/clause/FromOverride.java b/src/java/com/samskivert/jdbc/depot/clause/FromOverride.java index 9033446..d3f2ea4 100644 --- a/src/java/com/samskivert/jdbc/depot/clause/FromOverride.java +++ b/src/java/com/samskivert/jdbc/depot/clause/FromOverride.java @@ -20,11 +20,13 @@ package com.samskivert.jdbc.depot.clause; +import java.sql.PreparedStatement; +import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import com.samskivert.io.PersistenceException; -import com.samskivert.jdbc.depot.ConstructedQuery; +import com.samskivert.jdbc.depot.QueryBuilderContext; import com.samskivert.jdbc.depot.PersistentRecord; /** @@ -53,13 +55,13 @@ public class FromOverride extends QueryClause } // from QueryClause - public Collection> getClassSet () + public void addClasses (Collection> classSet) { - return _fromClasses; + classSet.addAll(_fromClasses); } // from QueryClause - public void appendClause (ConstructedQuery query, StringBuilder builder) + public void appendClause (QueryBuilderContext query, StringBuilder builder) { builder.append(" from " ); for (int ii = 0; ii < _fromClasses.size(); ii++) { @@ -74,4 +76,5 @@ public class FromOverride extends QueryClause /** The classes of the tables we're selecting from. */ protected ArrayList> _fromClasses = new ArrayList>(); + } diff --git a/src/java/com/samskivert/jdbc/depot/clause/GroupBy.java b/src/java/com/samskivert/jdbc/depot/clause/GroupBy.java index a21f8f1..915e030 100644 --- a/src/java/com/samskivert/jdbc/depot/clause/GroupBy.java +++ b/src/java/com/samskivert/jdbc/depot/clause/GroupBy.java @@ -22,8 +22,10 @@ package com.samskivert.jdbc.depot.clause; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.util.Collection; -import com.samskivert.jdbc.depot.ConstructedQuery; +import com.samskivert.jdbc.depot.QueryBuilderContext; +import com.samskivert.jdbc.depot.PersistentRecord; import com.samskivert.jdbc.depot.expression.SQLExpression; /** @@ -37,7 +39,7 @@ public class GroupBy extends QueryClause } // from QueryClause - public void appendClause (ConstructedQuery query, StringBuilder builder) + public void appendClause (QueryBuilderContext query, StringBuilder builder) { builder.append(" group by "); for (int ii = 0; ii < _values.length; ii++) { @@ -49,11 +51,11 @@ public class GroupBy extends QueryClause } // from QueryClause - public int bindArguments (PreparedStatement pstmt, int argIdx) + public int bindClauseArguments (PreparedStatement pstmt, int argIdx) throws SQLException { for (int ii = 0; ii < _values.length; ii++) { - argIdx = _values[ii].bindArguments(pstmt, argIdx); + argIdx = _values[ii].bindExpressionArguments(pstmt, argIdx); } return argIdx; } diff --git a/src/java/com/samskivert/jdbc/depot/clause/Join.java b/src/java/com/samskivert/jdbc/depot/clause/Join.java index 06e92fa..9d5996c 100644 --- a/src/java/com/samskivert/jdbc/depot/clause/Join.java +++ b/src/java/com/samskivert/jdbc/depot/clause/Join.java @@ -22,15 +22,14 @@ package com.samskivert.jdbc.depot.clause; import java.sql.PreparedStatement; import java.sql.SQLException; -import java.util.ArrayList; import java.util.Collection; import com.samskivert.io.PersistenceException; -import com.samskivert.jdbc.depot.ConstructedQuery; +import com.samskivert.jdbc.depot.QueryBuilderContext; import com.samskivert.jdbc.depot.PersistentRecord; import com.samskivert.jdbc.depot.expression.ColumnExp; +import com.samskivert.jdbc.depot.expression.SQLExpression; import com.samskivert.jdbc.depot.operator.Conditionals.*; -import com.samskivert.jdbc.depot.operator.SQLOperator; /** * Represents a JOIN. @@ -55,19 +54,16 @@ public class Join extends QueryClause _joinCondition = new Equals(primary, join); } - public Join (Class joinClass, SQLOperator joinCondition) + public Join (Class joinClass, SQLExpression joinCondition) { _joinClass = joinClass; _joinCondition = joinCondition; } // from QueryClause - public Collection> getClassSet () + public void addClasses (Collection> classSet) { - ArrayList> set = - new ArrayList>(); - set.add(_joinClass); - return set; + classSet.add(_joinClass); } /** @@ -80,7 +76,7 @@ public class Join extends QueryClause } // from QueryClause - public void appendClause (ConstructedQuery query, StringBuilder builder) + public void appendClause (QueryBuilderContext query, StringBuilder builder) { switch (_type) { case INNER: @@ -99,10 +95,10 @@ public class Join extends QueryClause } // from QueryClause - public int bindArguments (PreparedStatement pstmt, int argIdx) + public int bindClauseArguments (PreparedStatement pstmt, int argIdx) throws SQLException { - return _joinCondition.bindArguments(pstmt, argIdx); + return _joinCondition.bindExpressionArguments(pstmt, argIdx); } /** Indicates the type of join to be performed. */ @@ -112,5 +108,5 @@ public class Join extends QueryClause protected Class _joinClass; /** The condition used to join in the new table. */ - protected SQLOperator _joinCondition; + protected SQLExpression _joinCondition; } diff --git a/src/java/com/samskivert/jdbc/depot/clause/Limit.java b/src/java/com/samskivert/jdbc/depot/clause/Limit.java index 936524b..78c6a43 100644 --- a/src/java/com/samskivert/jdbc/depot/clause/Limit.java +++ b/src/java/com/samskivert/jdbc/depot/clause/Limit.java @@ -22,8 +22,10 @@ package com.samskivert.jdbc.depot.clause; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.util.Collection; -import com.samskivert.jdbc.depot.ConstructedQuery; +import com.samskivert.jdbc.depot.QueryBuilderContext; +import com.samskivert.jdbc.depot.PersistentRecord; /** * Represents a LIMIT/OFFSET clause, for pagination. @@ -37,13 +39,13 @@ public class Limit extends QueryClause } // from QueryClause - public void appendClause (ConstructedQuery query, StringBuilder builder) + public void appendClause (QueryBuilderContext query, StringBuilder builder) { builder.append(" limit ? offset ? "); } // from QueryClause - public int bindArguments (PreparedStatement pstmt, int argIdx) + public int bindClauseArguments (PreparedStatement pstmt, int argIdx) throws SQLException { pstmt.setObject(argIdx++, _count); diff --git a/src/java/com/samskivert/jdbc/depot/clause/OrderBy.java b/src/java/com/samskivert/jdbc/depot/clause/OrderBy.java index f834e1e..1c1652f 100644 --- a/src/java/com/samskivert/jdbc/depot/clause/OrderBy.java +++ b/src/java/com/samskivert/jdbc/depot/clause/OrderBy.java @@ -22,8 +22,10 @@ package com.samskivert.jdbc.depot.clause; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.util.Collection; -import com.samskivert.jdbc.depot.ConstructedQuery; +import com.samskivert.jdbc.depot.QueryBuilderContext; +import com.samskivert.jdbc.depot.PersistentRecord; import com.samskivert.jdbc.depot.expression.ColumnExp; import com.samskivert.jdbc.depot.expression.LiteralExp; import com.samskivert.jdbc.depot.expression.SQLExpression; @@ -83,7 +85,7 @@ public class OrderBy extends QueryClause } // from QueryClause - public void appendClause (ConstructedQuery query, StringBuilder builder) + public void appendClause (QueryBuilderContext query, StringBuilder builder) { builder.append(" order by "); for (int ii = 0; ii < _values.length; ii++) { @@ -96,11 +98,11 @@ public class OrderBy extends QueryClause } // from QueryClause - public int bindArguments (PreparedStatement pstmt, int argIdx) + public int bindClauseArguments (PreparedStatement pstmt, int argIdx) throws SQLException { for (int ii = 0; ii < _values.length; ii++) { - argIdx = _values[ii].bindArguments(pstmt, argIdx); + argIdx = _values[ii].bindExpressionArguments(pstmt, argIdx); } return argIdx; } diff --git a/src/java/com/samskivert/jdbc/depot/clause/QueryClause.java b/src/java/com/samskivert/jdbc/depot/clause/QueryClause.java index 83052e1..e6baa3b 100644 --- a/src/java/com/samskivert/jdbc/depot/clause/QueryClause.java +++ b/src/java/com/samskivert/jdbc/depot/clause/QueryClause.java @@ -2,7 +2,7 @@ // $Id$ // // samskivert library - useful routines for java programs -// Copyright (C) 2006 Michael Bayne, Pär Winzell +// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell // // This library is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -24,7 +24,7 @@ import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Collection; -import com.samskivert.jdbc.depot.ConstructedQuery; +import com.samskivert.jdbc.depot.QueryBuilderContext; import com.samskivert.jdbc.depot.PersistentRecord; /** @@ -33,26 +33,24 @@ import com.samskivert.jdbc.depot.PersistentRecord; public abstract class QueryClause { /** - * Return a set of all persistent classes referenced by this clause. The default implementation - * returns null to indicate that no classes are rererenced. + * Adds all persistent classes referenced by this clause, if any, to the supplied set. */ - public Collection> getClassSet () + public void addClasses (Collection> classSet) { - return null; } - - /** - * Construct the SQL form of this query clause. The implementor is expected to call methods on - * the Query object to e.g. resolve current table abbreviations associated with classes. - */ - public abstract void appendClause (ConstructedQuery query, StringBuilder builder); /** - * Bind any objects that were referenced in the generated SQL. For each ? that appears in the + * Constructs the SQL form of this query clause. The implementor is expected to call methods on + * the Query object to e.g. resolve current table abbreviations associated with classes. + */ + public abstract void appendClause (QueryBuilderContext query, StringBuilder builder); + + /** + * Binds any objects that were referenced in the generated SQL. For each ? that appears in the * SQL, precisely one parameter must be claimed and bound in this method, and argIdx * incremented and returned. The default implementation binds nothing. */ - public int bindArguments (PreparedStatement pstmt, int argIdx) + public int bindClauseArguments (PreparedStatement pstmt, int argIdx) throws SQLException { return argIdx; diff --git a/src/java/com/samskivert/jdbc/depot/clause/Where.java b/src/java/com/samskivert/jdbc/depot/clause/Where.java index e222634..7c0fdfe 100644 --- a/src/java/com/samskivert/jdbc/depot/clause/Where.java +++ b/src/java/com/samskivert/jdbc/depot/clause/Where.java @@ -22,16 +22,17 @@ package com.samskivert.jdbc.depot.clause; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.util.Collection; -import com.samskivert.jdbc.depot.ConstructedQuery; +import com.samskivert.jdbc.depot.QueryBuilderContext; import com.samskivert.jdbc.depot.PersistentRecord; import com.samskivert.jdbc.depot.clause.QueryClause; import com.samskivert.jdbc.depot.expression.ColumnExp; +import com.samskivert.jdbc.depot.expression.SQLExpression; import com.samskivert.jdbc.depot.expression.ValueExp; import com.samskivert.jdbc.depot.operator.Conditionals.Equals; import com.samskivert.jdbc.depot.operator.Conditionals.IsNull; import com.samskivert.jdbc.depot.operator.Logic.And; -import com.samskivert.jdbc.depot.operator.SQLOperator; /** * Represents a where clause: the condition can be any comparison operator or logical combination @@ -80,28 +81,28 @@ public class Where extends QueryClause this(toCondition(columns, values)); } - public Where (SQLOperator condition) + public Where (SQLExpression condition) { _condition = condition; } // from QueryClause - public void appendClause (ConstructedQuery query, StringBuilder builder) + public void appendClause (QueryBuilderContext query, StringBuilder builder) { builder.append(" where "); _condition.appendExpression(query, builder); } // from QueryClause - public int bindArguments (PreparedStatement pstmt, int argIdx) + public int bindClauseArguments (PreparedStatement pstmt, int argIdx) throws SQLException { - return _condition.bindArguments(pstmt, argIdx); + return _condition.bindExpressionArguments(pstmt, argIdx); } - protected static SQLOperator toCondition (ColumnExp[] columns, Comparable[] values) + protected static SQLExpression toCondition (ColumnExp[] columns, Comparable[] values) { - SQLOperator[] comparisons = new SQLOperator[columns.length]; + SQLExpression[] comparisons = new SQLExpression[columns.length]; for (int ii = 0; ii < columns.length; ii ++) { comparisons[ii] = (values[ii] == null) ? new IsNull(columns[ii]) : new Equals(columns[ii], new ValueExp(values[ii])); @@ -109,5 +110,5 @@ public class Where extends QueryClause return new And(comparisons); } - protected SQLOperator _condition; + protected SQLExpression _condition; } diff --git a/src/java/com/samskivert/jdbc/depot/expression/ColumnExp.java b/src/java/com/samskivert/jdbc/depot/expression/ColumnExp.java index 6c08740..652f4c1 100644 --- a/src/java/com/samskivert/jdbc/depot/expression/ColumnExp.java +++ b/src/java/com/samskivert/jdbc/depot/expression/ColumnExp.java @@ -23,7 +23,7 @@ package com.samskivert.jdbc.depot.expression; import java.sql.PreparedStatement; import java.sql.SQLException; -import com.samskivert.jdbc.depot.ConstructedQuery; +import com.samskivert.jdbc.depot.QueryBuilderContext; import com.samskivert.jdbc.depot.PersistentRecord; /** @@ -52,7 +52,7 @@ public class ColumnExp } // from SQLExpression - public void appendExpression (ConstructedQuery query, StringBuilder builder) + public void appendExpression (QueryBuilderContext query, StringBuilder builder) { if (pClass == null || query == null) { builder.append(pColumn); @@ -63,7 +63,7 @@ public class ColumnExp } // from SQLExpression - public int bindArguments (PreparedStatement pstmt, int argIdx) + public int bindExpressionArguments (PreparedStatement pstmt, int argIdx) throws SQLException { return argIdx; diff --git a/src/java/com/samskivert/jdbc/depot/expression/FunctionExp.java b/src/java/com/samskivert/jdbc/depot/expression/FunctionExp.java index 6c9abee..dc30aab 100644 --- a/src/java/com/samskivert/jdbc/depot/expression/FunctionExp.java +++ b/src/java/com/samskivert/jdbc/depot/expression/FunctionExp.java @@ -23,7 +23,7 @@ package com.samskivert.jdbc.depot.expression; import java.sql.PreparedStatement; import java.sql.SQLException; -import com.samskivert.jdbc.depot.ConstructedQuery; +import com.samskivert.jdbc.depot.QueryBuilderContext; /** * An expression for a function, e.g. FLOOR(blah). @@ -41,7 +41,7 @@ public class FunctionExp } // from SQLExpression - public void appendExpression (ConstructedQuery query, StringBuilder builder) + public void appendExpression (QueryBuilderContext query, StringBuilder builder) { builder.append(_function); builder.append("("); @@ -55,11 +55,11 @@ public class FunctionExp } // from SQLExpression - public int bindArguments (PreparedStatement pstmt, int argIdx) + public int bindExpressionArguments (PreparedStatement pstmt, int argIdx) throws SQLException { for (int ii = 0; ii < _arguments.length; ii ++) { - argIdx = _arguments[ii].bindArguments(pstmt, argIdx); + argIdx = _arguments[ii].bindExpressionArguments(pstmt, argIdx); } return argIdx; } diff --git a/src/java/com/samskivert/jdbc/depot/expression/LiteralExp.java b/src/java/com/samskivert/jdbc/depot/expression/LiteralExp.java index b8961b7..356f8b8 100644 --- a/src/java/com/samskivert/jdbc/depot/expression/LiteralExp.java +++ b/src/java/com/samskivert/jdbc/depot/expression/LiteralExp.java @@ -23,7 +23,7 @@ package com.samskivert.jdbc.depot.expression; import java.sql.PreparedStatement; import java.sql.SQLException; -import com.samskivert.jdbc.depot.ConstructedQuery; +import com.samskivert.jdbc.depot.QueryBuilderContext; /** * An expression for things we don't support natively, e.g. COUNT(*). @@ -38,13 +38,13 @@ public class LiteralExp } // from SQLExpression - public void appendExpression (ConstructedQuery query, StringBuilder builder) + public void appendExpression (QueryBuilderContext query, StringBuilder builder) { builder.append(_text); } // from SQLExpression - public int bindArguments (PreparedStatement pstmt, int argIdx) + public int bindExpressionArguments (PreparedStatement pstmt, int argIdx) throws SQLException { return argIdx; diff --git a/src/java/com/samskivert/jdbc/depot/expression/SQLExpression.java b/src/java/com/samskivert/jdbc/depot/expression/SQLExpression.java index 84cebdb..8d5c680 100644 --- a/src/java/com/samskivert/jdbc/depot/expression/SQLExpression.java +++ b/src/java/com/samskivert/jdbc/depot/expression/SQLExpression.java @@ -23,7 +23,7 @@ package com.samskivert.jdbc.depot.expression; import java.sql.PreparedStatement; import java.sql.SQLException; -import com.samskivert.jdbc.depot.ConstructedQuery; +import com.samskivert.jdbc.depot.QueryBuilderContext; /** * Represents an SQL expression, e.g. column name, function, or constant. @@ -34,13 +34,13 @@ public interface SQLExpression * Construct the SQL form of this expression. The implementor is invited to call methods on the * Query object to e.g. resolve the current table abbreviations associated with classes. */ - public void appendExpression (ConstructedQuery query, StringBuilder builder); + public void appendExpression (QueryBuilderContext query, StringBuilder builder); /** * Bind any objects that were referenced in the generated SQL. For each ? that appears in the * SQL, precisely one parameter must be claimed and bound in this method, and argIdx * incremented and returned. */ - public int bindArguments (PreparedStatement pstmt, int argIdx) + public int bindExpressionArguments (PreparedStatement pstmt, int argIdx) throws SQLException; } diff --git a/src/java/com/samskivert/jdbc/depot/expression/ValueExp.java b/src/java/com/samskivert/jdbc/depot/expression/ValueExp.java index aafd4b1..d170def 100644 --- a/src/java/com/samskivert/jdbc/depot/expression/ValueExp.java +++ b/src/java/com/samskivert/jdbc/depot/expression/ValueExp.java @@ -23,7 +23,7 @@ package com.samskivert.jdbc.depot.expression; import java.sql.PreparedStatement; import java.sql.SQLException; -import com.samskivert.jdbc.depot.ConstructedQuery; +import com.samskivert.jdbc.depot.QueryBuilderContext; /** * A Java value that is bound as a parameter to the query, e.g. 1 or 'abc'. @@ -37,13 +37,13 @@ public class ValueExp } // from SQLExpression - public void appendExpression (ConstructedQuery query, StringBuilder builder) + public void appendExpression (QueryBuilderContext query, StringBuilder builder) { builder.append("?"); } // from SQLExpression - public int bindArguments (PreparedStatement pstmt, int argIdx) + public int bindExpressionArguments (PreparedStatement pstmt, int argIdx) throws SQLException { pstmt.setObject(argIdx ++, _value); diff --git a/src/java/com/samskivert/jdbc/depot/operator/Conditionals.java b/src/java/com/samskivert/jdbc/depot/operator/Conditionals.java index 4bc56c2..2272abf 100644 --- a/src/java/com/samskivert/jdbc/depot/operator/Conditionals.java +++ b/src/java/com/samskivert/jdbc/depot/operator/Conditionals.java @@ -24,7 +24,7 @@ import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Collection; -import com.samskivert.jdbc.depot.ConstructedQuery; +import com.samskivert.jdbc.depot.QueryBuilderContext; import com.samskivert.jdbc.depot.PersistentRecord; import com.samskivert.jdbc.depot.expression.ColumnExp; import com.samskivert.jdbc.depot.expression.SQLExpression; @@ -57,14 +57,14 @@ public abstract class Conditionals } // from SQLExpression - public void appendExpression (ConstructedQuery query, StringBuilder builder) + public void appendExpression (QueryBuilderContext query, StringBuilder builder) { _column.appendExpression(query, builder); builder.append(" is null"); } // from SQLExpression - public int bindArguments (PreparedStatement pstmt, int argIdx) + public int bindExpressionArguments (PreparedStatement pstmt, int argIdx) throws SQLException { return argIdx; @@ -233,7 +233,7 @@ public abstract class Conditionals } // from SQLExpression - public void appendExpression (ConstructedQuery query, StringBuilder builder) + public void appendExpression (QueryBuilderContext query, StringBuilder builder) { _column.appendExpression(query, builder); builder.append(" in ("); @@ -247,7 +247,7 @@ public abstract class Conditionals } // from SQLExpression - public int bindArguments (PreparedStatement pstmt, int argIdx) + public int bindExpressionArguments (PreparedStatement pstmt, int argIdx) throws SQLException { for (int ii = 0; ii < _values.length; ii++) { @@ -286,7 +286,7 @@ public abstract class Conditionals } // from SQLExpression - public void appendExpression (ConstructedQuery query, StringBuilder builder) + public void appendExpression (QueryBuilderContext query, StringBuilder builder) { builder.append("match("); int idx = 0; @@ -312,7 +312,7 @@ public abstract class Conditionals } // from SQLExpression - public int bindArguments (PreparedStatement pstmt, int argIdx) + public int bindExpressionArguments (PreparedStatement pstmt, int argIdx) throws SQLException { pstmt.setString(argIdx++, _query); diff --git a/src/java/com/samskivert/jdbc/depot/operator/Logic.java b/src/java/com/samskivert/jdbc/depot/operator/Logic.java index bd8c83f..fe558e9 100644 --- a/src/java/com/samskivert/jdbc/depot/operator/Logic.java +++ b/src/java/com/samskivert/jdbc/depot/operator/Logic.java @@ -23,7 +23,8 @@ package com.samskivert.jdbc.depot.operator; import java.sql.PreparedStatement; import java.sql.SQLException; -import com.samskivert.jdbc.depot.ConstructedQuery; +import com.samskivert.jdbc.depot.QueryBuilderContext; +import com.samskivert.jdbc.depot.expression.SQLExpression; import com.samskivert.jdbc.depot.operator.SQLOperator.MultiOperator; /** @@ -38,7 +39,7 @@ public abstract class Logic */ public static class Or extends MultiOperator { - public Or (SQLOperator... conditions) + public Or (SQLExpression... conditions) { super(conditions); } @@ -55,7 +56,7 @@ public abstract class Logic */ public static class And extends MultiOperator { - public And (SQLOperator... conditions) + public And (SQLExpression... conditions) { super(conditions); } @@ -73,14 +74,14 @@ public abstract class Logic public static class Not implements SQLOperator { - public Not (SQLOperator condition) + public Not (SQLExpression condition) { super(); _condition = condition; } // from SQLExpression - public void appendExpression (ConstructedQuery query, StringBuilder builder) + public void appendExpression (QueryBuilderContext query, StringBuilder builder) { builder.append(" not ("); _condition.appendExpression(query, builder); @@ -88,12 +89,12 @@ public abstract class Logic } // from SQLExpression - public int bindArguments (PreparedStatement pstmt, int argIdx) + public int bindExpressionArguments (PreparedStatement pstmt, int argIdx) throws SQLException { - return _condition.bindArguments(pstmt, argIdx); + return _condition.bindExpressionArguments(pstmt, argIdx); } - protected SQLOperator _condition; + protected SQLExpression _condition; } } diff --git a/src/java/com/samskivert/jdbc/depot/operator/SQLOperator.java b/src/java/com/samskivert/jdbc/depot/operator/SQLOperator.java index e5d897d..92269fd 100644 --- a/src/java/com/samskivert/jdbc/depot/operator/SQLOperator.java +++ b/src/java/com/samskivert/jdbc/depot/operator/SQLOperator.java @@ -23,7 +23,7 @@ package com.samskivert.jdbc.depot.operator; import java.sql.PreparedStatement; import java.sql.SQLException; -import com.samskivert.jdbc.depot.ConstructedQuery; +import com.samskivert.jdbc.depot.QueryBuilderContext; import com.samskivert.jdbc.depot.expression.SQLExpression; import com.samskivert.jdbc.depot.expression.ValueExp; @@ -40,14 +40,14 @@ public interface SQLOperator extends SQLExpression public abstract static class MultiOperator implements SQLOperator { - public MultiOperator (SQLOperator... conditions) + public MultiOperator (SQLExpression ... conditions) { super(); _conditions = conditions; } // from SQLExpression - public void appendExpression (ConstructedQuery query, StringBuilder builder) + public void appendExpression (QueryBuilderContext query, StringBuilder builder) { for (int ii = 0; ii < _conditions.length; ii++) { if (ii > 0) { @@ -60,11 +60,11 @@ public interface SQLOperator extends SQLExpression } // from SQLExpression - public int bindArguments (PreparedStatement pstmt, int argIdx) + public int bindExpressionArguments (PreparedStatement pstmt, int argIdx) throws SQLException { for (int ii = 0; ii < _conditions.length; ii++) { - argIdx = _conditions[ii].bindArguments(pstmt, argIdx); + argIdx = _conditions[ii].bindExpressionArguments(pstmt, argIdx); } return argIdx; } @@ -74,7 +74,7 @@ public interface SQLOperator extends SQLExpression */ protected abstract String operator (); - protected SQLOperator[] _conditions; + protected SQLExpression[] _conditions; } /** @@ -94,7 +94,7 @@ public interface SQLOperator extends SQLExpression } // from SQLExpression - public void appendExpression (ConstructedQuery query, StringBuilder builder) + public void appendExpression (QueryBuilderContext query, StringBuilder builder) { _lhs.appendExpression(query, builder); builder.append(operator()); @@ -102,11 +102,11 @@ public interface SQLOperator extends SQLExpression } // from SQLExpression - public int bindArguments (PreparedStatement pstmt, int argIdx) + public int bindExpressionArguments (PreparedStatement pstmt, int argIdx) throws SQLException { - argIdx = _lhs.bindArguments(pstmt, argIdx); - argIdx = _rhs.bindArguments(pstmt, argIdx); + argIdx = _lhs.bindExpressionArguments(pstmt, argIdx); + argIdx = _rhs.bindExpressionArguments(pstmt, argIdx); return argIdx; }