More layout standardizing.

This commit is contained in:
Michael Bayne
2010-08-27 17:49:18 +00:00
parent 6778ada227
commit 28f9439b5f
135 changed files with 214 additions and 2 deletions
@@ -0,0 +1,81 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot;
import java.io.Serializable;
import com.samskivert.depot.impl.FindAllQuery;
/**
* Implementations of this interface are responsible for all the caching needs of Depot.
*
* From the point of view of this interface, there are a potentially very large number of
* caches available, each idenfided by a unique cacheId. Currently Depot creates up to three
* caches for each record type:
*
* Any record type with a primary key has a {@link CacheCategory#RECORD} cache, for storing
* record instances by primary key.
*
* Record types with primary keys may also have a {@link CacheCategory#SHORT_KEYSET} cache wherein
* {@link KeySet} instances are stored, identified by query strings. See {@link FindAllQuery}
* for more on this.
*
* Finally, clients may request {@link CacheCategory#RESULT} caching of entire result sets of
* some record type -- which does not need to have a primary key, in contrast to the other two
* categories. These are also identified by query strings, and end up in a third cache.
*/
public interface CacheAdapter
{
public enum CacheCategory { RECORD, SHORT_KEYSET, LONG_KEYSET, RESULT }
/** The encapsulated result of a cache lookup. */
public interface CachedValue<T>
{
/** Returns the cached value, which can be null. */
public T getValue ();
}
/**
* Searches the given cache using the given key and returns the resulting {@link CachedValue},
* or null if nothing exists in the cache for this key.
*/
public <T> CachedValue<T> lookup (String cacheId, Serializable key);
/**
* Stores a new value in the given cache under the given key.
*/
public <T> void store (CacheCategory category, String cacheId, Serializable key, T value);
/**
* Removes the cache entry, if any, associated with the given key.
*/
public void remove (String cacheId, Serializable key);
/**
* Provides a way to enumerate the currently cached entries for the given cache.
*/
public <T> Iterable<Serializable> enumerate (String cacheId);
/**
* Shut down all operations, e.g. persisting memory contents to disk.
*/
public void shutdown ();
}
@@ -0,0 +1,60 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot;
import java.io.Serializable;
/**
* Implementors of this interface performs perform cache invalidation for calls to
* {@link DepotRepository#updatePartial} and {@link DepotRepository#deleteAll}.
*/
public interface CacheInvalidator
{
public static abstract class TraverseWithFilter<T extends Serializable>
implements CacheInvalidator
{
public TraverseWithFilter (Class<T> pClass) {
this(pClass.getName());
}
public TraverseWithFilter (String cacheId) {
_cacheId = cacheId;
}
public void invalidate (PersistenceContext ctx) {
ctx.cacheTraverse(_cacheId, new PersistenceContext.CacheEvictionFilter<T>() {
@Override protected boolean testForEviction (Serializable key, T record) {
return TraverseWithFilter.this.testForEviction(key, record);
}
});
}
protected abstract boolean testForEviction (Serializable key, T record);
protected String _cacheId;
}
/**
* Must invalidate all cache entries that depend on the records being modified or deleted.
* This method is called just before the database statement is executed.
*/
public void invalidate (PersistenceContext ctx);
}
@@ -0,0 +1,44 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot;
import java.io.Serializable;
/**
* This interface uniquely identifies a single persistent entry for caching purposes.
* Queries that are given a {@link CacheKey} consult the cache before they hit the
* database.
*/
public interface CacheKey
{
/**
* Returns the id of the cache in whose scope this key makes sense.
*/
public String getCacheId ();
/**
* Returns the actual opaque serializable cache key under which results are stored in the cache
* identified by {@link #getCacheId}. The object returned by this method should <em>only</em>
* reference system classes (not application classes). Depot takes care to ensure this and you
* probably aren't implementing your own cache keys so this should be fine.
*/
public Serializable getCacheKey ();
}
@@ -0,0 +1,46 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2009 Michael Bayne and 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.depot;
import com.samskivert.depot.annotation.Computed;
import com.samskivert.depot.annotation.Entity;
import com.samskivert.depot.expression.ColumnExp;
/**
* Handy record for computing the count of something. For example:
* <pre>
* return load(CountRecord.class,
* new FromOverride(ForumThreadRecord.class),
* new Where(ForumThreadRecord.GROUP_ID.eq(groupId)).count;
* </pre>
*/
@Computed @Entity
public class CountRecord extends PersistentRecord
{
// AUTO-GENERATED: FIELDS START
public static final Class<CountRecord> _R = CountRecord.class;
public static final ColumnExp COUNT = colexp(_R, "count");
// AUTO-GENERATED: FIELDS END
/** The computed count. */
@Computed(fieldDefinition="count(*)")
public int count;
}
@@ -0,0 +1,73 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot;
/**
* Encapsulates a migration of data between entities that should be run only once and using the
* same safeguards applied to entity migrations. Note: this should not be used for schema
* migrations, use {@link SchemaMigration} for that. Data migrations are registered on a specific
* repository via {@link DepotRepository#registerMigration} and should be registered in the
* repository's constructor as they will be invoked (if appropriate) in the repository's {@link
* DepotRepository#init} method.
*
* <p> In general one will register an anonymous inner class in a repository's constructor and can
* then access methods in the repository directly:
*
* <pre>
* public class FooRepository extends DepotRepository {
* public FooRepository (PersistenceContext ctx) {
* super(ctx);
* registerMigration(new DataMigration("2008_09_25_referral_to_tracking_id") {
* public void invoke () throws DatabaseException {
* // feel free to use load() findAll(), update(), etc.
* }
* });
* }
* </pre>
*/
public abstract class DataMigration
{
/**
* Creates a data migration with the specified unique identifier. The identifier must be unique
* across all users of the database and for all time, so be careful. Best to include the date
* and pertintent information, e.g. "2008_09_25_referral_to_tracking_id".
*/
public DataMigration (String ident)
{
_ident = ident;
}
/**
* Returns the identifier of this migration.
*/
public String getIdent ()
{
return _ident;
}
/**
* Effects the data migration.
*/
public abstract void invoke () throws DatabaseException;
/** The unique identifier for this migration. */
protected String _ident;
}
@@ -0,0 +1,53 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot;
/**
* Represents a failure reported by the underlying database.
*/
public class DatabaseException extends RuntimeException
{
/**
* Constructs a database exception with the specified error message.
*/
public DatabaseException (String message)
{
super(message);
}
/**
* Constructs a database exception with the specified error message and the chained causing
* event.
*/
public DatabaseException (String message, Throwable cause)
{
super(message);
initCause(cause);
}
/**
* Constructs a database exception with the specified chained causing event.
*/
public DatabaseException (Throwable cause)
{
initCause(cause);
}
}
@@ -0,0 +1,128 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2009 Michael Bayne and 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.depot;
import com.samskivert.depot.expression.FluentExp;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.expression.DateFun.*;
/**
* Provides static methods for date-related function construction.
*/
public class DateFuncs
{
/**
* Creates an expression that extracts the date from the given timestamp expression.
*/
public static FluentExp date (SQLExpression exp)
{
return new DateTruncate(exp, DateTruncate.Truncation.DAY);
}
/**
* Creates an expression to extract the day-of-week from the the supplied timestamp expression.
*/
public static FluentExp dayOfWeek (SQLExpression exp)
{
return new DatePart(exp, DatePart.Part.DAY_OF_WEEK);
}
/**
* Creates an expression to extract the day-of-month from the the supplied timestamp expression.
*/
public static FluentExp dayOfMonth (SQLExpression exp)
{
return new DatePart(exp, DatePart.Part.DAY_OF_MONTH);
}
/**
* Creates an expression to extract the day-of-year from the the supplied timestamp expression.
*/
public static FluentExp dayOfYear (SQLExpression exp)
{
return new DatePart(exp, DatePart.Part.DAY_OF_YEAR);
}
/**
* Creates an expression to extract the hour of the the supplied timestamp expression.
*/
public static FluentExp hour (SQLExpression exp)
{
return new DatePart(exp, DatePart.Part.HOUR);
}
/**
* Creates an expression to extract the minute of the the supplied timestamp expression.
*/
public static FluentExp minute (SQLExpression exp)
{
return new DatePart(exp, DatePart.Part.MINUTE);
}
/**
* Creates an expression to extract the second of the the supplied timestamp expression.
*/
public static FluentExp second (SQLExpression exp)
{
return new DatePart(exp, DatePart.Part.SECOND);
}
/**
* Creates an expression to extract the week of the the supplied timestamp expression.
*/
public static FluentExp week (SQLExpression exp)
{
return new DatePart(exp, DatePart.Part.WEEK);
}
/**
* Creates an expression to extract the month of the the supplied timestamp expression.
*/
public static FluentExp month (SQLExpression exp)
{
return new DatePart(exp, DatePart.Part.MONTH);
}
/**
* Creates an expression to extract the year of the the supplied timestamp expression.
*/
public static FluentExp year (SQLExpression exp)
{
return new DatePart(exp, DatePart.Part.YEAR);
}
/**
* Creates an expression to extract the epoch (aka unix timestamp, aka seconds passed since
* 1970-01-01) of the the supplied timestamp expression.
*/
public static FluentExp epoch (SQLExpression exp)
{
return new DatePart(exp, DatePart.Part.EPOCH);
}
/**
* Creates an expression for the current timestamp.
*/
public static FluentExp now ()
{
return new Now();
}
}
@@ -0,0 +1,915 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.base.Function;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.samskivert.util.ArrayUtil;
import com.samskivert.depot.clause.FieldOverride;
import com.samskivert.depot.clause.InsertClause;
import com.samskivert.depot.clause.QueryClause;
import com.samskivert.depot.clause.WhereClause;
import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.util.Sequence;
import com.samskivert.jdbc.ConnectionProvider;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.depot.impl.DepotMarshaller;
import com.samskivert.depot.impl.DepotMigrationHistoryRecord;
import com.samskivert.depot.impl.DepotTypes;
import com.samskivert.depot.impl.FindAllKeysQuery;
import com.samskivert.depot.impl.FindAllQuery;
import com.samskivert.depot.impl.FindOneQuery;
import com.samskivert.depot.impl.Modifier.*;
import com.samskivert.depot.impl.Modifier;
import com.samskivert.depot.impl.SQLBuilder;
import com.samskivert.depot.impl.clause.DeleteClause;
import com.samskivert.depot.impl.clause.UpdateClause;
import com.samskivert.depot.impl.expression.ValueExp;
import com.samskivert.depot.impl.util.SeqImpl;
import static com.samskivert.depot.Log.log;
/**
* Provides a base for classes that provide access to persistent objects. Also defines the
* mechanism by which all persistent queries and updates are routed through the distributed cache.
*/
public abstract class DepotRepository
{
public enum CacheStrategy {
/** Completely bypass the cache for this query. */
NONE,
/** Use the {@link #SHORT_KEYS} strategy if possible, else revert to {@link #NONE}. */
BEST,
/**
* Resolve this collection query in two steps: first we enumerate the primary keys for
* all the records that satisfy the query, then we acquire the actual data corresponding
* to each key -- first by consulting the cache, and then only loading from the database
* the records for the keys that were not located in the cache.
*
* Note: This strategy may not be used on @Computed records, for records that do not in
* fact have a primary key, or for queries that use @FieldOverrides.
*/
RECORDS,
/**
* This strategy is identical to {@link #RECORDS}, but we also cache the keyset fetched
* in the first pass. This makes it much more efficient, but also less reliable because
* there is no invalidation of the keyset query: If records are inserted, deleted or
* modified, cached keysets will not be updated.
*
* Keysets cached using this strategy should have a short time-to-live.
*
* Note: This strategy may not be used on @Computed records, for records that do not in
* fact have a primary key, or for queries that use @FieldOverrides.
*/
SHORT_KEYS,
/**
* This strategy is identical to {@link #RECORDS}, but we also cache the keyset fetched
* in the first pass. This makes it much more efficient, but also less reliable because
* there is no invalidation of the keyset query: If records are inserted, deleted or
* modified, cached keysets will not be updated.
*
* Keysets cached using this strategy may have a long time-to-live.
*
* Note: This strategy may not be used on @Computed records, for records that do not in
* fact have a primary key, or for queries that use @FieldOverrides.
*/
LONG_KEYS,
/**
* This cache strategy is direct and explicit, eschewing the dual phases of the {@link
* #RECORDS} and {@link #SHORT_KEYS} approaches. However, before the database is invoked at
* all, we consult the cache hoping to find the entire result set already stashed away in
* there, using the entire query as the key. If we failed to find it, we execute the query
* and update the cache with the result.
*
* This strategy has none of the limitations of {@link #SHORT_KEYS} and can be used with
* key-less and @Computed records and arbitrarily complicated queries. Note however that as
* with {@link #SHORT_KEYS}, there is no automatic invalidation. It is also potentially
* very memory intensive.
*/
CONTENTS
}
/**
* Loads the persistent object that matches the specified primary key.
*
* @throws DatabaseException if any problem is encountered communicating with the database.
*/
public <T extends PersistentRecord> T load (Key<T> key, QueryClause... clauses)
throws DatabaseException
{
return load(key, CacheStrategy.BEST, clauses);
}
/**
* Loads the persistent object that matches the specified primary key.
*
* @throws DatabaseException if any problem is encountered communicating with the database.
*/
public <T extends PersistentRecord> T load (
Key<T> key, CacheStrategy strategy, QueryClause... clauses)
throws DatabaseException
{
clauses = ArrayUtil.append(clauses, key);
return _ctx.invoke(new FindOneQuery<T>(_ctx, key.getPersistentClass(), strategy, clauses));
}
/**
* Loads the first persistent object that matches the supplied query clauses.
*
* @throws DatabaseException if any problem is encountered communicating with the database.
*/
public <T extends PersistentRecord> T load (Class<T> type, QueryClause... clauses)
throws DatabaseException
{
return load(type, CacheStrategy.BEST, clauses);
}
/**
* Loads the first persistent object that matches the supplied query clauses.
*
* @throws DatabaseException if any problem is encountered communicating with the database.
*/
public <T extends PersistentRecord> T load (
Class<T> type, CacheStrategy strategy, QueryClause... clauses)
throws DatabaseException
{
return _ctx.invoke(new FindOneQuery<T>(_ctx, type, strategy, clauses));
}
/**
* Loads up all persistent records that match the supplied set of raw primary keys.
*
* @throws DatabaseException if any problem is encountered communicating with the database.
*/
public <T extends PersistentRecord> List<T> loadAll (
Class<T> type, Iterable<? extends Comparable<?>> primaryKeys)
throws DatabaseException
{
// convert the raw keys into real key records
return loadAll(
Iterables.transform(primaryKeys, _ctx.getMarshaller(type).primaryKeyFunction()));
}
/**
* Loads up all persistent records that match the supplied set of primary keys.
*
* @throws DatabaseException if any problem is encountered communicating with the database.
*/
public <T extends PersistentRecord> List<T> loadAll (Iterable<Key<T>> keys)
throws DatabaseException
{
return Iterables.isEmpty(keys) ? Collections.<T>emptyList() :
_ctx.invoke(new FindAllQuery.WithKeys<T>(_ctx, keys));
}
/**
* A varargs version of {@link #findAll(Class,Iterable)}.
*
* @throws DatabaseException if any problem is encountered communicating with the database.
*/
public <T extends PersistentRecord> List<T> findAll (Class<T> type, QueryClause... clauses)
throws DatabaseException
{
return findAll(type, Arrays.asList(clauses));
}
/**
* 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.
*
* @throws DatabaseException if any problem is encountered communicating with the database.
*/
public <T extends PersistentRecord> List<T> findAll (
Class<T> type, Iterable<? extends QueryClause> clauses)
throws DatabaseException
{
return findAll(type, CacheStrategy.BEST, clauses);
}
/**
* A varargs version of {@link #findAll(Class,CacheStrategy,Iterable)}.
*
* @throws DatabaseException if any problem is encountered communicating with the database.
*/
public <T extends PersistentRecord> List<T> findAll (
Class<T> type, CacheStrategy strategy, QueryClause... clauses)
throws DatabaseException
{
return findAll(type, strategy, Arrays.asList(clauses));
}
/**
* Loads all persistent objects that match the specified clauses.
*
* @throws DatabaseException if any problem is encountered communicating with the database.
*/
public <T extends PersistentRecord> List<T> findAll (
Class<T> type, CacheStrategy cache, Iterable<? extends QueryClause> clauses)
throws DatabaseException
{
DepotMarshaller<T> marsh = _ctx.getMarshaller(type);
switch (cache) {
case LONG_KEYS: case SHORT_KEYS: case BEST: case RECORDS:
String reason = null;
if (marsh.getTableName() == null) {
reason = type + " is computed";
} else if (!marsh.hasPrimaryKey()) {
reason = type + " has no primary key";
} else {
for (QueryClause clause : clauses) {
if (clause instanceof FieldOverride) {
reason = "query uses a FieldOverride clause";
break;
}
}
}
if (cache == CacheStrategy.BEST) {
cache = (reason != null) ? CacheStrategy.NONE : CacheStrategy.SHORT_KEYS;
} else if (reason != null) {
// if user explicitly asked for a strategy we can't do, protest
throw new IllegalArgumentException(
"Cannot use " + cache + " strategy because " + reason);
}
break;
case NONE: case CONTENTS:
break; // NONE and CONTENTS can always be used.
}
if (!_ctx.isUsingCache()) {
cache = CacheStrategy.NONE;
}
switch (cache) {
case SHORT_KEYS: case LONG_KEYS: case RECORDS:
return _ctx.invoke(new FindAllQuery.WithCache<T>(_ctx, type, clauses, cache));
default:
return _ctx.invoke(new FindAllQuery.Explicitly<T>(
_ctx, type, clauses, cache == CacheStrategy.CONTENTS));
}
}
/**
* Looks up and returns {@link Key} records for all rows that match the supplied query clauses.
*
* @param forUpdate if true, the query will be run using a read-write connection to ensure that
* it talks to the master database, if false, the query will be run on a read-only connection
* and may load keys from a slave. For performance reasons, you should always pass false unless
* you know you will be modifying the database as a result of this query and absolutely need
* the latest data.
*
* @throws DatabaseException if any problem is encountered communicating with the database.
*/
public <T extends PersistentRecord> List<Key<T>> findAllKeys (
Class<T> type, boolean forUpdate, QueryClause... clause)
throws DatabaseException
{
return findAllKeys(type, forUpdate, Arrays.asList(clause));
}
/**
* Looks up and returns {@link Key} records for all rows that match the supplied query clauses.
*
* @param forUpdate if true, the query will be run using a read-write connection to ensure that
* it talks to the master database, if false, the query will be run on a read-only connection
* and may load keys from a slave. For performance reasons, you should always pass false unless
* you know you will be modifying the database as a result of this query and absolutely need
* the latest data.
*
* @throws DatabaseException if any problem is encountered communicating with the database.
*/
public <T extends PersistentRecord> List<Key<T>> findAllKeys (
Class<T> type, boolean forUpdate, Iterable<? extends QueryClause> clauses)
throws DatabaseException
{
return _ctx.invoke(new FindAllKeysQuery<T>(_ctx, type, forUpdate, clauses));
}
/**
* Inserts the supplied persistent object into the database, assigning its primary key (if it
* has one) in the process.
*
* @return the number of rows modified by this action, this should always be one.
*
* @throws DuplicateKeyException if the inserted record conflicts with the primary key (or any
* other unique key) of a record already in the database.
* @throws DatabaseException if any problem is encountered communicating with the database.
*/
public <T extends PersistentRecord> int insert (T record)
throws DatabaseException
{
@SuppressWarnings("unchecked") final Class<T> pClass = (Class<T>) record.getClass();
final DepotMarshaller<T> marsh = _ctx.getMarshaller(pClass);
Key<T> key = marsh.getPrimaryKey(record, false);
DepotTypes types = DepotTypes.getDepotTypes(_ctx);
types.addClass(_ctx, pClass);
final SQLBuilder builder = _ctx.getSQLBuilder(types);
// key will be null if record was supplied without a primary key
return _ctx.invoke(new CachingModifier<T>(record, key, key) {
@Override
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
// if needed, update our modifier's key so that it can cache our results
Set<String> identityFields = Collections.emptySet();
if (_key == null) {
// set any auto-generated column values
identityFields = marsh.generateFieldValues(conn, liaison, _result, false);
updateKey(marsh.getPrimaryKey(_result, false));
}
builder.newQuery(new InsertClause(pClass, _result, identityFields));
int mods = builder.prepare(conn).executeUpdate();
// run any post-factum value generators and potentially generate our key
if (_key == null) {
marsh.generateFieldValues(conn, liaison, _result, true);
updateKey(marsh.getPrimaryKey(_result, false));
}
return mods;
}
});
}
/**
* Updates all fields of the supplied persistent object, using its primary key to identify the
* row to be updated.
*
* @return the number of rows modified by this action.
*
* @throws DatabaseException if any problem is encountered communicating with the database.
*/
public int update (PersistentRecord record)
throws DatabaseException
{
Class<? extends PersistentRecord> pClass = record.getClass();
requireNotComputed(pClass, "update");
DepotMarshaller<? extends PersistentRecord> marsh = _ctx.getMarshaller(pClass);
Key<? extends PersistentRecord> key = marsh.getPrimaryKey(record);
if (key == null) {
throw new IllegalArgumentException("Can't update record with null primary key.");
}
return doUpdate(key, new UpdateClause(pClass, key, marsh.getColumnFieldNames(), record));
}
/**
* Updates just the specified fields of the supplied persistent object, using its primary key
* to identify the row to be updated. This method currently flushes the associated record from
* the cache, but in the future it should be modified to update the modified fields in the
* cached value iff the record exists in the cache.
*
* @return the number of rows modified by this action.
*
* @throws DatabaseException if any problem is encountered communicating with the database.
*/
public <T extends PersistentRecord> int update (T record, final ColumnExp... modifiedFields)
throws DatabaseException
{
@SuppressWarnings("unchecked") Class<T> pClass = (Class<T>) record.getClass();
requireNotComputed(pClass, "update");
DepotMarshaller<T> marsh = _ctx.getMarshaller(pClass);
Key<T> key = marsh.getPrimaryKey(record);
if (key == null) {
throw new IllegalArgumentException("Can't update record with null primary key.");
}
return doUpdate(key, new UpdateClause(pClass, key, modifiedFields, record));
}
/**
* Updates the specified columns for all persistent objects matching the supplied key.
*
* @param key the key for the persistent objects to be modified.
* @param field the first field to be updated.
* @param value the value to assign to the first field. This may be a primitive (Integer,
* String, etc.) which will be wrapped in value expression or a SQLExpression instance.
* @param more additional (field, value) pairs to be updated.
*
* @return the number of rows modified by this action.
*
* @throws DuplicateKeyException if the update attempts to change the key columns of a row to
* values that duplicate another row already in the database.
* @throws DatabaseException if any problem is encountered communicating with the database.
*/
public <T extends PersistentRecord> int updatePartial (
Key<T> key, ColumnExp field, Object value, Object... more)
throws DatabaseException
{
return updatePartial(key.getPersistentClass(), key, key, field, value, more);
}
/**
* Updates the specified columns for all persistent objects matching the supplied key.
*
* @param key the key for the persistent objects to be modified.
* @param updates a mapping from field to value for all values to be changed. The values may be
* primitives (Integer, String, etc.) which will be wrapped in value expression instances or
* SQLExpression instances defining the value.
*
* @return the number of rows modified by this action.
*
* @throws DuplicateKeyException if the update attempts to change the key columns of a row to
* values that duplicate another row already in the database.
* @throws DatabaseException if any problem is encountered communicating with the database.
*/
public <T extends PersistentRecord> int updatePartial (Key<T> key, Map<ColumnExp, ?> updates)
throws DatabaseException
{
return updatePartial(key.getPersistentClass(), key, key, updates);
}
/**
* Updates the specified columns for all persistent objects matching the supplied key. This
* method currently flushes the associated record from the cache, but in the future it should
* be modified to update the modified fields in the cached value iff the record exists in the
* cache.
*
* @param type the type of the persistent object to be modified.
* @param key the key to match in the update.
* @param invalidator a cache invalidator that will be run prior to the update to flush the
* relevant persistent objects from the cache, or null if no invalidation is needed.
* @param updates a mapping from field to value for all values to be changed. The values may be
* primitives (Integer, String, etc.) which will be wrapped in value expression instances or
* SQLExpression instances defining the value.
*
* @return the number of rows modified by this action.
*
* @throws DuplicateKeyException if the update attempts to change the key columns of a row to
* values that duplicate another row already in the database.
* @throws DatabaseException if any problem is encountered communicating with the database.
*/
public <T extends PersistentRecord> int updatePartial (
Class<T> type, final WhereClause key, CacheInvalidator invalidator,
Map<ColumnExp, ?> updates)
throws DatabaseException
{
// separate the arguments into keys and values
final ColumnExp[] fields = new ColumnExp[updates.size()];
final SQLExpression[] values = new SQLExpression[fields.length];
int ii = 0;
for (Map.Entry<ColumnExp, ?> entry : updates.entrySet()) {
fields[ii] = entry.getKey();
values[ii++] = makeValue(entry.getValue());
}
return updatePartial(type, key, invalidator, fields, values);
}
/**
* Updates the specified columns for all persistent objects matching the supplied key. This
* method currently flushes the associated record from the cache, but in the future it should
* be modified to update the modified fields in the cached value iff the record exists in the
* cache.
*
* @param type the type of the persistent object to be modified.
* @param key the key to match in the update.
* @param invalidator a cache invalidator that will be run prior to the update to flush the
* relevant persistent objects from the cache, or null if no invalidation is needed.
* @param field the first field to be updated.
* @param value the value to assign to the first field. This may be a primitive (Integer,
* String, etc.) which will be wrapped in value expression or a SQLExpression instance.
* @param more additional (field, value) pairs to be updated.
*
* @return the number of rows modified by this action.
*
* @throws DuplicateKeyException if the update attempts to change the key columns of a row to
* values that duplicate another row already in the database.
* @throws DatabaseException if any problem is encountered communicating with the database.
*/
public <T extends PersistentRecord> int updatePartial (
Class<T> type, final WhereClause key, CacheInvalidator invalidator,
ColumnExp field, Object value, Object... more)
throws DatabaseException
{
// separate the updates into keys and values
final ColumnExp[] fields = new ColumnExp[1+more.length/2];
final SQLExpression[] values = new SQLExpression[fields.length];
fields[0] = field;
values[0] = makeValue(value);
for (int ii = 1, idx = 0; ii < fields.length; ii++) {
fields[ii] = (ColumnExp)more[idx++];
values[ii] = makeValue(more[idx++]);
}
return updatePartial(type, key, invalidator, fields, values);
}
/**
* Updates the specified columns for all persistent objects matching the supplied key. This
* method currently flushes the associated record from the cache, but in the future it should
* be modified to update the modified fields in the cached value iff the record exists in the
* cache.
*
* @param type the type of the persistent object to be modified.
* @param key the key to match in the update.
* @param invalidator a cache invalidator that will be run prior to the update to flush the
* relevant persistent objects from the cache, or null if no invalidation is needed.
* @param fields the fields in the objects to be updated.
* @param values the values to be assigned to the fields.
*
* @return the number of rows modified by this action.
*
* @throws DuplicateKeyException if the update attempts to change the key columns of a row to
* values that duplicate another row already in the database.
* @throws DatabaseException if any problem is encountered communicating with the database.
*/
public <T extends PersistentRecord> int updatePartial (
Class<T> type, final WhereClause key, CacheInvalidator invalidator,
ColumnExp[] fields, SQLExpression[] values)
throws DatabaseException
{
requireNotComputed(type, "updatePartial");
if (invalidator instanceof ValidatingCacheInvalidator) {
((ValidatingCacheInvalidator)invalidator).validateFlushType(type); // sanity check
}
key.validateQueryType(type); // and another
return doUpdate(invalidator, new UpdateClause(type, key, fields, values));
}
/**
* Stores the supplied persisent object in the database. If it has no primary key assigned (it
* is null or zero), it will be inserted directly. Otherwise an update will first be attempted
* and if that matches zero rows, the object will be inserted.
*
* @return true if the record was created, false if it was updated.
*
* @throws DatabaseException if any problem is encountered communicating with the database.
*/
public <T extends PersistentRecord> boolean store (T record)
throws DatabaseException
{
@SuppressWarnings("unchecked") final Class<T> pClass = (Class<T>) record.getClass();
requireNotComputed(pClass, "store");
final DepotMarshaller<T> marsh = _ctx.getMarshaller(pClass);
Key<T> key = marsh.hasPrimaryKey() ? marsh.getPrimaryKey(record) : null;
final UpdateClause update =
new UpdateClause(pClass, key, marsh.getColumnFieldNames(), record);
final SQLBuilder builder = _ctx.getSQLBuilder(DepotTypes.getDepotTypes(_ctx, update));
// if our primary key isn't null, we start by trying to update rather than insert
if (key != null) {
builder.newQuery(update);
}
final boolean[] created = new boolean[1];
_ctx.invoke(new CachingModifier<T>(record, key, key) {
@Override
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
if (_key != null) {
// run the update
int mods = builder.prepare(conn).executeUpdate();
if (mods > 0) {
// if it succeeded, we're done
return mods;
}
}
// if the update modified zero rows or the primary key was unset, insert
Set<String> identityFields = Collections.emptySet();
if (_key == null) {
// first, set any auto-generated column values
identityFields = marsh.generateFieldValues(conn, liaison, _result, false);
// update our modifier's key so that it can cache our results
updateKey(marsh.getPrimaryKey(_result, false));
}
builder.newQuery(new InsertClause(pClass, _result, identityFields));
int mods = builder.prepare(conn).executeUpdate();
// run any post-factum value generators and potentially generate our key
if (_key == null) {
marsh.generateFieldValues(conn, liaison, _result, true);
updateKey(marsh.getPrimaryKey(_result, false));
}
created[0] = true;
return mods;
}
});
return created[0];
}
/**
* Deletes all persistent objects from the database matching the primary key of the supplied
* object (which should be one or zero).
*
* @return the number of rows deleted by this action.
*
* @throws DatabaseException if any problem is encountered communicating with the database.
*/
public <T extends PersistentRecord> int delete (T record)
throws DatabaseException
{
@SuppressWarnings("unchecked") Class<T> type = (Class<T>)record.getClass();
Key<T> primaryKey = _ctx.getMarshaller(type).getPrimaryKey(record);
if (primaryKey == null) {
throw new IllegalArgumentException("Can't delete record with null primary key.");
}
return delete(primaryKey);
}
/**
* Deletes all persistent objects from the database matching the supplied primary key (which
* should be one or zero).
*
* @return the number of rows deleted by this action.
*
* @throws DatabaseException if any problem is encountered communicating with the database.
*/
public <T extends PersistentRecord> int delete (Key<T> primaryKey)
throws DatabaseException
{
return deleteAll(primaryKey.getPersistentClass(), primaryKey, primaryKey);
}
/**
* Deletes all persistent objects from the database that match the supplied where clause.
*
* @return the number of rows deleted by this action.
*
* @throws DatabaseException if any problem is encountered communicating with the database.
*/
public <T extends PersistentRecord> int deleteAll (Class<T> type, final WhereClause where)
throws DatabaseException
{
if (where instanceof CacheInvalidator) {
// our where clause knows how to do its own deletion, yay!
return deleteAll(type, where, (CacheInvalidator)where);
} else if (_ctx.getMarshaller(type).hasPrimaryKey()) {
// look up the primary keys for all matching rows matching and delete using those
KeySet<T> pwhere = KeySet.newKeySet(type, findAllKeys(type, true, where));
return deleteAll(type, pwhere, pwhere);
} else {
// otherwise just do the delete directly as we can't have cached a record that has no
// primary key in the first place
return deleteAll(type, where, null);
}
}
/**
* Deletes all persistent objects from the database that match the supplied key.
*
* @return the number of rows deleted by this action.
*
* @throws DatabaseException if any problem is encountered communicating with the database.
*/
public <T extends PersistentRecord> int deleteAll (
Class<T> type, final WhereClause where, CacheInvalidator invalidator)
throws DatabaseException
{
if (invalidator instanceof ValidatingCacheInvalidator) {
((ValidatingCacheInvalidator)invalidator).validateFlushType(type); // sanity check
}
where.validateQueryType(type); // and another
DeleteClause delete = new DeleteClause(type, where);
final SQLBuilder builder = _ctx.getSQLBuilder(DepotTypes.getDepotTypes(_ctx, delete));
builder.newQuery(delete);
return _ctx.invoke(new Modifier(invalidator) {
@Override
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
return builder.prepare(conn).executeUpdate();
}
});
}
/**
* Creates a repository with the supplied persistence context. Any schema migrations needed by
* this repository should be registered in its constructor. A repository should <em>not</em>
* perform any actual database operations in its constructor, only register schema
* migrations. Initialization related database operations should be performed in {@link #init}.
*/
protected DepotRepository (PersistenceContext context)
{
_ctx = context;
_ctx.repositoryCreated(this);
}
/**
* Creates a repository with the supplied connection provider and its own private persistence
* context. This should generally not be used for new systems, and is only included to
* facilitate the integration of small numbers of Depot-based repositories into systems using
* the older samskivert SimpleRepository system.
*/
protected DepotRepository (ConnectionProvider conprov)
{
_ctx = new PersistenceContext();
_ctx.init(getClass().getName(), conprov, null);
_ctx.repositoryCreated(this);
}
/**
* Resolves all persistent records registered to this repository (via {@link
* #getManagedRecords}. This will be done before the repository is initialized via {@link
* #init}.
*
* @throws DatabaseException if any problem is encountered communicating with the database.
*/
protected void resolveRecords ()
throws DatabaseException
{
Set<Class<? extends PersistentRecord>> classes = Sets.newHashSet();
getManagedRecords(classes);
for (Class<? extends PersistentRecord> rclass : classes) {
_ctx.getMarshaller(rclass);
}
}
/**
* Provides a place where a repository can perform any initialization that requires database
* operations.
*
* @throws DatabaseException if any problem is encountered communicating with the database.
*/
protected void init ()
throws DatabaseException
{
// run any registered data migrations
for (DataMigration migration : _dataMigs) {
runMigration(migration);
}
_dataMigs = null; // note that we've been initialized
}
/**
* Registers a data migration for this repository. This migration will only be run once and its
* unique identifier will be stored persistently to ensure that it is never run again on the
* same database. Nonetheless, migrations should strive to be idempotent because someone might
* come along and create a brand new system installation and all registered migrations will be
* run once on the freshly created database. As with all database migrations, understand
* clearly how the process works and think about edge cases when creating a migration.
*
* <p> See {@link PersistenceContext#registerMigration} for details on how schema migrations
* operate and how they might interact with data migrations.
*/
protected void registerMigration (DataMigration migration)
{
if (_dataMigs == null) {
// we've already been initialized, so we have to run this migration immediately
runMigration(migration);
} else {
_dataMigs.add(migration);
}
}
/**
* Adds the persistent classes used by this repository to the supplied set.
*/
protected abstract void getManagedRecords (Set<Class<? extends PersistentRecord>> classes);
// make sure the given type corresponds to a concrete class
protected void requireNotComputed (Class<? extends PersistentRecord> type, String action)
throws DatabaseException
{
DepotMarshaller<?> marsh = _ctx.getMarshaller(type);
if (marsh == null) {
throw new DatabaseException("Unknown persistent type [class=" + type + "]");
}
if (marsh.getTableName() == null) {
throw new DatabaseException(
"Can't " + action + " computed entities [class=" + type + "]");
}
}
/**
* A helper method for the various partial update methods.
*/
protected int doUpdate (CacheInvalidator invalidator, UpdateClause update)
{
final SQLBuilder builder = _ctx.getSQLBuilder(DepotTypes.getDepotTypes(_ctx, update));
builder.newQuery(update);
return _ctx.invoke(new Modifier(invalidator) {
@Override
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
return builder.prepare(conn).executeUpdate();
}
});
}
/**
* If the supplied migration has not already been run, it will be run and if it completes, we
* will note in the DepotMigrationHistory table that it has been run.
*/
protected void runMigration (DataMigration migration)
throws DatabaseException
{
// attempt to get a lock to run this migration (or detect that it has already been run)
DepotMigrationHistoryRecord record;
while (true) {
// check to see if the migration has already been completed
record = load(DepotMigrationHistoryRecord.getKey(migration.getIdent()),
CacheStrategy.NONE);
if (record != null && record.whenCompleted != null) {
return; // great, no need to do anything
}
// if no record exists at all, try to insert one and thereby obtain the migration lock
if (record == null) {
try {
record = new DepotMigrationHistoryRecord();
record.ident = migration.getIdent();
insert(record);
break; // we got the lock, break out of this loop and run the migration
} catch (DuplicateKeyException dke) {
// someone beat us to the punch, so we have to wait for them to finish
}
}
// we didn't get the lock, so wait 5 seconds and then check to see if the other process
// finished the update or failed in which case we'll try to grab the lock ourselves
try {
log.info("Waiting on migration lock for " + migration.getIdent() + ".");
Thread.sleep(5000);
} catch (InterruptedException ie) {
throw new DatabaseException("Interrupted while waiting on migration lock.");
}
}
log.info("Running data migration", "ident", migration.getIdent());
try {
// run the migration
migration.invoke();
// report to the world that we've done so
record.whenCompleted = new Timestamp(System.currentTimeMillis());
update(record);
} finally {
// clear out our migration history record if we failed to get the job done
if (record.whenCompleted == null) {
try {
delete(record);
} catch (Throwable dt) {
log.warning("Oh noez! Failed to delete history record for failed migration. " +
"All clients will loop forever waiting for the lock.",
"ident", migration.getIdent(), dt);
}
}
}
}
protected SQLExpression makeValue (Object value)
{
return (value instanceof SQLExpression) ? (SQLExpression)value : new ValueExp(value);
}
/**
* Concise way to transform query results.
*/
protected <F, T> Sequence<T> map (Collection<F> source, Function<? super F, ? extends T> func)
{
return new SeqImpl<F, T>(source, func);
}
protected PersistenceContext _ctx;
protected List<DataMigration> _dataMigs = Lists.newArrayList();
}
@@ -0,0 +1,33 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot;
/**
* Thrown when an insert or update results in a duplicate key on a column that has a uniqueness
* constraint.
*/
public class DuplicateKeyException extends DatabaseException
{
public DuplicateKeyException (String message)
{
super(message);
}
}
@@ -0,0 +1,434 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot;
import java.io.Serializable;
import java.util.Collections;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.samskivert.util.Histogram;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import net.sf.ehcache.distribution.RMICacheReplicatorFactory;
import net.sf.ehcache.event.CacheEventListener;
import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
import static com.samskivert.depot.Log.log;
/**
* An implementation of {@link CacheAdapter} for ehcache where each
* {@link CacheAdapter.CacheCategory} results in one {@link Ehcache}. All (cacheId, key)
* combinations within one category is stuffed into the same {@link Ehcache}, and all elements are
* cached under {@link EHCacheKey}, which basically wraps just such a tuple.
*
* Thus there are currently only four Ehcaches in play, called 'depotRecord', 'depotLongKeyset',
* 'depotShortKeyset' and 'depotResult'. These must be defined in your ehcache.xml configuration.
* If you use distributed replication/invalidation, you should replicate updates and removes but
* not puts nor updates-via-copy.
*/
public class EHCacheAdapter
implements CacheAdapter
{
public static class EHCachePerformance
{
public Histogram lookups;
public Histogram stores;
public Histogram removes;
public Histogram enumerations;
}
public static class EHCacheConfig
{
public String name;
public int maxElementsInMemory;
public int timeToIdleSeconds;
public int timeToLiveSeconds;
public boolean overflowToDisk = false;
public int maxElementsOnDisk = 0;
public boolean eternal = false;
public MemoryStoreEvictionPolicy msep = MemoryStoreEvictionPolicy.LRU;
public EHCacheConfig (String name, int maxElementsInMemory, int timeToIdleSeconds,
int timeToLiveSeconds) {
this.name = name;
this.maxElementsInMemory = maxElementsInMemory;
this.timeToIdleSeconds = timeToIdleSeconds;
this.timeToLiveSeconds = timeToLiveSeconds;
}
public Ehcache createCache (String uid) {
String cacheName = this.name + (uid == null ? "" : ("." + uid));
return new Cache(cacheName, maxElementsInMemory, msep, overflowToDisk, null,
eternal, timeToLiveSeconds, timeToIdleSeconds, false, 0, null);
}
}
public static final EHCacheConfig EHCACHE_RECORD_CONFIG =
new EHCacheConfig("depotRecord", 200000, 300, 600);
public static final EHCacheConfig EHCACHE_SHORT_KEYSET_CONFIG =
new EHCacheConfig("depotShortKeyset", 25000, 10, 10);
public static final EHCacheConfig EHCACHE_LONG_KEYSET_CONFIG =
new EHCacheConfig("depotLongKeyset", 25000, 300, 300);
public static final EHCacheConfig EHCACHE_RESULT_CONFIG =
new EHCacheConfig("depotResult", 5000, 300, 300);
/**
* Creates an adapter using the supplied cache manager with the default cache configurations.
*
* Note: this adapter does not shut down* the supplied manager when it is shutdown. The caller
* is responsible for shutting down the cache manager when it knows that Depot and any other
* clients no longer need it.
*/
public EHCacheAdapter (CacheManager cachemgr, String ident)
{
this(cachemgr, ident, EHCACHE_RECORD_CONFIG, EHCACHE_SHORT_KEYSET_CONFIG,
EHCACHE_LONG_KEYSET_CONFIG, EHCACHE_RESULT_CONFIG);
}
/**
* Creates an adapter using the supplied cache manager with the specific cache configurations.
*
* Note: this adapter does not shut down* the supplied manager when it is shutdown. The caller
* is responsible for shutting down the cache manager when it knows that Depot and any other
* clients no longer need it.
*/
public EHCacheAdapter (CacheManager cachemgr, String ident, EHCacheConfig recordConfig,
EHCacheConfig shortKeySetConfig, EHCacheConfig longKeySetConfig,
EHCacheConfig resultConfig)
{
_cachemgr = cachemgr;
createEHCache(ident, CacheCategory.RECORD, recordConfig);
createEHCache(ident, CacheCategory.SHORT_KEYSET, shortKeySetConfig);
createEHCache(ident, CacheCategory.LONG_KEYSET, longKeySetConfig);
createEHCache(ident, CacheCategory.RESULT, resultConfig);
}
// from CacheAdapter
public <T> CachedValue<T> lookup (String cacheId, Serializable key)
{
long now = System.currentTimeMillis();
@SuppressWarnings("unchecked")
EHCacheBin<T> bin = (EHCacheBin<T>) _bins.get(cacheId);
if (bin == null) {
return null;
}
CachedValue<T> result = lookup(bin.getCache(), cacheId, key);
_lookups.addValue((int) (System.currentTimeMillis() - now));
return result;
}
// from CacheAdapter
public <T> void store (CacheCategory category, String cacheId, Serializable key, T value)
{
long now = System.currentTimeMillis();
Ehcache cache = _categories.get(category);
if (cache == null) {
throw new IllegalArgumentException("Unknown category: " + category);
}
@SuppressWarnings("unchecked")
EHCacheBin<T> bin = (EHCacheBin<T>) _bins.get(cacheId);
if (bin == null) {
bin = new EHCacheBin<T>(cache, cacheId);
_bins.put(cacheId, bin);
}
bin.getCache().put(new Element(new EHCacheKey(cacheId, key), value != null ? value : NULL));
_stores.addValue((int) (System.currentTimeMillis() - now));
}
// from CacheAdapter
public void remove (String cacheId, Serializable key)
{
long now = System.currentTimeMillis();
EHCacheBin<?> bin = _bins.get(cacheId);
if (bin != null) {
bin.getCache().remove(new EHCacheKey(cacheId, key));
}
_removes.addValue((int) (System.currentTimeMillis() - now));
}
// from CacheAdapter
public <T> Iterable<Serializable> enumerate (final String cacheId)
{
long now = System.currentTimeMillis();
EHCacheBin<?> bin = _bins.get(cacheId);
if (bin == null) {
return Collections.emptySet();
}
// let's return a simple copy of the bin's fancy concurrent hashset
Set<Serializable> result = Sets.newHashSet(bin.getKeys());
_enumerations.addValue((int) (System.currentTimeMillis() - now));
return result;
}
// from CacheAdapter
public void shutdown ()
{
log.debug("EHCacheAdapter shutting down", "lookups", _lookups,
"stores", _stores, "removes", _removes, "enumerations", _enumerations);
// go through and remove all of the caches we resolved
for (Ehcache cache : _categories.values()) {
log.debug("Removing ehcache " + cache.getName());
_cachemgr.removeCache(cache.getName());
}
_categories.clear();
_bins.clear();
}
/**
* Return a snapshot of the current histograms detailing how much time the different
* operations lookup, store, remove and enumerate take.
*/
public EHCachePerformance getPerformanceSnapshot ()
{
EHCachePerformance result = new EHCachePerformance();
result.lookups = _lookups.clone();
result.stores = _stores.clone();
result.removes = _removes.clone();
result.enumerations = _enumerations.clone();
return result;
}
protected <T> CachedValue<T> lookup (Ehcache cache, String cacheId, Serializable key)
{
Element hit = cache.get(new EHCacheKey(cacheId, key));
if (hit == null) {
return null;
}
Serializable rawValue = hit.getValue();
@SuppressWarnings("unchecked")
final T value = (T) (rawValue instanceof NullValue ? null : rawValue);
return new CachedValue<T>() {
public T getValue () {
return value;
}
@Override public String toString () {
return String.valueOf(value);
}
};
}
protected Ehcache createEHCache (String ident, CacheCategory category, EHCacheConfig config)
{
Ehcache cache = config.createCache(ident);
addCacheListeners(cache);
_cachemgr.addCache(cache);
log.debug("Added new ehcache " + cache.getName());
_categories.put(category, cache);
return cache;
}
protected void addCacheListeners (Ehcache cache)
{
// add a listener that updates our local cache
cache.getCacheEventNotificationService().registerListener(_cacheEventListener);
// add an RMI replicator (TODO: this should be optional) (TODO: do we want only one of
// these and to register the same one with all caches?)
Properties props = new Properties();
props.setProperty("replicateAsynchronously", "true");
props.setProperty("replicatePuts", "false");
props.setProperty("replicateUpdates", "true");
props.setProperty("replicateUpdatesViaCopy", "false");
props.setProperty("replicateRemovals", "true");
cache.getCacheEventNotificationService().registerListener(
new RMICacheReplicatorFactory().createCacheEventListener(props));
}
protected static class EHCacheBin<T>
{
public EHCacheBin (Ehcache cache, String id)
{
_cache = cache;
_id = id;
}
public Set<Serializable> getKeys ()
{
return _keys;
}
public void addKey (Serializable key)
{
_keys.add(key);
}
public void removeKey (Serializable key)
{
_keys.remove(key);
}
protected Ehcache getCache ()
{
return _cache;
}
protected Ehcache _cache;
protected String _id;
protected Set<Serializable> _keys =
Sets.newSetFromMap(new ConcurrentHashMap<Serializable, Boolean>());
}
/** A class to wrap a Depot id/key into an EHCache key. */
protected static class EHCacheKey
implements Serializable
{
public EHCacheKey (String id, Serializable key)
{
if (id == null || key == null) {
throw new IllegalArgumentException("Can't handle null key or id");
}
_id = id;
_key = key;
}
public String getCacheId () {
return _id;
}
public Serializable getCacheKey ()
{
return _key;
}
@Override
public String toString ()
{
return "[" + _id + ", " + _key + "]";
}
@Override
public int hashCode ()
{
return 31 * _id.hashCode() + _key.hashCode();
}
@Override
public boolean equals (Object obj)
{
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
EHCacheKey okey = (EHCacheKey) obj;
return _id.equals(okey._id) && _key.equals(okey._key);
}
protected String _id;
protected Serializable _key;
}
/** A class to represent an explicitly Serializable concept of null for EHCache. */
protected static class NullValue implements Serializable
{
@Override public String toString ()
{
return "<EHCache Null>";
}
@Override public boolean equals (Object other)
{
return other != null && other.getClass().equals(NullValue.class);
}
@Override public int hashCode ()
{
return 1;
}
}
protected CacheEventListener _cacheEventListener = new CacheEventListener() {
@Override public Object clone () throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
public void dispose () {}
public void notifyElementEvicted (Ehcache cache, Element element) {
log.debug("notifyElementEvicted(" + cache + ", " + element + ")");
removeFromBin(cache, element);
}
public void notifyElementExpired (Ehcache cache, Element element) {
log.debug("notifyElementExpired(" + cache + ", " + element + ")");
removeFromBin(cache, element);
}
public void notifyElementPut (Ehcache cache, Element element) {
log.debug("notifyElementPut(" + cache + ", " + element + ")");
addToBin(cache, element);
}
public void notifyElementRemoved (Ehcache cache, Element element) {
log.debug("notifyElementRemoved(" + cache + ", " + element + ")");
removeFromBin(cache, element);
}
public void notifyElementUpdated (Ehcache cache, Element element) {
log.debug("notifyElementUpdated(" + cache + ", " + element + ")");
addToBin(cache, element);
}
public void notifyRemoveAll (Ehcache cache) {}
protected void removeFromBin (Ehcache cache, Element element)
{
EHCacheKey key = (EHCacheKey)element.getKey();
EHCacheBin<?> bin = _bins.get(key.getCacheId());
if (bin == null) {
log.debug("Dropping element removal without cache bin", "key", key);
return;
}
bin.removeKey(key.getCacheKey());
}
protected void addToBin (Ehcache cache, Element element)
{
EHCacheKey key = (EHCacheKey)element.getKey();
EHCacheBin<?> bin = _bins.get(key.getCacheId());
if (bin == null) {
log.debug("Dropping element addition without cache bin", "key", key);
return;
}
bin.addKey(key.getCacheKey());
}
};
protected CacheManager _cachemgr;
protected Histogram _lookups = new Histogram(0, 50, 20);
protected Histogram _stores = new Histogram(0, 50, 20);
protected Histogram _removes = new Histogram(0, 50, 20);
protected Histogram _enumerations = new Histogram(0, 50, 20);
protected Map<CacheCategory, Ehcache> _categories =
Collections.synchronizedMap(Maps.<CacheCategory, Ehcache>newHashMap());
protected Map<String, EHCacheBin<?>> _bins =
Collections.synchronizedMap(Maps.<String, EHCacheBin<?>> newHashMap());
// this is just for convenience and memory use; we don't rely on pointer equality anywhere
protected static Serializable NULL = new NullValue() {};
}
@@ -0,0 +1,98 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2009 Michael Bayne and 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.depot;
import com.samskivert.depot.expression.*;
import com.samskivert.depot.impl.expression.IntervalExp;
import com.samskivert.depot.impl.expression.LiteralExp;
import com.samskivert.depot.impl.expression.ValueExp;
/**
* Provides static methods for expression construction. For example: {@link #literal}, {@link
* #value} and {@link #years}.
*/
public class Exps
{
/**
* Wraps the supplied object in a value expression.
*/
public static FluentExp value (Object value)
{
return new ValueExp(value);
}
/**
* Creates a literal expression with the supplied SQL snippet. Note: you're probably breaking
* cross platform compatibility by using this construction.
*/
public static SQLExpression literal (String text)
{
return new LiteralExp(text);
}
/**
* Creates an interval for the specified number of years.
*/
public static SQLExpression years (int amount)
{
return new IntervalExp(IntervalExp.Unit.YEAR, amount);
}
/**
* Creates an interval for the specified number of months.
*/
public static SQLExpression months (int amount)
{
return new IntervalExp(IntervalExp.Unit.MONTH, amount);
}
/**
* Creates an interval for the specified number of days.
*/
public static SQLExpression days (int amount)
{
return new IntervalExp(IntervalExp.Unit.DAY, amount);
}
/**
* Creates an interval for the specified number of hours.
*/
public static SQLExpression hours (int amount)
{
return new IntervalExp(IntervalExp.Unit.HOUR, amount);
}
/**
* Creates an interval for the specified number of minutes.
*/
public static SQLExpression minutes (int amount)
{
return new IntervalExp(IntervalExp.Unit.MINUTE, amount);
}
/**
* Creates an interval for the specified number of seconds.
*/
public static SQLExpression seconds (int amount)
{
return new IntervalExp(IntervalExp.Unit.SECOND, amount);
}
}
@@ -0,0 +1,132 @@
//
// $Id: Exps.java 505 2009-08-07 01:58:58Z samskivert $
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2009 Michael Bayne and 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.depot;
import com.samskivert.depot.expression.FluentExp;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.expression.AggregateFun.*;
import com.samskivert.depot.impl.expression.ConditionalFun.*;
/**
* Provides static methods for function construction.
*/
public class Funcs
{
/**
* Creates an aggregate expression that averages all values from the supplied expression.
* This would usually be used in a FieldOverride and supplied with a ColumnExp.
*/
public static FluentExp average (SQLExpression expr)
{
return new Average(expr);
}
/**
* Creates an expression that averages all distinct values from the supplied expression.
* This would usually be used in a FieldOverride and supplied with a ColumnExp.
*/
public static FluentExp averageDistinct (SQLExpression expr)
{
return new Average(expr, true);
}
/**
* Creates an aggregate expression that counts the number of rows from the supplied
* expression. This would usually be used in a FieldOverride and supplied with a ColumnExp.
*/
public static FluentExp count (SQLExpression expr)
{
return new Count(expr);
}
/**
* Creates an aggregate expression that counts the number of distinct values from the
* supplied expression. This would usually be used in a FieldOverride and supplied with a
* ColumnExp.
*/
public static FluentExp countDistinct (SQLExpression expr)
{
return new Count(expr, true);
}
/**
* Creates an aggregate expression that evaluates to true iff every value from the supplied
* expression is also true. This would usually be used in a FieldOverride and supplied with
* a ColumnExp.
*/
public static FluentExp every (SQLExpression expr)
{
return new Every(expr);
}
/**
* Creates an aggregate expression that finds the largest value in the values from the
* supplied expression. This would usually be used in a FieldOverride and supplied with
* a ColumnExp.
*/
public static FluentExp max (SQLExpression expr)
{
return new Max(expr);
}
/**
* Creates an aggregate expression that finds the largest value in the values from the
* supplied expression. This would usually be used in a FieldOverride and supplied with
* a ColumnExp.
*/
public static FluentExp min (SQLExpression expr)
{
return new Min(expr);
}
/**
* Creates an aggregate expression that sums all the values from the supplied expression.
* This would usually be used in a FieldOverride and supplied with a ColumnExp.
*/
public static FluentExp sum (SQLExpression expr)
{
return new Sum(expr);
}
/**
* Creates an expression that evaluates to the first supplied expression that is not null.
*/
public static FluentExp coalesce (SQLExpression... args)
{
return new Coalesce(args);
}
/**
* Creates an expression that evaluates to the largest of the given expressions.
*/
public static FluentExp greatest (SQLExpression... args)
{
return new Greatest(args);
}
/**
* Creates an expression that evaluates to the smallest of the given expressions.
*/
public static FluentExp least (SQLExpression... args)
{
return new Least(args);
}
}
+297
View File
@@ -0,0 +1,297 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import com.google.common.base.Function;
import com.google.common.collect.Maps;
import com.samskivert.util.StringUtil;
import com.samskivert.depot.clause.WhereClause;
import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.DepotMarshaller;
import com.samskivert.depot.impl.DepotUtil;
import com.samskivert.depot.impl.FragmentVisitor;
/**
* A special form of {@link WhereClause} that uniquely specifies a single database row and thus
* also a single persistent object. It 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<T extends PersistentRecord> extends WhereClause
implements ValidatingCacheInvalidator
{
/** Handles the matching of the key columns to its bound values. This is needed so that we can
* combine a bunch of keys into a {@link KeySet}. */
public static class Expression implements SQLExpression
{
public Expression (Class<? extends PersistentRecord> pClass, Comparable<?>[] values) {
_pClass = pClass;
_values = values;
}
public Class<? extends PersistentRecord> getPersistentClass () {
return _pClass;
}
public Comparable<?>[] getValues () {
return _values;
}
public Object accept (FragmentVisitor<?> builder) {
return builder.visit(this);
}
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet) {
classSet.add(getPersistentClass());
}
protected Class<? extends PersistentRecord> _pClass;
protected Comparable<?>[] _values;
}
/**
* Creates a single column key.
*/
public static <T extends PersistentRecord> Key<T> newKey (
Class<T> pClass, ColumnExp ix, Comparable<?> val)
{
return new Key<T>(pClass, new ColumnExp[] { ix }, new Comparable[] { val });
}
/**
* Creates a two column key.
*/
public static <T extends PersistentRecord> Key<T> newKey (
Class<T> pClass, ColumnExp ix1, Comparable<?> val1, ColumnExp ix2, Comparable<?> val2)
{
return new Key<T>(pClass, new ColumnExp[] { ix1, ix2 }, new Comparable[] { val1, val2 });
}
/**
* Creates a three column key.
*/
public static <T extends PersistentRecord> Key<T> newKey (
Class<T> pClass, ColumnExp ix1, Comparable<?> val1, ColumnExp ix2, Comparable<?> val2,
ColumnExp ix3, Comparable<?> val3)
{
return new Key<T>(pClass, new ColumnExp[] { ix1, ix2, ix3 },
new Comparable[] { val1, val2, val3 });
}
/**
* Returns a function that extracts an integer from a record's {@link Key}. This should only be
* used on records whose primary key is a single integer.
*/
public static <T extends PersistentRecord> Function<Key<T>, Integer> toInt ()
{
return extract(0);
}
/**
* Returns a function that extracts an element key from a record's {@link Key}.
*
* @param index the index in the key of the element to be extracted.
*/
public static <T extends PersistentRecord, E> Function<Key<T>, E> extract (final int index)
{
return new Function<Key<T>, E>() {
public E apply (Key<T> key) {
@SuppressWarnings("unchecked") E value = (E)key.getValues()[index];
return value;
}
};
}
/**
* Constructs a new multi-column {@code Key} with the given values.
*/
public Key (Class<T> pClass, ColumnExp[] fields, Comparable<?>[] values)
{
this(pClass, toCanonicalOrder(pClass, fields, values));
}
/**
* Used to create a key when you know you have the canonical values array. Don't call this
* unless you know what you're doing!
*/
// TODO: This should perhaps be made package-private, but DepotMarshaller uses it
public Key (Class<T> pClass, Comparable<?>[] values)
{
_pClass = pClass;
_values = values;
}
/**
* Returns the persistent class for which we represent a key.
*/
public Class<T> getPersistentClass ()
{
return _pClass;
}
/**
* Returns the values bound to this key.
*/
public Comparable<?>[] getValues ()
{
return _values;
}
@Override // from WhereClause
public SQLExpression getWhereExpression ()
{
return new Expression(_pClass, _values);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
classSet.add(_pClass);
}
// from SQLExpression
public Object accept (FragmentVisitor<?> builder)
{
return builder.visit(this);
}
// from ValidatingCacheInvalidator
public void validateFlushType (Class<?> pClass)
{
if (!pClass.equals(_pClass)) {
throw new IllegalArgumentException(
"Class mismatch between persistent record and cache invalidator " +
"[record=" + pClass.getSimpleName() +
", invtype=" + _pClass.getSimpleName() + "].");
}
}
// from CacheInvalidator
public void invalidate (PersistenceContext ctx)
{
ctx.cacheInvalidate(this);
}
/**
* Appends just the key=value portion of our {@link #toString} to the supplied buffer.
*/
public void toShortString (StringBuilder builder)
{
ColumnExp[] keyFields = DepotUtil.getKeyFields(_pClass);
for (int ii = 0; ii < keyFields.length; ii ++) {
if (ii > 0) {
builder.append(":");
}
builder.append(keyFields[ii].name).append("=").append(_values[ii]);
}
}
@Override // from WhereClause
public void validateQueryType (Class<?> pClass)
{
super.validateQueryType(pClass);
validateTypesMatch(pClass, _pClass);
}
@Override
public boolean equals (Object obj)
{
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
return Arrays.equals(_values, ((Key<?>) obj)._values);
}
@Override
public int hashCode ()
{
return Arrays.hashCode(_values);
}
@Override
public String toString ()
{
StringBuilder builder = new StringBuilder(_pClass.getSimpleName());
builder.append("(");
toShortString(builder);
builder.append(")");
return builder.toString();
}
protected static Comparable<?>[] toCanonicalOrder (
Class<? extends PersistentRecord> pClass, ColumnExp[] fields, Comparable<?>[] values)
{
if (fields.length != values.length) {
throw new IllegalArgumentException("Field and Value arrays must be of equal length.");
}
// look up the cached primary key fields for this object
ColumnExp[] keyFields = DepotUtil.getKeyFields(pClass);
// fast path!
if (fields.length == 1 && keyFields.length == 1 && keyFields[0].equals(fields[0])) {
return new Comparable<?>[] { values[0] };
}
// build a local map of field name -> field value
Map<ColumnExp, Comparable<?>> map = Maps.newHashMap();
for (int ii = 0; ii < fields.length; ii++) {
map.put(fields[ii], values[ii]);
}
// now extract the values in field order and ensure none are extra or missing
Comparable<?>[] cvalues = new Comparable<?>[values.length];
for (int ii = 0; ii < keyFields.length; ii++) {
Comparable<?> value = map.remove(keyFields[ii]);
if (value == null) {
// make sure we were provided with a value for this primary key field
throw new IllegalArgumentException(
"Missing value for key field: " + keyFields[ii]);
}
if (!(value instanceof Serializable)) {
throw new IllegalArgumentException(
"Non-serializable argument [key=" + keyFields[ii] +
", value=" + value + "]");
}
cvalues[ii] = value;
}
// finally make sure we were not given any fields that are not primary key fields
if (map.size() > 0) {
throw new IllegalArgumentException(
"Non-key columns given: " + StringUtil.join(map.keySet().toArray(), ", "));
}
return cvalues;
}
/** The persistent record type for which we are a key. */
protected final Class<T> _pClass;
/** The expression that identifies our row. */
protected final Comparable<?>[] _values;
}
@@ -0,0 +1,275 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot;
import java.io.Serializable;
import java.util.AbstractCollection;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import com.google.common.collect.Iterators;
import com.google.common.base.Function;
import com.samskivert.util.Logger;
import com.samskivert.util.StringUtil;
import com.samskivert.depot.clause.WhereClause;
import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.DepotUtil;
import com.samskivert.depot.impl.FragmentVisitor;
import com.samskivert.depot.impl.expression.LiteralExp;
import com.samskivert.depot.impl.operator.In;
/**
* Contains a set of primary keys that match a set of persistent records. This is used internally
* in Depot when decomposing queries into two parts: first a query for the primary keys that
* identify the records that match a free-form query and then another query that operates on the
* previously identified keys. The keys obtained in the first query are used to create a KeySet and
* modifications and deletions using this set will automatically flush the appropriate records from
* the cache.
*/
public abstract class KeySet<T extends PersistentRecord> extends WhereClause
implements Serializable, ValidatingCacheInvalidator, Iterable<Key<T>>
{
/**
* Creates a key set for the supplied persistent record and keys.
*/
public static <T extends PersistentRecord> KeySet<T> newKeySet (
Class<T> pClass, Collection<Key<T>> keys)
{
if (keys.size() == 0) {
return new EmptyKeySet<T>(pClass);
}
ColumnExp[] keyFields = DepotUtil.getKeyFields(pClass);
if (keyFields.length == 1) {
Comparable<?> first = keys.iterator().next().getValues()[0];
Comparable<?>[] keyArray;
if (first instanceof Integer) {
keyArray = new Integer[keys.size()];
} else if (first instanceof Integer) {
keyArray = new String[keys.size()];
} else {
keyArray = new Comparable<?>[keys.size()];
}
int ii = 0;
for (Key<T> key : keys) {
keyArray[ii++] = key.getValues()[0];
}
return new SingleKeySet<T>(pClass, keyArray);
} else {
// TODO: is there a maximum size of an or query? 32768?
Comparable<?>[][] keysValues = new Comparable<?>[keys.size()][];
int ii = 0;
for (Key<T> key : keys) {
keysValues[ii++] = key.getValues();
}
return new MultiKeySet<T>(pClass, keyFields, keysValues);
}
}
/**
* Creates a key set for the supplied persistent record and collection of simple keys.
*
* @exception IllegalArgumentException thrown if the supplied record does not use a simple
* (single-column) primay key.
*/
public static <T extends PersistentRecord> KeySet<T> newSimpleKeySet (
Class<T> pClass, Collection<? extends Comparable<?>> keys)
{
ColumnExp[] keyFields = DepotUtil.getKeyFields(pClass);
if (keyFields.length != 1) {
throw new IllegalArgumentException(
"Cannot create KeySet using simple keys for record with non-simple primary key " +
"[record=" + pClass + "]");
}
if (keys.size() == 0) {
return new EmptyKeySet<T>(pClass);
} else {
Comparable<?> first = keys.iterator().next();
Comparable<?>[] keyArray;
if (first instanceof Integer) {
keyArray = new Integer[keys.size()];
} else if (first instanceof Integer) {
keyArray = new String[keys.size()];
} else {
keyArray = new Comparable<?>[keys.size()];
}
return new SingleKeySet<T>(pClass, keys.toArray(keyArray));
}
}
protected static class EmptyKeySet<T extends PersistentRecord> extends KeySet<T>
{
public EmptyKeySet (Class<T> pClass) {
super(pClass);
}
@Override public SQLExpression getWhereExpression () {
return new LiteralExp("(1 = 0)");
}
// from Iterable<Key<T>>
public Iterator<Key<T>> iterator () {
return Collections.<Key<T>>emptyList().iterator();
}
@Override public int size () {
return 0;
}
@Override public boolean equals (Object obj) {
if (this == obj) {
return true;
}
return (obj instanceof EmptyKeySet<?>) &&
_pClass.equals(((EmptyKeySet<?>)obj)._pClass);
}
@Override public int hashCode () {
return _pClass.hashCode();
}
@Override public String toString () {
return DepotUtil.justClassName(_pClass) + "(empty)";
}
}
protected static class SingleKeySet<T extends PersistentRecord> extends KeySet<T>
{
public SingleKeySet (Class<T> pClass, Comparable<?>[] keys) {
super(pClass);
// TODO: remove when we update to 1.6 and change Postgres In handling
if (keys.length > In.MAX_KEYS) {
throw new IllegalArgumentException(
"Cannot create where clause for more than " + In.MAX_KEYS + " keys at a time.");
}
_keys = keys;
}
@Override public SQLExpression getWhereExpression () {
// Single-column keys result in the compact IN(keyVal1, keyVal2, ...)
return new In(DepotUtil.getKeyFields(_pClass)[0], _keys);
}
// from Iterable<Key<T>>
public Iterator<Key<T>> iterator () {
return Iterators.transform(
Iterators.forArray(_keys), new Function<Comparable<?>, Key<T>>() {
public Key<T> apply (Comparable<?> key) {
return new Key<T>(_pClass, new Comparable<?>[] { key });
}
});
}
@Override public int size () {
return _keys.length;
}
@Override public boolean equals (Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof SingleKeySet<?>)) {
return false;
}
SingleKeySet<?> oset = (SingleKeySet<?>)obj;
return _pClass.equals(oset._pClass) && Arrays.equals(_keys, oset._keys);
}
@Override public int hashCode () {
return 31 * _pClass.hashCode() + Arrays.hashCode(_keys);
}
@Override public String toString () {
return DepotUtil.justClassName(_pClass) + StringUtil.toString(_keys);
}
protected Comparable<?>[] _keys;
}
/**
* Returns an unmodifiable {@link Collection} view on this KeySet.
*/
public Collection<Key<T>> toCollection ()
{
return new AbstractCollection<Key<T>>() {
@Override public Iterator<Key<T>> iterator () {
return KeySet.this.iterator();
}
@Override public int size () {
return KeySet.this.size();
}
};
}
/**
* Returns the number of keys in this set.
*/
public abstract int size ();
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
classSet.add(_pClass);
}
// from SQLExpression
public Object accept (FragmentVisitor<?> builder)
{
return builder.visit(this);
}
// from ValidatingCacheInvalidator
public void invalidate (PersistenceContext ctx) {
for (Key<T> key : this) {
ctx.cacheInvalidate(key);
}
}
// from ValidatingCacheInvalidator
public void validateFlushType (Class<?> pClass)
{
if (!pClass.equals(_pClass)) {
throw new IllegalArgumentException(Logger.format(
"Class mismatch between persistent record and cache invalidator",
"record", pClass.getSimpleName(), "invtype", _pClass.getSimpleName()));
}
}
@Override // from WhereClause
public void validateQueryType (Class<?> pClass)
{
super.validateQueryType(pClass);
validateTypesMatch(pClass, _pClass);
}
protected KeySet (Class<T> pClass)
{
_pClass = pClass;
}
protected Class<T> _pClass;
}
@@ -0,0 +1,32 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot;
import com.samskivert.util.Logger;
/**
* Contains a reference to the log object used by this package.
*/
public class Log
{
/** We dispatch our log messages through this logger. */
public static Logger log = Logger.getLogger("com.samskivert.depot");
}
@@ -0,0 +1,136 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2009 Michael Bayne and 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.depot;
import com.samskivert.depot.expression.FluentExp;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.expression.NumericalFun.*;
/**
* Provides static methods for math-related function construction.
*/
public class MathFuncs
{
/**
* Creates an expression that computes the absolute value of the supplied expression.
*/
public static FluentExp abs (SQLExpression expr)
{
return new Abs(expr);
}
/**
* Creates an expression that computes the integer ceiling of the supplied expression.
*/
public static FluentExp ceil (SQLExpression exp)
{
return new Ceil(exp);
}
/**
* Creates an expression that computes the exponential of the supplied expression.
*/
public static FluentExp exp (SQLExpression exp)
{
return new Exp(exp);
}
/**
* Creates an expression that computes the integer floor of the supplied expression.
*/
public static FluentExp floor (SQLExpression exp)
{
return new Floor(exp);
}
/**
* Creates an expression that computes the natural logarithm of the supplied expression.
*/
public static FluentExp ln (SQLExpression exp)
{
return new Ln(exp);
}
/**
* Creates an expression that computes the base-10 logarithm of the supplied expression.
*/
public static FluentExp log10 (SQLExpression value)
{
return new Log10(value);
}
/**
* Creates an expression that evaluates to the constant PI.
*/
public static FluentExp pi ()
{
return new Pi();
}
/**
* Creates an expression that computes the value expression to the given power.
*/
public static FluentExp power (SQLExpression value, SQLExpression power)
{
return new Power(value, power);
}
/**
* Creates an expression that returns a random number between 0.0 and 1.0.
*/
public static FluentExp random ()
{
return new Random();
}
/**
* Creates an expression that computes the whole number nearest the supplied expression.
*/
public static FluentExp round (SQLExpression exp)
{
return new Round(exp);
}
/**
* Creates an expression that computes the sign of the supplied expression.
*/
public static FluentExp sign (SQLExpression exp)
{
return new Sign(exp);
}
/**
* Creates an expression that computes the square root of the supplied expression.
*/
public static FluentExp sqrt (SQLExpression exp)
{
return new Sqrt(exp);
}
/**
* Creates an expression that computes the truncation of the supplied expression,
* i.e. the next closest whole number to zero.
*/
public static FluentExp trunc (SQLExpression exp)
{
return new Trunc(exp);
}
}
@@ -0,0 +1,211 @@
/**
*
*/
package com.samskivert.depot;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.base.Function;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.DepotUtil;
import com.samskivert.util.StringUtil;
import com.samskivert.util.Tuple;
/**
* This class handles the construction of a Where clause from a set of multi-column keys.
* The naive implementation would construct logical structures like
* (A=1 and B="foo" and C=5.95) or (A=1 and B="foo" and C=7.98) or (A=1 and B="foo" and C=11.3)
* for a large number of the keysets we see in practice. Sending such structures to the database
* is needlessly verbose and it's not known to which degree the database is able to optimize index
* access from them.
*
* Thus we do our own optimization here; the example above would be turned into
* (A=1 and B="foo" and C in (5.95, 7.98, 11.3))
*
*/
class MultiKeySet<T extends PersistentRecord> extends KeySet<T>
{
public MultiKeySet (Class<T> pClass, ColumnExp[] keyFields, Comparable<?>[][] keys)
{
super(pClass);
_keys = keys;
_keyFields = keyFields;
}
@Override public SQLExpression getWhereExpression ()
{
Set<Integer> columns = Sets.newHashSet();
for (int ii = 0; ii < _keyFields.length; ii ++) {
columns.add(ii);
}
return rowsToSQLExpression(Lists.newLinkedList(Arrays.asList(_keys)), columns);
}
// from Iterable<Key<T>>
public Iterator<Key<T>> iterator () {
return Iterators.transform(
Iterators.forArray(_keys), new Function<Comparable<?>[], Key<T>>() {
public Key<T> apply (Comparable<?>[] key) {
return new Key<T>(_pClass, key);
}
});
}
@Override public int size () {
return _keys.length;
}
@Override public boolean equals (Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof MultiKeySet<?>)) {
return false;
}
MultiKeySet<?> oset = (MultiKeySet<?>)obj;
return _pClass.equals(oset._pClass) && Arrays.equals(_keys, oset._keys);
}
@Override public int hashCode () {
return 31 * _pClass.hashCode() + Arrays.hashCode(_keys);
}
@Override public String toString () {
return DepotUtil.justClassName(_pClass) + StringUtil.toString(_keys);
}
// note: this method will destructively modify its arguments
protected SQLExpression rowsToSQLExpression (
List<Comparable<?>[]> keys, Set<Integer> columnsLeft)
{
List<SQLExpression> matches = Lists.newArrayList();
while (!keys.isEmpty()) {
// go through each column that is still in play, finding the single largest common
// chunk of any single value in each column
int maxSize = 0;
int maxColumn = -1;
Comparable<?> maxValue = null;
for (int column : columnsLeft) {
Tuple<Comparable<?>, Integer> colChunk = findBiggestChunk(keys, column);
if (colChunk.right > maxSize) {
maxColumn = column;
maxSize = colChunk.right;
maxValue = colChunk.left;
}
}
if (maxSize > 3) {
// if there's a reasonable chunk, extract it & modify 'keys' in the process
matches.add(extractChunk(keys, columnsLeft, maxColumn, maxValue));
} else {
// but if there are no large chunks (left), revert to the traditional
// (A=1 and B=2) or (A=1 and B=3) or ...
// algorithm for the remaining rows.
matches.addAll(gatherDetritus(keys, columnsLeft));
}
}
return Ops.or(matches);
}
// iterate key rows and find the most common value across those rows, in the given column
protected Tuple<Comparable<?>, Integer> findBiggestChunk (List<Comparable<?>[]> rows, int col)
{
int maxCount = 0;
Comparable<?> maxValue = null;
// was Ray writing a CountingMap?
Map<Comparable<?>, Integer> countMap = Maps.newHashMap();
for (Comparable<?>[] row : rows) {
Comparable<?> element = row[col];
Integer count = countMap.get(element);
if (count == null) {
countMap.put(element, count = 1);
} else {
countMap.put(element, ++count);
}
if (count > maxCount) {
maxCount = count;
maxValue = element;
}
}
return new Tuple<Comparable<?>, Integer>(maxValue, maxCount);
}
// find all the rows that contain the given chunk value in the given column. delete these
// (destructively modifying the input argument) and replace them with an optimized
// SQLExpression, which is returned
protected SQLExpression extractChunk (List<Comparable<?>[]> rows, Set<Integer> columnsLeft,
int column, Comparable<?> value)
{
Iterator<Comparable<?>[]> iterator = rows.iterator();
LinkedList<Comparable<?>[]> newRows = Lists.newLinkedList();
while (iterator.hasNext()) {
Comparable<?>[] row = iterator.next();
if (row[column].equals(value)) {
newRows.add(row);
iterator.remove();
}
}
Set<Integer> otherColumns = Sets.newHashSet(columnsLeft);
otherColumns.remove(column);
SQLExpression otherCondition;
if (otherColumns.size() == 1) {
// if there's just two columns, we're doing (A = ? and B in (?, ?, ?, ...))
int otherColumn = otherColumns.iterator().next();
List<Comparable<?>> otherValues = Lists.newArrayList();
for (Comparable<?>[] row : newRows) {
otherValues.add(row[otherColumn]);
}
otherCondition = _keyFields[otherColumn].in(otherValues);
} else {
// otherwise we'll be recursing into i.e.
// (A = ? and ((B = ? and C = ?) or (B = ? and C = ?) or ...))
otherCondition = rowsToSQLExpression(newRows, otherColumns);
}
return Ops.and(_keyFields[column].eq(value), otherCondition);
}
// given unoptimizable key rows, gather them up into simple SQLExpressions according to
// the naive algorithm
protected List<SQLExpression> gatherDetritus (
List<Comparable<?>[]> keys, Set<Integer> columnsLeft)
{
List<SQLExpression> conditions = Lists.newArrayList();
Iterator<Comparable<?>[]> iterator = keys.iterator();
while (iterator.hasNext()) {
Comparable<?>[] row = iterator.next();
List<SQLExpression> bits = Lists.newArrayList();
for (int column : columnsLeft) {
bits.add(_keyFields[column].eq(row[column]));
}
conditions.add(Ops.and(bits));
iterator.remove();
}
return conditions;
}
protected Comparable<?>[][] _keys;
protected ColumnExp[] _keyFields;
}
+176
View File
@@ -0,0 +1,176 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2009 Michael Bayne and 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.depot;
import com.google.common.collect.Iterables;
import com.samskivert.depot.clause.SelectClause;
import com.samskivert.depot.expression.FluentExp;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.operator.Add;
import com.samskivert.depot.impl.operator.Exists;
import com.samskivert.depot.impl.operator.Like;
import com.samskivert.depot.impl.operator.Mul;
import com.samskivert.depot.impl.operator.MultiOperator;
import com.samskivert.depot.impl.operator.Not;
/**
* Provides static methods for operator construction that don't fit nicely into the fluent style.
* For example: Ops.and(), Ops.or() and Ops.not().
*/
public class Ops
{
/**
* Creates a NOT expression with the supplied target expression.
*/
public static SQLExpression not (SQLExpression expr)
{
return new Not(expr);
}
/**
* Creates an AND expression with the supplied target expressions.
*/
public static FluentExp and (Iterable<? extends SQLExpression> conditions)
{
return and(Iterables.toArray(conditions, SQLExpression.class));
}
/**
* Creates an AND expression with the supplied target expressions.
*/
public static FluentExp and (SQLExpression... conditions)
{
return new MultiOperator(conditions) {
@Override public String operator() {
return " and ";
}
@Override public Object evaluate (Object[] values) {
// if all operands are true, return true, else if all operands are boolean, return
// false, else return NO_VALUE
boolean allTrue = true;
for (Object value : values) {
if (value instanceof NoValue) {
return value;
}
if (Boolean.FALSE.equals(value)) {
allTrue = false;
} else if (!Boolean.TRUE.equals(value)) {
return new NoValue("Non-boolean operand to AND: " + value);
}
}
return allTrue;
}
};
}
/**
* Creates an OR expression with the supplied target expressions.
*/
public static FluentExp or (Iterable<? extends SQLExpression> conditions)
{
return or(Iterables.toArray(conditions, SQLExpression.class));
}
/**
* Creates an OR expression with the supplied target expressions.
*/
public static FluentExp or (SQLExpression... conditions)
{
return new MultiOperator(conditions) {
@Override public String operator() {
return " or ";
}
@Override public Object evaluate (Object[] values) {
boolean anyTrue = false;
for (Object value : values) {
if (value instanceof NoValue) {
return value;
}
if (Boolean.TRUE.equals(value)) {
anyTrue = true;
} else if (!Boolean.FALSE.equals(value)) {
return new NoValue("Non-boolean operand to OR: " + value);
}
}
return anyTrue;
}
};
}
/**
* Returns an expression that matches when the source is like the supplied value.
*/
public static FluentExp like (SQLExpression source, Comparable<?> value)
{
return new Like(source, value, true);
}
/**
* Returns an expression that matches when the source is like the supplied expression.
*/
public static FluentExp like (SQLExpression source, SQLExpression expr)
{
return new Like(source, expr, true);
}
/**
* Returns an expression that matches when the source is NOT like the supplied value.
*/
public static FluentExp notLike (SQLExpression source, Comparable<?> value)
{
return new Like(source, value, false);
}
/**
* Returns an expression that matches when the source is NOT like the supplied expression.
*/
public static FluentExp notLike (SQLExpression source, SQLExpression expr)
{
return new Like(source, expr, false);
}
/**
* Creates an EXISTS expression with the supplied select clause.
*/
public static SQLExpression exists (SelectClause target)
{
return new Exists(target);
}
/**
* Adds the supplied expressions together.
*/
public static FluentExp add (SQLExpression... exprs)
{
return new Add(exprs);
}
/**
* Multiplies the supplied expressions together.
*/
public static FluentExp mul (SQLExpression... exprs)
{
return new Mul(exprs);
}
}
@@ -0,0 +1,641 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.samskivert.io.PersistenceException;
import com.samskivert.util.StringUtil;
import com.samskivert.jdbc.ConnectionProvider;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.jdbc.JDBCUtil;
import com.samskivert.jdbc.LiaisonRegistry;
import com.samskivert.depot.CacheAdapter.CacheCategory;
import com.samskivert.depot.CacheAdapter.CachedValue;
import com.samskivert.depot.annotation.TableGenerator;
import com.samskivert.depot.impl.DepotMarshaller;
import com.samskivert.depot.impl.DepotMetaData;
import com.samskivert.depot.impl.DepotTypes;
import com.samskivert.depot.impl.KeyCacheKey;
import com.samskivert.depot.impl.Modifier;
import com.samskivert.depot.impl.Operation;
import com.samskivert.depot.impl.Query;
import com.samskivert.depot.impl.SQLBuilder;
import static com.samskivert.depot.Log.log;
/**
* Defines a scope in which global annotations are shared.
*/
public class PersistenceContext
{
/** Allow toggling of query logging and other debug output via a system property. */
public static final boolean DEBUG = Boolean.getBoolean("com.samskivert.depot.debug");
/** Allow toggling of cache-related logging via a system property. */
public static final boolean CACHE_DEBUG =
Boolean.getBoolean("com.samskivert.depot.cache_debug");
/** Map {@link TableGenerator} instances by name. */
public Map<String, TableGenerator> tableGenerators = Maps.newHashMap();
/**
* A cache listener is notified when cache entries change. Its purpose is typically to do
* further invalidation of dependent entries in other caches.
*/
public static interface CacheListener<T>
{
/**
* The given entry (which is never null) has just been evicted from the cache.
*
* This method is most commonly used to trigger custom cache invalidation of records that
* depend on the one that was just invalidated.
*/
public void entryInvalidated (T oldEntry);
/**
* The given entry, which may be an explicit null, has just been placed into the cache. The
* previous cache entry, if any, is also supplied.
*
* This method is most likely used by repositories to index entries by attribute for quick
* cache invalidation when brute force is unrealistically time consuming.
*/
public void entryCached (T newEntry, T oldEntry);
}
/**
* The callback for {@link #cacheTraverse}; this is called for each entry in a given cache.
*/
public static interface CacheTraverser<T extends Serializable>
{
/**
* 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 #testForEviction}.
*/
public static abstract class CacheEvictionFilter<T extends Serializable>
implements CacheTraverser<T>
{
// 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);
}
/**
* Returns the cache adapter used by this context or null if caching is disabled.
*/
public CacheAdapter getCacheAdapter ()
{
return _cache;
}
/**
* Creates an uninitialized persistence context. {@link #init} must later be called on this
* context to prepare it for operation.
*/
public PersistenceContext ()
{
}
/**
* Creates and initializes a persistence context. See {@link #init}.
*/
public PersistenceContext (String ident, ConnectionProvider conprov, CacheAdapter adapter)
{
init(ident, conprov, adapter);
}
/**
* Initializes this context with its connection provider and cache adapter.
*
* @param ident the identifier to provide to the connection provider when requesting a
* connection.
* @param conprov provides JDBC {@link Connection} instances.
* @param adapter an optional adapter to a cache management system.
*/
public void init (String ident, ConnectionProvider conprov, CacheAdapter adapter)
{
_ident = ident;
_conprov = conprov;
_liaison = LiaisonRegistry.getLiaison(conprov.getURL(ident));
_cache = adapter;
// set up some basic meta-meta-data
_meta.init(this);
}
/**
* Shuts this persistence context down, shutting down any caching system in use and shutting
* down the JDBC connection pool.
*/
public void shutdown ()
{
try {
if (_cache != null) {
_cache.shutdown();
}
} catch (Throwable t) {
log.warning("Failure shutting down Depot cache.", t);
}
if (_conprov != null) {
_conprov.shutdown();
}
}
/**
* Returns a snapshot of our current runtime statistics.
*/
public Stats.Snapshot getStats ()
{
return _stats.getSnapshot();
}
/**
* Creates and return a new {@link SQLBuilder} for the appropriate dialect.
*/
public SQLBuilder getSQLBuilder (DepotTypes types)
{
return _meta.getSQLBuilder(types, _liaison);
}
/**
* Registers a schema migration for the specified entity class.
*
* <p> This method must be called <b>before</b> an Entity is used by any repository. Thus you
* should register all migrations in the constructor of the repository that declares them in
* its {@link DepotRepository#getManagedRecords} method.
*
* <p> Note that the migration process is as follows:
*
* <ul><li> Note the difference between the entity's declared version and the version recorded
* in the database.
* <li> Run all registered pre-migrations
* <li> Perform all default migrations (column and index additions, index removals)
* <li> Run all registered post-migrations </ul>
*
* Thus you must either be prepared for the entity to be at <b>any</b> version prior to your
* migration target version because we may start up, find the schema at version 1 and the
* Entity class at version 8 and do all "standard" migrations in one fell swoop. So if a column
* got added in version 2 and renamed in version 6 and your migration was registered for
* version 6 to do that migration, it must be prepared for the column not to exist at all.
*
* <p> If you want a completely predictable migration process, never use the default migrations
* and register a pre-migration for every single schema migration and they will then be
* guaranteed to be run in registration order and with predictable pre- and post-conditions.
*
* <p> Note that if {@link PersistenceContext#initializeRepositories} is used, then all schema
* migrations for all known repositories will be run and then all data migrations for all known
* repositories will be run. This is recommeneded because schema migrations may fail and it is
* generally better to have not yet done the data migration rather than having schema and data
* migrations interleaved and potentially leaving the database in a strange state.
*/
public <T extends PersistentRecord> void registerMigration (
Class<T> type, SchemaMigration migration)
{
DepotMarshaller<T> marshaller = getRawMarshaller(type);
if (marshaller.isInitialized()) {
throw new IllegalStateException(
"Migrations must be registered before initializeRepositories() is called.");
}
marshaller.registerMigration(migration);
}
/**
* Returns the marshaller for the specified persistent object class, creating and initializing
* it if necessary.
*/
public <T extends PersistentRecord> DepotMarshaller<T> getMarshaller (Class<T> type)
throws DatabaseException
{
checkAreInitialized(); // le check du sanity
DepotMarshaller<T> marshaller = getRawMarshaller(type);
try {
if (!marshaller.isInitialized()) {
// initialize the marshaller which may create or migrate the table for its
// underlying persistent object
marshaller.init(this, _meta);
if (marshaller.getTableName() != null && _warnOnLazyInit) {
log.warning("Record initialized lazily", "type", type.getName(),
new Exception());
}
}
} catch (DatabaseException pe) {
throw (DatabaseException)new DatabaseException(
"Failed to initialize marshaller [type=" + type + "].").initCause(pe);
}
return marshaller;
}
/**
* Invokes a non-modifying query and returns its result.
*/
public <T> T invoke (Query<T> query)
throws DatabaseException
{
// we check to see if the query is already cached before invoking it to avoid requesting a
// database connection if we don't actually need one
T result = query.getCachedResult(this);
if (result != null) {
query.updateStats(_stats);
return result;
}
return invoke(query, true);
}
/**
* Invokes a modifying query and returns the number of rows modified.
*/
public int invoke (Modifier modifier)
throws DatabaseException
{
return invoke(modifier, true);
}
/**
* Returns true if there is a {@link CacheAdapter} configured, false otherwise.
*/
public boolean isUsingCache ()
{
return _cache != null;
}
/**
* Looks up an entry in the cache by the given key.
*/
public <T> T cacheLookup (CacheKey key)
{
if (_cache == null) {
return null;
}
CacheAdapter.CachedValue<T> ref = _cache.lookup(key.getCacheId(), key.getCacheKey());
return (ref == null) ? null : ref.getValue();
}
/**
* Stores a new entry indexed by the given key.
*/
public <T> void cacheStore (CacheCategory category, CacheKey key, T entry)
{
if (_cache == null) {
return;
}
if (key == null) {
log.warning("Cache key must not be null [entry=" + entry + "]", new Exception());
return;
}
log.debug("storing [key=" + key + ", value=" + entry + "]");
CacheAdapter.CachedValue<T> element = _cache.lookup(key.getCacheId(), key.getCacheKey());
T oldEntry = (element != null ? element.getValue() : null);
// update the cache
_cache.store(category, key.getCacheId(), key.getCacheKey(), entry);
// then do cache invalidations
Set<CacheListener<?>> listeners = _listenerSets.get(key.getCacheId());
if (listeners != null && listeners.size() > 0) {
for (CacheListener<?> listener : listeners) {
log.debug("cascading [listener=" + listener + "]");
@SuppressWarnings("unchecked")
CacheListener<T> casted = (CacheListener<T>)listener;
casted.entryCached(entry, oldEntry);
}
}
}
/**
* Evicts the cache entry indexed under the given key, if there is one. The eviction may
* trigger further cache invalidations.
*/
public void cacheInvalidate (Key<?> key)
{
if (key == null) {
log.warning("Cache key to invalidate must not be null.", new Exception());
} else {
cacheInvalidate(new KeyCacheKey(key));
}
}
/**
* 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)
{
if (key == null) {
log.warning("Cache key to invalidate must not be null.", new Exception());
} else {
cacheInvalidate(key.getCacheId(), key.getCacheKey());
}
}
/**
* 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 <T extends Serializable> void cacheInvalidate (String cacheId, Serializable cacheKey)
{
if (_cache == null) {
return;
}
if (CACHE_DEBUG) {
log.info("Invalidating", "id", cacheId, "key", cacheKey);
}
CacheAdapter.CachedValue<T> element = _cache.lookup(cacheId, cacheKey);
if (element != null) {
// find the old entry, if any
T oldEntry = element.getValue();
if (oldEntry != null) {
// if there was one, do (possibly cascading) cache invalidations
Set<CacheListener<?>> listeners = _listenerSets.get(cacheId);
if (listeners != null && listeners.size() > 0) {
for (CacheListener<?> listener : listeners) {
log.debug("cascading [listener=" + listener + "]");
@SuppressWarnings("unchecked") CacheListener<T> casted =
(CacheListener<T>)listener;
casted.entryInvalidated(oldEntry);
}
}
}
}
// then remove the keyed entry from the cache system
_cache.remove(cacheId, cacheKey);
}
/**
* Brutally iterates over the entire contents of the cache associated with the given class,
* invoking the callback for each cache entry.
*/
public <T extends Serializable> void cacheTraverse (
Class<? extends PersistentRecord> pClass, CacheTraverser<T> 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 <T extends Serializable> void cacheTraverse (String cacheId, CacheTraverser<T> filter)
{
if (_cache == null) {
return;
}
for (Serializable key : _cache.enumerate(cacheId)) {
CachedValue<T> result = _cache.lookup(cacheId, key);
if (result != null && result.getValue() != null) {
filter.visitCacheEntry(this, cacheId, key, result.getValue());
}
}
}
/**
* Registers a new cache listener with the cache associated with the given class.
*/
public <T extends Serializable> void addCacheListener (
Class<T> pClass, CacheListener<T> listener)
{
addCacheListener(pClass.getName(), listener);
}
/**
* Registers a new cache listener with the identified cache.
*/
public <T extends Serializable> void addCacheListener (
String cacheId, CacheListener<T> listener)
{
Set<CacheListener<?>> listenerSet = _listenerSets.get(cacheId);
if (listenerSet == null) {
listenerSet = Sets.newHashSet();
_listenerSets.put(cacheId, listenerSet);
}
listenerSet.add(listener);
}
/**
* Initializes all repositories that have been created and registered with this persistence
* context. Any repositories that are constructed after this call will be immediately
* initialized at the time they are constructed (which is probably undesirable). When a
* repository is initialized, schema migrations for all of its managed persistent records are
* run. It is best to do all schema migrations when the system initializes which is why lazy
* initialization of repositories is undesirable.
*
* @param warnOnLazyInit if true, any repositories are constructed after this method is called
* will result in a warning so that the application developer can restructure their code to
* ensure that those repositories are properly created prior to this call.
*/
public void initializeRepositories (boolean warnOnLazyInit)
throws DatabaseException
{
// resolve all persistent records and trigger all schema migrations
for (DepotRepository repo : _repositories) {
repo.resolveRecords();
}
// then run all repository initialization methods, triggering all data migrations
for (DepotRepository repo : _repositories) {
repo.init();
}
// now potentially issue a warning if we lazily initialize any other persistent record
_warnOnLazyInit = warnOnLazyInit;
// finally note that we've now been initialized
_repositories = null;
}
/**
* Called when a depot repository is created. We register all persistent record classes used by
* the repository so that systems that desire it can force the resolution of all database
* tables rather than allowing resolution to happen on demand.
*/
protected void repositoryCreated (DepotRepository repo)
{
if (_repositories == null) {
if (_warnOnLazyInit) {
log.warning("Repository created lazily: " + repo.getClass().getName());
}
repo.resolveRecords();
repo.init();
} else {
_repositories.add(repo);
}
}
/**
* Looks up and creates, but does not initialize, the marshaller for the specified Entity type.
*/
protected <T extends PersistentRecord> DepotMarshaller<T> getRawMarshaller (Class<T> type)
{
@SuppressWarnings("unchecked") DepotMarshaller<T> marshaller =
(DepotMarshaller<T>)_marshallers.get(type);
if (marshaller == null) {
_marshallers.put(type, marshaller = new DepotMarshaller<T>(type, this));
}
return marshaller;
}
/**
* Internal invoke method that takes care of transient retries for both queries and modifiers.
*/
protected <T> T invoke (Operation<T> op, boolean retryOnTransientFailure)
throws DatabaseException
{
checkAreInitialized(); // le check du sanity
boolean isReadOnly = op.isReadOnly();
Connection conn;
long preConnect = System.nanoTime();
try {
conn = _conprov.getConnection(_ident, isReadOnly);
} catch (PersistenceException pe) {
throw new DatabaseException(pe.getMessage(), pe.getCause());
}
// wrap the connection in a proxy that will collect all opened statements
List<Statement> stmts = Lists.newArrayListWithCapacity(1);
conn = JDBCUtil.makeCollector(conn, stmts);
// TEMP: we synchronize on the connection to cooperate with SimpleRepository when used in
// conjunction with a StaticConnectionProvider; at some point we'll switch to standard JDBC
// connection pooling which will block in getConnection() instead of returning a connection
// that someone else may be using
synchronized (conn) {
long preInvoke = System.nanoTime();
try {
// invoke our database operation
T value;
try {
value = op.invoke(this, conn, _liaison);
} finally {
// close all opened statements; if any close fails, abort the close process as
// the whole connection is now unusable and will be discarded
for (Statement stmt : stmts) {
stmt.close();
}
}
// Always commit if auto-commit is off. If the read-only operation managed to
// acquire some locks, this will release them. Also, we've seen a MySQL bug where
// not committing after a select causes later selects to see stale results.
if (!conn.getAutoCommit()) {
conn.commit();
}
// note the time it took to invoke this operation
_stats.noteOp(isReadOnly, preConnect, preInvoke, System.nanoTime());
// have the operation update any appropriate runtime statistics as well
op.updateStats(_stats);
return value;
} catch (SQLException sqe) {
if (!isReadOnly) {
// convert this exception to a DuplicateKeyException if appropriate
if (_liaison.isDuplicateRowException(sqe)) {
throw new DuplicateKeyException(sqe.getMessage());
}
}
// let the provider know that the connection failed
_conprov.connectionFailed(_ident, isReadOnly, conn, sqe);
conn = null;
if (retryOnTransientFailure && _liaison.isTransientException(sqe)) {
// the MySQL JDBC driver has the annoying habit of including the embedded
// exception stack trace in the message of their outer exception; if I want a
// fucking stack trace, I'll call printStackTrace() thanksverymuch
String msg = StringUtil.split(String.valueOf(sqe), "\n")[0];
log.info("Transient failure executing op, retrying [error=" + msg + "].");
} else {
throw new DatabaseException("Operation failure " + op, sqe);
}
} finally {
if (conn != null) {
_conprov.releaseConnection(_ident, isReadOnly, conn);
}
}
}
// if we got here, we want to retry a transient failure
return invoke(op, false);
}
protected void checkAreInitialized ()
{
if (_conprov == null) {
throw new IllegalStateException(
"This persistence context has not yet been initialized. You are probably " +
"doing something too early, like in a repository's constructor. Don't do that.");
}
}
protected String _ident;
protected ConnectionProvider _conprov;
protected DatabaseLiaison _liaison;
protected DepotMetaData _meta = new DepotMetaData();
protected boolean _warnOnLazyInit;
/** Used to track various statistics. */
protected Stats _stats = new Stats();
/** The object through which all our caching is relayed, or null, for no caching. */
protected CacheAdapter _cache;
/** Tracks repositories during the pre-initialization phase. */
protected List<DepotRepository> _repositories = Lists.newArrayList();
/** A mapping from persistent record class to resolved marshaller. */
protected Map<Class<?>, DepotMarshaller<?>> _marshallers = Maps.newHashMap();
/** A mapping of cache listeners by cache id. */
protected Map<String, Set<CacheListener<?>>> _listenerSets = Maps.newHashMap();
}
@@ -0,0 +1,75 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot;
import java.io.Serializable;
import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.impl.DepotUtil;
/**
* The base class for all persistent records used in Depot. Persistent records must be cloneable
* and serializable; this class is used to enforce those requirements.
*/
public class PersistentRecord
implements Cloneable, Serializable
{
// AUTO-GENERATED: FIELDS START
public static final Class<PersistentRecord> _R = PersistentRecord.class;
// AUTO-GENERATED: FIELDS END
@Override // from Object
public PersistentRecord clone ()
{
try {
return (PersistentRecord) super.clone();
} catch (CloneNotSupportedException cnse) {
throw new AssertionError(cnse); // this should never happen since we are Cloneable
}
}
/**
* Creates a column expression for this class with the specified field name. Used by the
* autogenerated column expression constants.
*/
protected static ColumnExp colexp (Class<? extends PersistentRecord> clazz, String fieldName)
{
return new ColumnExp(clazz, fieldName);
}
/**
* Creates a new Key for this class. Called by autogenerated code, and placed here because
* we hope to make this form of the Key constructor protected.
*/
protected static <R extends PersistentRecord> Key<R> newKey (
Class<R> pClass, Comparable... values)
{
return new Key<R>(pClass, values);
}
/**
* Register the key fields for a subclass. Called by autogenerated code.
*/
protected static void registerKeyFields (ColumnExp... fields)
{
DepotUtil.registerKeyFields(fields);
}
}
@@ -0,0 +1,32 @@
//
// $Id$
package com.samskivert.depot;
import java.util.Collection;
import com.samskivert.depot.impl.FragmentVisitor;
import com.samskivert.depot.impl.SQLBuilder;
/**
* Represents a fragment of an SQL statement, generally a clause (WHERE, FROM, ...) or an
* expression (1+1, 'foo', "columnName", ...).
*/
public interface SQLFragment
{
/**
* Most uses of this class have been implemented with a visitor pattern. Create your own
* {@link FragmentVisitor} and call this method with it.
*
* @see SQLBuilder
*/
public Object accept (FragmentVisitor<?> visitor);
/**
* Adds all persistent classes that are brought into the SQL context by this clause: FROM
* clauses, JOINs, UPDATEs, anything that could create a new table abbreviation. This method
* should recurse into any subordinate state that may in turn bring in new classes so that
* sub-queries work correctly.
*/
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet);
}
@@ -0,0 +1,286 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import com.samskivert.jdbc.ColumnDefinition;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.depot.annotation.Column;
import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.impl.FieldMarshaller;
import com.samskivert.depot.impl.Modifier;
import static com.samskivert.depot.Log.log;
/**
* Encapsulates the migration of a persistent record's database schema. These can be registered
* with the {@link PersistenceContext} to effect hand-coded migrations between entity versions. The
* modifier should override {@link #invoke} to perform its migrations. See {@link
* PersistenceContext#registerMigration} for details on the migration process.
*
* <p> Note: these should only be used for actual schema changes (column additions, removals,
* renames, retypes, etc.). It should not be used for data migration, use {@link DataMigration} for
* that.
*/
public abstract class SchemaMigration extends Modifier
{
/**
* A convenient migration for dropping a column from an entity.
*/
public static class Drop extends SchemaMigration
{
public Drop (int targetVersion, String columnName) {
super(targetVersion);
_columnName = columnName;
}
@Override
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
if (!liaison.tableContainsColumn(conn, _tableName, _columnName)) {
// we'll accept this inconsistency
log.warning(_tableName + "." + _columnName + " already dropped.");
return 0;
}
log.info("Dropping '" + _columnName + "' from " + _tableName);
return liaison.dropColumn(conn, _tableName, _columnName) ? 1 : 0;
}
protected String _columnName;
}
/**
* A convenient migration for renaming a column in an entity.
*/
public static class Rename extends SchemaMigration
{
public Rename (int targetVersion, String oldColumnName, ColumnExp newColumn) {
super(targetVersion);
_oldColumnName = oldColumnName;
_fieldName = newColumn.name;
}
@Override public boolean runBeforeDefault () {
return true;
}
@Override
public void init (String tableName, Map<String, FieldMarshaller<?>> marshallers) {
super.init(tableName, marshallers);
FieldMarshaller<?> fm = requireMarshaller(marshallers, _fieldName);
_newColumnName = fm.getColumnName();
_newColumnDef = fm.getColumnDefinition();
}
@Override
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
if (!liaison.tableContainsColumn(conn, _tableName, _oldColumnName)) {
if (liaison.tableContainsColumn(conn, _tableName, _newColumnName)) {
// we'll accept this inconsistency
log.warning(_tableName + "." + _oldColumnName + " already renamed to " +
_newColumnName + ".");
return 0;
}
// but this is not OK
throw new IllegalArgumentException(
_tableName + " does not contain '" + _oldColumnName + "'");
}
// nor is this
if (liaison.tableContainsColumn(conn, _tableName, _newColumnName)) {
throw new IllegalArgumentException(
_tableName + " already contains '" + _newColumnName + "'");
}
log.info("Renaming '" + _oldColumnName + "' to '" + _newColumnName + "' in: " +
_tableName);
return liaison.renameColumn(
conn, _tableName, _oldColumnName, _newColumnName, _newColumnDef) ? 1 : 0;
}
protected String _oldColumnName, _fieldName, _newColumnName;
protected ColumnDefinition _newColumnDef;
}
/**
* A convenient migration for changing the type of an existing field. NOTE: This object is
* instantiated with the name of a persistent field, not the name of a database column. These
* can be very different things for classes that use @Column annotations.
*/
public static class Retype extends SchemaMigration
{
public Retype (int targetVersion, ColumnExp column) {
super(targetVersion);
_fieldName = column.name;
}
@Override public boolean runBeforeDefault () {
return false;
}
@Override
public void init (String tableName, Map<String, FieldMarshaller<?>> marshallers) {
super.init(tableName, marshallers);
FieldMarshaller<?> marsh = requireMarshaller(marshallers, _fieldName);
_columnName = marsh.getColumnName();
_newColumnDef = marsh.getColumnDefinition();
}
@Override
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
log.info("Updating type of '" + _columnName + "' in " + _tableName);
return liaison.changeColumn(conn, _tableName, _columnName, _newColumnDef.type,
_newColumnDef.nullable, _newColumnDef.unique,
_newColumnDef.defaultValue) ? 1 : 0;
}
protected String _fieldName, _columnName;
protected ColumnDefinition _newColumnDef;
}
/**
* A convenient migration for adding a new column that requires a default value to be specified
* during the addition. Normally Depot will automatically handle column addition, but if you
* have a column that normally does not have a default value but needs one when it is added to
* a table with existing rows, you can use this migration.
*
* @see Column#defaultValue
*/
public static class Add extends SchemaMigration
{
public Add (int targetVersion, ColumnExp column, String defaultValue) {
super(targetVersion);
_fieldName = column.name;
_defaultValue = defaultValue;
}
@Override public boolean runBeforeDefault () {
return true;
}
@Override
public void init (String tableName, Map<String, FieldMarshaller<?>> marshallers) {
super.init(tableName, marshallers);
FieldMarshaller<?> marsh = requireMarshaller(marshallers, _fieldName);
_columnName = marsh.getColumnName();
_newColumnDef = marsh.getColumnDefinition();
}
@Override
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
// override the default value in the column definition with the one provided
ColumnDefinition defColumnDef = new ColumnDefinition(
_newColumnDef.type, _newColumnDef.nullable,
_newColumnDef.unique, _defaultValue);
// first add the column with the overridden default value
if (liaison.addColumn(conn, _tableName, _fieldName, defColumnDef, true)) {
// then change the column to the permanent default value
liaison.changeColumn(conn, _tableName, _fieldName, _newColumnDef.type,
null, null, _newColumnDef.defaultValue);
return 1;
}
return 0;
}
protected String _fieldName, _columnName, _defaultValue;
protected ColumnDefinition _newColumnDef;
}
/**
* A convenient migration for dropping a column from an entity.
*/
public static class DropIndex extends SchemaMigration
{
public DropIndex (int targetVersion, String ixName) {
super(targetVersion);
_ixName = ixName;
}
@Override
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
String fullIxName = _tableName + "_" + _ixName;
if (!liaison.tableContainsIndex(conn, _tableName, fullIxName)) {
// we'll accept this inconsistency
log.warning("No index '" + fullIxName + "' found on " + _tableName);
return 0;
}
log.info("Dropping index '" + fullIxName + "' from " + _tableName);
liaison.dropIndex(conn, _tableName, fullIxName);
return 1;
}
protected String _ixName;
}
/**
* If this method returns true, this migration will be run <b>before</b> the default
* migrations, if false it will be run after.
*/
public boolean runBeforeDefault ()
{
return true;
}
/**
* When an Entity is being migrated, this method will be called to check whether this migration
* should be run. The default implementation runs as long as the currentVersion is less than
* the target version supplied to the migration at construct time.
*/
public boolean shouldRunMigration (int currentVersion, int targetVersion)
{
return (currentVersion < _targetVersion);
}
/**
* This is called to provide the migration with the name of the entity table and access to its
* field marshallers prior to being invoked. This will <em>only</em> be called after this
* migration has been determined to be runnable so one cannot rely on this method having been
* called in {@link #shouldRunMigration}.
*/
public void init (String tableName, Map<String, FieldMarshaller<?>> marshallers)
{
_tableName = tableName;
}
protected SchemaMigration (int targetVersion)
{
super();
_targetVersion = targetVersion;
}
protected FieldMarshaller<?> requireMarshaller (
Map<String, FieldMarshaller<?>> marshallers, String fieldName)
{
FieldMarshaller<?> marsh = marshallers.get(fieldName);
if (marsh == null) {
throw new IllegalArgumentException(
"'" + _tableName + "' does not contain field '" + fieldName + "'");
}
return marsh;
}
protected int _targetVersion;
protected String _tableName;
}
@@ -0,0 +1,144 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot;
import com.samskivert.util.Histogram;
import static com.samskivert.depot.Log.log;
/**
* Manages Depot performance statistics.
*/
public class Stats
{
/**
* An immutable class used to report statistics on repository activity. Statistics are tracked
* from the start of the VM and are never reset.
*/
public static class Snapshot
{
/** The total number of queries and modifiers executed. */
public final int totalOps;
/** The total number of milliseconds spent waiting for a JDBC connection. */
public final long connectionWaitTime;
/** The total number of collection queries that were loaded from the cache. */
public final int cachedQueries;
/** The total number of collection queries that were loaded from the database. */
public final int uncachedQueries;
/** The total number of one-phase collection queries that executed. */
public final int explicitQueries;
/** The number of record loads (individual or as part of a collection query) that were
* loaded from the cache. */
public final long cachedRecords;
/** The number of record loads (individual or as part of a collection query) that were
* loaded from the database. */
public final long uncachedRecords;
/** A histogram of query durations (with 500ms buckets). */
public final Histogram queryHisto;
/** The total number of milliseconds spent executing queries. */
public final long queryTime;
/** A histogram of modifier durations (with 500ms buckets). */
public final Histogram modifierHisto;
/** The total number of milliseconds spent executing modifiers. */
public final long modifierTime;
/** Creates a stats instance. */
protected Snapshot (int totalOps, long connectionWaitTime,
int cachedQueries, int uncachedQueries, int explicitQueries,
int cachedRecords, int uncachedRecords,
Histogram queryHisto, long queryTime,
Histogram modifierHisto, long modifierTime)
{
this.totalOps = totalOps;
this.connectionWaitTime = connectionWaitTime;
this.cachedQueries = cachedQueries;
this.uncachedQueries = uncachedQueries;
this.explicitQueries = explicitQueries;
this.cachedRecords = cachedRecords;
this.uncachedRecords = uncachedRecords;
this.queryHisto = queryHisto;
this.queryTime = queryTime;
this.modifierHisto = modifierHisto;
this.modifierTime = modifierTime;
}
}
public synchronized Snapshot getSnapshot ()
{
return new Snapshot(_totalOps, _connectionWaitTime,
_cachedQueries, _uncachedQueries, _explicitQueries,
_cachedRecords, _uncachedRecords,
_readHisto.clone(), _readTime, _writeHisto.clone(), _writeTime);
}
public synchronized void noteOp (
boolean isReadOnly, long preConnect, long preInvoke, long postInvoke)
{
_totalOps++;
_connectionWaitTime += (preInvoke - preConnect) / 1000000L;
long opTime = (postInvoke - preInvoke) / 1000000L;
if (opTime > Integer.MAX_VALUE) {
log.warning("ZOMG! A database operation took " + opTime + "ms to complete!");
opTime = Integer.MAX_VALUE;
}
if (isReadOnly) {
_readTime += opTime;
_readHisto.addValue((int)opTime);
} else {
_writeTime += opTime;
_writeHisto.addValue((int)opTime);
}
}
public synchronized void noteQuery (int cachedQueries, int uncachedQueries, int explicitQueries,
int cachedRecords, int uncachedRecords)
{
_cachedQueries += cachedQueries;
_uncachedQueries += uncachedQueries;
_explicitQueries += explicitQueries;
_cachedRecords += cachedRecords;
_uncachedRecords += uncachedRecords;
}
protected int _totalOps;
protected long _connectionWaitTime;
protected Histogram _readHisto = new Histogram(0, 500, 20);
protected long _readTime;
protected Histogram _writeHisto = new Histogram(0, 500, 20);
protected long _writeTime;
protected int _cachedQueries, _uncachedQueries, _explicitQueries;
protected int _cachedRecords, _uncachedRecords;
}
@@ -0,0 +1,83 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2009 Michael Bayne and 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.depot;
import com.samskivert.depot.expression.FluentExp;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.expression.StringFun.*;
/**
* Provides static methods for string-related function construction.
*/
public class StringFuncs
{
/**
* Creates an expression that evaluates to the string length of the supplied expression.
*/
public static FluentExp length (SQLExpression exp)
{
return new Length(exp);
}
/**
* Creates an expression that down-cases the supplied expression.
*/
public static FluentExp lower (SQLExpression exp)
{
return new Lower(exp);
}
/**
* Creates an expression that locates the given substring expression within the given
* string expression and returns the index.
*/
public static FluentExp position (SQLExpression substring, SQLExpression string)
{
return new Position(substring, string);
}
/**
* Creates an expression that evaluates to a substring of the given string expression,
* starting at the given index and of the given length.
*/
public static FluentExp substring (
SQLExpression string, SQLExpression from, SQLExpression count)
{
return new Substring(string, from, count);
}
/**
* Creates an expression that removes whitespace from the beginning and end of the supplied
* string expression.
*/
public static FluentExp trim (SQLExpression exp)
{
return new Trim(exp);
}
/**
* Creates an expression that up-cases the supplied string expression.
*/
public static FluentExp upper (SQLExpression exp)
{
return new Upper(exp);
}
}
@@ -0,0 +1,54 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2010 Michael Bayne and 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.depot;
import java.lang.reflect.Type;
/**
* Transforms a persistent record field into a format that can be read and written by the
* underlying database. For example, one might transform an enum into a byte, short or integer. Or
* one might transform a string array into a single string, using a separator known to be
* appropriate for the contents.
*
* @see Transformers
*/
public interface Transformer<F,T>
{
/**
* Transforms a runtime value into a value that can be persisted.
*
* @param value the value just read from a persistent record.
*
* @return the transformed value, which will be written to the database.
*/
T toPersistent (F value);
/**
* Transforms a persisted value into a value that can be store in a runtime field.
*
* @param fieldType the type of the persistent record field to which the transformed value will
* be written.
* @param value the value just read from the database.
*
* @return the transformed value, which will be stored in a field of the persistent record.
*/
F fromPersistent (Type fieldType, T value);
}
@@ -0,0 +1,190 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2010 Michael Bayne and 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.depot;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import com.samskivert.util.ByteEnum;
import com.samskivert.util.ByteEnumUtil;
import com.samskivert.depot.annotation.Column;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
/**
* Contains various generally useful {@link Transformer} implementations. To use a transformer, you
* specify it via a {@link Column} annotation. For example:
* <pre>
* public class MyRecord extends PersistentRecord {
* @Transform(Transformers.StringArray.class)
* public String[] cities;
* }
* </pre>
*/
public class Transformers
{
/**
* Combines the contents of a String[] column into a single String, terminating each String
* element with a newline. A backslash ('\') in Strings will be prefixed by another backslash,
* newlines will be encoded as "\n", and null elements will be encoded as "\0" (but not
* terminated by a newline).
*/
public static class StringArray extends StringBase<String[]>
{
public String toPersistent (String[] value)
{
return (value == null) ? null : encode(Arrays.asList(value));
}
public String[] fromPersistent (Type ftype, String encoded)
{
return (encoded == null) ? null : Iterables.toArray(decode(encoded), String.class);
}
}
/**
* Combines the contents of an Iterable<String> column into a single String, terminating each
* String element with a newline. A backslash ('\') in Strings will be prefixed by another
* backslash, newlines will be encoded as "\n", and null elements will be encoded as "\0" (but
* not terminated by a newline).
*/
public static class StringIterable extends StringBase<Iterable<String>>
{
public String toPersistent (Iterable<String> value)
{
return (value == null) ? null : encode(value);
}
public Iterable<String> fromPersistent (Type ftype, String encoded)
{
if (encoded == null) {
return null;
}
ArrayList<String> value = decode(encoded);
Type fclass = (ftype instanceof ParameterizedType) ?
((ParameterizedType)ftype).getRawType() : ftype;
if (fclass == ArrayList.class || fclass == List.class ||
fclass == Collection.class || fclass == Iterable.class) {
return value;
}
if (fclass == LinkedList.class) {
return Lists.newLinkedList(value);
}
if (fclass == HashSet.class || fclass == Set.class) {
return Sets.newHashSet(value);
}
// else: reflection? See if it's a collection, call the 0-arg constructor, add all
// and return? Something?
return value;
}
}
public static class ByteEnumSet<E extends Enum<E> & ByteEnum>
implements Transformer<Set<E>, Integer>
{
public Integer toPersistent (Set<E> value)
{
return (value == null) ? null : ByteEnumUtil.setToInt(value);
}
public Set<E> fromPersistent (Type ftype, Integer encoded)
{
if (encoded == null) {
return null;
}
@SuppressWarnings("unchecked")
Class<E> eclass = (Class<E>) ((ParameterizedType) ftype).getActualTypeArguments()[0];
return ByteEnumUtil.intToSet(eclass, encoded);
}
}
protected abstract static class StringBase<F> implements Transformer<F, String>
{
protected static String encode (Iterable<String> value)
{
StringBuilder buf = new StringBuilder();
for (String s : value) {
if (s == null) {
buf.append("\\\n"); // encode nulls as slash followed by the terminator
} else {
s = s.replace("\\", "\\\\"); // turn \ into \\
s = s.replace("\n", "\\n"); // turn a newline in a String to "\n"
buf.append(s).append('\n');
}
}
return buf.toString();
}
protected static ArrayList<String> decode (String encoded)
{
ArrayList<String> value = Lists.newArrayList();
StringBuilder buf = new StringBuilder(encoded.length());
for (int ii = 0, nn = encoded.length(); ii < nn; ii++) {
char c = encoded.charAt(ii);
switch (c) {
case '\n':
value.add(buf.toString()); // TODO: intern?
buf.setLength(0);
break;
case '\\':
Preconditions.checkArgument(++ii < nn, "Invalid encoded string");
char slashed = encoded.charAt(ii);
switch (slashed) {
case '\n': // turn back into a null element
Preconditions.checkArgument(buf.length() == 0, "Invalid encoded string");
value.add(null);
break;
case 'n': // turn \n back into a newline
buf.append('\n');
break;
default: // this should only be a slash...
buf.append(slashed);
break;
}
break;
default:
buf.append(c);
break;
}
}
// make sure the last element was terminated
Preconditions.checkArgument(buf.length() == 0, "Invalid encoded string");
return value;
}
}
}
@@ -0,0 +1,38 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot;
/**
* An augmented cache invalidator interface for invalidators that can ensure that they are
* operating on the proper persistent record class.
*/
public interface ValidatingCacheInvalidator extends CacheInvalidator
{
/**
* Validates that this invalidator operates on the supplied persistent record class. This helps
* to catch programmer errors where one record type is used for a query clause and another is
* used for the cache invalidator.
*
* @exception IllegalArgumentException thrown if the supplied persistent record class does not
* match the class that this invalidator will flush from the cache.
*/
public void validateFlushType (Class<?> pClass);
}
@@ -0,0 +1,66 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Is used to specify a mapped column for a persistent property or field. If no Column annotation
* is specified, the default values are applied.
*/
@Target(value=ElementType.FIELD)
@Retention(value=RetentionPolicy.RUNTIME)
public @interface Column
{
/**
* The name of the column. Defaults to the field name.
*/
String name () default "";
/**
* Whether the property is a unique key. This is a shortcut for the UniqueConstraint annotation
* at the table level and is useful for when the unique key constraint is only a single
* field. This constraint applies in addition to any constraint entailed by primary key mapping
* and to constraints specified at the table level.
*/
boolean unique () default false;
/**
* Whether the database column is nullable. <em>Note:</em> this default differs from the value
* used by the EJB3 persistence framework.
*/
boolean nullable () default false;
/**
* The column length. (Applies to String and byte[] columns.)
*/
int length () default 255;
/**
* The SQL literal value to be used when defining this column's default value. The value must
* be quoted and escaped if it is not a SQL primitive datatype. For example:
* <code>'2006-01-01'</code> or <code>25</code> or <code>NULL</code>.
*/
String defaultValue () default "";
}
@@ -0,0 +1,64 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.clause.FieldOverride;
import com.samskivert.depot.clause.FromOverride;
import com.samskivert.depot.clause.Join;
/**
* Marks a field as computed, meaning it is ignored for schema purposes and it does not directly
* correspond to a column in a table.
*/
@Retention(value=RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.TYPE })
public @interface Computed
{
/** If this value is false, the field is not populated at all. */
boolean required () default true;
/** A non-empty value here is taken as literal SQL and used to populate the computed field. */
String fieldDefinition () default "";
/**
* A computed record can shadow a concrete record, which causes any field the former has in
* common with the latter and which is not otherwise overriden to inherit its definition. The
* shadowed class may also be given at the field level.
*
* The purpose of shadowing is largely to avoid having to supply a {@link FieldOverride} when
* querying for objects that that contain large subsets of some other persistent object's
* fields -- in other words, when you use a computed entity to query only some of the columns
* from a table.
*
* The shadowed class must have been brought into the query using e.g. {@link FromOverride}
* or {@link Join} clauses. The referenced fields must be simple concrete columns in a table;
* they must themselves be computed or overridden or shadowing.
*
* TODO: Do in fact let the shadowed field be computed, overriden or shadowing.
*/
Class<? extends PersistentRecord> shadowOf () default PersistentRecord.class;
}
@@ -0,0 +1,48 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specifies the primary field(s) of an entity.
*/
@Target(value=ElementType.TYPE)
@Retention(value=RetentionPolicy.RUNTIME)
public @interface Entity
{
/** The name of an entity. Defaults to the unqualified name of the entity class. */
String name () default "";
/** Unique constraints that are to be placed on this entity's table. These constraints apply in
* addition to any constraints specified by the Column annotation and constraints entailed by
* primary key mappings. Defaults to no additional constraints. */
UniqueConstraint[] uniqueConstraints () default {};
/** Indices to add to this entity's table. */
Index[] indices () default {};
/** Full-text search indexes defined on this entity, if any. Defaults to none. */
FullTextIndex[] fullTextIndices () default {};
}
@@ -0,0 +1,56 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation is used to specify that a full text index is to be included in the generated DDL
* for a table.
*/
@Target(value={})
@Retention(value=RetentionPolicy.RUNTIME)
public @interface FullTextIndex
{
public enum Configuration {
/** For searching generic strings; makes no linguistic/semantic assumptions. */
Simple,
/** For English text search; does basic stemming. */
English
}
/**
* An identifier for this index, unique with the scope of the record.
*/
public String name ();
/**
* An array of the field names that should be indexed.
*/
public String[] fields ();
/**
* What parser/dictionary to use for this index.
*/
public Configuration configuration () default Configuration.English;
}
@@ -0,0 +1,64 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Provides for the specification of generation strategies for the values of primary keys. The
* GeneratedValue annotation may be applied to a primary field in conjunction with the {@link Id}
* annotation.
*/
@Target(value=ElementType.FIELD)
@Retention(value=RetentionPolicy.RUNTIME)
public @interface GeneratedValue
{
/** Identifies which generator should be used to generate this value when using a table or
* sequence generator. */
String generator () default "";
/** Identifies the strategy to be used to generate this value. */
GenerationType strategy () default GenerationType.AUTO;
/**
* The initial value to be used when allocating id numbers from the generator. The default
* initial value is 1. <em>Note:</em> this default differs from the value used by the EJB3
* persistence framework.
*/
int initialValue () default 1;
/**
* If there are rows in our corresponding table, this boolean determines whether or not to
* attempt to initialize the generator to the maximum value of our associated field over
* those rows. This attribute does not exist in the EJB3 framework.
*/
boolean migrateIfExists () default true;
/**
* The amount to increment by when allocating id numbers from the generator. The default
* allocation size is 1. <em>Note:</em> this default differs from the value used by the EJB3
* persistence framework.
*/
int allocationSize () default 1;
}
@@ -0,0 +1,51 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.annotation;
/**
* Defines the types of primary key generation.
*/
public enum GenerationType
{
/**
* Indicates that the persistence provider must assign primary keys for the entity using an
* underlying database table to ensure uniqueness.
*/
TABLE,
/**
* Indicates that the persistence provider must assign primary keys for the entity using
* database sequences.
*/
SEQUENCE,
/**
* Indicates that the persistence provider must assign primary keys for the entity using
* database identity column.
*/
IDENTITY,
/**
* Indicates that the persistence provider should pick an appropriate strategy for the
* particular database.
*/
AUTO;
}
@@ -0,0 +1,35 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specifies the primary key property or field of an entity.
*/
@Target(value=ElementType.FIELD)
@Retention(value=RetentionPolicy.RUNTIME)
public @interface Id
{
}
@@ -0,0 +1,55 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Defines an index on an entity table.
*/
@Target(value=ElementType.FIELD)
@Retention(value=RetentionPolicy.RUNTIME)
public @interface Index
{
/**
* Defines the name of the index.<p>
*
* If this annotation is on a field, an index is created for all fields annotated with this
* name in the order of the fields in the class.<p>
*
* If this is defined, a static method must be defined on the record that provides the index
* configuration. The method must match one of the following two signatures:
* <pre>
* public static ColumnExp[] indexName ()
* public static List&lt;Tuple&lt;SQLExpression, OrderBy.Order>> indexName ()
* </pre>
* The first form will result in a simple multicolum index being created
* with the supplied columns. The second will create a function index using the supplied
* SQLExpressions.
*/
String name () default "";
/** Does this index enforce a uniqueness constraint? */
boolean unique () default false;
}
@@ -0,0 +1,66 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation defines a primary key generator that may be referenced by name when a generator
* element is specified for the GeneratedValue annotation. A table generator may be specified on
* the entity class or on the primary key field. The scope of the generator name is global to the
* persistence unit (across all generator types).
*/
@Target(value={ElementType.TYPE, ElementType.FIELD})
@Retention(value=RetentionPolicy.RUNTIME)
public @interface TableGenerator
{
/**
* A unique generator name that can be referenced by one or more classes to be the
* generator for id values.
*/
String name ();
/**
* Name of table that stores the generated id values. Defaults to a name chosen by persistence
* provider.
*/
String table () default "";
/**
* Name of the primary key column in the table Defaults to a provider-chosen name.
*/
String pkColumnName () default "";
/**
* Name of the column that stores the last value generated Defaults to a provider-chosen name.
*/
String valueColumnName () default "";
/**
* The primary key value in the generator table that distinguishes this set of generated values
* from others that may be stored in the table Defaults to a provider-chosen value to store in
* the primary key column of the generator table
*/
String pkColumnValue () default "";
}
@@ -0,0 +1,69 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2010 Michael Bayne and 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.depot.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.samskivert.depot.Transformer;
/**
* Declares that the target of this annotation, either a field in a {@link PersistentRecord} or a
* type (which will eventually be stored in a persistent record), should be transformed before
* saving to the database and after loading from the database.
*
* <p> For example, one may choose to transform a particular persistent record field into a type
* supported directly by Depot:
*
* <pre>
* public class MyRecord extends PersistentRecord {
* @Transform(Transformers.CommaSeparatedString.class)
* public String[] cities;
* }
* </pre>
*
* or one may opt to specify a transformation for all fields that contain a value of a particular
* type:
*
* <pre>
* @Transform(ByteEnumTransformer.class)
* public interface ByteEnum { ... }
* </pre>
*
* Note that because Depot honors @Transform annotations on any interface implemented by a type, or
* on a type's superclass, it is possible that conflicting transformations may be specified. In
* this case, Depot will fail with a runtime error during initialization, to indicate the problem.
*
* @see Transformer
*/
@Target(value={ElementType.TYPE, ElementType.FIELD})
@Retention(value=RetentionPolicy.RUNTIME)
@Inherited
public @interface Transform
{
/**
* Specifies a transformer to be used when persisting the target of this annotation.
*/
Class<? extends Transformer> value ();
}
@@ -0,0 +1,36 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation specifies that the property or field is not persistent. It is used to annotate
* a property or field of an entity class, mapped superclass, or embeddable class.
*/
@Target(value=ElementType.FIELD)
@Retention(value=RetentionPolicy.RUNTIME)
public @interface Transient
{
}
@@ -0,0 +1,40 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Used to specify a uniqueness constraint on a set of columns. If you want a single column to be
* unique, simply use {@link Column#unique}.
*/
@Target(value={})
@Retention(value=RetentionPolicy.RUNTIME)
public @interface UniqueConstraint
{
/** The name of the index that will be created to enforce this constraint. */
String name ();
/** An array of the field names that make up the constraint */
String[] fields ();
}
@@ -0,0 +1,24 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2009 Michael Bayne and 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
/**
* Annotations used on persistent record classes.
*/
package com.samskivert.depot.annotation;
@@ -0,0 +1,95 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.clause;
import java.util.Collection;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.FragmentVisitor;
import com.samskivert.depot.impl.expression.LiteralExp;
/**
* Supplies a definition for a computed field of the persistent object we're creating.
*
* Thus the select portion of a query can include a reference to a different column in a different
* table through a {@link ColumnExp}, or a literal expression such as COUNT(*) through a
* {@link LiteralExp}.
*
* @see FieldOverride
*/
public class FieldDefinition implements QueryClause
{
public FieldDefinition (String field, String str)
{
this(field, new LiteralExp(str));
}
public FieldDefinition (String field, Class<? extends PersistentRecord> pClass, String pCol)
{
this(field, new ColumnExp(pClass, pCol));
}
public FieldDefinition (String field, SQLExpression override)
{
_field = field;
_definition = override;
}
public FieldDefinition (ColumnExp field, SQLExpression override)
{
_field = field.name;
_definition = override;
}
/**
* The field we're defining. The Query object uses this for indexing.
*/
public String getField ()
{
return _field;
}
public SQLExpression getDefinition ()
{
return _definition;
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
_definition.addClasses(classSet);
}
// from SQLExpression
public Object accept (FragmentVisitor<?> visitor)
{
return visitor.visit(this);
}
/** The name of the field on the persistent object to override. */
protected String _field;
/** The defining expression. */
protected SQLExpression _definition;
}
@@ -0,0 +1,57 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.clause;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.expression.LiteralExp;
/**
* Redirects one field of the persistent object we're creating from its default associated column
* to a general {@link SQLExpression}.
*
* Thus the select portion of a query can include a reference to a different column in a different
* table through a {@link ColumnExp}, or a literal expression such as COUNT(*) through a
* {@link LiteralExp}.
*/
public class FieldOverride extends FieldDefinition
{
public FieldOverride (String field, String str)
{
super(field, str);
}
public FieldOverride (String field, Class<? extends PersistentRecord> pClass, String pCol)
{
super(field, pClass, pCol);
}
public FieldOverride (String field, SQLExpression override)
{
super(field, override);
}
public FieldOverride (ColumnExp field, SQLExpression override)
{
super(field, override);
}
}
@@ -0,0 +1,43 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.clause;
import java.util.Collection;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.impl.FragmentVisitor;
/**
* Represents a FOR UPDATE clause.
*/
public class ForUpdate implements QueryClause
{
// from SQLExpression
public Object accept (FragmentVisitor<?> builder)
{
return builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
}
}
@@ -0,0 +1,86 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.clause;
import java.util.Collection;
import java.util.List;
import com.google.common.collect.Lists;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.impl.DepotUtil;
import com.samskivert.depot.impl.FragmentVisitor;
/**
* Completely overrides the FROM clause, if it exists.
*/
public class FromOverride implements QueryClause
{
public FromOverride (Class<? extends PersistentRecord> fromClass)
{
_fromClasses.add(fromClass);
}
public FromOverride (Class<? extends PersistentRecord> fromClass1,
Class<? extends PersistentRecord> fromClass2)
{
_fromClasses.add(fromClass1);
_fromClasses.add(fromClass2);
}
public FromOverride (Collection<Class<? extends PersistentRecord>> fromClasses)
{
_fromClasses.addAll(fromClasses);
}
public List<Class<? extends PersistentRecord>> getFromClasses ()
{
return _fromClasses;
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
classSet.addAll(getFromClasses());
}
// from SQLExpression
public Object accept (FragmentVisitor<?> builder)
{
return builder.visit(this);
}
@Override // from Object
public String toString ()
{
StringBuilder builder = new StringBuilder();
for (Class<? extends PersistentRecord> clazz : _fromClasses) {
if (builder.length() > 0) {
builder.append(", ");
}
builder.append(DepotUtil.justClassName(clazz));
}
return builder.toString();
}
/** The classes of the tables we're selecting from. */
protected List<Class<? extends PersistentRecord>> _fromClasses = Lists.newArrayList();
}
@@ -0,0 +1,58 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.clause;
import java.util.Collection;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.FragmentVisitor;
/**
* Represents a GROUP BY clause.
*/
public class GroupBy implements QueryClause
{
public GroupBy (SQLExpression... values)
{
_values = values;
}
public SQLExpression[] getValues ()
{
return _values;
}
// from SQLExpression
public Object accept (FragmentVisitor<?> builder)
{
return builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
}
/** The expressions that are generated for the clause. */
protected SQLExpression[] _values;
}
@@ -0,0 +1,76 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.clause;
import java.util.Collection;
import java.util.Set;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.impl.FragmentVisitor;
/**
* Builds actual SQL given a main persistent type and some {@link QueryClause} objects.
*/
public class InsertClause implements QueryClause
{
public InsertClause (Class<? extends PersistentRecord> pClass, Object pojo,
Set<String> identityFields)
{
_pClass = pClass;
_pojo = pojo;
_idFields = identityFields;
}
public Class<? extends PersistentRecord> getPersistentClass ()
{
return _pClass;
}
public Object getPojo ()
{
return _pojo;
}
public Set<String> getIdentityFields ()
{
return _idFields;
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
classSet.add(_pClass);
// If we add SQLExpression[] values INSERT, remember to recurse into them here.
}
// from SQLExpression
public Object accept (FragmentVisitor<?> builder)
{
return builder.visit(this);
}
protected Class<? extends PersistentRecord> _pClass;
/** The object from which to fetch values, or null. */
protected Object _pojo;
protected Set<String> _idFields;
}
@@ -0,0 +1,103 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.clause;
import java.util.Collection;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.DepotUtil;
import com.samskivert.depot.impl.FragmentVisitor;
import com.samskivert.depot.impl.operator.Equals;
/**
* Represents a JOIN.
*/
public class Join implements QueryClause
{
/** Indicates the join type to be used. The default is INNER. */
public static enum Type { INNER, LEFT_OUTER, RIGHT_OUTER }
public Join (ColumnExp primary, ColumnExp join)
{
_joinClass = join.getPersistentClass();
_joinCondition = new Equals(primary, join);
}
public Join (Class<? extends PersistentRecord> joinClass, SQLExpression joinCondition)
{
_joinClass = joinClass;
_joinCondition = joinCondition;
}
/**
* Configures the type of join to be performed.
*/
public Join setType (Type type)
{
_type = type;
return this;
}
public Type getType ()
{
return _type;
}
public Class<? extends PersistentRecord> getJoinClass ()
{
return _joinClass;
}
public SQLExpression getJoinCondition ()
{
return _joinCondition;
}
// from SQLExpression
public Object accept (FragmentVisitor<?> builder)
{
return builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
classSet.add(_joinClass);
_joinCondition.addClasses(classSet);
}
@Override // from Object
public String toString ()
{
return DepotUtil.justClassName(_joinClass) + ":" + _type + ":" + _joinCondition;
}
/** Indicates the type of join to be performed. */
protected Type _type = Type.INNER;
/** The class of the table we're to join against. */
protected Class<? extends PersistentRecord> _joinClass;
/** The condition used to join in the new table. */
protected SQLExpression _joinCondition;
}
@@ -0,0 +1,71 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.clause;
import java.util.Collection;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.impl.FragmentVisitor;
/**
* Represents a LIMIT/OFFSET clause, for pagination.
*/
public class Limit implements QueryClause
{
public Limit (int offset, int count)
{
_offset = offset;
_count = count;
}
public int getOffset ()
{
return _offset;
}
public int getCount ()
{
return _count;
}
// from SQLExpression
public Object accept (FragmentVisitor<?> builder)
{
return builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
}
@Override // from Object
public String toString ()
{
return _offset + "-" + (_offset+_count);
}
/** The first row of the result set to return. */
protected int _offset;
/** The number of rows, at most, to return. */
protected int _count;
}
@@ -0,0 +1,130 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.clause;
import java.util.Collection;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.util.ArrayUtil;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.FragmentVisitor;
import com.samskivert.depot.impl.expression.LiteralExp;
/**
* Represents an ORDER BY clause.
*/
public class OrderBy implements QueryClause
{
/** Indicates the order of the clause. */
public enum Order { ASC, DESC }
/**
* Creates and returns a random order by clause.
*/
public static OrderBy random ()
{
return ascending(new LiteralExp("rand()"));
}
/**
* Creates and returns an ascending order by clause on the supplied expression.
*/
public static OrderBy ascending (SQLExpression value)
{
return new OrderBy(new SQLExpression[] { value } , new Order[] { Order.ASC });
}
/**
* Creates and returns a descending order by clause on the supplied expression.
*/
public static OrderBy descending (SQLExpression value)
{
return new OrderBy(new SQLExpression[] { value }, new Order[] { Order.DESC });
}
public OrderBy (SQLExpression[] values, Order[] orders)
{
_values = values;
_orders = orders;
}
public SQLExpression[] getValues ()
{
return _values;
}
public Order[] getOrders ()
{
return _orders;
}
/**
* Concatenates the supplied order expression to this one, returns a new expression.
*/
public OrderBy thenAscending (SQLExpression value)
{
return new OrderBy(ArrayUtil.append(_values, value),
ArrayUtil.append(_orders, Order.ASC));
}
/**
* Creates and returns a descending order by clause on the supplied expression.
*/
public OrderBy thenDescending (SQLExpression value)
{
return new OrderBy(ArrayUtil.append(_values, value),
ArrayUtil.append(_orders, Order.DESC));
}
// from SQLExpression
public Object accept (FragmentVisitor<?> builder)
{
return builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
for (SQLExpression expression : _values) {
expression.addClasses(classSet);
}
}
@Override // from Object
public String toString ()
{
StringBuilder builder = new StringBuilder();
for (int ii = 0; ii < _values.length; ii++) {
if (ii > 0) {
builder.append(", ");
}
builder.append(_values[ii]).append(" ").append(_orders[ii]);
}
return builder.toString();
}
/** The expressions that are generated for the clause. */
protected SQLExpression[] _values;
/** Whether the ordering is to be ascending or descending. */
protected Order[] _orders;
}
@@ -0,0 +1,30 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.clause;
import com.samskivert.depot.SQLFragment;
/**
* Represents a piece or modifier of an SQL query.
*/
public interface QueryClause extends SQLFragment
{
}
@@ -0,0 +1,256 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.clause;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.impl.FragmentVisitor;
/**
* Represents a complete select clause.
*/
public class SelectClause
implements QueryClause
{
/**
* Creates a new select clause, selecting the supplied columns from the specified persistent
* class for rows that match the supplied clauses.
*/
public SelectClause (Class<? extends PersistentRecord> pClass,
ColumnExp[] columns, QueryClause... clauses)
{
this(pClass, columns, Arrays.asList(clauses));
}
/**
* Creates a new select clause, selecting the supplied columns from the specified persistent
* class for rows that match the supplied clauses.
*/
public SelectClause (Class<? extends PersistentRecord> pClass, ColumnExp[] columns,
Iterable<? extends QueryClause> clauses)
{
_pClass = pClass;
_fields = columns;
// iterate over the clauses and sort them into the different types we understand
for (QueryClause clause : clauses) {
if (clause == null) {
continue;
}
if (clause instanceof WhereClause) {
if (_where != null) {
throw new IllegalArgumentException(
"Query can't contain multiple Where clauses.");
}
_where = (WhereClause) 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 FieldDefinition) {
_disMap.put(((FieldDefinition) clause).getField(), ((FieldDefinition) 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;
} else {
throw new IllegalArgumentException(
"Unknown clause provided in select " + clause + ".");
}
}
}
public FieldDefinition lookupDefinition (String field)
{
return _disMap.get(field);
}
public Collection<FieldDefinition> getFieldDefinitions ()
{
return _disMap.values();
}
public Class<? extends PersistentRecord> getPersistentClass ()
{
return _pClass;
}
public ColumnExp[] getFields ()
{
return _fields;
}
public FromOverride getFromOverride ()
{
return _fromOverride;
}
public WhereClause getWhereClause ()
{
return _where;
}
public List<Join> getJoinClauses ()
{
return _joinClauses;
}
public OrderBy getOrderBy ()
{
return _orderBy;
}
public GroupBy getGroupBy ()
{
return _groupBy;
}
public Limit getLimit ()
{
return _limit;
}
public ForUpdate getForUpdate ()
{
return _forUpdate;
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
classSet.add(_pClass);
if (_fromOverride != null) {
_fromOverride.addClasses(classSet);
}
if (_where != null) {
_where.addClasses(classSet);
}
for (Join join : _joinClauses) {
join.addClasses(classSet);
}
for (FieldDefinition override : _disMap.values()) {
override.addClasses(classSet);
}
}
// from SQLExpression
public Object accept (FragmentVisitor<?> builder)
{
return builder.visit(this);
}
@Override // from Object
public String toString ()
{
StringBuilder builder = new StringBuilder();
builder.append("(where=").append(_where);
if (_fromOverride != null) {
builder.append(", from=").append(_fromOverride);
}
if (!_joinClauses.isEmpty()) {
builder.append(", join=").append(_joinClauses);
}
if (_orderBy != null) {
builder.append(", orderBy=").append(_orderBy);
}
if (_groupBy != null) {
builder.append(", groupBy=").append(_groupBy);
}
if (_limit != null) {
builder.append(", limit=").append(_limit);
}
if (_forUpdate != null) {
builder.append(", forUpdate=").append(_forUpdate);
}
return builder.append(")").toString();
}
/** Persistent class fields mapped to field override clauses. */
protected Map<String, FieldDefinition> _disMap = Maps.newHashMap();
/** The persistent class this select defines. */
protected Class<? extends PersistentRecord> _pClass;
/** The persistent fields to select. */
protected ColumnExp[] _fields;
/** The from override clause, if any. */
protected FromOverride _fromOverride;
/** The where clause. */
protected WhereClause _where;
/** A list of join clauses, each potentially referencing a new class. */
protected List<Join> _joinClauses = Lists.newArrayList();
/** 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;
}
@@ -0,0 +1,104 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.clause;
import java.util.Collection;
import com.samskivert.depot.Ops;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.FragmentVisitor;
import com.samskivert.depot.impl.expression.ValueExp;
import com.samskivert.depot.impl.operator.Equals;
import com.samskivert.depot.impl.operator.IsNull;
/**
* Represents a where clause: the condition can be any comparison operator or logical combination
* thereof.
*/
public class Where extends WhereClause
{
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 (ColumnExp[] columns, Comparable<?>[] values)
{
this(toCondition(columns, values));
}
public Where (SQLExpression condition)
{
_condition = condition;
}
@Override // from WhereClause
public SQLExpression getWhereExpression ()
{
return _condition;
}
// from SQLExpression
public Object accept (FragmentVisitor<?> builder)
{
return builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
_condition.addClasses(classSet);
}
@Override // from Object
public String toString ()
{
return String.valueOf(_condition);
}
protected static SQLExpression toCondition (ColumnExp[] columns, Comparable<?>[] values)
{
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]));
}
return Ops.and(comparisons);
}
protected SQLExpression _condition;
}
@@ -0,0 +1,59 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.clause;
import com.samskivert.depot.expression.SQLExpression;
/**
* Currently only exists as a type without any functionality of its own.
*/
public abstract class WhereClause implements QueryClause
{
/**
* Returns the condition associated with this where clause.
*/
public abstract SQLExpression getWhereExpression ();
/**
* Validates that the supplied persistent record type is the type matched by this where clause.
* Not all clauses will be able to perform this validation, but those that can, should do so to
* help alleviate programmer error.
*
* @exception IllegalArgumentException thrown if the supplied class is known not to by the type
* matched by this where clause.
*/
public void validateQueryType (Class<?> pClass)
{
// nothing by default
}
/**
* A helper function for implementing {@link #validateQueryType}.
*/
protected void validateTypesMatch (Class<?> qClass, Class<?> kClass)
{
if (!qClass.equals(kClass)) {
throw new IllegalArgumentException(
"Class mismatch between persistent record and key in query " +
"[qtype=" + qClass.getSimpleName() + ", ktype=" + kClass.getSimpleName() + "].");
}
}
}
@@ -0,0 +1,24 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2009 Michael Bayne and 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
/**
* Models the clauses of a SQL query: where, join, limit, etc.
*/
package com.samskivert.depot.clause;
@@ -0,0 +1,100 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.expression;
import java.util.Collection;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.clause.Join;
import com.samskivert.depot.impl.FragmentVisitor;
/**
* An expression that unambiguously identifies a field of a class, for example
* <code>GameRecord.itemId</code>.
*/
public class ColumnExp extends FluentExp
{
/** The name of the column we reference. */
public final String name;
public ColumnExp (Class<? extends PersistentRecord> pClass, String field)
{
super();
_pClass = pClass;
this.name = field;
}
/**
* Returns a column expression for the supplied persistent class with the same name as this
* expression. This is useful for "casting" a column expression from a parent class to a
* derived class.
*/
public ColumnExp as (Class<? extends PersistentRecord> oClass)
{
return new ColumnExp(oClass, name);
}
/** Returns a {@link Join} on this column and the supplied target. */
public Join join (ColumnExp join)
{
return new Join(this, join);
}
// from SQLExpression
public Object accept (FragmentVisitor<?> builder)
{
return builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
classSet.add(_pClass);
}
public Class<? extends PersistentRecord> getPersistentClass ()
{
return _pClass;
}
@Override // from Object
public int hashCode ()
{
return _pClass.hashCode() ^ this.name.hashCode();
}
@Override // from Object
public boolean equals (Object other)
{
return (other instanceof ColumnExp) &&
((ColumnExp)other)._pClass.equals(_pClass) &&
((ColumnExp)other).name.equals(this.name);
}
@Override // from Object
public String toString ()
{
return "\"" + name + "\""; // TODO: qualify with record name and be uber verbose?
}
/** The table that hosts the column we reference, or null. */
protected final Class<? extends PersistentRecord> _pClass;
}
@@ -0,0 +1,243 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2009 Michael Bayne and 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.depot.expression;
import com.samskivert.depot.Ops;
import com.samskivert.depot.impl.operator.Add;
import com.samskivert.depot.impl.operator.BitAnd;
import com.samskivert.depot.impl.operator.BitOr;
import com.samskivert.depot.impl.operator.Div;
import com.samskivert.depot.impl.operator.Equals;
import com.samskivert.depot.impl.operator.GreaterThan;
import com.samskivert.depot.impl.operator.GreaterThanEquals;
import com.samskivert.depot.impl.operator.In;
import com.samskivert.depot.impl.operator.IsNull;
import com.samskivert.depot.impl.operator.LessThan;
import com.samskivert.depot.impl.operator.LessThanEquals;
import com.samskivert.depot.impl.operator.Like;
import com.samskivert.depot.impl.operator.Mul;
import com.samskivert.depot.impl.operator.NotEquals;
import com.samskivert.depot.impl.operator.Sub;
/**
* Provides a fluent API for creating most SQL expressions like and, or, equal, not equal, etc.
*/
public abstract class FluentExp
implements SQLExpression
{
/** Returns an {@link Equals} with this expression and the supplied target. */
public FluentExp eq (Comparable<?> value)
{
return new Equals(this, value);
}
/** Returns an {@link Equals} with this expression and the supplied target. */
public FluentExp eq (SQLExpression expr)
{
return new Equals(this, expr);
}
/** Returns a {@link NotEquals} with this expression and the supplied target. */
public FluentExp notEq (Comparable<?> value)
{
return new NotEquals(this, value);
}
/** Returns a {@link NotEquals} with this expression and the supplied target. */
public FluentExp notEq (SQLExpression expr)
{
return new NotEquals(this, expr);
}
/** Returns an {@link IsNull} with this expression as its target. */
public IsNull isNull ()
{
return new IsNull(this);
}
/** Returns an {@link In} with this expression and the supplied values. */
public In in (Comparable<?>... values)
{
return new In(this, values);
}
/** Returns an {@link In} with this column and the supplied values. */
public In in (Iterable<? extends Comparable<?>> values)
{
return new In(this, values);
}
/** Returns a {@link GreaterThan} with this expression and the supplied target. */
public FluentExp greaterThan (Comparable<?> value)
{
return new GreaterThan(this, value);
}
/** Returns a {@link GreaterThan} with this expression and the supplied target. */
public FluentExp greaterThan (SQLExpression expr)
{
return new GreaterThan(this, expr);
}
/** Returns a {@link LessThan} with this expression and the supplied target. */
public FluentExp lessThan (Comparable<?> value)
{
return new LessThan(this, value);
}
/** Returns a {@link LessThan} with this expression and the supplied target. */
public FluentExp lessThan (SQLExpression expr)
{
return new LessThan(this, expr);
}
/** Returns a {@link GreaterThanEquals} with this expression and the supplied target. */
public FluentExp greaterEq (Comparable<?> value)
{
return new GreaterThanEquals(this, value);
}
/** Returns a {@link GreaterThanEquals} with this expression and the supplied target. */
public FluentExp greaterEq (SQLExpression expr)
{
return new GreaterThanEquals(this, expr);
}
/** Returns a {@link LessThanEquals} with this expression and the supplied target. */
public FluentExp lessEq (Comparable<?> value)
{
return new LessThanEquals(this, value);
}
/** Returns a {@link LessThanEquals} with this expression and the supplied target. */
public FluentExp lessEq (SQLExpression expr)
{
return new LessThanEquals(this, expr);
}
/** Returns a boolean and of this expression and the supplied target. */
public FluentExp and (SQLExpression expr)
{
return Ops.and(this, expr);
}
/** Returns a boolean or of this expression and the supplied target. */
public FluentExp or (SQLExpression expr)
{
return Ops.or(this, expr);
}
/** Returns a bitwise and of this expression and the supplied target. */
public FluentExp bitAnd (Comparable<?> value)
{
return new BitAnd(this, value);
}
/** Returns a bitwise and of this expression and the supplied target. */
public FluentExp bitAnd (SQLExpression expr)
{
return new BitAnd(this, expr);
}
/** Returns a bitwise or of this expression and the supplied target. */
public FluentExp bitOr (Comparable<?> value)
{
return new BitOr(this, value);
}
/** Returns a bitwise or of this expression and the supplied target. */
public FluentExp bitOr (SQLExpression expr)
{
return new BitOr(this, expr);
}
/** Returns the sum of this expression and the supplied target. */
public FluentExp plus (Comparable<?> value)
{
return new Add(this, value);
}
/** Returns the sum of this expression and the supplied target. */
public FluentExp plus (SQLExpression expr)
{
return new Add(this, expr);
}
/** Returns this expression minus the supplied target. */
public FluentExp minus (Comparable<?> value)
{
return new Sub(this, value);
}
/** Returns this expression minus the supplied target. */
public FluentExp minus (SQLExpression expr)
{
return new Sub(this, expr);
}
/** Returns this expression times the supplied target. */
public FluentExp times (Comparable<?> value)
{
return new Mul(this, value);
}
/** Returns this expression times the supplied target. */
public FluentExp times (SQLExpression expr)
{
return new Mul(this, expr);
}
/** Returns this expression divided by the supplied target. */
public FluentExp div (Comparable<?> value)
{
return new Div(this, value);
}
/** Returns this expression divided by the supplied target. */
public FluentExp div (SQLExpression expr)
{
return new Div(this, expr);
}
/** Returns a {@link Like} on this column and the supplied target. */
public FluentExp like (Comparable<?> value)
{
return new Like(this, value, true);
}
/** Returns a {@link Like} on this column and the supplied target. */
public FluentExp like (SQLExpression expr)
{
return new Like(this, expr, true);
}
/** Returns a negated {@link Like} on this column and the supplied target. */
public FluentExp notLike (Comparable<?> value)
{
return new Like(this, value, false);
}
/** Returns a negated {@link Like} on this column and the supplied target. */
public FluentExp notLike (SQLExpression expr)
{
return new Like(this, expr, false);
}
}
@@ -0,0 +1,45 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.expression;
import com.samskivert.depot.SQLFragment;
/**
* Represents an SQL expression, e.g. column name, function, or constant.
*/
public interface SQLExpression extends SQLFragment
{
/** Used internally to represent the lack of a value. */
public static final class NoValue
{
public NoValue (String reason)
{
_reason = reason;
}
@Override public String toString () {
return "[unknown value, reason=" + _reason + "]";
}
protected String _reason;
}
}
@@ -0,0 +1,24 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2009 Michael Bayne and 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
/**
* Contains the API by which SQL expressions are created.
*/
package com.samskivert.depot.expression;
@@ -0,0 +1,987 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.impl;
import java.nio.ByteBuffer;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.samskivert.util.ByteEnum;
import com.samskivert.util.Tuple;
import com.samskivert.depot.Key;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.annotation.Computed;
import com.samskivert.depot.clause.FieldDefinition;
import com.samskivert.depot.clause.FieldOverride;
import com.samskivert.depot.clause.ForUpdate;
import com.samskivert.depot.clause.FromOverride;
import com.samskivert.depot.clause.GroupBy;
import com.samskivert.depot.clause.InsertClause;
import com.samskivert.depot.clause.Join;
import com.samskivert.depot.clause.Limit;
import com.samskivert.depot.clause.OrderBy.Order;
import com.samskivert.depot.clause.OrderBy;
import com.samskivert.depot.clause.SelectClause;
import com.samskivert.depot.clause.WhereClause;
import com.samskivert.depot.expression.*;
import com.samskivert.depot.operator.Case;
import com.samskivert.depot.operator.FullText;
import com.samskivert.depot.impl.clause.CreateIndexClause;
import com.samskivert.depot.impl.clause.DeleteClause;
import com.samskivert.depot.impl.clause.DropIndexClause;
import com.samskivert.depot.impl.clause.UpdateClause;
import com.samskivert.depot.impl.expression.AggregateFun;
import com.samskivert.depot.impl.expression.IntervalExp;
import com.samskivert.depot.impl.expression.LiteralExp;
import com.samskivert.depot.impl.expression.ValueExp;
import com.samskivert.depot.impl.expression.AggregateFun.Average;
import com.samskivert.depot.impl.expression.AggregateFun.Count;
import com.samskivert.depot.impl.expression.AggregateFun.Every;
import com.samskivert.depot.impl.expression.AggregateFun.Max;
import com.samskivert.depot.impl.expression.AggregateFun.Min;
import com.samskivert.depot.impl.expression.AggregateFun.Sum;
import com.samskivert.depot.impl.expression.ConditionalFun.Coalesce;
import com.samskivert.depot.impl.expression.ConditionalFun.Greatest;
import com.samskivert.depot.impl.expression.ConditionalFun.Least;
import com.samskivert.depot.impl.expression.DateFun.DatePart;
import com.samskivert.depot.impl.expression.DateFun.DateTruncate;
import com.samskivert.depot.impl.expression.DateFun.Now;
import com.samskivert.depot.impl.expression.NumericalFun.Abs;
import com.samskivert.depot.impl.expression.NumericalFun.Ceil;
import com.samskivert.depot.impl.expression.NumericalFun.Exp;
import com.samskivert.depot.impl.expression.NumericalFun.Floor;
import com.samskivert.depot.impl.expression.NumericalFun.Ln;
import com.samskivert.depot.impl.expression.NumericalFun.Log10;
import com.samskivert.depot.impl.expression.NumericalFun.Pi;
import com.samskivert.depot.impl.expression.NumericalFun.Power;
import com.samskivert.depot.impl.expression.NumericalFun.Random;
import com.samskivert.depot.impl.expression.NumericalFun.Round;
import com.samskivert.depot.impl.expression.NumericalFun.Sign;
import com.samskivert.depot.impl.expression.NumericalFun.Sqrt;
import com.samskivert.depot.impl.expression.NumericalFun.Trunc;
import com.samskivert.depot.impl.expression.StringFun.Length;
import com.samskivert.depot.impl.expression.StringFun.Lower;
import com.samskivert.depot.impl.expression.StringFun.Position;
import com.samskivert.depot.impl.expression.StringFun.Substring;
import com.samskivert.depot.impl.expression.StringFun.Trim;
import com.samskivert.depot.impl.expression.StringFun.Upper;
import com.samskivert.depot.impl.operator.BinaryOperator;
import com.samskivert.depot.impl.operator.Exists;
import com.samskivert.depot.impl.operator.In;
import com.samskivert.depot.impl.operator.IsNull;
import com.samskivert.depot.impl.operator.MultiOperator;
import com.samskivert.depot.impl.operator.Not;
/**
* Implements the base functionality of the SQL-building pass of {@link SQLBuilder}. Dialectal
* subclasses of this should be created and returned from {@link SQLBuilder#getBuildVisitor()}.
*/
public abstract class BuildVisitor implements FragmentVisitor<Void>
{
public String getQuery ()
{
return _builder.toString();
}
public Iterable<Bindable> getBindables ()
{
return _bindables;
}
public Void visit (FromOverride override)
{
_builder.append(" from " );
List<Class<? extends PersistentRecord>> from = override.getFromClasses();
for (int ii = 0; ii < from.size(); ii++) {
if (ii > 0) {
_builder.append(", ");
}
appendTableName(from.get(ii));
_builder.append(" as ");
appendTableAbbreviation(from.get(ii));
}
return null;
}
public Void visit (FieldDefinition definition)
{
definition.getDefinition().accept(this);
return null;
}
public Void visit (WhereClause where)
{
_builder.append(" where ");
where.getWhereExpression().accept(this);
return null;
}
public Void visit (Key.Expression key)
{
Class<? extends PersistentRecord> pClass = key.getPersistentClass();
ColumnExp[] keyFields = DepotUtil.getKeyFields(pClass);
Comparable<?>[] values = key.getValues();
for (int ii = 0; ii < keyFields.length; ii ++) {
if (ii > 0) {
_builder.append(" and ");
}
// A Key's WHERE clause must mirror what's actually retrieved for the persistent
// object, so we turn on overrides here just as we do when expanding SELECT fields
boolean saved = _enableOverrides;
_enableOverrides = true;
appendRhsColumn(pClass, keyFields[ii]);
_enableOverrides = saved;
if (values[ii] == null) {
_builder.append(" is null ");
} else {
_builder.append(" = ");
bindValue(values[ii]);
}
}
return null;
}
public Void visit (MultiOperator multiOperator)
{
SQLExpression[] conditions = multiOperator.getArgs();
for (int ii = 0; ii < conditions.length; ii++) {
if (ii > 0) {
_builder.append(" ").append(multiOperator.operator()).append(" ");
}
_builder.append("(");
conditions[ii].accept(this);
_builder.append(")");
}
return null;
}
public Void visit (BinaryOperator binaryOperator)
{
_builder.append('(');
binaryOperator.getLeftHandSide().accept(this);
_builder.append(binaryOperator.operator());
binaryOperator.getRightHandSide().accept(this);
_builder.append(')');
return null;
}
public Void visit (IsNull isNull)
{
isNull.getExpression().accept(this);
_builder.append(" is null");
return null;
}
public Void visit (In in)
{
// if the In() expression is empty, replace it with a 'false'
if (in.getValues().length == 0) {
new ValueExp(false).accept(this);
return null;
}
in.getExpression().accept(this);
_builder.append(" in (");
Comparable<?>[] values = in.getValues();
for (int ii = 0; ii < values.length; ii ++) {
if (ii > 0) {
_builder.append(", ");
}
bindValue(values[ii]);
}
_builder.append(")");
return null;
}
public abstract Void visit (FullText.Match match);
public abstract Void visit (FullText.Rank rank);
public Void visit (Case caseExp)
{
_builder.append("(case ");
for (Tuple<SQLExpression, SQLExpression> tuple : caseExp.getWhenExps()) {
_builder.append(" when ");
tuple.left.accept(this);
_builder.append(" then ");
tuple.right.accept(this);
}
SQLExpression elseExp = caseExp.getElseExp();
if (elseExp != null) {
_builder.append(" else ");
elseExp.accept(this);
}
_builder.append(" end)");
return null;
}
public Void visit (ColumnExp columnExp)
{
appendRhsColumn(columnExp.getPersistentClass(), columnExp);
return null;
}
public Void visit (Not not)
{
_builder.append(" not (");
not.getCondition().accept(this);
_builder.append(")");
return null;
}
public Void visit (GroupBy groupBy)
{
_builder.append(" group by ");
SQLExpression[] values = groupBy.getValues();
for (int ii = 0; ii < values.length; ii++) {
if (ii > 0) {
_builder.append(", ");
}
values[ii].accept(this);
}
return null;
}
public Void visit (ForUpdate forUpdate)
{
_builder.append(" for update ");
return null;
}
public Void visit (OrderBy orderBy)
{
_builder.append(" order by ");
SQLExpression[] values = orderBy.getValues();
OrderBy.Order[] orders = orderBy.getOrders();
for (int ii = 0; ii < values.length; ii++) {
if (ii > 0) {
_builder.append(", ");
}
values[ii].accept(this);
_builder.append(" ").append(orders[ii]);
}
return null;
}
public Void visit (Join join)
{
switch (join.getType()) {
case INNER:
_builder.append(" inner join " );
break;
case LEFT_OUTER:
_builder.append(" left outer join " );
break;
case RIGHT_OUTER:
_builder.append(" right outer join " );
break;
}
appendTableName(join.getJoinClass());
_builder.append(" as ");
appendTableAbbreviation(join.getJoinClass());
_builder.append(" on ");
join.getJoinCondition().accept(this);
return null;
}
public Void visit (Limit limit)
{
_builder.append(" limit ").append(limit.getCount()).
append(" offset ").append(limit.getOffset());
return null;
}
public Void visit (LiteralExp literalExp)
{
_builder.append(literalExp.getText());
return null;
}
public Void visit (ValueExp valueExp)
{
bindValue(valueExp.getValue());
return null;
}
public Void visit (IntervalExp interval)
{
_builder.append("interval ").append(interval.amount).append(" ").append(interval.unit);
return null;
}
public Void visit (Exists exists)
{
_builder.append("exists ");
exists.getSubClause().accept(this);
return null;
}
public Void visit (SelectClause selectClause)
{
Class<? extends PersistentRecord> pClass = selectClause.getPersistentClass();
boolean isInner = _innerClause;
_innerClause = true;
if (isInner) {
_builder.append("(");
}
_builder.append("select ");
if (_definitions.containsKey(pClass)) {
throw new IllegalArgumentException(
"Can not yet nest SELECTs on the same persistent record.");
}
Map<String, FieldDefinition> definitionMap = Maps.newHashMap();
for (FieldDefinition definition : selectClause.getFieldDefinitions()) {
definitionMap.put(definition.getField(), definition);
}
_definitions.put(pClass, definitionMap);
try {
// iterate over the fields we're filling in and figure out whence each one comes
boolean comma = false;
// while expanding column names in the SELECT query, do aliasing and expansion
_enableAliasing = _enableOverrides = true;
for (ColumnExp field : selectClause.getFields()) {
// write column to a temporary buffer
StringBuilder saved = _builder;
_builder = new StringBuilder();
appendRhsColumn(pClass, field);
String column = _builder.toString();
_builder = saved;
// append if non-empty
if (column.length() > 0) {
if (comma) {
_builder.append(", ");
}
comma = true;
_builder.append(column);
}
}
// then stop
_enableAliasing = _enableOverrides = false;
if (selectClause.getFromOverride() != null) {
selectClause.getFromOverride().accept(this);
} else {
Computed computed = _types.getMarshaller(pClass).getComputed();
Class<? extends PersistentRecord> tClass;
if (computed != null && !PersistentRecord.class.equals(computed.shadowOf())) {
tClass = computed.shadowOf();
} else if (_types.getTableName(pClass) != null) {
tClass = pClass;
} else {
throw new IllegalStateException(
"Query on @Computed entity with no FromOverrideClause.");
}
_builder.append(" from ");
appendTableName(tClass);
_builder.append(" as ");
appendTableAbbreviation(tClass);
}
for (Join clause : selectClause.getJoinClauses()) {
clause.accept(this);
}
if (selectClause.getWhereClause() != null) {
selectClause.getWhereClause().accept(this);
}
if (selectClause.getGroupBy() != null) {
selectClause.getGroupBy().accept(this);
}
if (selectClause.getOrderBy() != null) {
selectClause.getOrderBy().accept(this);
}
if (selectClause.getLimit() != null) {
selectClause.getLimit().accept(this);
}
if (selectClause.getForUpdate() != null) {
selectClause.getForUpdate().accept(this);
}
} finally {
_definitions.remove(pClass);
}
if (isInner) {
_builder.append(")");
}
return null;
}
public Void visit (UpdateClause updateClause)
{
if (updateClause.getWhereClause() == null) {
throw new IllegalArgumentException(
"I dare not currently perform UPDATE without a WHERE clause.");
}
Class<? extends PersistentRecord> pClass = updateClause.getPersistentClass();
_innerClause = true;
_builder.append("update ");
appendTableName(pClass);
_builder.append(" as ");
appendTableAbbreviation(pClass);
_builder.append(" set ");
ColumnExp[] fields = updateClause.getFields();
Object pojo = updateClause.getPojo();
SQLExpression[] values = updateClause.getValues();
for (int ii = 0; ii < fields.length; ii ++) {
if (ii > 0) {
_builder.append(", ");
}
appendLhsColumn(pClass, fields[ii]);
_builder.append(" = ");
if (pojo != null) {
bindField(pClass, fields[ii], pojo);
} else {
values[ii].accept(this);
}
}
updateClause.getWhereClause().accept(this);
return null;
}
public Void visit (DeleteClause deleteClause)
{
_builder.append("delete from ");
appendTableName(deleteClause.getPersistentClass());
_builder.append(" as ");
appendTableAbbreviation(deleteClause.getPersistentClass());
_builder.append(" ");
deleteClause.getWhereClause().accept(this);
return null;
}
public Void visit (InsertClause insertClause)
{
_innerClause = true;
_builder.append("insert into ");
appendTableName(insertClause.getPersistentClass());
_builder.append(" ");
appendInsertColumns(insertClause);
return null;
}
public Void visit (CreateIndexClause createIndexClause)
{
_builder.append("create ");
if (createIndexClause.isUnique()) {
_builder.append("unique ");
}
_builder.append("index ");
appendIdentifier(createIndexClause.getName());
_builder.append(" on ");
appendTableName(createIndexClause.getPersistentClass());
_builder.append(" (");
// turn off table abbreviations here
_defaultType = createIndexClause.getPersistentClass();
boolean comma = false;
for (Tuple<SQLExpression, Order> field : createIndexClause.getFields()) {
if (comma) {
_builder.append(", ");
}
comma = true;
appendIndexComponent(field.left);
if (field.right == Order.DESC) {
// ascending is default, print nothing unless explicitly descending
_builder.append(" desc");
}
}
// turn them back on
_defaultType = null;
_builder.append(")");
return null;
}
public Void visit (DropIndexClause dropIndexClause)
{
_builder.append("drop index ");
appendIdentifier(dropIndexClause.getName());
return null;
}
//
// NUMERICAL FUNCTIONS
public Void visit (Abs exp)
{
return appendFunctionCall("abs", exp.getArg());
}
public Void visit (Ceil exp)
{
return appendFunctionCall("ceil", exp.getArg());
}
public Void visit (Exp exp)
{
return appendFunctionCall("exp", exp.getArg());
}
public Void visit (Floor exp)
{
return appendFunctionCall("floor", exp.getArg());
}
public Void visit (Ln exp)
{
return appendFunctionCall("ln", exp.getArg());
}
public Void visit (Log10 exp)
{
return appendFunctionCall("log", exp.getArg());
}
public Void visit (Pi exp)
{
return appendFunctionCall("PI");
}
public Void visit (Power exp)
{
return appendFunctionCall("power", exp.getPower(), exp.getValue());
}
public Void visit (Random exp)
{
return appendFunctionCall("random");
}
public Void visit (Round exp)
{
return appendFunctionCall("round", exp.getArg());
}
public Void visit (Sign exp)
{
return appendFunctionCall("sign", exp.getArg());
}
public Void visit (Sqrt exp)
{
return appendFunctionCall("sqrt", exp.getArg());
}
public Void visit (Trunc exp)
{
return appendFunctionCall("trunc", exp.getArg());
}
//
// STRING FUNCTIONS
public Void visit (Length exp)
{
return appendFunctionCall("length", exp.getArg());
}
public Void visit (Lower exp)
{
return appendFunctionCall("lower", exp.getArg());
}
public Void visit (Position exp)
{
_builder.append(" position(").append(exp.getSubString()).append(" in ").
append(exp.getString()).append(")");
return null;
}
public Void visit (Substring exp)
{
return appendFunctionCall("substr", exp.getArgs());
}
public Void visit (Trim exp)
{
return appendFunctionCall("trim", exp.getArg());
}
public Void visit (Upper exp)
{
return appendFunctionCall("upper", exp.getArg());
}
public abstract Void visit (DatePart exp);
public abstract Void visit (DateTruncate exp);
public Void visit (Now exp)
{
appendFunctionCall("now");
return null;
}
public Void visit (Average exp)
{
return appendAggregateFunctionCall("average", exp);
}
public Void visit (Count exp)
{
return appendAggregateFunctionCall("count", exp);
}
public Void visit (Every exp)
{
return appendAggregateFunctionCall("every", exp);
}
public Void visit (Max exp)
{
return appendAggregateFunctionCall("max", exp);
}
public Void visit (Min exp)
{
return appendAggregateFunctionCall("min", exp);
}
public Void visit (Sum exp)
{
return appendAggregateFunctionCall("sum", exp);
}
//
// CONDITIONAL FUNCTIONS
public Void visit (Coalesce exp)
{
return appendFunctionCall("coalesce", exp.getArgs());
}
public Void visit (Greatest exp)
{
return appendFunctionCall("greatest", exp.getArgs());
}
public Void visit (Least exp)
{
return appendFunctionCall("least", exp.getArgs());
}
protected Void appendAggregateFunctionCall (String function, AggregateFun exp)
{
_builder.append(" ").append(function).append("(");
if (exp.isDistinct()) {
_builder.append("DISTINCT ");
}
appendArguments(exp.getArg());
_builder.append(")");
return null;
}
protected Void appendFunctionCall (String function, SQLExpression... args)
{
_builder.append(" ").append(function).append("(");
appendArguments(args);
_builder.append(")");
return null;
}
protected Void appendArguments (SQLExpression... args)
{
for (int ii = 0; ii < args.length; ii ++) {
if (ii > 0) {
_builder.append(", ");
}
(args[ii]).accept(this);
}
return null;
}
protected Void bindValue (Object object)
{
_bindables.add(newBindable(object));
_builder.append("?");
return null;
}
protected Void bindField (
Class<? extends PersistentRecord> pClass, ColumnExp field, Object pojo)
{
final DepotMarshaller<?> marshaller = _types.getMarshaller(pClass);
_bindables.add(newBindable(marshaller, field, pojo));
_builder.append("?");
return null;
}
protected abstract void appendIdentifier (String field);
protected void appendTableName (Class<? extends PersistentRecord> type)
{
appendIdentifier(_types.getTableName(type));
}
protected void appendTableAbbreviation (Class<? extends PersistentRecord> type)
{
appendIdentifier(_types.getTableAbbreviation(type));
}
// Constructs a name used for assignment in e.g. INSERT/UPDATE. This is the SQL
// equivalent of an lvalue; something that can appear to the left of an equals sign.
// We do not prepend this identifier with a table abbreviation, nor do we expand
// field overrides, shadowOf declarations, or the like: it is just a column name.
protected void appendLhsColumn (Class<? extends PersistentRecord> type, ColumnExp field)
{
// TODO: nix type and use the class from the supplied ColumnExp
DepotMarshaller<?> dm = _types.getMarshaller(type);
if (dm == null) {
throw new IllegalArgumentException(
"Unknown field on persistent record [record=" + type + ", field=" + field + "]");
}
FieldMarshaller<?> fm = dm.getFieldMarshaller(field.name);
appendIdentifier(fm.getColumnName());
}
// Appends an expression for the given field on the given persistent record; this can
// appear in a SELECT list, in WHERE clauses, etc, etc.
protected void appendRhsColumn (Class<? extends PersistentRecord> type, ColumnExp field)
{
DepotMarshaller<?> dm = _types.getMarshaller(type);
if (dm == null) {
throw new IllegalArgumentException(
"Unknown field on persistent record [record=" + type + ", field=" + field + "]");
}
// first, see if there's a field definition
FieldMarshaller<?> fm = dm.getFieldMarshaller(field.name);
Map<String, FieldDefinition> fieldDefs = _definitions.get(type);
if (fieldDefs != null) {
FieldDefinition fieldDef = fieldDefs.get(field.name);
if (fieldDef != null) {
boolean useOverride;
if (fieldDef instanceof FieldOverride) {
if (fm.getComputed() != null && dm.getComputed() != null) {
throw new IllegalArgumentException(
"FieldOverride cannot be used on @Computed field: " + field);
}
useOverride = _enableOverrides;
} else if (fm.getComputed() == null && dm.getComputed() == null) {
throw new IllegalArgumentException(
"FieldDefinition must not be used on concrete field: " + field);
} else {
useOverride = true;
}
if (useOverride) {
// If a FieldOverride's target is in turn another FieldOverride, the second one
// is ignored. As an example, when creating ItemRecords from CloneRecords, we
// make Item.itemId = Clone.itemId. We also make Item.parentId = Item.itemId
// and would be dismayed to find Item.parentID = Item.itemId = Clone.itemId.
boolean saved = _enableOverrides;
_enableOverrides = false;
fieldDef.accept(this);
if (_enableAliasing) {
_builder.append(" as ");
appendIdentifier(fm.getColumnName());
}
_enableOverrides = saved;
return;
}
}
}
Computed entityComputed = dm.getComputed();
// figure out the class we're selecting from unless we're otherwise overriden:
// for a concrete record, simply use the corresponding table; for a computed one,
// default to the shadowed concrete record, or null if there isn't one
Class<? extends PersistentRecord> tableClass;
if (entityComputed == null) {
tableClass = type;
} else if (!PersistentRecord.class.equals(entityComputed.shadowOf())) {
tableClass = entityComputed.shadowOf();
} else {
tableClass = null;
}
// handle the field-level @Computed annotation, if there is one
Computed fieldComputed = fm.getComputed();
if (fieldComputed != null) {
// check if the computed field has a literal SQL definition
if (fieldComputed.fieldDefinition().length() > 0) {
_builder.append(fieldComputed.fieldDefinition());
if (_enableAliasing) {
_builder.append(" as ");
appendIdentifier(fm.getColumnName());
}
return;
}
// or if we can simply ignore the field
if (!fieldComputed.required()) {
return;
}
// else see if there's an overriding shadowOf definition
if (fieldComputed.shadowOf() != null) {
tableClass = fieldComputed.shadowOf();
}
}
// if we get this far we hopefully have a table to select from, if not we're probably doing
// something like an order by on a synthetic field "select count(distinct foo) as bar from
// ... order by bar", so we just skip the table qualifier
if (tableClass != null && _defaultType != tableClass) {
appendTableAbbreviation(tableClass);
_builder.append(".");
}
// if the field is shadowed, be sure to use the shadowed column's name
if (tableClass != type && tableClass != null) {
appendIdentifier(_types.getColumnName(tableClass, field.name));
} else {
appendIdentifier(fm.getColumnName());
}
}
// output one of potentially many fields in an index expression
protected void appendIndexComponent (SQLExpression expression)
{
// the standard builder wraps each field in its own parens
_builder.append("(");
expression.accept(this);
_builder.append(")");
}
// output the column names and values for an insert
protected void appendInsertColumns (InsertClause insertClause)
{
Class<? extends PersistentRecord> pClass = insertClause.getPersistentClass();
Object pojo = insertClause.getPojo();
DepotMarshaller<?> marsh = _types.getMarshaller(pClass);
Set<String> idFields = insertClause.getIdentityFields();
ColumnExp[] fields = marsh.getColumnFieldNames();
_builder.append("(");
boolean comma = false;
for (ColumnExp field : fields) {
if (idFields.contains(field.name)) {
continue;
}
if (comma) {
_builder.append(", ");
}
comma = true;
appendLhsColumn(pClass, field);
}
_builder.append(") values (");
comma = false;
for (ColumnExp field : fields) {
if (idFields.contains(field.name)) {
continue;
}
if (comma) {
_builder.append(", ");
}
comma = true;
bindField(pClass, field, pojo);
}
_builder.append(")");
}
protected BuildVisitor (DepotTypes types)
{
_types = types;
}
protected static interface Bindable
{
void doBind (Connection conn, PreparedStatement stmt, int argIx) throws Exception;
}
protected static Bindable newBindable (
final DepotMarshaller<?> marshaller, final ColumnExp field, final Object pojo)
{
return new Bindable() {
public void doBind (Connection conn, PreparedStatement stmt, int argIx)
throws Exception {
marshaller.getFieldMarshaller(field.name).getAndWriteToStatement(stmt, argIx, pojo);
}
};
}
protected static Bindable newBindable (final Object value)
{
return new Bindable() {
public void doBind (Connection conn, PreparedStatement stmt, int argIx)
throws Exception {
// TODO: how can we abstract this fieldless marshalling
if (value instanceof ByteEnum) {
// byte enums require special conversion
stmt.setByte(argIx, ((ByteEnum)value).toByte());
} else if (value instanceof int[]) {
// int arrays require conversion to byte arrays
int[] data = (int[])value;
ByteBuffer bbuf = ByteBuffer.allocate(data.length * 4);
bbuf.asIntBuffer().put(data);
stmt.setObject(argIx, bbuf.array());
} else {
stmt.setObject(argIx, value);
}
}
};
}
protected DepotTypes _types;
/** For each SQL parameter ? we add an {@link Comparable} to bind to this list. */
protected List<Bindable> _bindables = Lists.newLinkedList();
/** A StringBuilder to hold the constructed SQL. */
protected StringBuilder _builder = new StringBuilder();
/** A mapping of field overrides per persistent record. */
protected Map<Class<? extends PersistentRecord>, Map<String, FieldDefinition>> _definitions=
Maps.newHashMap();
/** Set this to non-null to suppress table abbreviations from being prepended for a class. */
protected Class<? extends PersistentRecord> _defaultType;
/** A flag that's set to true for inner SELECT's */
protected boolean _innerClause = false;
protected boolean _enableOverrides = false;
protected boolean _enableAliasing = false;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,222 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2009 Michael Bayne and 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.depot.impl;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Map;
import com.google.common.collect.Maps;
import com.samskivert.jdbc.ColumnDefinition;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.jdbc.HsqldbLiaison;
import com.samskivert.jdbc.MySQLLiaison;
import com.samskivert.jdbc.PostgreSQLLiaison;
import com.samskivert.depot.PersistenceContext;
/**
* Does something extraordinary.
*/
public class DepotMetaData
{
public void init (PersistenceContext ctx)
{
_ctx = ctx;
_ctx.invoke(new SimpleModifier() {
@Override
protected int invoke (DatabaseLiaison liaison, Statement stmt) throws SQLException {
// determine our JDBC major version
_jdbcMajorVersion = stmt.getConnection().getMetaData().getJDBCMajorVersion();
// create our schema version table if needed
liaison.createTableIfMissing(
stmt.getConnection(), SCHEMA_VERSION_TABLE,
new String[] { P_COLUMN, V_COLUMN, MV_COLUMN },
new ColumnDefinition[] {
new ColumnDefinition("VARCHAR(255)", false, true, null),
new ColumnDefinition("INTEGER", false, false, null),
new ColumnDefinition("INTEGER", false, false, null)
},
null,
new String[] { P_COLUMN });
// slurp in the current versions of all records
readVersions(liaison, stmt);
return 0;
}
});
}
/**
* Returns the current version of the specified persistent class.
*
* @param forceUpdate if true the latest version will be read from the database rather than
* returned from memory. Only force an update when you know that the value may have changed in
* the database since Depot was initialized.
*
* @return the current version of the specified persistent class or -1 if the class has no
* recorded version.
*/
public int getVersion (String pClass, boolean forceUpdate)
{
if (forceUpdate) {
_ctx.invoke(new SimpleModifier() {
@Override
protected int invoke (DatabaseLiaison liaison, Statement stmt) throws SQLException {
readVersions(liaison, stmt);
return 0;
}
});
}
Integer curvers = _curvers.get(pClass);
return (curvers == null) ? -1 : curvers;
}
/**
* Initializes the version of the specified persistent class to zero.
*/
public void initializeVersion (final String pClass)
{
_ctx.invoke(new SimpleModifier() {
@Override
protected int invoke (DatabaseLiaison liaison, Statement stmt) throws SQLException {
try {
return stmt.executeUpdate(
"insert into " + liaison.tableSQL(SCHEMA_VERSION_TABLE) +
" values('" + pClass + "', 0 , 0)");
} catch (SQLException e) {
// someone else might be doing this at the exact same time which is OK,
// we'll coordinate with that other process in the next phase
if (liaison.isDuplicateRowException(e)) {
return 0;
} else {
throw e;
}
}
}
});
}
/**
* Updates the version of the specified persistent class to the specified version.
*/
public void updateVersion (final String pClass, final int newVersion)
{
_ctx.invoke(new SimpleModifier() {
@Override
protected int invoke (DatabaseLiaison liaison, Statement stmt) throws SQLException {
return stmt.executeUpdate(
"update " + liaison.tableSQL(SCHEMA_VERSION_TABLE) +
" set " + liaison.columnSQL(V_COLUMN) + " = " + newVersion +
" where " + liaison.columnSQL(P_COLUMN) + " = '" + pClass + "'");
}
});
}
/**
* Updates the current migrating version of the supplied persistent class.
*
* @return true if the migration lock was obtained and the migrating version was updated, false
* if some other process acquired the lock.
*/
public boolean updateMigratingVersion (
final String pClass, final int newMigratingVersion, final int guardVersion)
{
return _ctx.invoke(new SimpleModifier() {
@Override
protected int invoke (DatabaseLiaison liaison, Statement stmt) throws SQLException {
return stmt.executeUpdate(
"update " + liaison.tableSQL(SCHEMA_VERSION_TABLE) +
" set " + liaison.columnSQL(MV_COLUMN) + " = " + newMigratingVersion +
" where " + liaison.columnSQL(P_COLUMN) + " = '" + pClass + "'" +
" and " + liaison.columnSQL(MV_COLUMN) + " = " + guardVersion);
}
}) > 0;
}
/**
* Creates and return a new {@link SQLBuilder} for the appropriate dialect.
*
* TODO: At some point perhaps use a more elegant way of discerning our dialect.
*/
public SQLBuilder getSQLBuilder (DepotTypes types, DatabaseLiaison liaison)
{
if (liaison instanceof PostgreSQLLiaison) {
if (_jdbcMajorVersion >= 4) {
return new PostgreSQL4Builder(types);
} else {
return new PostgreSQLBuilder(types);
}
}
if (liaison instanceof MySQLLiaison) {
return new MySQLBuilder(types);
}
if (liaison instanceof HsqldbLiaison) {
return new HSQLBuilder(types);
}
throw new IllegalArgumentException("Unknown liaison type: " + liaison.getClass());
}
protected void readVersions (DatabaseLiaison liaison, Statement stmt)
throws SQLException
{
ResultSet rs = stmt.executeQuery(
" select " + liaison.columnSQL(P_COLUMN) + ", " + liaison.columnSQL(V_COLUMN) +
" from " + liaison.tableSQL(SCHEMA_VERSION_TABLE));
while (rs.next()) {
_curvers.put(rs.getString(1), rs.getInt(2));
}
}
protected abstract class SimpleModifier extends Modifier {
@Override
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
Statement stmt = conn.createStatement();
try {
return invoke(liaison, stmt);
} finally {
stmt.close();
}
}
protected abstract int invoke (DatabaseLiaison liaison, Statement stmt) throws SQLException;
}
protected PersistenceContext _ctx;
protected int _jdbcMajorVersion;
protected Map<String, Integer> _curvers = Maps.newHashMap();
/** The name of the table we use to track schema versions. */
protected static final String SCHEMA_VERSION_TABLE = "DepotSchemaVersion";
/** The name of the 'persistentClass' column in the {@link #SCHEMA_VERSION_TABLE}. */
protected static final String P_COLUMN = "persistentClass";
/** The name of the 'version' column in the {@link #SCHEMA_VERSION_TABLE}. */
protected static final String V_COLUMN = "version";
/** The name of the 'migratingVersion' column in the {@link #SCHEMA_VERSION_TABLE}. */
protected static final String MV_COLUMN = "migratingVersion";
}
@@ -0,0 +1,65 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.impl;
import java.sql.Timestamp;
import com.samskivert.depot.Key;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.annotation.Column;
import com.samskivert.depot.annotation.Id;
import com.samskivert.depot.expression.ColumnExp;
/**
* Maintains a record of all successfully invoked data migrations.
*/
public class DepotMigrationHistoryRecord extends PersistentRecord
{
// AUTO-GENERATED: FIELDS START
public static final Class<DepotMigrationHistoryRecord> _R = DepotMigrationHistoryRecord.class;
public static final ColumnExp IDENT = colexp(_R, "ident");
public static final ColumnExp WHEN_COMPLETED = colexp(_R, "whenCompleted");
// AUTO-GENERATED: FIELDS END
/** Our schema version. Probably not likely to change. */
public static final int SCHEMA_VERSION = 1;
/** The unique identifier for this migration. */
@Id public String ident;
/** The time at which the migration was completed. */
@Column(nullable=true)
public Timestamp whenCompleted;
// AUTO-GENERATED: METHODS START
/**
* Create and return a primary {@link Key} to identify a {@link DepotMigrationHistoryRecord}
* with the supplied key values.
*/
public static Key<DepotMigrationHistoryRecord> getKey (String ident)
{
return newKey(_R, ident);
}
/** Register the key fields in an order matching the getKey() factory. */
static { registerKeyFields(IDENT); }
// AUTO-GENERATED: METHODS END
}
@@ -0,0 +1,222 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.impl;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.samskivert.depot.DatabaseException;
import com.samskivert.depot.PersistenceContext;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.clause.QueryClause;
import com.samskivert.depot.expression.SQLExpression;
/**
* Maintains a record of the persistent classes brought into the context of the associated SQL,
* i.e. any class associated with a concrete table that would appear in FROM or JOIN clauses or as
* the target of an UPDATE or an INSERT or any other place where a table abbreviation could be
* constructed.
*
* The main motivation for breaking this functionality out into its own class is to encapsulate the
* operation that throws {@link DatabaseException} as separate from the operations that throw
* {@link SQLException}. Once this class has been constructed, it may be used to create {@link
* SQLBuilder} instances without any {@link DatabaseException} worries.
*/
public class DepotTypes
{
/** A trivial instance that is accessible in places where we want the dialectal benefits of the
* SQLBuilder without really requiring per-persistent-class context. */
public static DepotTypes TRIVIAL = new DepotTypes();
/**
* Conveniently constructs a {@link DepotTypes} object given {@link QueryClause} objects, which
* are interrogated for their class definition sets through {@link SQLExpression#addClasses}.
*/
public static <T extends PersistentRecord> DepotTypes getDepotTypes (
PersistenceContext ctx, Iterable<? extends QueryClause> clauses)
throws DatabaseException
{
Set<Class<? extends PersistentRecord>> classSet = Sets.newLinkedHashSet();
for (QueryClause clause : clauses) {
if (clause != null) {
clause.addClasses(classSet);
}
}
return new DepotTypes(ctx, classSet);
}
/**
* A varargs version of {@link #getDepotTypes(PersistenceContext,Iterable)}.
*/
public static <T extends PersistentRecord> DepotTypes getDepotTypes (
PersistenceContext ctx, QueryClause... clauses)
throws DatabaseException
{
return getDepotTypes(ctx, Arrays.asList(clauses));
}
/**
* Create a new DepotTypes with the given {@link PersistenceContext} and a collection of
* persistent record classes.
*/
public DepotTypes (PersistenceContext ctx, Iterable<Class<? extends PersistentRecord>> others)
throws DatabaseException
{
for (Class<? extends PersistentRecord> c : others) {
addClass(ctx, c);
}
}
/**
* Create a new DepotTypes with the given {@link PersistenceContext} and the given
* persistent record.
*/
public DepotTypes (PersistenceContext ctx, Class<? extends PersistentRecord> pClass)
throws DatabaseException
{
addClass(ctx, pClass);
}
/**
* Return the full table name of the given persistent class, which must have been previously
* registered with this object.
*/
public String getTableName (Class<? extends PersistentRecord> cl)
{
return getMarshaller(cl).getTableName();
}
/**
* Return the current abbreviation by which we refer to the table associated with the given
* persistent record -- which must have been previously registered with this object. If the
* useTableAbbreviations flag is false, we return the full table name instead.
*
* @exception IllegalArgumentException thrown if the specified class is not known.
*/
public String getTableAbbreviation (Class<? extends PersistentRecord> cl)
{
if (_useTableAbbreviations) {
Integer ix = _classIx.get(cl);
if (ix == null) {
throw new IllegalArgumentException("Unknown persistence class: " + cl);
}
return "T" + (ix+1);
}
return getTableName(cl);
}
/**
* Return the associated database column of the given field of the given persistent class,
* throwing an exception if the record has not been registered with this object, or if the
* field is unknown on the record.
*
* @exception IllegalArgumentException thrown if the specified field is not part of the
* specified persistent class.
*/
public String getColumnName (Class<? extends PersistentRecord> cl, String field)
{
FieldMarshaller<?> fm = getMarshaller(cl).getFieldMarshaller(field);
if (fm == null) {
throw new IllegalArgumentException(
"Field not known on class [field=" + field + ", class=" + cl + "]");
}
return fm.getColumnName();
}
/**
* Return the {@link DepotMarshaller} associated with the given persistent class, if it's been
* registered with this object.
*
* @exception IllegalArgumentException thrown if the specified class is not known.
*/
public DepotMarshaller<?> getMarshaller (Class<? extends PersistentRecord> cl)
{
DepotMarshaller<?> marsh = _classMap.get(cl);
if (marsh == null) {
throw new IllegalArgumentException("Persistent class not known: " + cl);
}
return marsh;
}
/**
* Register a new persistent class with this object.
*/
public void addClass (PersistenceContext ctx, Class <? extends PersistentRecord> type)
throws DatabaseException
{
if (_classMap.containsKey(type)) {
return;
}
// add the class in question
DepotMarshaller<?> marsh = ctx.getMarshaller(type);
_classMap.put(type, marsh);
_classIx.put(type, _classIx.size());
// if this class is @Computed and has a shadow, add its shadow
if (marsh.getComputed() != null &&
!PersistentRecord.class.equals(marsh.getComputed().shadowOf())) {
addClass(ctx, marsh.getComputed().shadowOf());
}
}
/**
* Return the value of the useTableAbbreviations flag, which governs the behaviour when
* referencing columns during SQL construction. Normally, this flag is on, and tables are
* referenced as e.g. T1.itemId, but there are cases of weak/broken SQL where abbreviations
* may not be brought into play. In these cases we prepend the full table name.
*/
public boolean getUseTableAbbreviations ()
{
return _useTableAbbreviations;
}
/**
* Sets the value of the useTableAbbreviations flag, which governs the behaviour when
* referencing columns during SQL construction. Normally, this flag is on, and tables are
* referenced as e.g. T1.itemId, but there are cases of weak/broken SQL where abbreviations
* may not be brought into play. In these cases we prepend the full table name.
*/
public void setUseTableAbbreviations (boolean doUse)
{
_useTableAbbreviations = doUse;
}
// constructor used to create TRIVIAL
protected DepotTypes ()
{
}
/** Classes mapped to integers, used for table abbreviation indexing. */
protected Map<Class<?>, Integer> _classIx = Maps.newHashMap();
/** Classes mapped to marshallers, used for table names and field lists. */
protected Map<Class<?>, DepotMarshaller<?>> _classMap = Maps.newHashMap();
/** When false, override the normal table abbreviations and return full table names instead. */
protected boolean _useTableAbbreviations = true;
}
@@ -0,0 +1,89 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.impl;
import java.lang.reflect.Field;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.common.collect.MapMaker;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.annotation.Id;
import com.samskivert.depot.expression.ColumnExp;
/**
* Simple utility methods used by various things.
*/
public class DepotUtil
{
/**
* Returns an array containing the names of the primary key fields for the supplied persistent
* class. The values are introspected and cached for the lifetime of the VM.
*/
public static ColumnExp[] getKeyFields (Class<? extends PersistentRecord> pClass)
{
return _keyFields.get(pClass);
}
/**
* Register the key fields to their PersistentRecord class. It should never be necessary
* to do this manually, as it is done via a static initializer in generated PersistentRecord
* subclasses. Calling this method after the key fields have already been registered will
* have no effect.
*/
public static void registerKeyFields (ColumnExp... fields)
{
// TODO: Checks? For example: Validate all exps from same class?
// Make a defensive copy of the array? Hide this method from public consumption?
_keyFields.putIfAbsent(fields[0].getPersistentClass(), fields);
}
/**
* Returns the name of the supplied class minus its package.
*/
public static String justClassName (Class<?> clazz)
{
return clazz.getName().substring(clazz.getName().lastIndexOf(".") + 1);
}
/** A (never expiring) cache of primary key field names for all persistent classes (of which
* there are merely dozens, so we don't need to worry about expiring). */
protected static ConcurrentMap<Class<? extends PersistentRecord>, ColumnExp[]> _keyFields =
new MapMaker()
// newly generated PersistentRecord classes will register their key fields via
// registerKeyFields, which will return an ordering determined at genrecord time.
// We fall back to computing the fields at runtime for older PersistentRecord classes.
.makeComputingMap(new Function<Class<? extends PersistentRecord>, ColumnExp[]>() {
public ColumnExp[] apply (Class<? extends PersistentRecord> pClass) {
List<ColumnExp> kflist = Lists.newArrayList();
for (Field field : pClass.getFields()) {
// look for @Id fields
if (field.getAnnotation(Id.class) != null) {
kflist.add(new ColumnExp(pClass, field.getName()));
}
}
return kflist.toArray(new ColumnExp[kflist.size()]);
}
});
}
@@ -0,0 +1,512 @@
//
// $Id: ExpressionEvaluator.java 377 2009-01-08 02:31:28Z samskivert $
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.impl;
import java.lang.reflect.Field;
import com.samskivert.depot.Key;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.clause.FieldDefinition;
import com.samskivert.depot.clause.ForUpdate;
import com.samskivert.depot.clause.FromOverride;
import com.samskivert.depot.clause.GroupBy;
import com.samskivert.depot.clause.InsertClause;
import com.samskivert.depot.clause.Join;
import com.samskivert.depot.clause.Limit;
import com.samskivert.depot.clause.OrderBy;
import com.samskivert.depot.clause.SelectClause;
import com.samskivert.depot.clause.WhereClause;
import com.samskivert.depot.expression.*;
import com.samskivert.depot.expression.SQLExpression.NoValue;
import com.samskivert.depot.operator.Case;
import com.samskivert.depot.operator.FullText;
import com.samskivert.depot.impl.clause.CreateIndexClause;
import com.samskivert.depot.impl.clause.DeleteClause;
import com.samskivert.depot.impl.clause.DropIndexClause;
import com.samskivert.depot.impl.clause.UpdateClause;
import com.samskivert.depot.impl.expression.IntervalExp;
import com.samskivert.depot.impl.expression.LiteralExp;
import com.samskivert.depot.impl.expression.ValueExp;
import com.samskivert.depot.impl.expression.AggregateFun.Average;
import com.samskivert.depot.impl.expression.AggregateFun.Count;
import com.samskivert.depot.impl.expression.AggregateFun.Every;
import com.samskivert.depot.impl.expression.AggregateFun.Max;
import com.samskivert.depot.impl.expression.AggregateFun.Min;
import com.samskivert.depot.impl.expression.AggregateFun.Sum;
import com.samskivert.depot.impl.expression.ConditionalFun.Coalesce;
import com.samskivert.depot.impl.expression.ConditionalFun.Greatest;
import com.samskivert.depot.impl.expression.ConditionalFun.Least;
import com.samskivert.depot.impl.expression.DateFun.DatePart;
import com.samskivert.depot.impl.expression.DateFun.DateTruncate;
import com.samskivert.depot.impl.expression.DateFun.Now;
import com.samskivert.depot.impl.expression.NumericalFun.Abs;
import com.samskivert.depot.impl.expression.NumericalFun.Ceil;
import com.samskivert.depot.impl.expression.NumericalFun.Exp;
import com.samskivert.depot.impl.expression.NumericalFun.Floor;
import com.samskivert.depot.impl.expression.NumericalFun.Ln;
import com.samskivert.depot.impl.expression.NumericalFun.Log10;
import com.samskivert.depot.impl.expression.NumericalFun.Pi;
import com.samskivert.depot.impl.expression.NumericalFun.Power;
import com.samskivert.depot.impl.expression.NumericalFun.Random;
import com.samskivert.depot.impl.expression.NumericalFun.Round;
import com.samskivert.depot.impl.expression.NumericalFun.Sign;
import com.samskivert.depot.impl.expression.NumericalFun.Sqrt;
import com.samskivert.depot.impl.expression.NumericalFun.Trunc;
import com.samskivert.depot.impl.expression.StringFun.Length;
import com.samskivert.depot.impl.expression.StringFun.Lower;
import com.samskivert.depot.impl.expression.StringFun.Position;
import com.samskivert.depot.impl.expression.StringFun.Substring;
import com.samskivert.depot.impl.expression.StringFun.Trim;
import com.samskivert.depot.impl.expression.StringFun.Upper;
import com.samskivert.depot.impl.operator.BinaryOperator;
import com.samskivert.depot.impl.operator.Exists;
import com.samskivert.depot.impl.operator.In;
import com.samskivert.depot.impl.operator.IsNull;
import com.samskivert.depot.impl.operator.MultiOperator;
import com.samskivert.depot.impl.operator.Not;
import com.samskivert.util.ArrayUtil;
import com.samskivert.util.Tuple;
import static com.samskivert.depot.Log.log;
/**
* Attempts to compute the actual values different SQL constructs would yield if they were
* actually send to the database to operate on rows, rather than on in-memory data objects.
*
* TODO: Many of the classes in com.samskivert.depot.functions.* have excellent implementations
* TODO: that should be written.
*/
public class ExpressionEvaluator
implements FragmentVisitor<Object>
{
public <T extends PersistentRecord> ExpressionEvaluator (Class<T> pClass, T pRec)
{
_pClass = pClass;
_pRec = pRec;
}
public Object visit (MultiOperator multiOperator)
{
SQLExpression[] operands = multiOperator.getArgs();
Object[] values = new Object[operands.length];
for (int ii = 0; ii < operands.length; ii ++) {
values[ii] = operands[ii].accept(this);
if (values[ii] instanceof NoValue) {
return values[ii];
}
}
return multiOperator.evaluate(values);
}
public Object visit (BinaryOperator binaryOperator)
{
Object left = binaryOperator.getLeftHandSide().accept(this);
Object right = binaryOperator.getRightHandSide().accept(this);
if (left instanceof NoValue) {
return left;
}
if (right instanceof NoValue) {
return right;
}
return binaryOperator.evaluate(left, right);
}
public Object visit (IsNull isNull)
{
Object operand = isNull.getExpression().accept(this);
return (operand instanceof NoValue) ? operand : operand != null;
}
public Object visit (In in)
{
Object operand = in.getExpression().accept(this);
return (operand instanceof NoValue) ? operand :
-1 != ArrayUtil.indexOf(in.getValues(), operand);
}
public Object visit (FullText.Match match)
{
return new NoValue("Full Text Match not implemented");
}
public Object visit (FullText.Rank rank)
{
return new NoValue("Full Text Match not implemented");
}
public Object visit (Case caseExp)
{
for (Tuple<SQLExpression, SQLExpression> exp : caseExp.getWhenExps()) {
Object result = exp.left.accept(this);
if (result instanceof NoValue || !(result instanceof Boolean)) {
return new NoValue("Failed to evaluate case: " + exp.left + " -> " + result);
}
if (((Boolean) result).booleanValue()) {
return exp.right.accept(this);
}
}
SQLExpression elseExp = caseExp.getElseExp();
if (elseExp != null) {
return elseExp.accept(this);
}
return null;
}
public Object visit (ColumnExp columnExp)
{
Class<? extends PersistentRecord> pClass = columnExp.getPersistentClass();
if (pClass != _pClass) {
// TODO: Accept Class -> Record mapping
return new NoValue("Column lookup on unknown persistent class: " + pClass);
}
try {
Field field = pClass.getField(columnExp.name);
if (field == null) {
log.warning("Couldn't locate field on class", "field", columnExp.name,
"class", pClass);
return new NoValue("Internal Error");
}
return field.get(_pRec);
} catch (Exception e) {
log.warning("Failed to retrieve field value", "field", columnExp.name, e);
return new NoValue("Internal Error");
}
}
public Object visit (Not not)
{
Object result = not.getCondition().accept(this);
if (result instanceof NoValue) {
return result;
}
if (result instanceof Boolean) {
return !((Boolean) result).booleanValue();
}
return new NoValue("Boolean negation of non-boolean value: " + result);
}
public Object visit (LiteralExp literalExp)
{
return new NoValue("Cannot evaluate LiteralExp: " + literalExp);
}
public Object visit (ValueExp valueExp)
{
return valueExp.getValue();
}
public Object visit (IntervalExp interval)
{
return new NoValue("Cannot evaluate IntervalExp: " + interval);
}
public Object visit (WhereClause where)
{
Object result = where.getWhereExpression().accept(this);
if (result instanceof NoValue || result instanceof Boolean) {
return result;
}
return new NoValue("Non-boolean result from Where expression: " + result);
}
public Object visit (Key.Expression key)
{
Class<? extends PersistentRecord> pClass = key.getPersistentClass();
if (pClass != _pClass) {
// TODO: Accept Class -> Record mapping
return new NoValue("Column lookup on unknown persistent class: " + pClass);
}
ColumnExp[] keyFields = DepotUtil.getKeyFields(pClass);
Comparable<?>[] values = key.getValues();
for (int ii = 0; ii < keyFields.length; ii ++) {
Object value;
try {
value = pClass.getDeclaredField(keyFields[ii].name).get(_pRec);
} catch (Exception e) {
log.warning("Failed to retrieve field value", "field", keyFields[ii], e);
return new NoValue("Internal Error");
}
if (value == null) {
if (values[ii] != null) {
return false;
}
} else if (!value.equals(values[ii])) {
return false;
}
}
return true;
}
public Object visit (Exists exists)
{
throw new IllegalArgumentException("Can't evaluate expression: " + exists);
}
public Object visit (GroupBy groupBy)
{
throw new IllegalArgumentException("Can't evaluate expression: " + groupBy);
}
public Object visit (ForUpdate forUpdate)
{
throw new IllegalArgumentException("Can't evaluate expression: " + forUpdate);
}
public Object visit (OrderBy orderBy)
{
throw new IllegalArgumentException("Can't evaluate expression: " + orderBy);
}
public Object visit (Join join)
{
throw new IllegalArgumentException("Can't evaluate expression: " + join);
}
public Object visit (Limit limit)
{
throw new IllegalArgumentException("Can't evaluate expression: " + limit);
}
public Object visit (FieldDefinition fieldOverride)
{
throw new IllegalArgumentException("Can't evaluate expression: " + fieldOverride);
}
public Object visit (FromOverride fromOverride)
{
throw new IllegalArgumentException("Can't evaluate expression: " + fromOverride);
}
public Object visit (SelectClause selectClause)
{
throw new IllegalArgumentException("Can't evaluate expression: " + selectClause);
}
public Object visit (UpdateClause updateClause)
{
throw new IllegalArgumentException("Can't evaluate expression: " + updateClause);
}
public Object visit (DeleteClause deleteClause)
{
throw new IllegalArgumentException("Can't evaluate expression: " + deleteClause);
}
public Object visit (InsertClause insertClause)
{
throw new IllegalArgumentException("Can't evaluate expression: " + insertClause);
}
public Object visit (CreateIndexClause createIndexClause)
{
throw new IllegalArgumentException("Can't evaluate expression: " + createIndexClause);
}
public Object visit (DropIndexClause dropIndexClause)
{
throw new IllegalArgumentException("Can't evaluate expression: " + dropIndexClause);
}
//
// NUMERICAL FUNCTIONS
public Void visit (Abs exp)
{
throw new IllegalArgumentException("Can't evaluate expression: " + exp);
}
public Void visit (Ceil exp)
{
throw new IllegalArgumentException("Can't evaluate expression: " + exp);
}
public Void visit (Exp exp)
{
throw new IllegalArgumentException("Can't evaluate expression: " + exp);
}
public Void visit (Floor exp)
{
throw new IllegalArgumentException("Can't evaluate expression: " + exp);
}
public Void visit (Ln exp)
{
throw new IllegalArgumentException("Can't evaluate expression: " + exp);
}
public Void visit (Log10 exp)
{
throw new IllegalArgumentException("Can't evaluate expression: " + exp);
}
public Void visit (Pi exp)
{
throw new IllegalArgumentException("Can't evaluate expression: " + exp);
}
public Void visit (Power exp)
{
throw new IllegalArgumentException("Can't evaluate expression: " + exp);
}
public Void visit (Random exp)
{
throw new IllegalArgumentException("Can't evaluate expression: " + exp);
}
public Void visit (Round exp)
{
throw new IllegalArgumentException("Can't evaluate expression: " + exp);
}
public Void visit (Sign exp)
{
throw new IllegalArgumentException("Can't evaluate expression: " + exp);
}
public Void visit (Sqrt exp)
{
throw new IllegalArgumentException("Can't evaluate expression: " + exp);
}
public Void visit (Trunc exp)
{
throw new IllegalArgumentException("Can't evaluate expression: " + exp);
}
//
// STRING FUNCTIONS
public Void visit (Length exp)
{
throw new IllegalArgumentException("Can't evaluate expression: " + exp);
}
public Void visit (Lower exp)
{
throw new IllegalArgumentException("Can't evaluate expression: " + exp);
}
public Void visit (Position exp)
{
throw new IllegalArgumentException("Can't evaluate expression: " + exp);
}
public Void visit (Substring exp)
{
throw new IllegalArgumentException("Can't evaluate expression: " + exp);
}
public Void visit (Trim exp)
{
throw new IllegalArgumentException("Can't evaluate expression: " + exp);
}
public Void visit (Upper exp)
{
throw new IllegalArgumentException("Can't evaluate expression: " + exp);
}
public Void visit (DatePart exp)
{
throw new IllegalArgumentException("Can't evaluate expression: " + exp);
}
public Void visit (DateTruncate exp)
{
throw new IllegalArgumentException("Can't evaluate expression: " + exp);
}
public Void visit (Now exp)
{
throw new IllegalArgumentException("Can't evaluate expression: " + exp);
}
public Void visit (Average exp)
{
throw new IllegalArgumentException("Can't evaluate expression: " + exp);
}
public Void visit (Count exp)
{
throw new IllegalArgumentException("Can't evaluate expression: " + exp);
}
public Void visit (Every exp)
{
throw new IllegalArgumentException("Can't evaluate expression: " + exp);
}
public Void visit (Max exp)
{
throw new IllegalArgumentException("Can't evaluate expression: " + exp);
}
public Void visit (Min exp)
{
throw new IllegalArgumentException("Can't evaluate expression: " + exp);
}
public Void visit (Sum exp)
{
throw new IllegalArgumentException("Can't evaluate expression: " + exp);
}
//
// CONDITIONAL FUNCTIONS
public Void visit (Coalesce exp)
{
throw new IllegalArgumentException("Can't evaluate expression: " + exp);
}
public Void visit (Greatest exp)
{
throw new IllegalArgumentException("Can't evaluate expression: " + exp);
}
public Void visit (Least exp)
{
throw new IllegalArgumentException("Can't evaluate expression: " + exp);
}
public static Double numerical (Object o)
{
return (o instanceof Number) ? ((Number) o).doubleValue() : null;
}
public static Long integral (Object o)
{
return ((o instanceof Integer) || (o instanceof Long)) ? ((Number) o).longValue() : null;
}
protected Class<? extends PersistentRecord> _pClass;
protected PersistentRecord _pRec;
}
@@ -0,0 +1,740 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.impl;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import com.samskivert.depot.DatabaseException;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.Transformer;
import com.samskivert.depot.annotation.Column;
import com.samskivert.depot.annotation.Computed;
import com.samskivert.depot.annotation.GeneratedValue;
import com.samskivert.depot.annotation.Transform;
import com.samskivert.jdbc.ColumnDefinition;
import com.samskivert.util.ByteEnum;
import com.samskivert.util.ByteEnumUtil;
import com.samskivert.util.Logger;
import com.samskivert.util.StringUtil;
/**
* Handles the marshalling and unmarshalling of a particular field of a persistent object.
*
* @see DepotMarshaller
*/
public abstract class FieldMarshaller<T>
{
/** Used by the {@link SQLBuilder} implementations. We factor this into an interface to avoid a
* bunch of instanceof casts and to ensure that if a new supported type is added, all of the
* builders will fail to compile instead of failing at runtime. */
public static interface ColumnTyper {
String getBooleanType (int length);
String getByteType (int length);
String getShortType (int length);
String getIntType (int length);
String getLongType (int length);
String getFloatType (int length);
String getDoubleType (int length);
String getStringType (int length);
String getDateType (int length);
String getTimeType (int length);
String getTimestampType (int length);
String getBlobType (int length);
String getClobType (int length);
}
/**
* Creates and returns a field marshaller for the specified field. Throws an exception if the
* field in question cannot be marshalled.
*/
public static FieldMarshaller<?> createMarshaller (Field field)
{
// first, look for a @Transform annotation on the field (cheap)
Transform xform = field.getAnnotation(Transform.class);
if (xform == null) {
// next look for a marshaller for the basic type (cheapish)
FieldMarshaller<?> marshaller = createMarshaller(field.getType());
if (marshaller != null) {
marshaller.create(field);
return marshaller;
}
// finally look for an @Transform annotation on the field type (expensive)
xform = findTransformAnnotation(field.getType());
if (xform == null) {
throw new IllegalArgumentException("Cannot marshall " + field + ".");
}
}
try {
@SuppressWarnings("unchecked") Transformer<?,?> xformer = xform.value().newInstance();
@SuppressWarnings("unchecked") TransformingMarshaller<?,?> xmarsh =
new TransformingMarshaller(xformer, field);
xmarsh.create(field);
return xmarsh;
} catch (InstantiationException e) {
throw new IllegalArgumentException(
Logger.format("Unable to create Transformer", "xclass", xform.value(),
"field", field), e);
} catch (IllegalAccessException e) {
throw new IllegalArgumentException(
Logger.format("Unable to create Transformer", "xclass", xform.value(),
"field", field), e);
}
}
/**
* Initializes this field marshaller with a SQL builder which it uses to construct its column
* definition according to the appropriate database dialect.
*/
public void init (SQLBuilder builder)
throws DatabaseException
{
_columnDefinition = builder.buildColumnDefinition(this);
}
/**
* Returns the {@link Field} handled by this marshaller.
*/
public Field getField ()
{
return _field;
}
/**
* Returns the Computed annotation on this field, if any.
*/
public Computed getComputed ()
{
return _computed;
}
/**
* Returns the GeneratedValue annotation on this field, if any.
*/
public GeneratedValue getGeneratedValue ()
{
return _generatedValue;
}
/**
* Returns the name of the table column used to store this field.
*/
public String getColumnName ()
{
return _columnName;
}
/**
* Returns the SQL used to define this field's column.
*/
public ColumnDefinition getColumnDefinition ()
{
return _columnDefinition;
}
/**
* Returns the appropriate column type for this field, given the database specific typer
* supplied as an argument.
*/
public abstract String getColumnType (ColumnTyper typer, int length);
/**
* Reads this field from the given persistent object.
*/
public abstract T getFromObject (Object po)
throws IllegalArgumentException, IllegalAccessException;
/**
* 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 the value of our field from the supplied persistent object and sets that value into
* the specified column of the supplied prepared statement.
*/
public void getAndWriteToStatement (PreparedStatement ps, int column, Object po)
throws SQLException, IllegalAccessException
{
writeToStatement(ps, column, getFromObject(po));
}
/**
* Reads and returns this field from the result set.
*/
public abstract T getFromSet (ResultSet rs)
throws SQLException;
/**
* Writes the given value to the given persistent value.
*/
public abstract void writeToObject (Object po, T value)
throws IllegalArgumentException, IllegalAccessException;
/**
* Reads the specified column from the supplied result set and writes it to the appropriate
* field of the persistent object.
*/
public void getAndWriteToObject (ResultSet rset, Object po)
throws SQLException, IllegalAccessException
{
writeToObject(po, getFromSet(rset));
}
protected void create (Field field)
{
_field = field;
_columnName = field.getName();
_computed = field.getAnnotation(Computed.class);
Column column = _field.getAnnotation(Column.class);
if (column == null) {
// look for a column annotation on the shadowed field, if any
Computed dcomputed = (_computed == null) ?
field.getDeclaringClass().getAnnotation(Computed.class) : _computed;
if (dcomputed != null) {
Class<? extends PersistentRecord> sclass = dcomputed.shadowOf();
if (!PersistentRecord.class.equals(sclass)) {
try {
column = sclass.getField(field.getName()).getAnnotation(Column.class);
} catch (NoSuchFieldException e) {
// no problem; assume that it will be defined in the query
}
}
}
}
if (column != null) {
if (!StringUtil.isBlank(column.name())) {
_columnName = column.name();
}
}
if (_computed != null) {
return;
}
// figure out how we're going to generate our primary key values
_generatedValue = field.getAnnotation(GeneratedValue.class);
}
protected static FieldMarshaller<?> createMarshaller (Class<?> ftype)
{
// primitive types
if (ftype.equals(Boolean.TYPE)) {
return new BooleanMarshaller();
} else if (ftype.equals(Byte.TYPE)) {
return new ByteMarshaller();
} else if (ftype.equals(Short.TYPE)) {
return new ShortMarshaller();
} else if (ftype.equals(Integer.TYPE)) {
return new IntMarshaller();
} else if (ftype.equals(Long.TYPE)) {
return new LongMarshaller();
} else if (ftype.equals(Float.TYPE)) {
return new FloatMarshaller();
} else if (ftype.equals(Double.TYPE)) {
return new DoubleMarshaller();
// boxed primitive types
} else if (ftype.equals(Boolean.class)) {
return new ObjectMarshaller() {
@Override public String getColumnType (ColumnTyper typer, int length) {
return typer.getBooleanType(length);
}
};
} else if (ftype.equals(Byte.class)) {
return new ObjectMarshaller() {
@Override public String getColumnType (ColumnTyper typer, int length) {
return typer.getByteType(length);
}
@Override public Object getFromSet (ResultSet rs)
throws SQLException {
// work around the fact that HSQLDB (at least) returns Integer rather than
// Byte for TINYINT columns
Object value = super.getFromSet(rs);
return (value == null) ? null : ((Number)value).byteValue();
}
};
} else if (ftype.equals(Short.class)) {
return new ObjectMarshaller() {
@Override public String getColumnType (ColumnTyper typer, int length) {
return typer.getShortType(length);
}
@Override public Object getFromSet (ResultSet rs)
throws SQLException {
// work around the fact that HSQLDB (at least) returns Integer rather than
// Short for SMALLINT columns
Object value = super.getFromSet(rs);
return (value == null) ? null : ((Number)value).shortValue();
}
};
} else if (ftype.equals(Integer.class)) {
return new ObjectMarshaller() {
@Override public String getColumnType (ColumnTyper typer, int length) {
return typer.getIntType(length);
}
};
} else if (ftype.equals(Long.class)) {
return new ObjectMarshaller() {
@Override public String getColumnType (ColumnTyper typer, int length) {
return typer.getLongType(length);
}
};
} else if (ftype.equals(Float.class)) {
return new ObjectMarshaller() {
@Override public String getColumnType (ColumnTyper typer, int length) {
return typer.getFloatType(length);
}
@Override public Object getFromSet (ResultSet rs)
throws SQLException {
// work around the fact that HSQLDB (at least) returns Double rather than
// Float for REAL columns
Object value = super.getFromSet(rs);
return (value == null) ? null : ((Number)value).floatValue();
}
};
} else if (ftype.equals(Double.class)) {
return new ObjectMarshaller() {
@Override public String getColumnType (ColumnTyper typer, int length) {
return typer.getDoubleType(length);
}
};
} else if (ftype.equals(String.class)) {
return new ObjectMarshaller() {
@Override public String getColumnType (ColumnTyper typer, int length) {
return typer.getStringType(length);
}
};
// some primitive array types
} else if (ftype.equals(byte[].class)) {
return new ByteArrayMarshaller();
} else if (ftype.equals(int[].class)) {
return new IntArrayMarshaller();
// SQL types
} else if (ftype.equals(Date.class)) {
return new ObjectMarshaller() {
@Override public String getColumnType (ColumnTyper typer, int length) {
return typer.getDateType(length);
}
};
} else if (ftype.equals(Time.class)) {
return new ObjectMarshaller() {
@Override public String getColumnType (ColumnTyper typer, int length) {
return typer.getTimeType(length);
}
};
} else if (ftype.equals(Timestamp.class)) {
return new ObjectMarshaller() {
@Override public String getColumnType (ColumnTyper typer, int length) {
return typer.getTimestampType(length);
}
};
} else if (ftype.equals(Blob.class)) {
return new ObjectMarshaller() {
@Override public String getColumnType (ColumnTyper typer, int length) {
return typer.getBlobType(length);
}
};
} else if (ftype.equals(Clob.class)) {
return new ObjectMarshaller() {
@Override public String getColumnType (ColumnTyper typer, int length) {
return typer.getClobType(length);
}
};
// special Enum type hackery (it would be nice to handle this with @Transform, but that
// introduces some undesirable samskivert-Depot dependencies)
} else if (ByteEnum.class.isAssignableFrom(ftype)) {
@SuppressWarnings("unchecked") ByteEnumMarshaller<?> bem =
new ByteEnumMarshaller(ftype);
return bem;
} else {
return null;
}
}
protected static Class<?> getTransformerType (Transformer<?, ?> xformer, String which)
{
Class<?> ttype = null;
for (Method method : xformer.getClass().getDeclaredMethods()) {
if (method.getName().equals(which + "Persistent")) {
if (ttype == null || ttype.isAssignableFrom(method.getReturnType())) {
ttype = method.getReturnType();
}
}
}
if (ttype == null) {
throw new IllegalArgumentException(
Logger.format("Transformer lacks " + which + "Persistent() method!?",
"xclass", xformer.getClass()));
}
return ttype;
}
protected static Transform findTransformAnnotation (Class<?> ftype)
{
Transform xform = ftype.getAnnotation(Transform.class);
if (xform != null) {
return xform;
}
for (Class<?> iface : ftype.getInterfaces()) {
xform = iface.getAnnotation(Transform.class);
if (xform != null) {
return xform;
}
}
Class<?> parent = ftype.getSuperclass();
return (parent == null) ? null : findTransformAnnotation(parent);
}
protected static class BooleanMarshaller extends FieldMarshaller<Boolean> {
@Override public String getColumnType (ColumnTyper typer, int length) {
return typer.getBooleanType(length);
}
@Override public Boolean getFromObject (Object po)
throws IllegalArgumentException, IllegalAccessException {
return _field.getBoolean(po);
}
@Override public Boolean getFromSet (ResultSet rs)
throws SQLException {
return rs.getBoolean(getColumnName());
}
@Override public void writeToObject (Object po, Boolean value)
throws IllegalArgumentException, IllegalAccessException {
_field.setBoolean(po, value);
}
@Override public void writeToStatement (PreparedStatement ps, int column, Boolean value)
throws SQLException {
ps.setBoolean(column, value);
}
}
protected static class ByteMarshaller extends FieldMarshaller<Byte> {
@Override public String getColumnType (ColumnTyper typer, int length) {
return typer.getByteType(length);
}
@Override public Byte getFromObject (Object po)
throws IllegalArgumentException, IllegalAccessException {
return _field.getByte(po);
}
@Override public Byte getFromSet (ResultSet rs)
throws SQLException {
return rs.getByte(getColumnName());
}
@Override public void writeToObject (Object po, Byte value)
throws IllegalArgumentException, IllegalAccessException {
_field.setByte(po, value);
}
@Override public void writeToStatement (PreparedStatement ps, int column, Byte value)
throws SQLException {
ps.setByte(column, value);
}
}
protected static class ShortMarshaller extends FieldMarshaller<Short> {
@Override public String getColumnType (ColumnTyper typer, int length) {
return typer.getShortType(length);
}
@Override public Short getFromObject (Object po)
throws IllegalArgumentException, IllegalAccessException {
return _field.getShort(po);
}
@Override public Short getFromSet (ResultSet rs)
throws SQLException {
return rs.getShort(getColumnName());
}
@Override public void writeToObject (Object po, Short value)
throws IllegalArgumentException, IllegalAccessException {
_field.setShort(po, value);
}
@Override public void writeToStatement (PreparedStatement ps, int column, Short value)
throws SQLException {
ps.setShort(column, value);
}
}
protected static class IntMarshaller extends FieldMarshaller<Integer> {
@Override public String getColumnType (ColumnTyper typer, int length) {
return typer.getIntType(length);
}
@Override public Integer getFromObject (Object po)
throws IllegalArgumentException, IllegalAccessException {
return _field.getInt(po);
}
@Override public Integer getFromSet (ResultSet rs)
throws SQLException {
return rs.getInt(getColumnName());
}
@Override public void writeToObject (Object po, Integer value)
throws IllegalArgumentException, IllegalAccessException {
_field.setInt(po, value);
}
@Override public void writeToStatement (PreparedStatement ps, int column, Integer value)
throws SQLException {
ps.setInt(column, value);
}
}
protected static class LongMarshaller extends FieldMarshaller<Long> {
@Override public String getColumnType (ColumnTyper typer, int length) {
return typer.getLongType(length);
}
@Override public Long getFromObject (Object po)
throws IllegalArgumentException, IllegalAccessException {
return _field.getLong(po);
}
@Override public Long getFromSet (ResultSet rs)
throws SQLException {
return rs.getLong(getColumnName());
}
@Override public void writeToObject (Object po, Long value)
throws IllegalArgumentException, IllegalAccessException {
_field.setLong(po, value);
}
@Override public void writeToStatement (PreparedStatement ps, int column, Long value)
throws SQLException {
ps.setLong(column, value);
}
}
protected static class FloatMarshaller extends FieldMarshaller<Float> {
@Override public String getColumnType (ColumnTyper typer, int length) {
return typer.getFloatType(length);
}
@Override public Float getFromObject (Object po)
throws IllegalArgumentException, IllegalAccessException {
return _field.getFloat(po);
}
@Override public Float getFromSet (ResultSet rs)
throws SQLException {
return rs.getFloat(getColumnName());
}
@Override public void writeToObject (Object po, Float value)
throws IllegalArgumentException, IllegalAccessException {
_field.setFloat(po, value);
}
@Override public void writeToStatement (PreparedStatement ps, int column, Float value)
throws SQLException {
ps.setFloat(column, value);
}
}
protected static class DoubleMarshaller extends FieldMarshaller<Double> {
@Override public String getColumnType (ColumnTyper typer, int length) {
return typer.getDoubleType(length);
}
@Override public Double getFromObject (Object po)
throws IllegalArgumentException, IllegalAccessException {
return _field.getDouble(po);
}
@Override public Double getFromSet (ResultSet rs)
throws SQLException {
return rs.getDouble(getColumnName());
}
@Override public void writeToObject (Object po, Double value)
throws IllegalArgumentException, IllegalAccessException {
_field.setDouble(po, value);
}
@Override public void writeToStatement (PreparedStatement ps, int column, Double value)
throws SQLException {
ps.setDouble(column, value);
}
}
protected static abstract class ObjectMarshaller extends FieldMarshaller<Object> {
@Override public Object getFromObject (Object po)
throws IllegalArgumentException, IllegalAccessException {
return _field.get(po);
}
@Override public Object getFromSet (ResultSet rs)
throws SQLException {
return rs.getObject(getColumnName());
}
@Override public void writeToObject (Object po, Object value)
throws IllegalArgumentException, IllegalAccessException {
_field.set(po, value);
}
@Override public void writeToStatement (PreparedStatement ps, int column, Object value)
throws SQLException {
ps.setObject(column, value);
}
}
protected static class ByteArrayMarshaller extends FieldMarshaller<byte[]> {
@Override public String getColumnType (ColumnTyper typer, int length) {
return typer.getBlobType(length);
}
@Override public byte[] getFromObject (Object po)
throws IllegalArgumentException, IllegalAccessException {
return (byte[]) _field.get(po);
}
@Override public byte[] getFromSet (ResultSet rs)
throws SQLException {
return rs.getBytes(getColumnName());
}
@Override public void writeToObject (Object po, byte[] value)
throws IllegalArgumentException, IllegalAccessException {
_field.set(po, value);
}
@Override public void writeToStatement (PreparedStatement ps, int column, byte[] value)
throws SQLException {
ps.setBytes(column, value);
}
}
protected static class IntArrayMarshaller extends FieldMarshaller<byte[]> {
@Override public String getColumnType (ColumnTyper typer, int length) {
return typer.getBlobType(length*4);
}
@Override public byte[] getFromObject (Object po)
throws IllegalArgumentException, IllegalAccessException {
int[] values = (int[]) _field.get(po);
if (values == null) {
return null;
}
ByteBuffer bbuf = ByteBuffer.allocate(values.length * 4);
bbuf.asIntBuffer().put(values);
return bbuf.array();
}
@Override public byte[] getFromSet (ResultSet rs)
throws SQLException {
return (byte[]) rs.getObject(getColumnName());
}
@Override public void writeToObject (Object po, byte[] data)
throws IllegalArgumentException, IllegalAccessException {
int[] value = null;
if (data != null) {
value = new int[data.length/4];
ByteBuffer.wrap(data).asIntBuffer().get(value);
}
_field.set(po, value);
}
@Override public void writeToStatement (PreparedStatement ps, int column, byte[] value)
throws SQLException {
ps.setObject(column, value);
}
}
protected static class ByteEnumMarshaller<E extends Enum<E> & ByteEnum>
extends FieldMarshaller<ByteEnum> {
public ByteEnumMarshaller (Class<E> clazz) {
_eclass = clazz;
}
@Override public void create (Field field) {
super.create(field);
// do some sanity checking so that the unsafe business we do below is safer
if (!Enum.class.isAssignableFrom(_eclass)) {
throw new IllegalArgumentException(
"ByteEnum not implemented by real Enum: " + field);
}
}
@Override public String getColumnType (ColumnTyper typer, int length) {
return typer.getByteType(length);
}
@Override public ByteEnum getFromObject (Object po)
throws IllegalArgumentException, IllegalAccessException {
return (ByteEnum) _field.get(po);
}
@Override public ByteEnum getFromSet (ResultSet rs) throws SQLException {
return ByteEnumUtil.fromByte(_eclass, rs.getByte(getColumnName()));
}
@Override public void writeToObject (Object po, ByteEnum value)
throws IllegalArgumentException, IllegalAccessException {
_field.set(po, value);
}
@Override public void writeToStatement (PreparedStatement ps, int column, ByteEnum value)
throws SQLException {
ps.setByte(column, value.toByte());
}
protected Class<E> _eclass;
}
protected static class TransformingMarshaller<F,T> extends FieldMarshaller<T> {
public TransformingMarshaller (Transformer<F, T> xformer, Field field) {
Class<?> pojoType = getTransformerType(xformer, "from");
if (!pojoType.isAssignableFrom(field.getType())) {
throw new IllegalArgumentException(
"@Transform error on " + field.getType().getName() + "." +
field.getName() + ": " + xformer.getClass().getName() + " cannot convert " +
field.getType().getName());
}
@SuppressWarnings("unchecked") FieldMarshaller<T> delegate =
(FieldMarshaller<T>)createMarshaller(getTransformerType(xformer, "to"));
_delegate = delegate;
_xformer = xformer;
}
@Override public void create (Field field) {
super.create(field);
_delegate.create(field);
}
@Override public String getColumnType (ColumnTyper typer, int length) {
return _delegate.getColumnType(typer, length);
}
@Override public T getFromObject (Object po)
throws IllegalArgumentException, IllegalAccessException {
@SuppressWarnings("unchecked") F value = (F)_field.get(po);
return _xformer.toPersistent(value);
}
@Override public T getFromSet (ResultSet rs) throws SQLException {
return _delegate.getFromSet(rs);
}
@Override public void writeToObject (Object po, T value)
throws IllegalArgumentException, IllegalAccessException {
_field.set(po, _xformer.fromPersistent(_field.getGenericType(), value));
}
@Override public void writeToStatement (PreparedStatement ps, int column, T value)
throws SQLException {
_delegate.writeToStatement(ps, column, value);
}
protected Transformer<F, T> _xformer;
protected FieldMarshaller<T> _delegate;
}
protected Field _field;
protected String _columnName;
protected ColumnDefinition _columnDefinition;
protected Computed _computed;
protected GeneratedValue _generatedValue;
}
@@ -0,0 +1,97 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.impl;
import java.util.ArrayList;
import java.util.List;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.depot.DatabaseException;
import com.samskivert.depot.Key;
import com.samskivert.depot.PersistenceContext;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.Stats;
import com.samskivert.depot.clause.QueryClause;
import com.samskivert.depot.clause.SelectClause;
import static com.samskivert.depot.Log.log;
/**
* Loads all primary keys for the records matching the supplied clause.
*/
public class FindAllKeysQuery<T extends PersistentRecord> extends Query<List<Key<T>>>
{
public FindAllKeysQuery (PersistenceContext ctx, Class<T> type, boolean forUpdate,
Iterable<? extends QueryClause> clauses)
throws DatabaseException
{
_forUpdate = forUpdate;
_marsh = ctx.getMarshaller(type);
_select = new SelectClause(type, _marsh.getPrimaryKeyFields(), clauses);
_builder = ctx.getSQLBuilder(DepotTypes.getDepotTypes(ctx, _select));
_builder.newQuery(_select);
}
@Override // from Query
public boolean isReadOnly ()
{
return !_forUpdate;
}
@Override // from Query
public List<Key<T>> getCachedResult (PersistenceContext ctx)
{
return null; // TODO
}
// from Query
public List<Key<T>> invoke (PersistenceContext ctx, Connection conn, DatabaseLiaison liaison)
throws SQLException
{
List<Key<T>> keys = new ArrayList<Key<T>>();
PreparedStatement stmt = _builder.prepare(conn);
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
keys.add(_marsh.makePrimaryKey(rs));
}
// TODO: cache this result?
if (PersistenceContext.CACHE_DEBUG) {
log.info("Loaded " + _marsh.getTableName(), "count", keys.size());
}
return keys;
}
// from Query
public void updateStats (Stats stats)
{
stats.noteQuery(0, 1, 0, 0, 0); // one uncached query
}
protected boolean _forUpdate;
protected SQLBuilder _builder;
protected DepotMarshaller<T> _marsh;
protected SelectClause _select;
}
@@ -0,0 +1,384 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.impl;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.collect.Maps;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.depot.CacheAdapter.CacheCategory;
import com.samskivert.depot.DatabaseException;
import com.samskivert.depot.DepotRepository.CacheStrategy;
import com.samskivert.depot.DepotRepository;
import com.samskivert.depot.Key;
import com.samskivert.depot.KeySet;
import com.samskivert.depot.PersistenceContext;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.Stats;
import com.samskivert.depot.clause.FieldOverride;
import com.samskivert.depot.clause.QueryClause;
import com.samskivert.depot.clause.SelectClause;
import com.samskivert.depot.impl.operator.In;
import static com.samskivert.depot.Log.log;
/**
* 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<T extends PersistentRecord> extends Query<List<T>>
{
/**
* The two-pass collection query implementation. See {@link DepotRepository#findAll} for
* details.
*/
public static class WithCache<T extends PersistentRecord> extends FindAllQuery<T>
{
public WithCache (PersistenceContext ctx, Class<T> type,
Iterable<? extends QueryClause> clauses, CacheStrategy strategy)
throws DatabaseException
{
super(ctx, type);
if (_marsh.getComputed() != null) {
throw new IllegalArgumentException(
"This algorithm doesn't work on @Computed records.");
}
for (QueryClause clause : clauses) {
if (clause instanceof FieldOverride) {
throw new IllegalArgumentException(
"This algorithm doesn't work with FieldOverrides.");
}
}
_select = new SelectClause(_type, _marsh.getPrimaryKeyFields(), clauses);
switch(strategy) {
case SHORT_KEYS: case LONG_KEYS:
_qkey = new SimpleCacheKey(_marsh.getTableName() + "Keys", _select.toString());
_category = (strategy == CacheStrategy.SHORT_KEYS) ?
CacheCategory.SHORT_KEYSET : CacheCategory.LONG_KEYSET;
break;
case RECORDS:
_qkey = null;
break;
default:
throw new IllegalArgumentException("Unexpected cache strategy: " + strategy);
}
}
@Override // from Query
public List<T> getCachedResult (PersistenceContext ctx)
{
if (_qkey == null) {
return null;
}
_keys = ctx.<KeySet<T>>cacheLookup(_qkey);
if (_keys == null) {
return null;
}
_cachedQueries++;
_fetchKeys = loadFromCache(ctx, _keys, _entities);
return (_fetchKeys.size() == 0) ? resolve(_keys, _entities) : null;
}
// from Query
public List<T> invoke (PersistenceContext ctx, Connection conn, DatabaseLiaison liaison)
throws SQLException
{
// we want this to remain null if our key set came from the cache
String stmtString = null;
// if we didn't find our key set in the cache, load the keys that match
if (_keys == null) {
List<Key<T>> keys = Lists.newArrayList();
SQLBuilder builder = ctx.getSQLBuilder(DepotTypes.getDepotTypes(ctx, _select));
builder.newQuery(_select);
PreparedStatement stmt = builder.prepare(conn);
stmtString = stmt.toString(); // for debugging
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
keys.add(_marsh.makePrimaryKey(rs));
}
_keys = KeySet.newKeySet(_type, keys);
_uncachedQueries++;
if (PersistenceContext.CACHE_DEBUG) {
log.info("Loaded " + _marsh.getTableName() + " keys", "query", _select,
"keys", keysToString(_keys), "cached", (_qkey != null));
}
if (_qkey != null) {
// cache the resulting key set
ctx.cacheStore(_category, _qkey, _keys);
}
// and fetch any records we can from the cache
_fetchKeys = loadFromCache(ctx, _keys, _entities);
}
// finally load the rest from the database
return loadAndResolve(ctx, conn, _keys, _fetchKeys, _entities, stmtString);
}
protected SimpleCacheKey _qkey;
protected CacheCategory _category;
protected SelectClause _select;
protected KeySet<T> _keys;
protected Set<Key<T>> _fetchKeys;
protected Map<Key<T>, T> _entities = Maps.newHashMap();
}
/**
* The two-pass collection query implementation. See {@link DepotRepository#findAll} for
* details.
*/
public static class WithKeys<T extends PersistentRecord> extends FindAllQuery<T>
{
public WithKeys (PersistenceContext ctx, Iterable<Key<T>> keys)
throws DatabaseException
{
super(ctx, keys.iterator().next().getPersistentClass());
_keys = keys;
}
@Override // from Query
public List<T> getCachedResult (PersistenceContext ctx)
{
// look up what we can from the cache
_fetchKeys = loadFromCache(ctx, _keys, _entities);
// if we found everything, we can just return our result straight away, yay!
return _fetchKeys.isEmpty() ? resolve(_keys, _entities) : null;
}
// from Query
public List<T> invoke (PersistenceContext ctx, Connection conn, DatabaseLiaison liaison)
throws SQLException
{
return loadAndResolve(ctx, conn, _keys, _fetchKeys, _entities, null);
}
protected Iterable<Key<T>> _keys;
protected Set<Key<T>> _fetchKeys;
protected Map<Key<T>, T> _entities = Maps.newHashMap();
}
/**
* The single-pass collection query implementation. See {@link DepotRepository#findAll} for
* details.
*/
public static class Explicitly<T extends PersistentRecord> extends FindAllQuery<T>
{
public Explicitly (PersistenceContext ctx, Class<T> type,
Iterable<? extends QueryClause> clauses, boolean cachedContents)
throws DatabaseException
{
super(ctx, type);
_select = new SelectClause(type, _marsh.getFieldNames(), clauses);
if (cachedContents) {
_qkey = new SimpleCacheKey(_marsh.getTableName() + "Contents", _select.toString());
} else {
_qkey = null;
}
}
@Override // from Query
public List<T> getCachedResult (PersistenceContext ctx)
{
if (_qkey != null) {
_cachedQueries++;
return ctx.cacheLookup(_qkey);
}
return null;
}
// from Query
public List<T> invoke (PersistenceContext ctx, Connection conn, DatabaseLiaison liaison)
throws SQLException
{
List<T> result = new ArrayList<T>();
SQLBuilder builder = ctx.getSQLBuilder(DepotTypes.getDepotTypes(ctx, _select));
builder.newQuery(_select);
ResultSet rs = builder.prepare(conn).executeQuery();
while (rs.next()) {
result.add(_marsh.createObject(rs));
}
_explicitQueries++;
if (PersistenceContext.CACHE_DEBUG) {
log.info("Loaded " + _marsh.getTableName(), "query", _select, "rows",
result.size(), "cacheKey", _qkey);
}
if (_qkey != null) {
ctx.cacheStore(CacheCategory.RESULT, _qkey, result); // cache the entire result set
}
_uncachedRecords += result.size();
return result;
}
protected SelectClause _select;
protected SimpleCacheKey _qkey;
}
// from Query
public void updateStats (Stats stats)
{
stats.noteQuery(_cachedQueries, _uncachedQueries, _explicitQueries,
_cachedRecords, _uncachedRecords);
}
protected FindAllQuery (PersistenceContext ctx, Class<T> type)
throws DatabaseException
{
_type = type;
_marsh = ctx.getMarshaller(type);
}
protected Set<Key<T>> loadFromCache (PersistenceContext ctx, Iterable<Key<T>> allKeys,
Map<Key<T>, T> entities)
{
Set<Key<T>> fetchKeys = Sets.newHashSet();
for (Key<T> key : allKeys) {
T value = ctx.<T>cacheLookup(new KeyCacheKey(key));
if (value != null) {
@SuppressWarnings("unchecked") T newValue = (T) value.clone();
entities.put(key, newValue);
continue;
}
fetchKeys.add(key);
}
if (PersistenceContext.CACHE_DEBUG) {
log.info("Loaded from cache " + _marsh.getTableName(), "count", entities.size());
}
_cachedRecords = entities.size();
_uncachedRecords = fetchKeys.size();
return fetchKeys;
}
protected List<T> resolve (Iterable<Key<T>> allKeys, Map<Key<T>, T> entities)
{
List<T> result = new ArrayList<T>();
for (Key<T> key : allKeys) {
T value = entities.get(key);
if (value != null) {
result.add(value);
}
}
return result;
}
protected List<T> loadAndResolve (PersistenceContext ctx, Connection conn,
Iterable<Key<T>> allKeys, Set<Key<T>> fetchKeys,
Map<Key<T>, T> entities, String origStmt)
throws SQLException
{
if (PersistenceContext.CACHE_DEBUG && fetchKeys.size() > 0) {
log.info("Loading " + _marsh.getTableName(), "keys", keysToString(fetchKeys));
}
// if we're fetching a huge number of records, we have to do it in multiple queries
if (fetchKeys.size() > In.MAX_KEYS) {
int keyCount = fetchKeys.size();
do {
Set<Key<T>> keys = Sets.newHashSet();
Iterator<Key<T>> iter = fetchKeys.iterator();
for (int ii = 0; ii < Math.min(keyCount, In.MAX_KEYS); ii++) {
keys.add(iter.next());
iter.remove();
}
keyCount -= keys.size();
loadRecords(ctx, conn, keys, entities, origStmt);
} while (keyCount > 0);
} else if (fetchKeys.size() > 0) {
loadRecords(ctx, conn, fetchKeys, entities, origStmt);
}
return resolve(allKeys, entities);
}
protected void loadRecords (PersistenceContext ctx, Connection conn, Set<Key<T>> keys,
Map<Key<T>, T> entities, String origStmt)
throws SQLException
{
SelectClause select = new SelectClause(
_type, _marsh.getFieldNames(), (QueryClause) KeySet.newKeySet(_type, keys));
SQLBuilder builder = ctx.getSQLBuilder(DepotTypes.getDepotTypes(ctx, select));
builder.newQuery(select);
Set<Key<T>> got = Sets.newHashSet();
ResultSet rs = builder.prepare(conn).executeQuery();
int cnt = 0, dups = 0;
while (rs.next()) {
T obj = _marsh.createObject(rs);
Key<T> key = _marsh.getPrimaryKey(obj);
if (entities.put(key, obj) != null) {
dups++;
}
ctx.cacheStore(CacheCategory.RECORD, new KeyCacheKey(key), obj.clone());
got.add(key);
cnt++;
}
// if we get more results than we planned, or if we're doing a two-phase query and got
// fewer, then complain
if (cnt > keys.size() || (origStmt != null && cnt < keys.size())) {
log.warning("Row count mismatch in second pass", "origQuery", origStmt,
// we need toString() here or StringUtil will get smart and dump our
// KeySet using its iterator which results in verbosity
"wanted", KeySet.newKeySet(_type, keys).toString(), "got", KeySet.newKeySet(
_type, got).toString(), "dups", dups, new Exception());
}
if (PersistenceContext.CACHE_DEBUG) {
log.info("Cached " + _marsh.getTableName(), "count", cnt);
}
}
protected String keysToString (Iterable<Key<T>> keySet)
{
StringBuilder builder = new StringBuilder("(");
for (Key<T> key : keySet) {
if (builder.length() > 1) {
builder.append(", ");
}
key.toShortString(builder);
}
return builder.append(")").toString();
}
protected Class<T> _type;
protected DepotMarshaller<T> _marsh;
protected int _cachedQueries, _uncachedQueries, _explicitQueries;
protected int _cachedRecords, _uncachedRecords;
}
@@ -0,0 +1,132 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.impl;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.depot.CacheKey;
import com.samskivert.depot.DatabaseException;
import com.samskivert.depot.DepotRepository;
import com.samskivert.depot.PersistenceContext;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.Stats;
import com.samskivert.depot.CacheAdapter.CacheCategory;
import com.samskivert.depot.clause.QueryClause;
import com.samskivert.depot.clause.SelectClause;
import com.samskivert.depot.clause.WhereClause;
import static com.samskivert.depot.Log.log;
/**
* The implementation of {@link DepotRepository#load} functionality.
*/
public class FindOneQuery<T extends PersistentRecord> extends Query<T>
{
public FindOneQuery (PersistenceContext ctx, Class<T> type,
DepotRepository.CacheStrategy strategy, QueryClause[] clauses)
throws DatabaseException
{
_strategy = strategy;
_marsh = ctx.getMarshaller(type);
_select = new SelectClause(type, _marsh.getFieldNames(), clauses);
WhereClause where = _select.getWhereClause();
if (where != null) {
_select.getWhereClause().validateQueryType(type); // sanity check
}
_builder = ctx.getSQLBuilder(DepotTypes.getDepotTypes(ctx, _select));
_builder.newQuery(_select);
}
@Override // from Query
public T getCachedResult (PersistenceContext ctx)
{
CacheKey key = getCacheKey();
if (key == null) {
return null;
}
T value = ctx.<T>cacheLookup(key);
if (value == null) {
return null;
}
_cachedRecords = 1;
// 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;
}
// from Query
public T invoke (PersistenceContext ctx, Connection conn, DatabaseLiaison liaison)
throws SQLException
{
// load up the record in question
T result = null;
ResultSet rs = _builder.prepare(conn).executeQuery();
if (rs.next()) {
result = _marsh.createObject(rs);
}
// TODO: if (rs.next()) issue warning?
rs.close();
// potentially cache the result
CacheKey key = getCacheKey();
if (key == null) {
// no row-specific cache key was given, if we can, create a key from the record
if (result != null && _marsh.hasPrimaryKey()) {
key = new KeyCacheKey(_marsh.getPrimaryKey(result));
}
}
if (PersistenceContext.CACHE_DEBUG) {
log.info("Loaded " + (key != null ? key : _marsh.getTableName()));
}
if (key != null) {
ctx.cacheStore(CacheCategory.RECORD, key, (result != null) ? result.clone() : null);
if (PersistenceContext.CACHE_DEBUG) {
log.info("Cached " + key);
}
}
return result;
}
// from Operation
public void updateStats (Stats stats)
{
stats.noteQuery(0, 0, 0, _cachedRecords, 1-_cachedRecords);
}
protected CacheKey getCacheKey ()
{
if (_strategy == DepotRepository.CacheStrategy.NONE) {
return null;
}
WhereClause where = _select.getWhereClause();
return (where != null && where instanceof CacheKey) ? (CacheKey)where : null;
}
protected DepotRepository.CacheStrategy _strategy;
protected DepotMarshaller<T> _marsh;
protected SelectClause _select;
protected SQLBuilder _builder;
protected int _cachedRecords;
}
@@ -0,0 +1,159 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.impl;
import com.samskivert.depot.Key;
import com.samskivert.depot.clause.FieldDefinition;
import com.samskivert.depot.clause.ForUpdate;
import com.samskivert.depot.clause.FromOverride;
import com.samskivert.depot.clause.GroupBy;
import com.samskivert.depot.clause.InsertClause;
import com.samskivert.depot.clause.Join;
import com.samskivert.depot.clause.Limit;
import com.samskivert.depot.clause.OrderBy;
import com.samskivert.depot.clause.SelectClause;
import com.samskivert.depot.clause.WhereClause;
import com.samskivert.depot.expression.*;
import com.samskivert.depot.operator.Case;
import com.samskivert.depot.operator.FullText;
import com.samskivert.depot.impl.clause.CreateIndexClause;
import com.samskivert.depot.impl.clause.DeleteClause;
import com.samskivert.depot.impl.clause.DropIndexClause;
import com.samskivert.depot.impl.clause.UpdateClause;
import com.samskivert.depot.impl.expression.IntervalExp;
import com.samskivert.depot.impl.expression.LiteralExp;
import com.samskivert.depot.impl.expression.ValueExp;
import com.samskivert.depot.impl.expression.AggregateFun.Average;
import com.samskivert.depot.impl.expression.AggregateFun.Count;
import com.samskivert.depot.impl.expression.AggregateFun.Every;
import com.samskivert.depot.impl.expression.AggregateFun.Max;
import com.samskivert.depot.impl.expression.AggregateFun.Min;
import com.samskivert.depot.impl.expression.AggregateFun.Sum;
import com.samskivert.depot.impl.expression.ConditionalFun.Coalesce;
import com.samskivert.depot.impl.expression.ConditionalFun.Greatest;
import com.samskivert.depot.impl.expression.ConditionalFun.Least;
import com.samskivert.depot.impl.expression.DateFun.DatePart;
import com.samskivert.depot.impl.expression.DateFun.DateTruncate;
import com.samskivert.depot.impl.expression.DateFun.Now;
import com.samskivert.depot.impl.expression.NumericalFun.Abs;
import com.samskivert.depot.impl.expression.NumericalFun.Ceil;
import com.samskivert.depot.impl.expression.NumericalFun.Exp;
import com.samskivert.depot.impl.expression.NumericalFun.Floor;
import com.samskivert.depot.impl.expression.NumericalFun.Ln;
import com.samskivert.depot.impl.expression.NumericalFun.Log10;
import com.samskivert.depot.impl.expression.NumericalFun.Pi;
import com.samskivert.depot.impl.expression.NumericalFun.Power;
import com.samskivert.depot.impl.expression.NumericalFun.Random;
import com.samskivert.depot.impl.expression.NumericalFun.Round;
import com.samskivert.depot.impl.expression.NumericalFun.Sign;
import com.samskivert.depot.impl.expression.NumericalFun.Sqrt;
import com.samskivert.depot.impl.expression.NumericalFun.Trunc;
import com.samskivert.depot.impl.expression.StringFun.Length;
import com.samskivert.depot.impl.expression.StringFun.Lower;
import com.samskivert.depot.impl.expression.StringFun.Position;
import com.samskivert.depot.impl.expression.StringFun.Substring;
import com.samskivert.depot.impl.expression.StringFun.Trim;
import com.samskivert.depot.impl.expression.StringFun.Upper;
import com.samskivert.depot.impl.operator.BinaryOperator;
import com.samskivert.depot.impl.operator.Exists;
import com.samskivert.depot.impl.operator.In;
import com.samskivert.depot.impl.operator.IsNull;
import com.samskivert.depot.impl.operator.MultiOperator;
import com.samskivert.depot.impl.operator.Not;
/**
* Enumerates visitation methods for every possible SQL expression type.
*/
public interface FragmentVisitor<T>
{
public T visit (FieldDefinition fieldOverride);
public T visit (FromOverride fromOverride);
public T visit (MultiOperator multiOperator);
public T visit (BinaryOperator binaryOperator);
public T visit (IsNull isNull);
public T visit (In in);
public T visit (FullText.Match match);
public T visit (FullText.Rank match);
public T visit (ColumnExp columnExp);
public T visit (Not not);
public T visit (GroupBy groupBy);
public T visit (ForUpdate forUpdate);
public T visit (OrderBy orderBy);
public T visit (Join join);
public T visit (Limit limit);
public T visit (LiteralExp literal);
public T visit (ValueExp value);
public T visit (IntervalExp interval);
public T visit (WhereClause where);
public T visit (Key.Expression key);
public T visit (Exists exists);
public T visit (SelectClause selectClause);
public T visit (UpdateClause updateClause);
public T visit (DeleteClause deleteClause);
public T visit (InsertClause insertClause);
public T visit (CreateIndexClause createIndexClause);
public T visit (DropIndexClause dropIndexClause);
public T visit (Case caseExp);
// Numerical
public T visit (Abs exp);
public T visit (Ceil exp);
public T visit (Exp exp);
public T visit (Floor exp);
public T visit (Ln exp);
public T visit (Log10 exp);
public T visit (Pi exp);
public T visit (Power exp);
public T visit (Random exp);
public T visit (Round exp);
public T visit (Sign exp);
public T visit (Sqrt exp);
public T visit (Trunc exp);
// String
public T visit (Length exp);
public T visit (Lower exp);
public T visit (Position exp);
public T visit (Substring exp);
public T visit (Trim exp);
public T visit (Upper exp);
// Date
public T visit (DatePart exp);
public T visit (DateTruncate exp);
public T visit (Now exp);
// Aggregation
public T visit (Average exp);
public T visit (Count exp);
public T visit (Every exp);
public T visit (Max exp);
public T visit (Min exp);
public T visit (Sum exp);
// Conditional
public T visit (Coalesce exp);
public T visit (Greatest exp);
public T visit (Least exp);
}
@@ -0,0 +1,291 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.impl;
import static com.samskivert.Log.log;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Set;
import com.google.common.collect.Lists;
import com.samskivert.jdbc.ColumnDefinition;
import com.samskivert.util.ArrayUtil;
import com.samskivert.util.Tuple;
import com.samskivert.depot.Ops;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.annotation.FullTextIndex;
import com.samskivert.depot.annotation.GeneratedValue;
import com.samskivert.depot.clause.OrderBy.Order;
import com.samskivert.depot.expression.*;
import com.samskivert.depot.operator.FullText;
import com.samskivert.depot.impl.clause.CreateIndexClause;
import com.samskivert.depot.impl.expression.DateFun.DatePart.Part;
import com.samskivert.depot.impl.expression.DateFun.DatePart;
import com.samskivert.depot.impl.expression.DateFun.DateTruncate;
import com.samskivert.depot.impl.expression.StringFun.Lower;
import com.samskivert.depot.impl.operator.BitAnd;
import com.samskivert.depot.impl.operator.BitOr;
import com.samskivert.depot.impl.operator.Like;
import com.samskivert.depot.impl.operator.MultiOperator;
public class HSQLBuilder
extends SQLBuilder
{
public class HBuildVisitor extends BuildVisitor
{
@Override public Void visit (FullText.Match match)
{
// HSQL doesn't have real full text search, so we fake it by creating a condition like
// (lower(COL1) like '%foo%') OR (lower(COL1) like '%bar%') OR ...
// (lower(COL2) like '%foo%') OR (lower(COL2) like '%bar%') OR ...
// ... and so on. Not efficient, but basically functional.
Class<? extends PersistentRecord> pClass = match.getDefinition().getPersistentClass();
// find the fields involved
String[] fields = _types.getMarshaller(pClass).
getFullTextIndex(match.getDefinition().getName()).fields();
// explode the query into words
String[] ftsWords = match.getDefinition().getQuery().toLowerCase().split("\\W+");
if (ftsWords.length > 0 && ftsWords[0].length() == 0) {
// if the query led with whitespace, the first 'word' will be empty; strip it
ftsWords = ArrayUtil.splice(ftsWords, 0, 1);
}
// now iterate over the cartesian product of the query words & the fields
List<SQLExpression> bits = Lists.newArrayList();
for (String field : fields) {
for (String ftsWord : ftsWords) {
// build comparisons between each word and column
bits.add(new Like(new Lower(
new ColumnExp(pClass, field)), "%"+ftsWord+"%", true));
}
}
// then just OR them all together and we have our query
_ftsCondition = Ops.or(bits);
_ftsCondition.accept(this);
return null;
}
@Override public Void visit (FullText.Rank rank)
{
// not implemented for HSQL
_builder.append("0");
return null;
}
@Override
public Void visit (MultiOperator operator)
{
// HSQL doesn't handle & and | operators
if (operator instanceof BitAnd) {
return appendFunctionCall("bitand", operator.getArgs());
}
if (operator instanceof BitOr) {
return appendFunctionCall("bitor", operator.getArgs());
}
return super.visit(operator);
}
@Override public Void visit (DatePart exp) {
if (exp.getPart() == Part.EPOCH) {
_builder.append("datediff('ss', ");
exp.getArg().accept(this);
_builder.append(", '1970-01-01')");
return null;
}
return appendFunctionCall(getDateFunction(exp.getPart()), exp.getArg());
}
@Override
public Void visit (DateTruncate exp)
{
throw new IllegalArgumentException("HSQL does not have built-in date truncation");
}
@Override
public Void visit (CreateIndexClause createIndexClause)
{
for (Tuple<SQLExpression, Order> field : createIndexClause.getFields()) {
if (!(field.left instanceof ColumnExp)) {
log.warning("This database can't handle complex indexes. Aborting creation.",
"ixName", createIndexClause.getName());
return null;
}
}
super.visit(createIndexClause);
return null;
}
protected HBuildVisitor (DepotTypes types)
{
super(types);
}
protected String getDateFunction (Part part)
{
switch(part) {
case DAY_OF_MONTH:
return "dayofmonth";
case DAY_OF_WEEK:
return "dayofweek";
case DAY_OF_YEAR:
return "dayofyear";
case HOUR:
return "hour";
case MINUTE:
return "minute";
case MONTH:
return "month";
case SECOND:
return "second";
case WEEK:
return "week";
case YEAR:
return "year";
case EPOCH:
// internal error, EPOCH is handled in the calling function, fall through to error
}
throw new IllegalArgumentException("Unknown date part: " + part);
}
@Override protected void appendIdentifier (String field) {
_builder.append("\"").append(field).append("\"");
}
}
public HSQLBuilder (DepotTypes types)
{
super(types);
}
@Override
public void getFtsIndexes (
Iterable<String> columns, Iterable<String> indexes, Set<String> target)
{
// do nothing
}
@Override
public <T extends PersistentRecord> boolean addFullTextSearch (
Connection conn, DepotMarshaller<T> marshaller, FullTextIndex fts)
throws SQLException
{
// nothing to do for HSQL
return true;
}
@Override
public boolean isPrivateColumn (String column)
{
// The HSQLDB builder does not yet have any private columns.
return false;
}
@Override
protected String getBooleanDefault ()
{
return "false";
}
@Override
protected BuildVisitor getBuildVisitor ()
{
return new HBuildVisitor(_types);
}
@Override
protected void maybeMutateForGeneratedValue (GeneratedValue genValue, ColumnDefinition column)
{
// HSQL's IDENTITY() implementation does not take the form of a type, as MySQL's
// and PostgreSQL's conveniently shared SERIAL alias, nor as MySQL's original
// AUTO_INCREMENT modifier -- but as a default value, which admittedly makes sense
switch (genValue.strategy()) {
case AUTO:
case IDENTITY:
column.defaultValue = "IDENTITY";
column.unique = true;
break;
default:
super.maybeMutateForGeneratedValue(genValue, column);
}
}
@Override
protected <T> String getColumnType (FieldMarshaller<?> fm, int length)
{
return fm.getColumnType(TYPER, length);
}
/** Holds the Full Text Seach condition between build and bind phases. */
protected SQLExpression _ftsCondition;
protected static final FieldMarshaller.ColumnTyper TYPER = new FieldMarshaller.ColumnTyper() {
public String getBooleanType (int length) {
return "BOOLEAN";
}
public String getByteType (int length) {
return "TINYINT";
}
public String getShortType (int length) {
return "SMALLINT";
}
public String getIntType (int length) {
return "INTEGER";
}
public String getLongType (int length) {
return "BIGINT";
}
public String getFloatType (int length) {
return "REAL";
}
public String getDoubleType (int length) {
return "DOUBLE PRECISION";
}
public String getStringType (int length) {
return "VARCHAR(" + length + ")";
}
public String getDateType (int length) {
return "DATE";
}
public String getTimeType (int length) {
return "TIME";
}
public String getTimestampType (int length) {
return "TIMESTAMP";
}
public String getBlobType (int length) {
return "VARBINARY(" + length + ")";
}
public String getClobType (int length) {
return "VARCHAR";
}
};
}
@@ -0,0 +1,65 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.impl;
import java.sql.Connection;
import java.sql.SQLException;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.depot.annotation.GeneratedValue;
/**
* Generates primary keys using an identity column.
*/
public class IdentityValueGenerator extends ValueGenerator
{
public IdentityValueGenerator (GeneratedValue gv, DepotMarshaller<?> dm, FieldMarshaller<?> fm)
{
super(gv, dm, fm);
}
@Override // from ValueGenerator
public boolean isPostFactum ()
{
return true;
}
@Override // from ValueGenerator
public void create (Connection conn, DatabaseLiaison liaison)
throws SQLException
{
liaison.createGenerator(conn, _dm.getTableName(), _fm.getColumnName(), _initialValue);
}
@Override // from ValueGenerator
public int nextGeneratedValue (Connection conn, DatabaseLiaison liaison)
throws SQLException
{
return liaison.lastInsertedId(conn, _dm.getTableName(), _fm.getColumnName());
}
@Override // from ValueGenerator
public void delete (Connection conn, DatabaseLiaison liaison)
throws SQLException
{
liaison.deleteGenerator(conn, _dm.getTableName(), _fm.getColumnName());
}
}
@@ -0,0 +1,94 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2009 Michael Bayne and 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.depot.impl;
import java.io.Serializable;
import java.util.Arrays;
import com.samskivert.depot.CacheKey;
import com.samskivert.depot.Key;
/**
* Converts a {@link CacheKey} to and from a {@link Key} in a way that eliminates references to
* non-Java classes (so that we don't have to deal with RMI classloader hell when replicating cache
* contents).
*/
public class KeyCacheKey
implements CacheKey, Serializable
{
public KeyCacheKey (Key<?> key)
{
_cacheId = key.getPersistentClass().getName();
Comparable<?>[] values = key.getValues();
_values = new Comparable<?>[values.length];
for (int ii = 0; ii < _values.length; ii++) {
_values[ii] = values[ii]; // TODO: check for non-system-class and serialize
}
}
// from CacheKey
public String getCacheId ()
{
return _cacheId;
}
// from CacheKey
public Serializable getCacheKey ()
{
return this;
}
@Override // from Object
public boolean equals (Object obj)
{
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
return Arrays.equals(_values, ((KeyCacheKey) obj)._values);
}
@Override // from Object
public int hashCode ()
{
return Arrays.hashCode(_values);
}
@Override // from Object
public String toString ()
{
StringBuilder builder = new StringBuilder(_cacheId);
builder.append("(");
for (int ii = 0; ii < _values.length; ii++) {
if (ii > 0) {
builder.append(", ");
}
builder.append(_values[ii]);
}
builder.append(")");
return builder.toString();
}
protected String _cacheId;
protected Comparable<?>[] _values;
}
@@ -0,0 +1,156 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.impl;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import com.samskivert.depot.CacheAdapter;
import com.samskivert.depot.CacheInvalidator;
import com.samskivert.depot.CacheKey;
import com.samskivert.depot.Key;
import com.samskivert.depot.PersistenceContext;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.Stats;
import com.samskivert.jdbc.DatabaseLiaison;
/**
* Encapsulates a modification of persistent objects.
*/
public abstract class Modifier implements Operation<Integer>
{
/**
* A simple modifier that executes a single SQL statement. No cache flushing is done as a
* result of this operation.
*/
public abstract static class Simple extends Modifier
{
public Simple () {
super(null);
}
@Override // from Modifier
protected int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
Statement stmt = conn.createStatement();
try {
return stmt.executeUpdate(createQuery(liaison));
} finally {
stmt.close();
}
}
protected abstract String createQuery (DatabaseLiaison liaison);
}
/**
* A convenience modifier that can perform cache updates in addition to invalidation:
* - Before {@link #invoke}, the {@link CacheInvalidator} is run, if given.
* - After {@link #invoke}, the cache is updated with the modified object,
* presuming both _key and _result are non-null. These variables may be set or modified
* during execution in addition to being supplied to the constructor.
*/
public static abstract class CachingModifier<T extends PersistentRecord> extends Modifier
{
/**
* Construct a new CachingModifier with the given result, cache key, and invalidator,
* all of which are optional, and may also be set during execution.
*/
protected CachingModifier (T result, Key<T> key, CacheInvalidator invalidator)
{
super(invalidator);
_result = result;
_key = (key == null) ? null : new KeyCacheKey(key);
}
/**
* Update this {@link CachingModifier}'s cache key, e.g. during insertion when a persistent
* object first receives a generated key.
*/
protected void updateKey (Key<T> key)
{
if (key != null) {
_key = new KeyCacheKey(key);
}
}
@Override // from Modifier
public Integer invoke (PersistenceContext ctx, Connection conn, DatabaseLiaison liaison)
throws SQLException
{
Integer rows = super.invoke(ctx, conn, liaison);
// if we have both a key and a record, cache
if (_key != null && _result != null) {
ctx.cacheStore(CacheAdapter.CacheCategory.RECORD, _key, _result.clone());
}
return rows;
}
protected CacheKey _key;
protected T _result;
}
/**
* Constructs a {@link Modifier} without a cache invalidator.
*/
public Modifier ()
{
this(null);
}
/**
* Constructs a {@link Modifier} with the given cache invalidator.
*/
public Modifier (CacheInvalidator invalidator)
{
_invalidator = invalidator;
}
// from interface Operation
public boolean isReadOnly ()
{
return false;
}
// from interface Operation
public Integer invoke (PersistenceContext ctx, Connection conn, DatabaseLiaison liaison)
throws SQLException
{
if (_invalidator != null) {
_invalidator.invalidate(ctx);
}
return invoke(conn, liaison);
}
// from interface Operation
public void updateStats (Stats stats)
{
// nothing to update for modifiers
}
/**
* Overriden to perform the actual database modifications represented by this object; should
* return the number of modified rows.
*/
protected abstract int invoke (Connection conn, DatabaseLiaison liaison) throws SQLException;
protected CacheInvalidator _invalidator;
}
@@ -0,0 +1,313 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.impl;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Set;
import com.samskivert.util.Tuple;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.annotation.FullTextIndex;
import com.samskivert.depot.clause.OrderBy.Order;
import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.operator.FullText;
import com.samskivert.depot.impl.clause.CreateIndexClause;
import com.samskivert.depot.impl.clause.DeleteClause;
import com.samskivert.depot.impl.clause.DropIndexClause;
import com.samskivert.depot.impl.expression.DateFun.DatePart;
import com.samskivert.depot.impl.expression.DateFun.DateTruncate;
import com.samskivert.depot.impl.expression.DateFun.DatePart.Part;
import com.samskivert.depot.impl.expression.NumericalFun.Trunc;
import static com.samskivert.Log.log;
public class MySQLBuilder
extends SQLBuilder
{
public class MSBuildVisitor extends BuildVisitor
{
@Override public Void visit (FullText.Match match)
{
renderMatch(match.getDefinition());
return null;
}
@Override public Void visit (FullText.Rank rank)
{
renderMatch(rank.getDefinition());
return null;
}
@Override public Void visit (DeleteClause deleteClause)
{
_builder.append("delete from ");
appendTableName(deleteClause.getPersistentClass());
_builder.append(" ");
// MySQL can't do DELETE FROM SomeTable AS T1, so we turn off abbreviations briefly.
boolean savedFlag = _types.getUseTableAbbreviations();
_types.setUseTableAbbreviations(false);
try {
deleteClause.getWhereClause().accept(this);
} finally {
_types.setUseTableAbbreviations(savedFlag);
}
return null;
}
@Override public Void visit (Trunc exp)
{
return appendFunctionCall("truncate", exp.getArg());
}
@Override public Void visit (DatePart exp) {
return appendFunctionCall(getDateFunction(exp.getPart()), exp.getArg());
}
@Override
public Void visit (DateTruncate exp)
{
// exp.getTruncation() is currently always DAY
_builder.append(" cast(");
appendFunctionCall("date", exp.getArg());
_builder.append(" as datetime)");
return null;
}
protected String getDateFunction (Part part)
{
switch(part) {
case DAY_OF_MONTH:
return "dayofmonth";
case DAY_OF_WEEK:
return "dayofweek";
case DAY_OF_YEAR:
return "dayofyear";
case HOUR:
return "hour";
case MINUTE:
return "minute";
case MONTH:
return "month";
case SECOND:
return "second";
case WEEK:
return "week";
case YEAR:
return "year";
case EPOCH:
return "unix_timestamp";
}
throw new IllegalArgumentException("Unknown date part: " + part);
}
@Override
public Void visit (CreateIndexClause createIndexClause)
{
for (Tuple<SQLExpression, Order> field : createIndexClause.getFields()) {
if (!(field.left instanceof ColumnExp)) {
log.warning("This database can't handle complex indexes. Aborting creation.",
"ixName", createIndexClause.getName());
return null;
}
}
super.visit(createIndexClause);
return null;
}
@Override
public Void visit (DropIndexClause dropIndexClause)
{
// MySQL's indexes are scoped on the table, not on the database, and the
// SQL syntax reflects it: DROP INDEX fooIx on fooTable
_builder.append("drop index ");
appendIdentifier(dropIndexClause.getName());
_builder.append(" on ");
appendTableName(dropIndexClause.getPersistentClass());
return null;
}
protected MSBuildVisitor (DepotTypes types)
{
super(types);
}
@Override protected void appendTableName (Class<? extends PersistentRecord> type)
{
_builder.append(_types.getTableName(type));
}
@Override protected void appendTableAbbreviation (Class<? extends PersistentRecord> type)
{
_builder.append(_types.getTableAbbreviation(type));
}
@Override protected void appendIdentifier (String field)
{
_builder.append(field);
}
@Override
protected void appendIndexComponent (SQLExpression expression)
{
// MySQL is never given complex expressions and hates parens, so just recurse
expression.accept(this);
}
protected void renderMatch (FullText fullText)
{
_builder.append("match(");
Class<? extends PersistentRecord> pClass = fullText.getPersistentClass();
String[] fields = _types.getMarshaller(pClass).getFullTextIndex(
fullText.getName()).fields();
for (int ii = 0; ii < fields.length; ii ++) {
if (ii > 0) {
_builder.append(", ");
}
new ColumnExp(pClass, fields[ii]).accept(this);
}
_builder.append(") against (");
bindValue(fullText.getQuery());
_builder.append(" in boolean mode)");
}
}
public MySQLBuilder (DepotTypes types)
{
super(types);
}
@Override
public void getFtsIndexes (
Iterable<String> columns, Iterable<String> indexes, Set<String> target)
{
for (String index : indexes) {
if (index.startsWith("ftsIx_")) {
target.add(index.substring("ftsIx_".length()));
}
}
}
@Override
public <T extends PersistentRecord> boolean addFullTextSearch (
Connection conn, DepotMarshaller<T> marshaller, FullTextIndex fts)
throws SQLException
{
Class<T> pClass = marshaller.getPersistentClass();
StringBuilder update = new StringBuilder("ALTER TABLE ").
append(marshaller.getTableName()).append(" ADD FULLTEXT INDEX ftsIx_").
append(fts.name()).append(" (");
String[] fields = fts.fields();
for (int ii = 0; ii < fields.length; ii ++) {
if (ii > 0) {
update.append(", ");
}
update.append(_types.getColumnName(pClass, fields[ii]));
}
update.append(")");
log.info("Adding full-text search index: ftsIx_" + fts.name());
conn.createStatement().executeUpdate(update.toString());
return true;
}
@Override
public boolean isPrivateColumn (String column)
{
// The MySQL builder does not yet have any private columns.
return false;
}
@Override
protected String getBooleanDefault ()
{
return "0";
}
@Override
protected BuildVisitor getBuildVisitor ()
{
return new MSBuildVisitor(_types);
}
@Override
protected <T> String getColumnType (FieldMarshaller<?> fm, int length)
{
return fm.getColumnType(TYPER, length);
}
protected static final FieldMarshaller.ColumnTyper TYPER = new FieldMarshaller.ColumnTyper() {
public String getBooleanType (int length) {
return "TINYINT";
}
public String getByteType (int length) {
return "TINYINT";
}
public String getShortType (int length) {
return "SMALLINT";
}
public String getIntType (int length) {
return "INTEGER";
}
public String getLongType (int length) {
return "BIGINT";
}
public String getFloatType (int length) {
return "FLOAT";
}
public String getDoubleType (int length) {
return "DOUBLE";
}
public String getStringType (int length) {
return (length < (1 << 15)) ? "VARCHAR(" + length + ")" : "TEXT";
}
public String getDateType (int length) {
return "DATE";
}
public String getTimeType (int length) {
return "DATETIME";
}
public String getTimestampType (int length) {
return "DATETIME";
}
public String getBlobType (int length) {
// semi-arbitrarily use VARBINARY() up to 32767
if (length < (1 << 15)) {
return "VARBINARY(" + length + ")";
}
// use BLOB to 65535
if (length < (1 << 16)) {
return "BLOB";
}
if (length < (1 << 24)) {
return "MEDIUMBLOB";
}
return "LONGBLOB";
}
public String getClobType (int length) {
return "TEXT";
}
};
}
@@ -0,0 +1,53 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.impl;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import com.samskivert.depot.PersistenceContext;
import com.samskivert.depot.Stats;
import com.samskivert.jdbc.DatabaseLiaison;
/**
* An abstraction that encompasses both {@link Query} and {@link Modifier} operations.
*/
public interface Operation<T>
{
/**
* Indicates whether or not this operation is safe to invoke on a database mirror.
*/
public boolean isReadOnly ();
/**
* Performs the actual JDBC interactions associated with this operation. Any {@link Statement}
* instances created with the given connection will be closed automatically after this method
* returns. The operation need not close them itself.
*/
public T invoke (PersistenceContext ctx, Connection conn, DatabaseLiaison liaison)
throws SQLException;
/**
* Called after the operation has been invoked so that it can update our runtime statistics.
*/
public void updateStats (Stats stats);
}
@@ -0,0 +1,104 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.impl;
import java.sql.Array;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import com.samskivert.depot.DatabaseException;
import com.samskivert.depot.impl.expression.ValueExp;
import com.samskivert.depot.impl.operator.In;
import com.samskivert.util.ByteEnum;
/**
* Specializes our PostgreSQL builder for JDBC4.
*/
public class PostgreSQL4Builder extends PostgreSQLBuilder
{
public class PG4BuildVisitor extends PGBuildVisitor
{
@Override public Void visit (In in) {
// if the In() expression is empty, replace it with a 'false'
final Comparable<?>[] values = in.getValues();
if (values.length == 0) {
new ValueExp(false).accept(this);
return null;
}
in.getExpression().accept(this);
_builder.append(" = any (?)");
_bindables.add(new Bindable() {
public void doBind (Connection conn, PreparedStatement stmt, int argIdx)
throws Exception
{
stmt.setObject(argIdx, createArray(conn, values));
}
protected Array createArray (Connection conn, Object[] values)
throws SQLException
{
String type;
Object testValue = values[0];
if (testValue instanceof Integer) {
type = "integer";
} else if (testValue instanceof Long) {
type = "bigint";
} else if (testValue instanceof String) {
type = "varchar";
} else if (testValue instanceof Short || testValue instanceof Byte) {
type = "smallint";
} else if (testValue instanceof ByteEnum) {
Byte[] bytes = new Byte[values.length];
for (int ii = 0; ii < bytes.length; ii ++) {
bytes[ii] = ((ByteEnum) values[ii]).toByte();
}
values = bytes;
type = "smallint"; // tinyint is in the spec, but PG doesn't recognize?
} else if (testValue instanceof Timestamp) {
type = "timestamp";
} else {
throw new DatabaseException(
"Don't know how to make Postgres array for " + testValue.getClass());
}
return conn.createArrayOf(type, values);
}
});
return null;
}
protected PG4BuildVisitor (DepotTypes types)
{
super(types);
}
}
public PostgreSQL4Builder (DepotTypes types)
{
super(types);
}
@Override
protected BuildVisitor getBuildVisitor ()
{
return new PG4BuildVisitor(_types);
}
}
@@ -0,0 +1,344 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.impl;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Set;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.jdbc.LiaisonRegistry;
import com.samskivert.util.ArrayUtil;
import com.samskivert.util.StringUtil;
import com.samskivert.depot.Exps;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.annotation.FullTextIndex.Configuration;
import com.samskivert.depot.annotation.FullTextIndex;
import com.samskivert.depot.clause.InsertClause;
import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.impl.expression.IntervalExp;
import com.samskivert.depot.impl.expression.DateFun.DatePart;
import com.samskivert.depot.impl.expression.DateFun.DateTruncate;
import com.samskivert.depot.impl.expression.DateFun.DatePart.Part;
import com.samskivert.depot.operator.FullText;
import static com.samskivert.Log.log;
public class PostgreSQLBuilder
extends SQLBuilder
{
// Are we running with PostgreSQL 8.3's new FTS engine, or the old one?
// TODO: Rip out when no longer needed.
public final static boolean PG83 = Boolean.getBoolean("com.samskivert.depot.pg83");
public class PGBuildVisitor extends BuildVisitor
{
@Override public Void visit (IntervalExp interval) {
_builder.append("interval '").append(interval.amount);
_builder.append(" ").append(interval.unit).append("'");
return null;
}
@Override public Void visit (FullText.Match match) {
appendIdentifier("ftsCol_" + match.getDefinition().getName());
_builder.append(" @@ to_tsquery('").
append(translateFTConfig(getFTIndex(match.getDefinition()).configuration())).
append("', ");
bindValue(massageFTQuery(match.getDefinition()));
_builder.append(")");
return null;
}
@Override public Void visit (FullText.Rank rank) {
_builder.append(PG83 ? "ts_rank" : "rank").append("(");
appendIdentifier("ftsCol_" + rank.getDefinition().getName());
_builder.append(", to_tsquery('").
append(translateFTConfig(getFTIndex(rank.getDefinition()).configuration())).
// TODO: The normalization parameter is really quite important, and should
// TODO: perhaps be configurable, but for the moment we hard-code it to 1:
// TODO: "divides the rank by the 1 + logarithm of the document length"
append("', ");
bindValue(massageFTQuery(rank.getDefinition()));
_builder.append("), 1)");
return null;
}
@Override public Void visit (DatePart exp)
{
return appendFunctionCall(
"date_part", Exps.value(translateDatePart(exp.getPart())), exp.getArg());
}
@Override
public Void visit (DateTruncate exp)
{
String field = "'" + exp.getTruncation().toString().toLowerCase() + "'";
return appendFunctionCall("date_trunc", Exps.literal(field), exp.getArg());
}
protected String translateDatePart (Part part)
{
switch(part) {
case DAY_OF_MONTH:
return "day";
case DAY_OF_WEEK:
return "dow";
case DAY_OF_YEAR:
return "doy";
case HOUR:
return "hour";
case MINUTE:
return "minute";
case MONTH:
return "month";
case SECOND:
return "second";
case WEEK:
return "week";
case YEAR:
return "year";
case EPOCH:
return "epoch";
}
throw new IllegalArgumentException("Unknown date part: " + part);
}
protected FullTextIndex getFTIndex (FullText definition)
{
DepotMarshaller<?> marsh = _types.getMarshaller(definition.getPersistentClass());
return marsh.getFullTextIndex(definition.getName());
}
@Override protected void appendIdentifier (String field) {
_builder.append("\"").append(field).append("\"");
}
@Override protected void appendInsertColumns (InsertClause insertClause)
{
// see if we will be inserting any columns whatsoever
Class<? extends PersistentRecord> pClass = insertClause.getPersistentClass();
Set<String> idFields = insertClause.getIdentityFields();
for (ColumnExp field : _types.getMarshaller(pClass).getColumnFieldNames()) {
if (!idFields.contains(field.name)) {
// we found a field we're inserting, so call super and finish
super.appendInsertColumns(insertClause);
return;
}
}
// we never found anything we'll actually be inserting
_builder.append("default values");
}
protected PGBuildVisitor (DepotTypes types)
{
super(types);
}
}
public PostgreSQLBuilder (DepotTypes types)
{
super(types);
}
@Override
public void getFtsIndexes (
Iterable<String> columns, Iterable<String> indexes, Set<String> target)
{
for (String column : columns) {
if (column.startsWith("ftsCol_")) {
target.add(column.substring("ftsCol_".length()));
}
}
}
@Override
public <T extends PersistentRecord> boolean addFullTextSearch (
Connection conn, DepotMarshaller<T> marshaller, FullTextIndex fts)
throws SQLException
{
Class<T> pClass = marshaller.getPersistentClass();
DatabaseLiaison liaison = LiaisonRegistry.getLiaison(conn);
String[] fields = fts.fields();
String table = marshaller.getTableName();
String column = "ftsCol_" + fts.name();
String index = table + "_ftsIx_" + fts.name();
String trigger = table + "_ftsTrig_" + fts.name();
// build the UPDATE
StringBuilder initColumn = new StringBuilder("UPDATE ").
append(liaison.tableSQL(table)).append(" SET ").append(liaison.columnSQL(column)).
append(" = TO_TSVECTOR('").
append(translateFTConfig(fts.configuration())).
append("', ");
for (int ii = 0; ii < fields.length; ii ++) {
if (ii > 0) {
initColumn.append(" || ' ' || ");
}
initColumn.append("COALESCE(").
append(liaison.columnSQL(_types.getColumnName(pClass, fields[ii]))).
append(", '')");
}
initColumn.append(")");
String triggerFun = PG83 ? "tsvector_update_trigger" : "tsearch2";
// build the CREATE TRIGGER
StringBuilder createTrigger = new StringBuilder("CREATE TRIGGER ").
append(liaison.columnSQL(trigger)).append(" BEFORE UPDATE OR INSERT ON ").
append(liaison.tableSQL(table)).
append(" FOR EACH ROW EXECUTE PROCEDURE ").append(triggerFun).append("(").
append(liaison.columnSQL(column)).append(", ");
if (PG83) {
createTrigger.append("'").
append(translateFTConfig(fts.configuration())).
append("', ");
}
for (int ii = 0; ii < fields.length; ii ++) {
if (ii > 0) {
createTrigger.append(", ");
}
createTrigger.append(liaison.columnSQL(_types.getColumnName(pClass, fields[ii])));
}
createTrigger.append(")");
// build the CREATE INDEX
StringBuilder createIndex = new StringBuilder("CREATE INDEX ").
append(liaison.columnSQL(index)).append(" ON " ).append(liaison.tableSQL(table)).
append(" USING ").append(PG83 ? "GIN" : "GIST").append("(").
append(liaison.columnSQL(column)).append(")");
Statement stmt = conn.createStatement();
log.info("Adding full-text search column, index and trigger: " + column + ", " +
index + ", " + trigger);
liaison.addColumn(conn, table, column, "TSVECTOR", true);
stmt.executeUpdate(initColumn.toString());
stmt.executeUpdate(createIndex.toString());
stmt.executeUpdate(createTrigger.toString());
return true;
}
@Override
public boolean isPrivateColumn (String column)
{
// filter out any column that we created as part of FTS support
return column.startsWith("ftsCol_");
}
@Override
protected BuildVisitor getBuildVisitor ()
{
return new PGBuildVisitor(_types);
}
@Override
protected <T> String getColumnType (FieldMarshaller<?> fm, int length)
{
return fm.getColumnType(TYPER, length);
}
protected static String massageFTQuery (FullText match)
{
// The tsearch2 engine takes queries on the form
// (foo&bar)|goop
// so in this first simple implementation, we just take the user query, chop it into
// words by space/punctuation and 'or' those together like so:
// 'ho! who goes there?' -> 'ho|who|goes|there'
String[] searchTerms = match.getQuery().toLowerCase().split("\\W+");
if (searchTerms.length > 0 && searchTerms[0].length() == 0) {
searchTerms = ArrayUtil.splice(searchTerms, 0, 1);
}
return StringUtil.join(searchTerms, "|");
}
// Translate the mildly abstracted full-text parser/dictionary configuration support
// in FullText to actual PostgreSQL configuration identifiers.
protected static String translateFTConfig (Configuration configuration)
{
// legacy support
if (!PG83) {
return "default";
}
switch(configuration) {
case Simple:
return "pg_catalog.simple";
case English:
return "pg_catalog.english";
default:
throw new IllegalArgumentException(
"Unknown full text configuration: " + configuration);
}
}
protected static final FieldMarshaller.ColumnTyper TYPER = new FieldMarshaller.ColumnTyper() {
public String getBooleanType (int length) {
return "BOOLEAN";
}
public String getByteType (int length) {
return "SMALLINT";
}
public String getShortType (int length) {
return "SMALLINT";
}
public String getIntType (int length) {
return "INTEGER";
}
public String getLongType (int length) {
return "BIGINT";
}
public String getFloatType (int length) {
return "REAL";
}
public String getDoubleType (int length) {
return "DOUBLE PRECISION";
}
public String getByteArrayType (int length) {
return "BYTEA";
}
public String getIntArrayType (int length) {
return "BYTEA";
}
public String getStringType (int length) {
return "VARCHAR(" + length + ")";
}
public String getDateType (int length) {
return "DATE";
}
public String getTimeType (int length) {
return "TIME";
}
public String getTimestampType (int length) {
return "TIMESTAMP";
}
public String getBlobType (int length) {
return "BYTEA";
}
public String getClobType (int length) {
return "TEXT";
}
};
}
@@ -0,0 +1,52 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.impl;
import com.samskivert.depot.PersistenceContext;
/**
* The base of all read-only queries.
*/
public abstract class Query<T>
implements Operation<T>
{
/** A simple base class for non-complex queries. */
public static abstract class Trivial<T> extends Query<T>
{
@Override // from Query
public T getCachedResult (PersistenceContext ctx) {
return null;
}
}
/**
* If this query has a simple cached result, it should return the non-null result from this
* method. If null is returned, the query will be {@link #invoke}d to obtain its result from
* persistent storage.
*/
public abstract T getCachedResult (PersistenceContext ctx);
// from interface Operation
public boolean isReadOnly ()
{
return true;
}
}
@@ -0,0 +1,233 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.impl;
import java.lang.reflect.Field;
import java.util.Set;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import com.samskivert.depot.PersistenceContext;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.annotation.Column;
import com.samskivert.depot.annotation.FullTextIndex;
import com.samskivert.depot.annotation.GeneratedValue;
import com.samskivert.depot.clause.QueryClause;
import com.samskivert.jdbc.ColumnDefinition;
import com.samskivert.util.ByteEnum;
import static com.samskivert.depot.Log.log;
/**
* At the heart of Depot's SQL generation, this object constructs an {@link FragmentVisitor}
* object and executes it, constructing SQL and parameter bindings as it recurses.
*/
public abstract class SQLBuilder
{
public SQLBuilder (DepotTypes types)
{
_types = types;
}
/**
* Construct an entirely new SQL query relative to our configured {@link DepotTypes} data.
* This method may be called multiple times, each time beginning a new query, and will return
* true if SQL was generated. If so, a call to {@link #prepare(Connection)} should follow,
* which creates, configures and returns the actual {@link PreparedStatement} to execute.
*/
public boolean newQuery (QueryClause clause)
{
_clause = clause;
_buildVisitor = getBuildVisitor();
_clause.accept(_buildVisitor);
return _buildVisitor.getQuery().trim().length() > 0;
}
/**
* After {@link #newQuery(QueryClause)} has been executed, this method is run to recurse
* through the {@link QueryClause} structure, setting the {@link PreparedStatement} arguments
* that were defined in the generated SQL.
*
* This method throws {@link SQLException} and is thus meant to be called from within
* {@link Query#invoke} and {@link Modifier#invoke}.
*/
public PreparedStatement prepare (Connection conn)
throws SQLException
{
if (_buildVisitor == null) {
throw new IllegalArgumentException("Cannot prepare query until it's been built.");
}
PreparedStatement stmt = conn.prepareStatement(_buildVisitor.getQuery());
int argIx = 1;
for (BuildVisitor.Bindable bindable : _buildVisitor.getBindables()) {
try {
bindable.doBind(conn, stmt, argIx);
} catch (Exception e) {
log.warning("Failed to bind statement argument", "argIx", argIx, e);
}
argIx ++;
}
if (PersistenceContext.DEBUG) {
log.info("SQL: " + stmt.toString());
}
return stmt;
}
protected String nullify (String str) {
return (str != null && str.length() > 0) ? str : null;
}
/**
* Generates the SQL needed to construct a database column for field represented by the given
* {@link FieldMarshaller}.
*
* TODO: This method should be split into several parts that are more easily overridden on a
* case-by-case basis in the dialectal subclasses.
*/
public ColumnDefinition buildColumnDefinition (FieldMarshaller<?> fm)
{
// if this field is @Computed, it has no SQL definition
if (fm.getComputed() != null) {
return null;
}
Field field = fm.getField();
Column column = field.getAnnotation(Column.class);
ColumnDefinition coldef = (column != null) ?
new ColumnDefinition(null, column.nullable(),
column.unique(), nullify(column.defaultValue())) :
new ColumnDefinition();
GeneratedValue genValue = fm.getGeneratedValue();
if (genValue != null) {
maybeMutateForGeneratedValue(genValue, coldef);
} else if (coldef.defaultValue == null) {
maybeMutateForPrimitive(field, coldef);
}
if (coldef.type == null) {
coldef.type = getColumnType(fm, (column != null) ? column.length() : 255);
}
// sanity check nullability
if (coldef.nullable && field.getType().isPrimitive()) {
throw new IllegalArgumentException(
"Primitive Java type cannot be nullable [field=" + field.getName() + "]");
}
return coldef;
}
protected void maybeMutateForPrimitive (Field field, ColumnDefinition coldef)
{
// Java primitive types cannot be null, so we provide a default value for these columns
// that matches Java's default for primitive types; however, if the column has a
// generated value, don't provide a default because that will anger the database Gods
if (field.getType().equals(Byte.TYPE) ||
field.getType().equals(Short.TYPE) ||
field.getType().equals(Integer.TYPE) ||
field.getType().equals(Long.TYPE) ||
field.getType().equals(Float.TYPE) ||
field.getType().equals(Double.TYPE) ||
ByteEnum.class.isAssignableFrom(field.getType())) {
coldef.defaultValue = "0";
} else if (field.getType().equals(Boolean.TYPE)) {
coldef.defaultValue = getBooleanDefault();
}
}
protected void maybeMutateForGeneratedValue (GeneratedValue genValue, ColumnDefinition coldef)
{
switch (genValue.strategy()) {
case AUTO:
case IDENTITY:
coldef.type = "SERIAL";
coldef.unique = true;
break;
case SEQUENCE: // TODO
throw new IllegalArgumentException(
"SEQUENCE key generation strategy not yet supported.");
case TABLE:
// nothing to do here, it'll be handled later
break;
}
}
/**
* Returns the boolean literal that corresponds to false.
*/
protected String getBooleanDefault ()
{
return "false";
}
/**
* Add full-text search capabilities, as defined by the provided {@link FullTextIndex}, on
* the table associated with the given {@link DepotMarshaller}. This is a highly database
* specific operation and must thus be implemented by each dialect subclass.
*
* @see FullTextIndex
*/
public abstract <T extends PersistentRecord> boolean addFullTextSearch (
Connection conn, DepotMarshaller<T> marshaller, FullTextIndex fts)
throws SQLException;
/**
* Return true if the supplied column is an internal consideration of this {@link SQLBuilder},
* e.g. PostgreSQL's full text search data is stored in a table column that should otherwise
* not be visible to Depot; this method helps mask it.
*/
public abstract boolean isPrivateColumn (String column);
/**
* Figure out what full text search indexes already exist on this table and add the names of
* those indexes to the supplied target set.
*/
public abstract void getFtsIndexes (
Iterable<String> columns, Iterable<String> indexes, Set<String> target);
/**
* Overridden by subclasses to create a dialect-specific {@link BuildVisitor}.
*/
protected abstract BuildVisitor getBuildVisitor ();
/**
* Overridden by subclasses to figure the dialect-specific SQL type of the given field.
* @param length
*/
protected abstract <T> String getColumnType (FieldMarshaller<?> fm, int length);
/** The class that maps persistent classes to marshallers. */
protected DepotTypes _types;
protected QueryClause _clause;
protected BuildVisitor _buildVisitor;
}
@@ -0,0 +1,87 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.impl;
import java.io.Serializable;
import com.samskivert.depot.CacheKey;
import com.google.common.base.Objects;
/**
* 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 the given cache id with the given cache key.
*/
public SimpleCacheKey (String cacheId, String cacheKey)
{
_cacheId = cacheId;
_cacheKey = cacheKey;
}
// 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;
return Objects.equal(_cacheId, other._cacheId) &&
Objects.equal(_cacheKey, other._cacheKey);
}
@Override
public String toString ()
{
return "[cacheId=" + _cacheId + ", value=" + _cacheKey + "]";
}
protected String _cacheId;
protected String _cacheKey;
}
@@ -0,0 +1,165 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.impl;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import com.samskivert.depot.annotation.GeneratedValue;
import com.samskivert.depot.annotation.TableGenerator;
import com.samskivert.jdbc.ColumnDefinition;
import com.samskivert.jdbc.DatabaseLiaison;
/**
* Generates primary keys using an external table .
*/
public class TableValueGenerator extends ValueGenerator
{
public TableValueGenerator (
TableGenerator tg, GeneratedValue gv, DepotMarshaller<?> dm, FieldMarshaller<?> fm)
{
super(gv, dm, fm);
_valueTable = defStr(tg.table(), "IdSequences");
_pkColumnName = defStr(tg.pkColumnName(), "sequence");
_pkColumnValue = defStr(tg.pkColumnValue(), "default");
_valueColumnName = defStr(tg.valueColumnName(), "value");
}
@Override // from ValueGenerator
public boolean isPostFactum ()
{
return false;
}
@Override // from ValueGenerator
public void create (Connection conn, DatabaseLiaison liaison)
throws SQLException
{
// make sure our table exists
liaison.createTableIfMissing(
conn, _valueTable,
new String[] { _pkColumnName, _valueColumnName },
new ColumnDefinition[] {
new ColumnDefinition("VARCHAR(255)", true, false, null),
new ColumnDefinition("INTEGER")
},
null,
new String[] { _pkColumnName });
// and also that there's a row in it for us
PreparedStatement stmt = conn.prepareStatement(
" SELECT * FROM " + liaison.tableSQL(_valueTable) +
" WHERE " + liaison.columnSQL(_pkColumnName) + " = ?");
stmt.setString(1, _pkColumnValue);
if (stmt.executeQuery().next()) {
return;
}
int initialValue = _initialValue;
if (_migrateIfExists) {
Integer max = getFieldMaximum(conn, liaison);
if (max != null) {
initialValue = 1 + max.intValue();
}
}
stmt = conn.prepareStatement(
" INSERT INTO " + liaison.tableSQL(_valueTable) + " (" +
liaison.columnSQL(_pkColumnName) + ", " + liaison.columnSQL(_valueColumnName) +
") VALUES (?, ?)");
stmt.setString(1, _pkColumnValue);
stmt.setInt(2, initialValue);
stmt.executeUpdate();
}
@Override // from ValueGenerator
public void delete (Connection conn, DatabaseLiaison liaison) throws SQLException
{
PreparedStatement stmt = conn.prepareStatement(
" DELETE FROM " + liaison.tableSQL(_valueTable) +
" WHERE " + liaison.columnSQL(_pkColumnName) + " = ?");
stmt.setString(1, _pkColumnValue);
stmt.executeUpdate();
}
@Override // from ValueGenerator
public int nextGeneratedValue (Connection conn, DatabaseLiaison liaison)
throws SQLException
{
// TODO: Make this lockless!
PreparedStatement readStatement = conn.prepareStatement(
" SELECT " + liaison.columnSQL(_valueColumnName) +
" FROM " + liaison.tableSQL(_valueTable) +
" WHERE " + liaison.columnSQL(_pkColumnName) + " = ? ");
readStatement.setString(1, _pkColumnValue);
PreparedStatement writeStatement = conn.prepareStatement(
" UPDATE " + liaison.tableSQL(_valueTable) +
" SET " + liaison.columnSQL(_valueColumnName) + " = ? " +
" WHERE " + liaison.columnSQL(_pkColumnName) + " = ? " +
" AND " + liaison.columnSQL(_valueColumnName) + " = ? ");
for (int tries = 0; tries < 10; tries ++) {
// execute the query
ResultSet rs = readStatement.executeQuery();
if (!rs.next()) {
throw new SQLException(
"Failed to find next primary key value [table=" + _valueTable +
", column=" + _valueColumnName + "]");
}
// fetch the next available value
int val = rs.getInt(1);
// claim this value locklessly
writeStatement.setInt(1, val + _allocationSize);
writeStatement.setString(2, _pkColumnValue);
writeStatement.setInt(3, val);
// if we modified a row, we know we and nobody else got this particular value!
if (writeStatement.executeUpdate() == 1) {
return val;
}
// else try again
}
throw new SQLException(
"Failed to claim next primary key value in 10 attempts [table=" + _valueTable +
", column=" + _valueColumnName + "]");
}
/**
* Convenience function to return a value or a default fallback.
*/
protected static String defStr (String value, String def)
{
if (value == null || value.trim().length() == 0) {
return def;
}
return value;
}
protected String _valueTable;
protected String _pkColumnName;
protected String _pkColumnValue;
protected String _valueColumnName;
}
@@ -0,0 +1,118 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.impl;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.depot.annotation.GeneratedValue;
import static com.samskivert.depot.Log.log;
/**
* Defines the interface to our value generators.
*/
public abstract class ValueGenerator
{
public ValueGenerator (GeneratedValue gv, DepotMarshaller<?> dm, FieldMarshaller<?> fm)
{
_allocationSize = gv.allocationSize();
_initialValue = gv.initialValue();
_migrateIfExists = gv.migrateIfExists();
_dm = dm;
_fm = fm;
}
/**
* If true, this key generator will be run after the insert statement, if false, it will be run
* before.
*/
public abstract boolean isPostFactum ();
/**
* Ensures the generator is prepared for operation, creating it if necessary. Care is taken to
* only run this the first time a column is created. However, if a column with a value
* generator is renamed, we can't distinguish that from a newly created column and we call this
* method again on the renamed column. If it's possible to not fail in that circumstance, try
* to avoid doing so.
*/
public abstract void create (Connection conn, DatabaseLiaison liaison)
throws SQLException;
/**
* Fetch/generate the next primary key value.
*/
public abstract int nextGeneratedValue (Connection conn, DatabaseLiaison liaison)
throws SQLException;
/**
* Delete all database entities associated with this value generator.
*/
public abstract void delete (Connection conn, DatabaseLiaison liaison)
throws SQLException;
/**
* Scans the table associated with this {@link ValueGenerator} and returns either null, if
* there are no rows, or an {@link Integer} containing the largest numerical value our
* field attains.
*/
protected Integer getFieldMaximum (Connection conn, DatabaseLiaison liaison)
throws SQLException
{
String column = _fm.getColumnName();
String table = _dm.getTableName();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(
" SELECT COUNT(*), MAX(" + liaison.columnSQL(column) + ") " +
" FROM " + liaison.tableSQL(table));
if (!rs.next()) {
log.warning("Query on count()/max() bizarrely returned no rows.");
return null;
}
int cnt = rs.getInt(1);
if (cnt > 0) {
return Integer.valueOf(rs.getInt(2));
}
return null;
}
public DepotMarshaller<?> getDepotMarshaller ()
{
return _dm;
}
public FieldMarshaller<?> getFieldMarshaller ()
{
return _fm;
}
protected int _initialValue;
protected int _allocationSize;
protected boolean _migrateIfExists;
protected DepotMarshaller<?> _dm;
protected FieldMarshaller<?> _fm;
}
@@ -0,0 +1,92 @@
//
// $Id: $
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.impl.clause;
import java.util.Collection;
import java.util.List;
import com.samskivert.util.Tuple;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.clause.OrderBy.Order;
import com.samskivert.depot.clause.QueryClause;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.FragmentVisitor;
/**
* Represents an CREATE INDEX instruction to the database.
*/
public class CreateIndexClause
implements QueryClause
{
/**
* Create a new {@link CreateIndexClause} clause. The name must be unique within the relevant
* database.
*/
public CreateIndexClause (Class<? extends PersistentRecord> pClass, String name, boolean unique,
List<Tuple<SQLExpression, Order>> fields)
{
_pClass = pClass;
_name = name;
_unique = unique;
_fields = fields;
}
public Class<? extends PersistentRecord> getPersistentClass ()
{
return _pClass;
}
public String getName ()
{
return _name;
}
public boolean isUnique ()
{
return _unique;
}
public List<Tuple<SQLExpression,Order>> getFields ()
{
return _fields;
}
// from SQLFragment
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
classSet.add(_pClass);
}
// from SQLFragment
public Object accept (FragmentVisitor<?> builder)
{
return builder.visit(this);
}
protected Class<? extends PersistentRecord> _pClass;
protected String _name;
protected boolean _unique;
/** The components of the index, e.g. columns or functions of columns. */
protected List<Tuple<SQLExpression,Order>> _fields;
}
@@ -0,0 +1,70 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.impl.clause;
import java.util.Collection;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.clause.QueryClause;
import com.samskivert.depot.clause.WhereClause;
import com.samskivert.depot.impl.FragmentVisitor;
/**
* Builds actual SQL given a main persistent type and some {@link QueryClause} objects.
*/
public class DeleteClause
implements QueryClause
{
public DeleteClause (Class<? extends PersistentRecord> pClass, WhereClause where)
{
_pClass = pClass;
_where = where;
}
public Class<? extends PersistentRecord> getPersistentClass ()
{
return _pClass;
}
public WhereClause getWhereClause ()
{
return _where;
}
// from SQLFragment
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
classSet.add(_pClass);
}
// from SQLFragment
public Object accept (FragmentVisitor<?> builder)
{
return builder.visit(this);
}
/** The type of persistent record on which we operate. */
protected Class<? extends PersistentRecord> _pClass;
/** The where clause. */
protected WhereClause _where;
}
@@ -0,0 +1,67 @@
//
// $Id: $
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.impl.clause;
import java.util.Collection;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.clause.QueryClause;
import com.samskivert.depot.impl.FragmentVisitor;
/**
* Represents an DROP INDEX instruction to the database.
*/
public class DropIndexClause
implements QueryClause
{
public DropIndexClause (Class<? extends PersistentRecord> pClass, String name)
{
_pClass = pClass;
_name = name;
}
public Class<? extends PersistentRecord> getPersistentClass ()
{
return _pClass;
}
public String getName ()
{
return _name;
}
// from SQLFragment
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
classSet.add(_pClass);
}
// from SQLFragment
public Object accept (FragmentVisitor<?> builder)
{
return builder.visit(this);
}
protected Class<? extends PersistentRecord> _pClass;
protected String _name;
}
@@ -0,0 +1,118 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.impl.clause;
import java.util.Collection;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.clause.QueryClause;
import com.samskivert.depot.clause.WhereClause;
import com.samskivert.depot.expression.ColumnExp;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.FragmentVisitor;
/**
* Builds actual SQL given a main persistent type and some {@link QueryClause} objects.
*/
public class UpdateClause
implements QueryClause
{
public UpdateClause (Class<? extends PersistentRecord> pClass, WhereClause where,
ColumnExp[] fields, PersistentRecord pojo)
{
_pClass = pClass;
_where = where;
_fields = fields;
_values = null;
_pojo = pojo;
}
public UpdateClause (Class<? extends PersistentRecord> pClass, WhereClause where,
ColumnExp[] fields, SQLExpression[] values)
{
_pClass = pClass;
_fields = fields;
_where = where;
_values = values;
_pojo = null;
}
public WhereClause getWhereClause ()
{
return _where;
}
public ColumnExp[] getFields ()
{
return _fields;
}
public SQLExpression[] getValues ()
{
return _values;
}
public PersistentRecord getPojo ()
{
return _pojo;
}
public Class<? extends PersistentRecord> getPersistentClass ()
{
return _pClass;
}
// from SQLFragment
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
classSet.add(_pClass);
if (_where != null) {
_where.addClasses(classSet);
}
if (_values != null) {
for (int ii = 0; ii < _values.length; ii ++) {
_values[ii].addClasses(classSet);
}
}
}
// from SQLFragment
public Object accept (FragmentVisitor<?> builder)
{
return builder.visit(this);
}
/** The class we're updating. */
protected Class<? extends PersistentRecord> _pClass;
/** The where clause. */
protected WhereClause _where;
/** The persistent fields to update. */
protected ColumnExp[] _fields;
/** The field values, or null. */
protected SQLExpression[] _values;
/** The object from which to fetch values, or null. */
protected PersistentRecord _pojo;
}
@@ -0,0 +1,138 @@
//
// $Id: $
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2009 Michael Bayne and 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.depot.impl.expression;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.FragmentVisitor;
import com.samskivert.depot.impl.expression.Function.OneArgFun;
public abstract class AggregateFun extends OneArgFun
{
public static class Average extends AggregateFun {
public Average (SQLExpression argument) {
this(argument, false);
}
public Average (SQLExpression argument, boolean distinct) {
super(argument, distinct);
}
public Object accept (FragmentVisitor<?> visitor) {
return visitor.visit(this);
}
public String getCanonicalFunctionName () {
return "average";
}
}
public static class Count extends AggregateFun {
public Count (SQLExpression argument) {
this(argument, false);
}
public Count (SQLExpression argument, boolean distinct) {
super(argument, distinct);
}
public Object accept (FragmentVisitor<?> visitor) {
return visitor.visit(this);
}
public String getCanonicalFunctionName () {
return "count";
}
}
public static class Every extends AggregateFun {
public Every (SQLExpression argument) {
this(argument, false);
}
public Every (SQLExpression argument, boolean distinct) {
super(argument, distinct);
}
public Object accept (FragmentVisitor<?> visitor) {
return visitor.visit(this);
}
public String getCanonicalFunctionName () {
return "every";
}
}
public static class Max extends AggregateFun {
public Max (SQLExpression argument) {
this(argument, false);
}
public Max (SQLExpression argument, boolean distinct) {
super(argument, distinct);
}
public Object accept (FragmentVisitor<?> visitor) {
return visitor.visit(this);
}
public String getCanonicalFunctionName () {
return "max";
}
}
public static class Min extends AggregateFun {
public Min (SQLExpression argument) {
this(argument, false);
}
public Min (SQLExpression argument, boolean distinct) {
super(argument, distinct);
}
public Object accept (FragmentVisitor<?> visitor) {
return visitor.visit(this);
}
public String getCanonicalFunctionName () {
return "min";
}
}
public static class Sum extends AggregateFun {
public Sum (SQLExpression argument) {
this(argument, false);
}
public Sum (SQLExpression argument, boolean distinct) {
super(argument, distinct);
}
public Object accept (FragmentVisitor<?> visitor) {
return visitor.visit(this);
}
public String getCanonicalFunctionName () {
return "sum";
}
}
public AggregateFun (SQLExpression argument, boolean distinct)
{
super(argument);
_distinct = distinct;
}
public boolean isDistinct ()
{
return _distinct;
}
@Override
public String toString ()
{
return getCanonicalFunctionName() + "(" + (_distinct ? "distinct " : "") + _arg + ")";
}
protected boolean _distinct;
}
@@ -0,0 +1,49 @@
//
// $Id: $
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2009 Michael Bayne and 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.depot.impl.expression;
import java.util.Collection;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.expression.FluentExp;
import com.samskivert.depot.expression.SQLExpression;
public abstract class ArgumentExp extends FluentExp
{
protected ArgumentExp (SQLExpression... args)
{
_args = args;
}
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
for (SQLExpression arg : _args) {
arg.addClasses(classSet);
}
}
public SQLExpression[] getArgs ()
{
return _args;
}
protected SQLExpression[] _args;
}
@@ -0,0 +1,64 @@
//
// $Id: $
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2009 Michael Bayne and 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.depot.impl.expression;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.FragmentVisitor;
import com.samskivert.depot.impl.expression.Function.ManyArgFun;
public abstract class ConditionalFun
{
public static class Coalesce extends ManyArgFun {
public Coalesce (SQLExpression... args) {
super(args);
}
public Object accept (FragmentVisitor<?> visitor) {
return visitor.visit(this);
}
public String getCanonicalFunctionName () {
return "coalesce";
}
}
public static class Greatest extends ManyArgFun {
public Greatest (SQLExpression... args) {
super(args);
}
public Object accept (FragmentVisitor<?> visitor) {
return visitor.visit(this);
}
public String getCanonicalFunctionName () {
return "greatest";
}
}
public static class Least extends ManyArgFun {
public Least (SQLExpression... args) {
super(args);
}
public Object accept (FragmentVisitor<?> visitor) {
return visitor.visit(this);
}
public String getCanonicalFunctionName () {
return "least";
}
}
}
@@ -0,0 +1,87 @@
//
// $Id: $
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2009 Michael Bayne and 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.depot.impl.expression;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.FragmentVisitor;
import com.samskivert.depot.impl.expression.Function.NoArgFun;
import com.samskivert.depot.impl.expression.Function.OneArgFun;
public abstract class DateFun
{
public static class DatePart extends OneArgFun {
public enum Part {
DAY_OF_MONTH, DAY_OF_WEEK, DAY_OF_YEAR, HOUR, MINUTE, MONTH,
SECOND, WEEK, YEAR, EPOCH
}
public DatePart (SQLExpression date, Part part) {
super(date);
_part = part;
}
public Object accept (FragmentVisitor<?> visitor) {
return visitor.visit(this);
}
public Part getPart () {
return _part;
}
public String getCanonicalFunctionName () {
return "datePart_" + _part;
}
protected Part _part;
}
public static class DateTruncate extends OneArgFun {
/**
* The degree of truncation to perform, in time units. Currently only DAY, due to lacking
* MySQL support, but we hope for future versions to match PostgreSQL.
*/
public enum Truncation {
DAY,
}
/**
* Truncate a SQL timestamp value, currently only to the nearest day (Truncation.DAY) due
* to lacking MySQL support, but we hope for future versions to match PostgreSQL.
*/
public DateTruncate (SQLExpression date, Truncation truncation) {
super(date);
_truncation= truncation;
}
public Object accept (FragmentVisitor<?> visitor) {
return visitor.visit(this);
}
public Truncation getTruncation () {
return _truncation;
}
public String getCanonicalFunctionName () {
return "dateTrunc_" + _truncation;
}
protected Truncation _truncation;
}
public static class Now extends NoArgFun {
public Object accept (FragmentVisitor<?> visitor) {
return visitor.visit(this);
}
public String getCanonicalFunctionName () {
return "now";
}
}
}
@@ -0,0 +1,104 @@
//
// $Id: $
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2009 Michael Bayne and 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.depot.impl.expression;
import java.util.Collection;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.expression.FluentExp;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.util.StringUtil;
public interface Function
{
String getCanonicalFunctionName ();
public static abstract class NoArgFun extends FluentExp implements Function
{
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
// nothing to add
}
}
public static abstract class OneArgFun extends FluentExp implements Function
{
protected OneArgFun (SQLExpression argument)
{
_arg = argument;
}
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
_arg.addClasses(classSet);
}
public SQLExpression getArg ()
{
return _arg;
}
@Override
public String toString ()
{
return getCanonicalFunctionName() + "(" + _arg + ")";
}
protected SQLExpression _arg;
}
public static abstract class TwoArgFun extends FluentExp implements Function
{
protected TwoArgFun (SQLExpression arg1, SQLExpression arg2)
{
_arg1 = arg1;
_arg2 = arg2;
}
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
_arg1.addClasses(classSet);
_arg2.addClasses(classSet);
}
@Override
public String toString ()
{
return getCanonicalFunctionName() + "(" + _arg1 + ", " + _arg2 + ")";
}
protected SQLExpression _arg1, _arg2;
}
public static abstract class ManyArgFun extends ArgumentExp implements Function
{
protected ManyArgFun (SQLExpression... args)
{
super(args);
}
@Override
public String toString ()
{
return getCanonicalFunctionName() + "(" + StringUtil.join(_args, ", ") + ")";
}
}
}
@@ -0,0 +1,66 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2009 Michael Bayne and 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.depot.impl.expression;
import java.util.Collection;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.FragmentVisitor;
/**
* A code for representing a date interval.
*/
public class IntervalExp
implements SQLExpression
{
/** The units that can be used for an interval. */
public enum Unit { YEAR, MONTH, DAY, HOUR, MINUTE, SECOND }
/** The unit for this interval. */
public final Unit unit;
/** The number of units for this interval. */
public final int amount;
public IntervalExp (Unit unit, int amount)
{
this.unit = unit;
this.amount = amount;
}
// from SQLFragment
public Object accept (FragmentVisitor<?> builder)
{
return builder.visit(this);
}
// from SQLFragment
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
}
@Override
public String toString ()
{
return amount + " " + unit;
}
}
@@ -0,0 +1,65 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.impl.expression;
import java.util.Collection;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.FragmentVisitor;
/**
* An expression for things we don't support natively, e.g. COUNT(*).
*/
public class LiteralExp
implements SQLExpression
{
public LiteralExp (String text)
{
super();
_text = text;
}
// from SQLFragment
public Object accept (FragmentVisitor<?> builder)
{
return builder.visit(this);
}
// from SQLFragment
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
}
public String getText ()
{
return _text;
}
@Override
public String toString ()
{
return _text;
}
/** The literal text of this expression, e.g. COUNT(*) */
protected String _text;
}
@@ -0,0 +1,186 @@
//
// $Id: $
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2009 Michael Bayne and 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.depot.impl.expression;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.FragmentVisitor;
import com.samskivert.depot.impl.expression.Function.NoArgFun;
import com.samskivert.depot.impl.expression.Function.OneArgFun;
import com.samskivert.depot.impl.expression.Function.TwoArgFun;
public abstract class NumericalFun
{
public static class Abs extends OneArgFun {
public Abs (SQLExpression argument) {
super(argument);
}
public Object accept (FragmentVisitor<?> visitor) {
return visitor.visit(this);
}
public String getCanonicalFunctionName () {
return "abs";
}
}
public static class Ceil extends OneArgFun {
public Ceil (SQLExpression argument) {
super(argument);
}
public Object accept (FragmentVisitor<?> visitor) {
return visitor.visit(this);
}
public String getCanonicalFunctionName () {
return "ceil";
}
}
public static class Exp extends OneArgFun {
public Exp (SQLExpression argument) {
super(argument);
}
public Object accept (FragmentVisitor<?> visitor) {
return visitor.visit(this);
}
public String getCanonicalFunctionName () {
return "exp";
}
}
public static class Floor extends OneArgFun {
public Floor (SQLExpression argument) {
super(argument);
}
public Object accept (FragmentVisitor<?> visitor) {
return visitor.visit(this);
}
public String getCanonicalFunctionName () {
return "floor";
}
}
public static class Ln extends OneArgFun {
public Ln (SQLExpression argument) {
super(argument);
}
public Object accept (FragmentVisitor<?> visitor) {
return visitor.visit(this);
}
public String getCanonicalFunctionName () {
return "ln";
}
}
public static class Log10 extends OneArgFun {
public Log10 (SQLExpression value) {
super(value);
}
public Object accept (FragmentVisitor<?> visitor) {
return visitor.visit(this);
}
public String getCanonicalFunctionName () {
return "log10";
}
}
public static class Pi extends NoArgFun {
public Object accept (FragmentVisitor<?> visitor) {
return visitor.visit(this);
}
public String getCanonicalFunctionName () {
return "pi";
}
}
public static class Power extends TwoArgFun {
public Power (SQLExpression value, SQLExpression power) {
super(value, power);
}
public Object accept (FragmentVisitor<?> visitor) {
return visitor.visit(this);
}
public String getCanonicalFunctionName () {
return "Power";
}
public SQLExpression getValue () {
return _arg1;
}
public SQLExpression getPower () {
return _arg2;
}
}
public static class Random extends NoArgFun {
public Object accept (FragmentVisitor<?> visitor) {
return visitor.visit(this);
}
public String getCanonicalFunctionName () {
return "random";
}
}
public static class Round extends OneArgFun {
public Round (SQLExpression argument) {
super(argument);
}
public Object accept (FragmentVisitor<?> visitor) {
return visitor.visit(this);
}
public String getCanonicalFunctionName () {
return "round";
}
}
public static class Sign extends OneArgFun {
public Sign (SQLExpression argument) {
super(argument);
}
public Object accept (FragmentVisitor<?> visitor) {
return visitor.visit(this);
}
public String getCanonicalFunctionName () {
return "sign";
}
}
public static class Sqrt extends OneArgFun {
public Sqrt (SQLExpression argument) {
super(argument);
}
public Object accept (FragmentVisitor<?> visitor) {
return visitor.visit(this);
}
public String getCanonicalFunctionName () {
return "sqrt";
}
}
public static class Trunc extends OneArgFun {
public Trunc (SQLExpression argument) {
super(argument);
}
public Object accept (FragmentVisitor<?> visitor) {
return visitor.visit(this);
}
public String getCanonicalFunctionName () {
return "trunc";
}
}
}
@@ -0,0 +1,108 @@
//
// $Id: $
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2009 Michael Bayne and 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.depot.impl.expression;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.FragmentVisitor;
import com.samskivert.depot.impl.expression.Function.ManyArgFun;
import com.samskivert.depot.impl.expression.Function.OneArgFun;
import com.samskivert.depot.impl.expression.Function.TwoArgFun;
public abstract class StringFun
{
public static class Length extends OneArgFun {
public Length (SQLExpression argument) {
super(argument);
}
public Object accept (FragmentVisitor<?> visitor) {
return visitor.visit(this);
}
public String getCanonicalFunctionName () {
return "length";
}
}
public static class Lower extends OneArgFun {
public Lower (SQLExpression argument) {
super(argument);
}
public Object accept (FragmentVisitor<?> visitor) {
return visitor.visit(this);
}
public String getCanonicalFunctionName () {
return "lower";
}
}
public static class Position extends TwoArgFun {
public Position (SQLExpression substring, SQLExpression string) {
super(substring, string);
}
public Object accept (FragmentVisitor<?> visitor) {
return visitor.visit(this);
}
public String getCanonicalFunctionName () {
return "position";
}
public SQLExpression getSubString () {
return _arg1;
}
public SQLExpression getString () {
return _arg2;
}
}
public static class Substring extends ManyArgFun {
public Substring (SQLExpression string, SQLExpression from, SQLExpression count) {
super(string, from, count);
}
public Object accept (FragmentVisitor<?> visitor) {
return visitor.visit(this);
}
public String getCanonicalFunctionName () {
return "substring";
}
}
public static class Trim extends OneArgFun {
public Trim (SQLExpression argument) {
super(argument);
}
public Object accept (FragmentVisitor<?> visitor) {
return visitor.visit(this);
}
public String getCanonicalFunctionName () {
return "trim";
}
}
public static class Upper extends OneArgFun {
public Upper (SQLExpression argument) {
super(argument);
}
public Object accept (FragmentVisitor<?> visitor) {
return visitor.visit(this);
}
public String getCanonicalFunctionName () {
return "upper";
}
}
}
@@ -0,0 +1,63 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.impl.expression;
import java.util.Collection;
import com.samskivert.depot.PersistentRecord;
import com.samskivert.depot.expression.FluentExp;
import com.samskivert.depot.impl.FragmentVisitor;
/**
* A Java value that is bound as a parameter to the query, e.g. 1 or 'abc'.
*/
public class ValueExp extends FluentExp
{
public ValueExp (Object value)
{
_value = value;
}
// from SQLFragment
public Object accept (FragmentVisitor<?> builder)
{
return builder.visit(this);
}
// from SQLFragment
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
}
public Object getValue ()
{
return _value;
}
@Override // from Object
public String toString ()
{
return (_value instanceof Number) ? String.valueOf(_value) : ("'" + _value + "'");
}
/** The value to be bound to the SQL parameters. */
protected Object _value;
}
@@ -0,0 +1,59 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2009 Michael Bayne and 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.depot.impl.operator;
import com.samskivert.depot.expression.SQLExpression;
/**
* The SQL '+' operator.
*/
public class Add extends Arithmetic
{
public Add (SQLExpression column, Comparable<?> value)
{
super(column, value);
}
public Add (SQLExpression... values)
{
super(values);
}
@Override // from Arithmetic
public String operator()
{
return "+";
}
@Override // from Arithmetic
public Object evaluate (Object[] operands)
{
return evaluate(operands, "+", new Accumulator<Double>() {
public Double accumulate (Double left, Double right) {
return left + right;
}
}, new Accumulator<Long>() {
public Long accumulate (Long left, Long right) {
return left + right;
}
});
}
}
@@ -0,0 +1,53 @@
//
// $Id$
//
// Depot library - a Java relational persistence library
// Copyright (C) 2006-2008 Michael Bayne and 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.depot.impl.operator;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.expression.ValueExp;
/**
* A convenient container for implementations of arithmetic operators.
*/
public abstract class Arithmetic extends MultiOperator
{
public Arithmetic (SQLExpression column, Comparable<?> value)
{
super(column, new ValueExp(value));
}
public Arithmetic (SQLExpression... values)
{
super(values);
}
protected Object evaluate (
Object[] ops, String name, Accumulator<Double> dAcc, Accumulator<Long> iAcc)
{
if (dAcc != null && all(NUMERICAL, ops)) {
return accumulate(NUMERICAL, ops, 0.0, dAcc);
}
if (iAcc != null && all(INTEGRAL, ops)) {
return accumulate(INTEGRAL, ops, 0L, iAcc);
}
return new NoValue("Non-numeric operand for '" + name + "' (" + ops + ")");
}
}
@@ -0,0 +1,72 @@
//
// $Id$
package com.samskivert.depot.impl.operator;
import java.util.Arrays;
import java.util.Date;
import com.google.common.base.Function;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.samskivert.depot.expression.SQLExpression;
import com.samskivert.depot.impl.expression.ArgumentExp;
/**
* A base class for all operators.
*/
public abstract class BaseOperator extends ArgumentExp
{
public static Function<Object, Long> INTEGRAL = new Function<Object, Long>() {
public Long apply (Object o) {
if ((o instanceof Integer) || (o instanceof Long)) {
return ((Number) o).longValue();
}
return null;
}
};
public static Function<Object, Double> NUMERICAL = new Function<Object, Double>() {
public Double apply (Object o) {
return (o instanceof Number) ? ((Number) o).doubleValue() : null;
}
};
public static Function<Object, String> STRING = new Function<Object, String>() {
public String apply (Object o) {
return (o instanceof String) ? (String) o : null;
}
};
public static Function<Object, Date> DATE = new Function<Object, Date>() {
public Date apply (Object o) {
return (o instanceof Date) ? (Date) o : null;
}
};
public static <S, T> boolean all (Function<S, T> fun, S... obj) {
return Iterables.all(Arrays.asList(obj), Predicates.compose(Predicates.isNull(), fun));
}
public static <S, T extends Comparable<T>> int compare (Function<S, T> fun, S lhs, S rhs) {
return fun.apply(lhs).compareTo(fun.apply(rhs));
}
public static <S, T> T accumulate (Function<S, T> fun, S[] ops, T v,
BaseOperator.Accumulator<T> acc) {
for (S op : ops) {
v = acc.accumulate(v, fun.apply(op));
}
return v;
}
protected BaseOperator (SQLExpression... operands)
{
super(operands);
}
protected static interface Accumulator<T>
{
T accumulate (T left, T right);
}
}

Some files were not shown because too many files have changed in this diff Show More