More layout standardizing.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<Tuple<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
Reference in New Issue
Block a user