diff --git a/src/java/com/samskivert/depot/DepotRepository.java b/src/java/com/samskivert/depot/DepotRepository.java index 2582a15..cd10210 100644 --- a/src/java/com/samskivert/depot/DepotRepository.java +++ b/src/java/com/samskivert/depot/DepotRepository.java @@ -125,6 +125,606 @@ public abstract class DepotRepository CONTENTS } + /** + * Loads the persistent object that matches the specified primary key. + * + * @throws DatabaseException if any problem is encountered communicating with the database. + */ + public T load (Key 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 load ( + Key key, CacheStrategy strategy, QueryClause... clauses) + throws DatabaseException + { + clauses = ArrayUtil.append(clauses, key); + return _ctx.invoke(new FindOneQuery(_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 load (Class 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 load ( + Class type, CacheStrategy strategy, QueryClause... clauses) + throws DatabaseException + { + return _ctx.invoke(new FindOneQuery(_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 XList loadAll ( + Class type, Collection> primaryKeys) + throws DatabaseException + { + // convert the raw keys into real key records + DepotMarshaller marsh = _ctx.getMarshaller(type); + List> keys = Lists.newArrayList(); + for (Comparable key : primaryKeys) { + keys.add(marsh.makePrimaryKey(key)); + } + return loadAll(keys); + } + + /** + * 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 XList loadAll (Collection> keys) + throws DatabaseException + { + return (keys.size() == 0) ? new XArrayList() : + _ctx.invoke(new FindAllQuery.WithKeys(_ctx, keys)); + } + + /** + * A varargs version of {@link #findAll(Class,Collection)}. + * + * @throws DatabaseException if any problem is encountered communicating with the database. + */ + public XList findAll (Class 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 XList findAll ( + Class type, Collection clauses) + throws DatabaseException + { + return findAll(type, CacheStrategy.BEST, clauses); + } + + /** + * A varargs version of {@link #findAll(Class,CacheStrategy,Collection)}. + * + * @throws DatabaseException if any problem is encountered communicating with the database. + */ + public XList findAll ( + Class 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 XList findAll ( + Class type, CacheStrategy cache, Collection clauses) + throws DatabaseException + { + DepotMarshaller 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(_ctx, type, clauses, cache)); + + default: + return _ctx.invoke(new FindAllQuery.Explicitly( + _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 XList> findAllKeys ( + Class 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 XList> findAllKeys ( + Class type, boolean forUpdate, Collection clauses) + throws DatabaseException + { + return _ctx.invoke(new FindAllKeysQuery(_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 int insert (T record) + throws DatabaseException + { + @SuppressWarnings("unchecked") final Class pClass = (Class) record.getClass(); + final DepotMarshaller marsh = _ctx.getMarshaller(pClass); + Key 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(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 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 pClass = record.getClass(); + requireNotComputed(pClass, "update"); + DepotMarshaller marsh = _ctx.getMarshaller(pClass); + Key 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 int update (T record, final ColumnExp... modifiedFields) + throws DatabaseException + { + @SuppressWarnings("unchecked") Class pClass = (Class) record.getClass(); + requireNotComputed(pClass, "update"); + DepotMarshaller marsh = _ctx.getMarshaller(pClass); + Key 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 int updatePartial ( + Key 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 int updatePartial (Key key, Map 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 int updatePartial ( + Class type, final WhereClause key, CacheInvalidator invalidator, + Map 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 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 int updatePartial ( + Class 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 int updatePartial ( + Class 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 boolean store (T record) + throws DatabaseException + { + @SuppressWarnings("unchecked") final Class pClass = (Class) record.getClass(); + requireNotComputed(pClass, "store"); + + final DepotMarshaller marsh = _ctx.getMarshaller(pClass); + Key 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(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 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 int delete (T record) + throws DatabaseException + { + @SuppressWarnings("unchecked") Class type = (Class)record.getClass(); + Key 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 int delete (Key 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 int deleteAll (Class 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 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 int deleteAll ( + Class 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 not @@ -209,606 +809,6 @@ public abstract class DepotRepository */ protected abstract void getManagedRecords (Set> classes); - /** - * Loads the persistent object that matches the specified primary key. - * - * @throws DatabaseException if any problem is encountered communicating with the database. - */ - protected T load (Key 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. - */ - protected T load (Key key, CacheStrategy strategy, - QueryClause... clauses) - throws DatabaseException - { - clauses = ArrayUtil.append(clauses, key); - return _ctx.invoke(new FindOneQuery(_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. - */ - protected T load (Class 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. - */ - protected T load (Class type, CacheStrategy strategy, - QueryClause... clauses) - throws DatabaseException - { - return _ctx.invoke(new FindOneQuery(_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. - */ - protected XList loadAll ( - Class type, Collection> primaryKeys) - throws DatabaseException - { - // convert the raw keys into real key records - DepotMarshaller marsh = _ctx.getMarshaller(type); - List> keys = Lists.newArrayList(); - for (Comparable key : primaryKeys) { - keys.add(marsh.makePrimaryKey(key)); - } - return loadAll(keys); - } - - /** - * Loads up all persistent records that match the supplied set of primary keys. - * - * @throws DatabaseException if any problem is encountered communicating with the database. - */ - protected XList loadAll (Collection> keys) - throws DatabaseException - { - return (keys.size() == 0) ? new XArrayList() : - _ctx.invoke(new FindAllQuery.WithKeys(_ctx, keys)); - } - - /** - * A varargs version of {@link #findAll(Class,Collection)}. - * - * @throws DatabaseException if any problem is encountered communicating with the database. - */ - protected XList findAll (Class 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. - */ - protected XList findAll ( - Class type, Collection clauses) - throws DatabaseException - { - return findAll(type, CacheStrategy.BEST, clauses); - } - - /** - * A varargs version of {@link #findAll(Class,CacheStrategy,Collection)}. - * - * @throws DatabaseException if any problem is encountered communicating with the database. - */ - protected XList findAll ( - Class 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. - */ - protected XList findAll ( - Class type, CacheStrategy cache, Collection clauses) - throws DatabaseException - { - DepotMarshaller 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(_ctx, type, clauses, cache)); - - default: - return _ctx.invoke(new FindAllQuery.Explicitly( - _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. - */ - protected XList> findAllKeys ( - Class 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. - */ - protected XList> findAllKeys ( - Class type, boolean forUpdate, Collection clauses) - throws DatabaseException - { - return _ctx.invoke(new FindAllKeysQuery(_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. - */ - protected int insert (T record) - throws DatabaseException - { - @SuppressWarnings("unchecked") final Class pClass = (Class) record.getClass(); - final DepotMarshaller marsh = _ctx.getMarshaller(pClass); - Key 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(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 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. - */ - protected int update (PersistentRecord record) - throws DatabaseException - { - Class pClass = record.getClass(); - requireNotComputed(pClass, "update"); - DepotMarshaller marsh = _ctx.getMarshaller(pClass); - Key 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. - */ - protected int update (T record, final ColumnExp... modifiedFields) - throws DatabaseException - { - @SuppressWarnings("unchecked") Class pClass = (Class) record.getClass(); - requireNotComputed(pClass, "update"); - DepotMarshaller marsh = _ctx.getMarshaller(pClass); - Key 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. - */ - protected int updatePartial ( - Key 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. - */ - protected int updatePartial (Key key, Map 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. - */ - protected int updatePartial ( - Class type, final WhereClause key, CacheInvalidator invalidator, - Map 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 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. - */ - protected int updatePartial ( - Class 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. - */ - protected int updatePartial ( - Class 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. - */ - protected boolean store (T record) - throws DatabaseException - { - @SuppressWarnings("unchecked") final Class pClass = (Class) record.getClass(); - requireNotComputed(pClass, "store"); - - final DepotMarshaller marsh = _ctx.getMarshaller(pClass); - Key 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(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 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. - */ - protected int delete (T record) - throws DatabaseException - { - @SuppressWarnings("unchecked") Class type = (Class)record.getClass(); - Key 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. - */ - protected int delete (Key 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. - */ - protected int deleteAll (Class 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 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. - */ - protected int deleteAll ( - Class 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(); - } - }); - } - // make sure the given type corresponds to a concrete class protected void requireNotComputed (Class type, String action) throws DatabaseException