type, final Where key, CacheInvalidator invalidator,
+ Object... fieldsValues)
throws PersistenceException
{
// separate the arguments into keys and values
@@ -254,13 +318,13 @@ public class DepotRepository
}
final DepotMarshaller marsh = _ctx.getMarshaller(type);
- return _ctx.invoke(new Modifier(key) {
+ return _ctx.invoke(new Modifier(invalidator) {
public int invoke (Connection conn) throws SQLException {
- PreparedStatement stmt = marsh.createPartialUpdate(conn, _key, fields, values);
+ PreparedStatement stmt = marsh.createPartialUpdate(conn, key, fields, values);
try {
return stmt.executeUpdate();
} finally {
- stmt.close();
+ JDBCUtil.close(stmt);
}
}
});
@@ -268,8 +332,8 @@ public class DepotRepository
/**
* Updates the specified columns for all persistent objects matching the supplied primary
- * key. The values in this case must be literal SQL to be inserted into the update
- * statement. In general this is used when you want to do something like the following:
+ * key. The values in this case must be literal SQL to be inserted into the update statement.
+ * In general this is used when you want to do something like the following:
*
*
* update FOO set BAR = BAR + 1;
@@ -286,14 +350,14 @@ public class DepotRepository
protected int updateLiteral (Class type, Comparable primaryKey, String... fieldsValues)
throws PersistenceException
{
- return updateLiteral(
- type, _ctx.getMarshaller(type).makePrimaryKey(primaryKey), fieldsValues);
+ Key key = _ctx.getMarshaller(type).makePrimaryKey(primaryKey);
+ return updateLiteral(type, key, key, fieldsValues);
}
/**
* Updates the specified columns for all persistent objects matching the supplied primary
- * key. The values in this case must be literal SQL to be inserted into the update
- * statement. In general this is used when you want to do something like the following:
+ * key. The values in this case must be literal SQL to be inserted into the update statement.
+ * In general this is used when you want to do something like the following:
*
*
* update FOO set BAR = BAR + 1;
@@ -307,7 +371,8 @@ public class DepotRepository
*
* @return the number of rows modified by this action.
*/
- protected int updateLiteral (Class type, Where key, String... fieldsValues)
+ protected int updateLiteral (Class type, final Where key, CacheInvalidator invalidator,
+ String... fieldsValues)
throws PersistenceException
{
// separate the arguments into keys and values
@@ -319,13 +384,13 @@ public class DepotRepository
}
final DepotMarshaller marsh = _ctx.getMarshaller(type);
- return _ctx.invoke(new Modifier(key) {
+ return _ctx.invoke(new Modifier(invalidator) {
public int invoke (Connection conn) throws SQLException {
- PreparedStatement stmt = marsh.createLiteralUpdate(conn, _key, fields, values);
+ PreparedStatement stmt = marsh.createLiteralUpdate(conn, key, fields, values);
try {
return stmt.executeUpdate();
} finally {
- stmt.close();
+ JDBCUtil.close(stmt);
}
}
});
@@ -338,24 +403,24 @@ public class DepotRepository
*
* @return the number of rows modified by this action, this should always be one.
*/
- protected int store (final Object record)
+ protected int store (final T record)
throws PersistenceException
{
final DepotMarshaller marsh = _ctx.getMarshaller(record.getClass());
- Key key = marsh.hasPrimaryKey() ? marsh.getPrimaryKey(record) : null;
- return _ctx.invoke(new Modifier(key) {
+ final Key key = marsh.hasPrimaryKey() ? marsh.getPrimaryKey(record) : null;
+ return _ctx.invoke(new CachingModifier(key, key) {
public int invoke (Connection conn) throws SQLException {
PreparedStatement stmt = null;
try {
// if our primary key is null or is the integer 0, assume the record has never
// before been persisted and insert
- if (_key != null && !Integer.valueOf(0).equals(_key)) {
- stmt = marsh.createUpdate(conn, record, _key);
+ if (key != null && !Integer.valueOf(0).equals(key)) {
+ stmt = marsh.createUpdate(conn, record, key);
int mods = stmt.executeUpdate();
if (mods > 0) {
return mods;
}
- stmt.close();
+ JDBCUtil.close(stmt);
}
// if the update modified zero rows or the primary key was obviously unset, do
@@ -364,10 +429,11 @@ public class DepotRepository
stmt = marsh.createInsert(conn, record);
int mods = stmt.executeUpdate();
updateKey(marsh.assignPrimaryKey(conn, record, true));
+ setInstance(record);
return mods;
} finally {
- stmt.close();
+ JDBCUtil.close(stmt);
}
}
});
@@ -383,8 +449,8 @@ public class DepotRepository
throws PersistenceException
{
@SuppressWarnings("unchecked") Class type = (Class)record.getClass();
- DepotMarshaller marsh = _ctx.getMarshaller(type);
- return deleteAll(type, marsh.getPrimaryKey(record));
+ Key primaryKey = _ctx.getMarshaller(type).getPrimaryKey(record);
+ return deleteAll(type, primaryKey, primaryKey);
}
/**
@@ -393,10 +459,11 @@ public class DepotRepository
*
* @return the number of rows deleted by this action.
*/
- protected int delete (Class type, Comparable primaryKey)
+ protected int delete (Class type, Comparable primaryKeyValue)
throws PersistenceException
{
- return deleteAll(type, _ctx.getMarshaller(type).makePrimaryKey(primaryKey));
+ Key primaryKey = _ctx.getMarshaller(type).makePrimaryKey(primaryKeyValue);
+ return deleteAll(type, primaryKey, primaryKey);
}
/**
@@ -404,31 +471,41 @@ public class DepotRepository
*
* @return the number of rows deleted by this action.
*/
- protected int deleteAll (Class type, Where key)
+ protected int deleteAll (Class type, final Where key, CacheInvalidator invalidator)
throws PersistenceException
{
final DepotMarshaller marsh = _ctx.getMarshaller(type);
- return _ctx.invoke(new Modifier(key) {
+ return _ctx.invoke(new Modifier(invalidator) {
public int invoke (Connection conn) throws SQLException {
- PreparedStatement stmt = marsh.createDelete(conn, _key);
+ PreparedStatement stmt = marsh.createDelete(conn, key);
try {
return stmt.executeUpdate();
} finally {
- stmt.close();
+ JDBCUtil.close(stmt);
}
}
});
}
- protected static abstract class CollectionQuery extends Query
+ protected static abstract class CollectionQuery implements Query
{
- public CollectionQuery (PersistenceContext ctx, Class type, Key key)
+ public CollectionQuery (CacheKey key)
throws PersistenceException
{
- super(ctx, type, key);
+ _key = key;
}
- public abstract T invoke (Connection conn) throws SQLException;
+ public CacheKey getCacheKey ()
+ {
+ return _key;
+ }
+
+ public void updateCache (PersistenceContext ctx, T result)
+ {
+ ctx.cacheStore(_key, result);
+ }
+
+ protected CacheKey _key;
}
protected PersistenceContext _ctx;
diff --git a/src/java/com/samskivert/jdbc/depot/EntityMigration.java b/src/java/com/samskivert/jdbc/depot/EntityMigration.java
index d5ec6cc6..a1400b5b 100644
--- a/src/java/com/samskivert/jdbc/depot/EntityMigration.java
+++ b/src/java/com/samskivert/jdbc/depot/EntityMigration.java
@@ -113,7 +113,7 @@ public abstract class EntityMigration extends Modifier
protected EntityMigration (int targetVersion)
{
- super(null);
+ super();
_targetVersion = targetVersion;
}
diff --git a/src/java/com/samskivert/jdbc/depot/Key.java b/src/java/com/samskivert/jdbc/depot/Key.java
index 315d8fd4..7f63490d 100644
--- a/src/java/com/samskivert/jdbc/depot/Key.java
+++ b/src/java/com/samskivert/jdbc/depot/Key.java
@@ -20,71 +20,161 @@
package com.samskivert.jdbc.depot;
+import java.io.Serializable;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
import com.samskivert.jdbc.depot.clause.Where;
-import com.samskivert.jdbc.depot.expression.ColumnExp;
-import com.samskivert.jdbc.depot.expression.LiteralExp;
-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;
/**
- * Encapsulates a key used to match persistent objects in a query. This is a special form of
- * where clause that is meant to uniquely identify a specific row for caching purposes. It is
- * generated by the update/insert code, as well as instantiated directly by users.
+ * A special form of {@link Where} clause that uniquely specifices 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
+public class Key extends Where
+ implements CacheKey, CacheInvalidator
{
- public Key (String index, Comparable value)
+ /**
+ * Constructs a new single-column {@code Key} with the given value.
+ */
+ public Key (Class pClass, String ix, Comparable val)
{
- this(new ColumnExp(index), value);
+ this(pClass, new String[] { ix }, new Comparable[] { val });
}
- public Key (ColumnExp column, Comparable value)
+ /**
+ * Constructs a new two-column {@code Key} with the given values.
+ */
+ public Key (Class pClass, String ix1, Comparable val1,
+ String ix2, Comparable val2)
{
- this(new ColumnExp[] { column }, new Comparable[] { value });
+ this(pClass, new String[] { ix1, ix2 }, new Comparable[] { val1, val2 });
}
- public Key (ColumnExp index1, Comparable value1,
- ColumnExp index2, Comparable value2)
+ /**
+ * Constructs a new three-column {@code Key} with the given values.
+ */
+ public Key (Class pClass, String ix1, Comparable val1,
+ String ix2, Comparable val2, String ix3, Comparable val3)
{
- this(new ColumnExp[] { index1, index2 },
- new Comparable[] { value1, value2 });
+ this(pClass, new String[] { ix1, ix2, ix3 }, new Comparable[] { val1, val2, val3 });
}
- public Key (ColumnExp index1, Comparable value1,
- ColumnExp index2, Comparable value2,
- ColumnExp index3, Comparable value3)
+ /**
+ * Constructs a new multi-column {@code Key} with the given values.
+ *
+ * TODO: There is no reason to store both fields and values here (and doing so probably more
+ * than doubles the space the key consumes in the cache). The primary key fields are known
+ * by the DepotMarshaller; we should simply check tha the given indices match those, and
+ * store the values in the order defined by the DepotMarshaller. The sanity check would be
+ * welcome in any case. The only problem with this is that we don't currently have access to
+ * a {@link PersistenceContext}. It may be that we should make this class internal to
+ * {@link DepotRepository} and only create it ourselves. This would require *lots* more
+ * convenience methods in {@link DepotRepository} though.
+ */
+ public Key (Class pClass, String[] fields, Comparable[] values)
{
- this(new ColumnExp[] { index1, index2, index3 },
- new Comparable[] { value1, value2, value3 });
- }
+ // TODO: make Where an interface so we don't have to do this ugly super call
+ super(null);
- public Key (String index1, Comparable value1, String index2, Comparable value2)
- {
- this(new ColumnExp(index1), value1, new ColumnExp(index2), value2);
- }
-
- public Key (String index1, Comparable value1, String index2, Comparable value2,
- String index3, Comparable value3)
- {
- this(new ColumnExp(index1), value1, new ColumnExp(index2), value2,
- new ColumnExp(index3), value3);
- }
-
- public Key (ColumnExp[] columns, Comparable[] values)
- {
- super(toCondition(columns, values));
- }
-
- protected static SQLOperator toCondition (ColumnExp[] columns, Comparable[] values)
- {
- SQLOperator[] comparisons = new SQLOperator[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]));
+ if (fields.length != values.length) {
+ throw new IllegalArgumentException("Field and Value arrays must be of equal length.");
+ }
+ _pClass = pClass;
+ _map = new HashMap();
+ for (int i = 0; i < fields.length; i ++) {
+ _map.put(fields[i], values[i]);
}
- return new And(comparisons);
}
+
+ // from QueryClause
+ public List getClassSet()
+ {
+ return Arrays.asList(new Class[] { _pClass });
+ }
+
+ // from QueryClause
+ public void appendClause (ConstructedQuery query, StringBuilder builder)
+ {
+ builder.append(" where ");
+ boolean first = true;
+ for (Map.Entry entry : _map.entrySet()) {
+ if (first) {
+ first = false;
+ } else {
+ builder.append(" and ");
+ }
+ builder.append(entry.getKey());
+ builder.append(entry.getValue() == null ? " is null " : " = ? ");
+ }
+ }
+
+ // from QueryClause
+ public int bindArguments (PreparedStatement pstmt, int argIdx)
+ throws SQLException
+ {
+ for (Map.Entry entry : _map.entrySet()) {
+ if (entry.getValue() != null) {
+ pstmt.setObject(argIdx ++, entry.getValue());
+ }
+ }
+ return argIdx;
+ }
+
+ // from CacheKey
+ public String getCacheId ()
+ {
+ return _pClass.getName();
+ }
+
+ // from CacheKey
+ public Serializable getCacheKey ()
+ {
+ return _map;
+ }
+
+ // from CacheInvalidator
+ public void invalidate (PersistenceContext ctx)
+ {
+ ctx.cacheInvalidate(this);
+ }
+
+ @Override
+ public int hashCode ()
+ {
+ return _pClass.hashCode() + 31 * _map.hashCode();
+ }
+
+ @Override
+ public boolean equals (Object obj)
+ {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ Key other = (Key) obj;
+ return (_pClass == other._pClass && _map.equals(other._map));
+ }
+
+ @Override
+ public String toString ()
+ {
+ StringBuilder builder = new StringBuilder("[Key pClass=" + _pClass.getName());
+ for (Map.Entry entry : _map.entrySet()) {
+ builder.append(", ").append(entry.getKey()).append("=").append(entry.getValue());
+ }
+ builder.append("]");
+ return builder.toString();
+ }
+
+ protected Class _pClass;
+ protected HashMap _map;
}
diff --git a/src/java/com/samskivert/jdbc/depot/Modifier.java b/src/java/com/samskivert/jdbc/depot/Modifier.java
index a5b208e6..632274f9 100644
--- a/src/java/com/samskivert/jdbc/depot/Modifier.java
+++ b/src/java/com/samskivert/jdbc/depot/Modifier.java
@@ -3,7 +3,7 @@
//
// 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
@@ -24,15 +24,15 @@ import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
-import com.samskivert.jdbc.depot.clause.Where;
-
/**
* Encapsulates a modification of persistent objects.
*/
public abstract class Modifier
{
- /** A simple modifier that executes a single SQL statement. No cache flushing is done as a
- * result of this operation. */
+ /**
+ * A simple modifier that executes a single SQL statement. No cache flushing is done as a
+ * result of this operation.
+ */
public static class Simple extends Modifier
{
public Simple (String query) {
@@ -52,19 +52,74 @@ public abstract class Modifier
protected String _query;
}
- public abstract int invoke (Connection conn) throws SQLException;
-
- protected Modifier (Where key)
+ /**
+ * A simple modifier that updates the cache with its modified object on completion. The derived
+ * class must call {@link #setInstance} to inform the modifier of its object at some point
+ * during the modification operation.
+ */
+ public static abstract class CachingModifier extends Modifier
{
- _key = key;
+ protected CachingModifier (CacheKey key, CacheInvalidator invalidator)
+ {
+ super(invalidator);
+ _key = key;
+ }
+
+ protected void setInstance (T result)
+ {
+ _result = result;
+ }
+
+ protected void updateKey (CacheKey key)
+ {
+ if (key != null) {
+ _key = key;
+ }
+ }
+
+ @Override
+ public void cacheUpdate (PersistenceContext ctx)
+ {
+ super.cacheUpdate(ctx);
+ if (_key != null) {
+ ctx.cacheStore(_key, _result);
+ }
+ }
+
+ protected CacheKey _key;
+ protected T _result;
}
- protected void updateKey (Key key)
+ public abstract int invoke (Connection conn) throws SQLException;
+
+ public Modifier ()
{
- if (key != null) {
- _key = key;
+ this(null);
+ }
+
+ public Modifier (CacheInvalidator invalidator)
+ {
+ _invalidator = invalidator;
+ }
+
+ /**
+ * Do any cache invalidation needed for this modification. This method is called just
+ * before the database statement is executed.
+ */
+ public void cacheInvalidation (PersistenceContext ctx)
+ {
+ if (_invalidator != null) {
+ _invalidator.invalidate(ctx);
}
}
- protected Where _key;
+ /**
+ * Do any cache updates needed for this modification. This method is called just after
+ * the successful execution of the database statement.
+ */
+ public void cacheUpdate (PersistenceContext ctx)
+ {
+ }
+
+ protected CacheInvalidator _invalidator;
}
diff --git a/src/java/com/samskivert/jdbc/depot/MultiKey.java b/src/java/com/samskivert/jdbc/depot/MultiKey.java
new file mode 100644
index 00000000..0176d84f
--- /dev/null
+++ b/src/java/com/samskivert/jdbc/depot/MultiKey.java
@@ -0,0 +1,149 @@
+//
+// $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.PreparedStatement;
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.samskivert.jdbc.depot.clause.Where;
+
+/**
+ * A special form of {@link Where} clause that specifies an explicit range of database rows. It
+ * does not implement {@link CacheKey} but it does implement {@link CacheInvalidator} which means
+ * it can be sent into e.g. {@link DepotRepository#deleteAll) and have it clean up after itself.
+ */
+public class MultiKey extends Where
+ implements CacheInvalidator
+{
+ /**
+ * Constructs a new single-column {@code MultiKey} with the given value range.
+ */
+ public MultiKey (Class pClass, String ix, Comparable... val)
+ {
+ this(pClass, new String[0], new Comparable[0], ix, val);
+ }
+
+ /**
+ * Constructs a new two-column {@code MultiKey} with the given value range.
+ */
+ public MultiKey (Class pClass, String ix1, Comparable val1, String ix2, Comparable... val2)
+ {
+ this(pClass, new String[] { ix1 }, new Comparable[] { val1 }, ix2, val2);
+ }
+
+ /**
+ * Constructs a new three-column {@code MultiKey} with the given value range.
+ */
+ public MultiKey (Class pClass, String ix1, Comparable val1, String ix2, Comparable val2,
+ String ix3, Comparable... val3)
+ {
+ this(pClass, new String[] { ix1, ix2 }, new Comparable[] { val1, val2 }, ix3, val3);
+ }
+
+ /**
+ * Constructs a new multi-column {@code MultiKey} with the given value range.
+ * @TODO: See {@link Key#Key(Class, String[], Comparable[]) for somewhat relevant comments.
+ */
+ public MultiKey (Class pClass, String[] sFields, Comparable[] sValues,
+ String mField, Comparable[] mValues)
+ {
+ // TODO
+ super(null);
+ if (sFields.length != sValues.length) {
+ throw new IllegalArgumentException(
+ "Key field and values arrays must be of equal length.");
+ }
+ _pClass = pClass;
+ _mField = mField;
+ _mValues = mValues;
+ _map = new HashMap();
+ for (int i = 0; i < sFields.length; i ++) {
+ _map.put(sFields[i], sValues[i]);
+ }
+ }
+
+ // from QueryClause
+ public List getClassSet()
+ {
+ return Arrays.asList(new Class[] { _pClass });
+ }
+
+ // from QueryClause
+ public void appendClause (ConstructedQuery query, StringBuilder builder)
+ {
+ builder.append(" where ");
+ boolean first = true;
+ for (Map.Entry entry : _map.entrySet()) {
+ if (first) {
+ first = false;
+ } else {
+ builder.append(" and ");
+ }
+ builder.append(entry.getKey());
+ builder.append(entry.getValue() == null ? " is null " : " = ? ");
+ }
+ if (!first) {
+ builder.append(" and ");
+ }
+ builder.append(_mField).append(" in (");
+ for (int ii = 0; ii < _mValues.length; ii ++) {
+ if (ii > 0) {
+ builder.append(", ");
+ }
+ builder.append("?");
+ }
+ builder.append(")");
+ }
+
+ // from QueryClause
+ public int bindArguments (PreparedStatement pstmt, int argIdx)
+ throws SQLException
+ {
+ for (Map.Entry entry : _map.entrySet()) {
+ if (entry.getValue() != null) {
+ pstmt.setObject(argIdx ++, entry.getValue());
+ }
+ }
+ for (int ii = 0; ii < _mValues.length; ii++) {
+ pstmt.setObject(argIdx ++, _mValues[ii]);
+ }
+ return argIdx;
+ }
+
+ // from CacheInvalidator
+ public void invalidate (PersistenceContext ctx)
+ {
+ HashMap newMap = new HashMap(_map);
+ for (int i = 0; i < _mValues.length; i ++) {
+ newMap.put(_mField, _mValues[i]);
+ ctx.cacheInvalidate(new SimpleCacheKey(_pClass, newMap));
+ }
+ }
+
+ protected Class _pClass;
+ protected HashMap _map;
+ protected String _mField;
+ protected Comparable[] _mValues;
+}
diff --git a/src/java/com/samskivert/jdbc/depot/PersistenceContext.java b/src/java/com/samskivert/jdbc/depot/PersistenceContext.java
index 94468efd..92b56fc7 100644
--- a/src/java/com/samskivert/jdbc/depot/PersistenceContext.java
+++ b/src/java/com/samskivert/jdbc/depot/PersistenceContext.java
@@ -21,12 +21,20 @@
package com.samskivert.jdbc.depot;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
import com.samskivert.jdbc.depot.annotation.TableGenerator;
+import java.io.Serializable;
import java.sql.Connection;
import java.sql.SQLException;
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.CacheManager;
+import net.sf.ehcache.Element;
+
import com.samskivert.io.PersistenceException;
import com.samskivert.util.StringUtil;
@@ -44,16 +52,66 @@ public class PersistenceContext
public HashMap tableGenerators = new HashMap();
/**
- * Creates a persistence context that will use the supplied provider to
- * obtain JDBC connections.
+ * A cache listener is notified when cache entries are invalited through creation, deletion,
+ * or modification. Its purpose is typically to do further invalidation of dependent entries
+ * in other caches.
+ */
+ public static interface CacheListener
+ {
+ /**
+ * The given entry has just been deleted, modified or created. Do what thou wilt.
+ */
+ public void entryModified (CacheKey key, T entry);
+ }
+
+ /**
+ * The callback for {@link #cacheTraverse}; this is called for each entry in a given cache.
+ */
+ public static interface CacheTraverser
+ {
+ /**
+ * Performs whatever cache-related tasks need doing for this cache entry. This method
+ * is called for each cache entry in a full-cache enumeration.
+ */
+ public void visitCacheEntry (
+ PersistenceContext ctx, String cacheId, Serializable key, T record);
+ }
+
+ /**
+ * A simple implementation of {@link CacheTraverser} that selectively deletes entries in
+ * a cache depending on the return value of {@link #testCacheEntry}.
+ */
+ public static abstract class CacheEvictionFilter
+ implements CacheTraverser
+ {
+ // from CacheTraverser
+ public void visitCacheEntry (
+ PersistenceContext ctx, String cacheId, Serializable key, T record)
+ {
+ if (testForEviction(key, record)) {
+ ctx.cacheInvalidate(cacheId, key);
+ }
+ }
+
+ /**
+ * Decides whether or not this entry should be evicted and returns true if yes, false if
+ * no.
+ */
+ protected abstract boolean testForEviction (Serializable key, T record);
+ }
+
+ /**
+ * Creates a persistence context that will use the supplied provider to obtain JDBC
+ * connections.
*
- * @param ident the identifier to provide to the connection provider when
- * requesting a connection.
+ * @param ident the identifier to provide to the connection provider when requesting a
+ * connection.
*/
public PersistenceContext (String ident, ConnectionProvider conprov)
{
_ident = ident;
_conprov = conprov;
+ _cachemgr = CacheManager.getInstance();
}
/**
@@ -106,12 +164,28 @@ public class PersistenceContext
/**
* Invokes a non-modifying query and returns its result.
*/
- @SuppressWarnings("unchecked")
public T invoke (Query query)
throws PersistenceException
{
- // TODO: check the cache using query.getKey()
- return (T) invoke(query, null, true);
+ CacheKey key = query.getCacheKey();
+ // if there is a cache key, check the cache
+ if (key != null) {
+ Cache cache = getCache(key.getCacheId());
+ if (cache != null) {
+ Element cacheHit = cache.get(key.getCacheKey());
+ if (cacheHit != null) {
+ Log.debug("invoke: cache hit [hit=" + cacheHit + "]");
+ @SuppressWarnings("unchecked") T value = (T) cacheHit.getValue();
+ return value;
+ }
+ }
+ Log.debug("invoke: cache miss [key=" + key + "]");
+ }
+ // otherwise, perform the query
+ @SuppressWarnings("unchecked") T result = (T) invoke(query, null, true);
+ // and let the caller figure out if it wants to cache itself somehow
+ query.updateCache(this, result);
+ return result;
}
/**
@@ -120,11 +194,143 @@ public class PersistenceContext
public int invoke (Modifier modifier)
throws PersistenceException
{
- // TODO: invalidate the cache using the modifier's key
+ modifier.cacheInvalidation(this);
+ int rows = (Integer) invoke(null, modifier, true);
+ if (rows > 0) {
+ modifier.cacheUpdate(this);
+ }
+ return rows;
+ }
- int result = (Integer) invoke(null, modifier, true);
- // TODO: (optionally) cache the results of the modifier
- return result;
+ /**
+ * Returns the {@link Cache} for the given cache id, or creates one if necessary.
+ */
+ public Cache getCache (String cacheId)
+ {
+ Cache cache = _cachemgr.getCache(cacheId);
+ if (cache == null) {
+ cache = new Cache(cacheId, 5000, false, false, 600, 60);
+ _cachemgr.addCache(cache);
+ }
+ return cache;
+ }
+
+ /**
+ * Stores a new entry indexed by the given key.
+ */
+ public void cacheStore (CacheKey key, T value)
+ {
+ Log.debug("cacheStore: entry [key=" + key + ", value=" + value + "]");
+ getCache(key.getCacheId()).put(new Element(key.getCacheKey(), value));
+
+ // first do cascading invalidations
+ Set> listeners = _listenerSets.get(key.getCacheId());
+ if (listeners != null && listeners.size() > 0) {
+ for (CacheListener> listener : listeners) {
+ Log.debug("cacheInvalidate: cascading [listener=" + listener + "]");
+ @SuppressWarnings("unchecked") CacheListener casted = (CacheListener)listener;
+ casted.entryModified(key, value);
+ }
+ }
+ }
+
+ /**
+ * Evicts the cache entry indexed under the given key, if there is one.
+ * The eviction may trigger further cache invalidations.
+ */
+ public void cacheInvalidate (CacheKey key)
+ {
+ cacheInvalidate(key.getCacheId(), key.getCacheKey());
+ }
+
+ /**
+ * Evicts the cache entry indexed under the given class and cache key, if there is one.
+ * The eviction may trigger further cache invalidations.
+ */
+ public void cacheInvalidate (Class pClass, Serializable cacheKey)
+ {
+ cacheInvalidate(pClass.getName(), cacheKey);
+ }
+
+ /**
+ * Evicts the cache entry indexed under the given cache id and cache key, if there is one.
+ * The eviction may trigger further cache invalidations.
+ */
+ public void cacheInvalidate (String cacheId, Serializable cacheKey)
+ {
+ Log.debug("cacheInvalidate: entry [cacheId=" + cacheId + ", cacheKey=" + cacheKey + "]");
+ Cache cache = getCache(cacheId);
+ Element element = cache.get(cacheKey);
+
+ // first do cascading invalidations
+ Set> listeners = _listenerSets.get(cacheId);
+ if (listeners != null && listeners.size() > 0) {
+ CacheKey key = new SimpleCacheKey(cacheId, cacheKey);
+ for (CacheListener> listener : listeners) {
+ Log.debug("cacheInvalidate: cascading [listener=" + listener + "]");
+ @SuppressWarnings("unchecked") CacheListener casted = (CacheListener)listener;
+ @SuppressWarnings("unchecked") T value =
+ (element != null ? (T) element.getValue() : null);
+ casted.entryModified(key, value);
+ }
+ }
+
+ // then evict the keyed entry, if needed
+ if (element != null) {
+ Log.debug("cacheInvalidate: evicting [cacheKey=" + cacheKey + "]");
+ cache.remove(cacheKey);
+ }
+ }
+
+ /**
+ * Brutally iterates over the entire contents of the cache associated with the given class,
+ * invoking the callback for each cache entry.
+ */
+ public void cacheTraverse (Class pClass, CacheTraverser filter)
+ {
+ cacheTraverse(pClass.getName(), filter);
+ }
+
+ /**
+ * Brutally iterates over the entire contents of the cache identified by the given cache id,
+ * invoking the callback for each cache entry.
+ */
+ public void cacheTraverse (String cacheId, CacheTraverser filter)
+ {
+ Cache cache = getCache(cacheId);
+ if (cache != null) {
+ for (Object key : cache.getKeys()) {
+ Serializable sKey = (Serializable) key;
+ Element element = cache.get(sKey);
+ if (element != null) {
+ @SuppressWarnings("unchecked") T value = (T) element.getValue();
+ filter.visitCacheEntry(this, cacheId, sKey, value);
+ }
+ }
+ }
+ }
+
+ /**
+ * Registers a new cache listener with the cache associated with the given class.
+ */
+ public void addCacheListener (
+ Class pClass, CacheListener listener)
+ {
+ addCacheListener(pClass.getName(), listener);
+ }
+
+ /**
+ * Registers a new cache listener with the identified cache.
+ */
+ public void addCacheListener (
+ String cacheId, CacheListener listener)
+ {
+ Set> listenerSet = _listenerSets.get(cacheId);
+ if (listenerSet == null) {
+ listenerSet = new HashSet>();
+ _listenerSets.put(cacheId, listenerSet);
+ }
+ listenerSet.add(listener);
}
/**
@@ -203,22 +409,24 @@ public class PersistenceContext
}
/**
- * Check whether the specified exception is a transient failure
- * that can be retried.
+ * Check whether the specified exception is a transient failure that can be retried.
*/
protected boolean isTransientException (SQLException sqe)
{
// TODO: this is MySQL specific. This was snarfed from MySQLLiaison.
String msg = sqe.getMessage();
- return (msg != null &&
- (msg.indexOf("Lost connection") != -1 ||
- msg.indexOf("link failure") != -1 ||
- msg.indexOf("Broken pipe") != -1));
+ return (msg != null && (msg.indexOf("Lost connection") != -1 ||
+ msg.indexOf("link failure") != -1 ||
+ msg.indexOf("Broken pipe") != -1));
}
protected String _ident;
protected ConnectionProvider _conprov;
+ protected CacheManager _cachemgr;
- protected HashMap, DepotMarshaller>> _marshallers =
+ protected Map>> _listenerSets =
+ new HashMap>>();
+
+ protected Map, DepotMarshaller>> _marshallers =
new HashMap, DepotMarshaller>>();
}
diff --git a/src/java/com/samskivert/jdbc/depot/Query.java b/src/java/com/samskivert/jdbc/depot/Query.java
index f0a95be7..58b8cc58 100644
--- a/src/java/com/samskivert/jdbc/depot/Query.java
+++ b/src/java/com/samskivert/jdbc/depot/Query.java
@@ -21,277 +21,26 @@
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.
+ * The base of all read-only queries.
*/
-public abstract class Query
+public interface Query
{
+ /**
+ * Returns the {@link CacheKey} associated with this query, if relevant, or null.
+ */
+ public CacheKey getCacheKey ();
+
/**
* Performs the actual JDBC operations associated with this query.
*/
- public abstract T invoke (Connection conn) throws SQLException;
-
+ public T invoke (Connection conn)
+ throws SQLException;
+
/**
- * Maps a class referenced by this query to its associated table.
- *
- * This method is called by individual clauses.
+ * Overriden by subclasses to perform case-by-case cache updates.
*/
- 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);
- }
-
- /**
- * 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");
- }
-
- DepotMarshaller> mainMarshaller = _classMap.get(_mainType);
- String[] fields = mainMarshaller._allFields;
- StringBuilder query = new StringBuilder("select ");
- boolean skip = true;
- for (int ii = 0; ii < fields.length; ii ++) {
- if (!skip) {
- query.append(", ");
- }
- skip = false;
-
- FieldOverride clause = _disMap.get(fields[ii]);
- if (clause != null) {
- clause.appendClause(this, query);
- continue;
- }
-
- Computed computed = mainMarshaller._fields.get(fields[ii]).getComputed();
- if (computed == null) {
- // make sure the object corresponds to a table, otherwise the whole thing is
- // computed
- if (mainMarshaller.getTableName() != null) {
- // if it's neither overridden nor computed, it's a standard field
- query.append(getTableAbbreviation(_mainType)).append(".").append(fields[ii]);
- continue;
- }
- throw new SQLException(
- "@Computed entity field without definition [field=" + fields[ii] + "]");
- }
-
- // check if the computed field has a literal SQL definition
- if (computed.fieldDefinition().length() > 0) {
- query.append(computed.fieldDefinition() + " as " + fields[ii]);
-
- } else if (!computed.required()) {
- // or if we can simply ignore the field
- skip = true;
-
- } else {
- throw new SQLException(
- "@Computed(required) field without definition [field=" + fields[ii] + "]");
- }
- }
-
- 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 Query (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;
+ void updateCache (PersistenceContext ctx, T result);
}
diff --git a/src/java/com/samskivert/jdbc/depot/SimpleCacheKey.java b/src/java/com/samskivert/jdbc/depot/SimpleCacheKey.java
new file mode 100644
index 00000000..17545ee8
--- /dev/null
+++ b/src/java/com/samskivert/jdbc/depot/SimpleCacheKey.java
@@ -0,0 +1,113 @@
+//
+// $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.io.Serializable;
+
+/**
+ * Convenience class that implements {@link CacheKey} as simply as possibly. This class is
+ * typically used when the caller wants to cache a non-obvious query such as a collection,
+ * and needs to specify their own cache key and file it under a hand-picked cache id.
+ */
+public class SimpleCacheKey
+ implements CacheKey
+{
+ /**
+ * Construct a {@link SimpleCacheKey} for a query that has no parameters whatsoever.
+ */
+ public SimpleCacheKey (String cacheId)
+ {
+ this(cacheId, Boolean.TRUE);
+ }
+
+ /**
+ * Construct a {@link SimpleCacheKey} associated with the given persistent class with
+ * the given cache key.
+ */
+ public SimpleCacheKey (Class cacheClass, Serializable cacheKey)
+ {
+ this(cacheClass.getName(), cacheKey);
+ }
+
+ /**
+ * Construct a {@link SimpleCacheKey} for the given cache id with the given cache key.
+ */
+ public SimpleCacheKey (String cacheId, Serializable value)
+ {
+ _cacheId = cacheId;
+ _cacheKey = value;
+ }
+
+ // from CacheKey
+ public String getCacheId ()
+ {
+ return _cacheId;
+ }
+
+ // from CacheKey
+ public Serializable getCacheKey ()
+ {
+ return _cacheKey;
+ }
+
+ @Override
+ public int hashCode ()
+ {
+ final int PRIME = 31;
+ int result = 1;
+ result = PRIME * result + ((_cacheId == null) ? 0 : _cacheId.hashCode());
+ result = PRIME * result + ((_cacheKey == null) ? 0 : _cacheKey.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals (Object obj)
+ {
+ if (obj == null || obj.getClass() != getClass()) {
+ return false;
+ }
+ SimpleCacheKey other = (SimpleCacheKey) obj;
+ if (_cacheId == null) {
+ if (other._cacheId != null) {
+ return false;
+ }
+ } else if (!_cacheId.equals(other._cacheId)) {
+ return false;
+ }
+ if (_cacheKey == null) {
+ if (other._cacheKey != null) {
+ return false;
+ }
+ } else if (!_cacheKey.equals(other._cacheKey)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString ()
+ {
+ return "[cacheId=" + _cacheId + ", value=" + _cacheKey + "]";
+ }
+
+ protected String _cacheId;
+ protected Serializable _cacheKey;
+}
diff --git a/src/java/com/samskivert/jdbc/depot/annotation/Computed.java b/src/java/com/samskivert/jdbc/depot/annotation/Computed.java
index 7580a5dd..6cf3e8b7 100644
--- a/src/java/com/samskivert/jdbc/depot/annotation/Computed.java
+++ b/src/java/com/samskivert/jdbc/depot/annotation/Computed.java
@@ -25,11 +25,11 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-import com.samskivert.jdbc.depot.Query;
+import com.samskivert.jdbc.depot.ConstructedQuery;
/**
* 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 Query}.
+ * correspond to a column in a table, thus its value must be overridden in the {@link ConstructedQuery}.
*/
@Retention(value=RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.TYPE })
diff --git a/src/java/com/samskivert/jdbc/depot/clause/FieldOverride.java b/src/java/com/samskivert/jdbc/depot/clause/FieldOverride.java
index c2f5e2b7..ef04dcf3 100644
--- a/src/java/com/samskivert/jdbc/depot/clause/FieldOverride.java
+++ b/src/java/com/samskivert/jdbc/depot/clause/FieldOverride.java
@@ -25,7 +25,7 @@ import java.sql.SQLException;
import java.util.Collection;
import com.samskivert.io.PersistenceException;
-import com.samskivert.jdbc.depot.Query;
+import com.samskivert.jdbc.depot.ConstructedQuery;
import com.samskivert.jdbc.depot.expression.ColumnExp;
import com.samskivert.jdbc.depot.expression.LiteralExp;
import com.samskivert.jdbc.depot.expression.SQLExpression;
@@ -75,7 +75,7 @@ public class FieldOverride
}
// from QueryClause
- public void appendClause (Query query, StringBuilder builder)
+ public void appendClause (ConstructedQuery 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 a2fe165f..20972ed9 100644
--- a/src/java/com/samskivert/jdbc/depot/clause/ForUpdate.java
+++ b/src/java/com/samskivert/jdbc/depot/clause/ForUpdate.java
@@ -24,7 +24,7 @@ import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
-import com.samskivert.jdbc.depot.Query;
+import com.samskivert.jdbc.depot.ConstructedQuery;
/**
* Represents a FOR UPDATE clause.
@@ -39,7 +39,7 @@ public class ForUpdate
}
// from QueryClause
- public void appendClause (Query query, StringBuilder builder)
+ public void appendClause (ConstructedQuery 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 e37082cc..f9a1366f 100644
--- a/src/java/com/samskivert/jdbc/depot/clause/FromOverride.java
+++ b/src/java/com/samskivert/jdbc/depot/clause/FromOverride.java
@@ -26,7 +26,7 @@ import java.util.Arrays;
import java.util.Collection;
import com.samskivert.io.PersistenceException;
-import com.samskivert.jdbc.depot.Query;
+import com.samskivert.jdbc.depot.ConstructedQuery;
/**
* Completely overrides the FROM clause, if it exists.
@@ -47,7 +47,7 @@ public class FromOverride
}
// from QueryClause
- public void appendClause (Query query, StringBuilder builder)
+ public void appendClause (ConstructedQuery query, StringBuilder builder)
{
builder.append(" from " );
for (int ii = 0; ii < _fromClasses.length; ii++) {
diff --git a/src/java/com/samskivert/jdbc/depot/clause/GroupBy.java b/src/java/com/samskivert/jdbc/depot/clause/GroupBy.java
index 5eb04376..a40ca05e 100644
--- a/src/java/com/samskivert/jdbc/depot/clause/GroupBy.java
+++ b/src/java/com/samskivert/jdbc/depot/clause/GroupBy.java
@@ -24,7 +24,7 @@ import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
-import com.samskivert.jdbc.depot.Query;
+import com.samskivert.jdbc.depot.ConstructedQuery;
import com.samskivert.jdbc.depot.expression.SQLExpression;
/**
@@ -45,7 +45,7 @@ public class GroupBy
}
// from QueryClause
- public void appendClause (Query query, StringBuilder builder)
+ public void appendClause (ConstructedQuery query, StringBuilder builder)
{
builder.append(" group by ");
for (int ii = 0; ii < _values.length; ii++) {
diff --git a/src/java/com/samskivert/jdbc/depot/clause/Join.java b/src/java/com/samskivert/jdbc/depot/clause/Join.java
index 1183e3e9..52a10b35 100644
--- a/src/java/com/samskivert/jdbc/depot/clause/Join.java
+++ b/src/java/com/samskivert/jdbc/depot/clause/Join.java
@@ -26,7 +26,7 @@ import java.util.Arrays;
import java.util.Collection;
import com.samskivert.io.PersistenceException;
-import com.samskivert.jdbc.depot.Query;
+import com.samskivert.jdbc.depot.ConstructedQuery;
import com.samskivert.jdbc.depot.expression.ColumnExp;
import com.samskivert.jdbc.depot.operator.SQLOperator;
import com.samskivert.jdbc.depot.operator.Conditionals.*;
@@ -64,7 +64,7 @@ public class Join
}
// from QueryClause
- public void appendClause (Query query, StringBuilder builder)
+ public void appendClause (ConstructedQuery query, StringBuilder builder)
{
builder.append(" inner join " );
builder.append(query.getTableName(_joinClass)).append(" as ");
diff --git a/src/java/com/samskivert/jdbc/depot/clause/Limit.java b/src/java/com/samskivert/jdbc/depot/clause/Limit.java
index b54c03f9..50063d5b 100644
--- a/src/java/com/samskivert/jdbc/depot/clause/Limit.java
+++ b/src/java/com/samskivert/jdbc/depot/clause/Limit.java
@@ -24,7 +24,7 @@ import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
-import com.samskivert.jdbc.depot.Query;
+import com.samskivert.jdbc.depot.ConstructedQuery;
/**
* Represents a LIMIT/OFFSET clause, for pagination.
@@ -45,7 +45,7 @@ public class Limit
}
// from QueryClause
- public void appendClause (Query query, StringBuilder builder)
+ public void appendClause (ConstructedQuery query, StringBuilder builder)
{
builder.append(" limit ? offset ? ");
}
diff --git a/src/java/com/samskivert/jdbc/depot/clause/OrderBy.java b/src/java/com/samskivert/jdbc/depot/clause/OrderBy.java
index 7ddc3911..dc248911 100644
--- a/src/java/com/samskivert/jdbc/depot/clause/OrderBy.java
+++ b/src/java/com/samskivert/jdbc/depot/clause/OrderBy.java
@@ -24,7 +24,7 @@ import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
-import com.samskivert.jdbc.depot.Query;
+import com.samskivert.jdbc.depot.ConstructedQuery;
import com.samskivert.jdbc.depot.expression.ColumnExp;
import com.samskivert.jdbc.depot.expression.SQLExpression;
@@ -82,7 +82,7 @@ public class OrderBy
}
// from QueryClause
- public void appendClause (Query query, StringBuilder builder)
+ public void appendClause (ConstructedQuery query, StringBuilder builder)
{
builder.append(" order by ");
for (int ii = 0; ii < _values.length; ii++) {
diff --git a/src/java/com/samskivert/jdbc/depot/clause/QueryClause.java b/src/java/com/samskivert/jdbc/depot/clause/QueryClause.java
index b9440374..4c0a8ada 100644
--- a/src/java/com/samskivert/jdbc/depot/clause/QueryClause.java
+++ b/src/java/com/samskivert/jdbc/depot/clause/QueryClause.java
@@ -24,7 +24,7 @@ import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
-import com.samskivert.jdbc.depot.Query;
+import com.samskivert.jdbc.depot.ConstructedQuery;
/**
* Represents a piece or modifier of an SQL query.
@@ -41,7 +41,7 @@ public interface QueryClause
* 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 void appendClause (Query query, StringBuilder builder);
+ public void appendClause (ConstructedQuery query, StringBuilder builder);
/**
* Bind any objects that were referenced in the generated SQL. For each ? that appears in the
diff --git a/src/java/com/samskivert/jdbc/depot/clause/Where.java b/src/java/com/samskivert/jdbc/depot/clause/Where.java
index 93c120bb..3fcb0bac 100644
--- a/src/java/com/samskivert/jdbc/depot/clause/Where.java
+++ b/src/java/com/samskivert/jdbc/depot/clause/Where.java
@@ -25,9 +25,14 @@ import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
-import com.samskivert.jdbc.depot.Query;
+import com.samskivert.jdbc.depot.ConstructedQuery;
import com.samskivert.jdbc.depot.clause.QueryClause;
+import com.samskivert.jdbc.depot.expression.ColumnExp;
+import com.samskivert.jdbc.depot.expression.ValueExp;
import com.samskivert.jdbc.depot.operator.SQLOperator;
+import com.samskivert.jdbc.depot.operator.Conditionals.Equals;
+import com.samskivert.jdbc.depot.operator.Conditionals.IsNull;
+import com.samskivert.jdbc.depot.operator.Logic.And;
/**
* Represents a where clause: the condition can be any comparison operator or logical combination
@@ -36,6 +41,47 @@ import com.samskivert.jdbc.depot.operator.SQLOperator;
public class Where
implements QueryClause
{
+ public Where (String index, Comparable value)
+ {
+ this(new ColumnExp(index), value);
+ }
+
+ public Where (ColumnExp column, Comparable value)
+ {
+ this(new ColumnExp[] { column }, new Comparable[] { value });
+ }
+
+ public Where (ColumnExp index1, Comparable value1,
+ ColumnExp index2, Comparable value2)
+ {
+ this(new ColumnExp[] { index1, index2 }, new Comparable[] { value1, value2 });
+ }
+
+ public Where (ColumnExp index1, Comparable value1,
+ ColumnExp index2, Comparable value2,
+ ColumnExp index3, Comparable value3)
+ {
+ this(new ColumnExp[] { index1, index2, index3 },
+ new Comparable[] { value1, value2, value3 });
+ }
+
+ public Where (String index1, Comparable value1, String index2, Comparable value2)
+ {
+ this(new ColumnExp(index1), value1, new ColumnExp(index2), value2);
+ }
+
+ public Where (String index1, Comparable value1, String index2, Comparable value2,
+ String index3, Comparable value3)
+ {
+ this(new ColumnExp(index1), value1, new ColumnExp(index2), value2,
+ new ColumnExp(index3), value3);
+ }
+
+ public Where (ColumnExp[] columns, Comparable[] values)
+ {
+ this(toCondition(columns, values));
+ }
+
public Where (SQLOperator condition)
{
_condition = condition;
@@ -48,7 +94,7 @@ public class Where
}
// from QueryClause
- public void appendClause (Query query, StringBuilder builder)
+ public void appendClause (ConstructedQuery query, StringBuilder builder)
{
builder.append(" where ");
_condition.appendExpression(query, builder);
@@ -61,5 +107,15 @@ public class Where
return _condition.bindArguments(pstmt, argIdx);
}
+ protected static SQLOperator toCondition (ColumnExp[] columns, Comparable[] values)
+ {
+ SQLOperator[] comparisons = new SQLOperator[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]));
+ }
+ return new And(comparisons);
+ }
+
protected SQLOperator _condition;
}
diff --git a/src/java/com/samskivert/jdbc/depot/expression/ColumnExp.java b/src/java/com/samskivert/jdbc/depot/expression/ColumnExp.java
index 19022fa0..6f811c7f 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.Query;
+import com.samskivert.jdbc.depot.ConstructedQuery;
/**
* An expression identifying a column of a class, e.g. GameRecord.itemId. If no class is given,
@@ -51,7 +51,7 @@ public class ColumnExp
}
// from SQLExpression
- public void appendExpression (Query query, StringBuilder builder)
+ public void appendExpression (ConstructedQuery query, StringBuilder builder)
{
if (pClass == null || query == null) {
builder.append(pColumn);
diff --git a/src/java/com/samskivert/jdbc/depot/expression/FunctionExp.java b/src/java/com/samskivert/jdbc/depot/expression/FunctionExp.java
index acdcd214..6c9abeef 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.Query;
+import com.samskivert.jdbc.depot.ConstructedQuery;
/**
* An expression for a function, e.g. FLOOR(blah).
@@ -41,7 +41,7 @@ public class FunctionExp
}
// from SQLExpression
- public void appendExpression (Query query, StringBuilder builder)
+ public void appendExpression (ConstructedQuery query, StringBuilder builder)
{
builder.append(_function);
builder.append("(");
diff --git a/src/java/com/samskivert/jdbc/depot/expression/LiteralExp.java b/src/java/com/samskivert/jdbc/depot/expression/LiteralExp.java
index 3befae52..b8961b7a 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.Query;
+import com.samskivert.jdbc.depot.ConstructedQuery;
/**
* An expression for things we don't support natively, e.g. COUNT(*).
@@ -38,7 +38,7 @@ public class LiteralExp
}
// from SQLExpression
- public void appendExpression (Query query, StringBuilder builder)
+ public void appendExpression (ConstructedQuery query, StringBuilder builder)
{
builder.append(_text);
}
diff --git a/src/java/com/samskivert/jdbc/depot/expression/SQLExpression.java b/src/java/com/samskivert/jdbc/depot/expression/SQLExpression.java
index 69d32cac..dc2a0ad9 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.Query;
+import com.samskivert.jdbc.depot.ConstructedQuery;
/**
* Represents an SQL expression, e.g. column name, function, or constant.
@@ -34,7 +34,7 @@ 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 (Query query, StringBuilder builder);
+ public void appendExpression (ConstructedQuery query, StringBuilder builder);
/**
* Bind any objects that were referenced in the generated SQL. For each ? that appears in the
diff --git a/src/java/com/samskivert/jdbc/depot/expression/ValueExp.java b/src/java/com/samskivert/jdbc/depot/expression/ValueExp.java
index 8f1e4be8..aafd4b13 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.Query;
+import com.samskivert.jdbc.depot.ConstructedQuery;
/**
* A Java value that is bound as a parameter to the query, e.g. 1 or 'abc'.
@@ -37,7 +37,7 @@ public class ValueExp
}
// from SQLExpression
- public void appendExpression (Query query, StringBuilder builder)
+ public void appendExpression (ConstructedQuery query, StringBuilder builder)
{
builder.append("?");
}
diff --git a/src/java/com/samskivert/jdbc/depot/operator/Conditionals.java b/src/java/com/samskivert/jdbc/depot/operator/Conditionals.java
index fe4c9365..92700144 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.Query;
+import com.samskivert.jdbc.depot.ConstructedQuery;
import com.samskivert.jdbc.depot.expression.ColumnExp;
import com.samskivert.jdbc.depot.expression.SQLExpression;
import com.samskivert.jdbc.depot.operator.SQLOperator.BinaryOperator;
@@ -56,7 +56,7 @@ public abstract class Conditionals
}
// from SQLExpression
- public void appendExpression (Query query, StringBuilder builder)
+ public void appendExpression (ConstructedQuery query, StringBuilder builder)
{
_column.appendExpression(query, builder);
builder.append(" is null");
@@ -181,7 +181,7 @@ public abstract class Conditionals
}
// from SQLExpression
- public void appendExpression (Query query, StringBuilder builder)
+ public void appendExpression (ConstructedQuery query, StringBuilder builder)
{
_column.appendExpression(query, builder);
builder.append(" in (");
@@ -234,7 +234,7 @@ public abstract class Conditionals
}
// from SQLExpression
- public void appendExpression (Query query, StringBuilder builder)
+ public void appendExpression (ConstructedQuery query, StringBuilder builder)
{
builder.append("match(");
int idx = 0;
diff --git a/src/java/com/samskivert/jdbc/depot/operator/Logic.java b/src/java/com/samskivert/jdbc/depot/operator/Logic.java
index f84c7c1c..bd8c83f4 100644
--- a/src/java/com/samskivert/jdbc/depot/operator/Logic.java
+++ b/src/java/com/samskivert/jdbc/depot/operator/Logic.java
@@ -23,7 +23,7 @@ package com.samskivert.jdbc.depot.operator;
import java.sql.PreparedStatement;
import java.sql.SQLException;
-import com.samskivert.jdbc.depot.Query;
+import com.samskivert.jdbc.depot.ConstructedQuery;
import com.samskivert.jdbc.depot.operator.SQLOperator.MultiOperator;
/**
@@ -80,7 +80,7 @@ public abstract class Logic
}
// from SQLExpression
- public void appendExpression (Query query, StringBuilder builder)
+ public void appendExpression (ConstructedQuery query, StringBuilder builder)
{
builder.append(" not (");
_condition.appendExpression(query, builder);
diff --git a/src/java/com/samskivert/jdbc/depot/operator/SQLOperator.java b/src/java/com/samskivert/jdbc/depot/operator/SQLOperator.java
index 49672dc9..e5d897d1 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.Query;
+import com.samskivert.jdbc.depot.ConstructedQuery;
import com.samskivert.jdbc.depot.expression.SQLExpression;
import com.samskivert.jdbc.depot.expression.ValueExp;
@@ -47,7 +47,7 @@ public interface SQLOperator extends SQLExpression
}
// from SQLExpression
- public void appendExpression (Query query, StringBuilder builder)
+ public void appendExpression (ConstructedQuery query, StringBuilder builder)
{
for (int ii = 0; ii < _conditions.length; ii++) {
if (ii > 0) {
@@ -94,7 +94,7 @@ public interface SQLOperator extends SQLExpression
}
// from SQLExpression
- public void appendExpression (Query query, StringBuilder builder)
+ public void appendExpression (ConstructedQuery query, StringBuilder builder)
{
_lhs.appendExpression(query, builder);
builder.append(operator());