summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShawn O. Pearce <sop@google.com>2010-06-07 12:59:29 -0700
committerShawn O. Pearce <sop@google.com>2010-06-07 12:59:29 -0700
commit099e8d35121a774afd57bfdafd763646f2e8bbf1 (patch)
treef92d45c9542e13cb5f7b3a3999fd52ce03498d96
parentfb190fe5de8151f0633016167f09fc805bc62286 (diff)
downloadgwtorm-099e8d35121a774afd57bfdafd763646f2e8bbf1.tar.gz
Update missing Javadoc around the NoSQL API
Change-Id: I6863f7b2241fc13ae169a7d19e013cdfb76334b0 Signed-off-by: Shawn O. Pearce <sop@google.com>
-rw-r--r--src/main/java/com/google/gwtorm/nosql/IndexFunction.java4
-rw-r--r--src/main/java/com/google/gwtorm/nosql/NoSqlAccess.java23
-rw-r--r--src/main/java/com/google/gwtorm/nosql/generic/GenericAccess.java60
-rw-r--r--src/main/java/com/google/gwtorm/nosql/generic/GenericDatabase.java27
-rw-r--r--src/main/java/com/google/gwtorm/nosql/generic/GenericSchema.java113
5 files changed, 204 insertions, 23 deletions
diff --git a/src/main/java/com/google/gwtorm/nosql/IndexFunction.java b/src/main/java/com/google/gwtorm/nosql/IndexFunction.java
index 4678500..edc83b9 100644
--- a/src/main/java/com/google/gwtorm/nosql/IndexFunction.java
+++ b/src/main/java/com/google/gwtorm/nosql/IndexFunction.java
@@ -34,8 +34,8 @@ public abstract class IndexFunction<T> {
* Should this object exist in the index?
* <p>
* Objects that shouldn't appear in this index are skipped because field
- * values are currently {@code null} or because one or more constants doesn't
- * match as expected.
+ * values are currently {@code null}, or because one or more field values do
+ * not match the constants used in the query that defines the index.
*
* @param object the object to read fields from.
* @return true if the object should be indexed by this index.
diff --git a/src/main/java/com/google/gwtorm/nosql/NoSqlAccess.java b/src/main/java/com/google/gwtorm/nosql/NoSqlAccess.java
index b52a65f..7ba92bb 100644
--- a/src/main/java/com/google/gwtorm/nosql/NoSqlAccess.java
+++ b/src/main/java/com/google/gwtorm/nosql/NoSqlAccess.java
@@ -47,6 +47,13 @@ public abstract class NoSqlAccess<T, K extends Key<?>> extends
protected abstract ResultSet<T> scan(String indexName, byte[] fromKey,
byte[] toKey, int limit) throws OrmException;
+ /**
+ * Lookup a query index by its unique name.
+ *
+ * @param indexName the name of the index.
+ * @return the IndexFunction for {@code indexName}
+ * @throws OrmException if no IndexFunction exists for the supplied name.
+ */
protected IndexFunction<T> getQueryIndex(String indexName)
throws OrmException {
for (IndexFunction<T> f : getQueryIndexes()) {
@@ -60,15 +67,29 @@ public abstract class NoSqlAccess<T, K extends Key<?>> extends
throw new OrmException("No index named " + indexName);
}
- // -- These are all provided by AccessGen when builds a subclass --
+ // -- These are all provided by AccessGen when it builds a subclass --
+ /** @return name of the relation in the schema. */
protected abstract String getRelationName();
+ /** @return encoder/decoder for the object data. */
protected abstract ProtobufCodec<T> getObjectCodec();
+ /** @return encoder/decoder for the primary key of the object. */
protected abstract IndexFunction<T> getKeyIndex();
+ /** @return array of indexes to support queries. */
protected abstract IndexFunction<T>[] getQueryIndexes();
+ /**
+ * Encode the primary key of the object.
+ * <p>
+ * This method is similar to {@link #getKeyIndex()}, except it can encode the
+ * key without needing an object instance. This is suitable for key based
+ * lookup of the row.
+ *
+ * @param dst builder the key components will be added into.
+ * @param key the object primary key.
+ */
protected abstract void encodeKey(IndexKeyBuilder dst, K key);
}
diff --git a/src/main/java/com/google/gwtorm/nosql/generic/GenericAccess.java b/src/main/java/com/google/gwtorm/nosql/generic/GenericAccess.java
index 7578d70..19cd2c5 100644
--- a/src/main/java/com/google/gwtorm/nosql/generic/GenericAccess.java
+++ b/src/main/java/com/google/gwtorm/nosql/generic/GenericAccess.java
@@ -239,9 +239,17 @@ public abstract class GenericAccess<T, K extends Key<?>> extends
}
}
- private void pruneOldIndexes(final T oldObj, T newObj) throws OrmException {
- // Prune any old index records which no longer match.
- //
+ /**
+ * Remove old secondary index rows that are no longer valid for an object.
+ *
+ * @param oldObj an old copy of the object, prior to the current update taking
+ * place. If null the method does nothing and simply returns.
+ * @param newObj the new copy of the object. Index rows that are still valid
+ * for {@code #newObj} are left alone. If null, all index rows for
+ * {@code oldObj} are removed.
+ * @throws OrmException the data store is unable to remove an index row.
+ */
+ protected void pruneOldIndexes(final T oldObj, T newObj) throws OrmException {
if (oldObj != null) {
for (IndexFunction<T> f : getQueryIndexes()) {
if (f.includes(oldObj)) {
@@ -308,7 +316,19 @@ public abstract class GenericAccess<T, K extends Key<?>> extends
}
}
- private boolean matches(IndexFunction<T> f, T obj, byte[] exp) {
+ /**
+ * Determine if an object still matches the index row.
+ * <p>
+ * This method checks that the object's fields still match the criteria
+ * necessary for it to be part of the index defined by {@code f}. It also
+ * formats the index key and validates it is still identical to {@code exp}.
+ *
+ * @param f the function that defines the index.
+ * @param obj the object instance being tested; must not be null.
+ * @param exp the index row key, as scanned from the index.
+ * @return true if the object still matches the data encoded in {@code #exp}.
+ */
+ protected boolean matches(IndexFunction<T> f, T obj, byte[] exp) {
return f.includes(obj) && Arrays.equals(exp, indexRowKey(f, obj));
}
@@ -320,19 +340,43 @@ public abstract class GenericAccess<T, K extends Key<?>> extends
return b.toByteArray();
}
- private byte[] indexRowKey(IndexFunction<T> f, T obj) {
+ /**
+ * Generate the row key for an object's secondary index row.
+ * <p>
+ * The default implementation uses the relation name, '.', the index name, a
+ * delimiter, the indexed fields encoded, a delimiter, and then the encoded
+ * primary key (without the relation name prefix).
+ * <p>
+ * The object's primary key is always appended onto the end of the secondary
+ * index row key to ensure that objects with the same field values still get
+ * distinct rows in the secondary index.
+ *
+ * @param idx function that describes the index.
+ * @param obj the object the index record should reference.
+ * @return the encoded secondary index row key.
+ */
+ protected byte[] indexRowKey(IndexFunction<T> idx, T obj) {
IndexKeyBuilder b = new IndexKeyBuilder();
b.add(getRelationName());
b.add('.');
- b.add(f.getName());
+ b.add(idx.getName());
b.delimiter();
- f.encode(b, obj);
+ idx.encode(b, obj);
b.delimiter();
getKeyIndex().encode(b, obj);
return b.toByteArray();
}
- private byte[] indexRowData(T obj) {
+ /**
+ * Generate the data to store in a secondary index row for an object.
+ * <p>
+ * The default implementation of this method stores the encoded primary key,
+ * and the current system timestamp.
+ *
+ * @param obj the object the index record should reference.
+ * @return the encoded secondary index row data.
+ */
+ protected byte[] indexRowData(T obj) {
final long now = System.currentTimeMillis();
final IndexKeyBuilder b = new IndexKeyBuilder();
diff --git a/src/main/java/com/google/gwtorm/nosql/generic/GenericDatabase.java b/src/main/java/com/google/gwtorm/nosql/generic/GenericDatabase.java
index 90828f9..3ee0f2f 100644
--- a/src/main/java/com/google/gwtorm/nosql/generic/GenericDatabase.java
+++ b/src/main/java/com/google/gwtorm/nosql/generic/GenericDatabase.java
@@ -17,9 +17,36 @@ package com.google.gwtorm.nosql.generic;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.Schema;
import com.google.gwtorm.nosql.NoSqlDatabase;
+import com.google.gwtorm.nosql.NoSqlSchema;
import java.util.concurrent.TimeUnit;
+/**
+ * Base class for generic NoSQL typed databases.
+ * <p>
+ * The generic types provide basic NoSQL implementation assuming a handful of
+ * primitive operations are available inside of the implementation's extension
+ * of {@link GenericSchema}. All relations are stored within the same key space,
+ * using the relation name as a prefix for the row's primary or secondary key.
+ * <p>
+ * Applications should use the database class to create instances of their
+ * Schema extension interface, and thus open and connect to the data store.
+ * <p>
+ * Creating a new database instance is expensive, due to the type analysis and
+ * code generation performed to implement the Schema and Access interfaces.
+ * Applications should create and cache their database instance for the life of
+ * the application.
+ * <p>
+ * Database instances are thread-safe, but returned Schema instances are not.
+ * <p>
+ * This class must be further extended by the NoSQL implementation to configure
+ * the connectivity with the data store and supply the correct subclass of
+ * {@link NoSqlSchema} that knows how to interact with the data store.
+ *
+ * @param <T> type of the application's Schema.
+ * @param <S> type of the implementation's base for Schema implementations.
+ * @param <A> type of the implementation's base for Access implementations.
+ */
public abstract class GenericDatabase<T extends Schema, S extends GenericSchema, A extends GenericAccess>
extends NoSqlDatabase<T, S, A> {
private static final long DEFAULT_FOSSIL_AGE =
diff --git a/src/main/java/com/google/gwtorm/nosql/generic/GenericSchema.java b/src/main/java/com/google/gwtorm/nosql/generic/GenericSchema.java
index 79e65bd..34dc09a 100644
--- a/src/main/java/com/google/gwtorm/nosql/generic/GenericSchema.java
+++ b/src/main/java/com/google/gwtorm/nosql/generic/GenericSchema.java
@@ -21,10 +21,18 @@ import com.google.gwtorm.nosql.CounterShard;
import com.google.gwtorm.nosql.IndexKeyBuilder;
import com.google.gwtorm.nosql.IndexRow;
import com.google.gwtorm.nosql.NoSqlSchema;
+import com.google.gwtorm.schema.SequenceModel;
import java.util.Map;
-/** Base implementation for {@link Schema} in a {@link GenericDatabase}. */
+/**
+ * Base implementation for {@link Schema} in a {@link GenericDatabase}.
+ * <p>
+ * NoSQL implementors must extend this class and provide implementations for the
+ * abstract methods declared here. Each schema instance will wrap one thread's
+ * connection to the data store. Therefore, unlike database, this class does not
+ * need to be thread-safe.
+ */
public abstract class GenericSchema extends NoSqlSchema {
private final GenericDatabase<?, ?, ?> db;
@@ -33,10 +41,28 @@ public abstract class GenericSchema extends NoSqlSchema {
db = d;
}
+ /** @return the database that created this schema instance. */
public GenericDatabase<?, ?, ?> getDatabase() {
return db;
}
+ /**
+ * Allocate a new unique value from a pool of values.
+ * <p>
+ * This method is only required to return a unique value per invocation.
+ * Implementors may override the method to provide an implementation that
+ * returns values out of order.
+ * <p>
+ * The default implementation of this method stores a {@link CounterShard}
+ * under the row key {@code ".sequence." + poolName}, and updates it through
+ * the atomic semantics of {@link #atomicUpdate(byte[], AtomicUpdate)}. If the
+ * row does not yet exist, it is initialized and the value 1 is returned.
+ *
+ * @param poolName name of the value pool to allocate from. This is typically
+ * the name of a sequence in the schema.
+ * @return a new unique value.
+ * @throws OrmException a unique value cannot be obtained.
+ */
@Override
protected long nextLong(final String poolName) throws OrmException {
IndexKeyBuilder b = new IndexKeyBuilder();
@@ -87,40 +113,103 @@ public abstract class GenericSchema extends NoSqlSchema {
upsert(key, data);
}
+ /**
+ * Update a single row, inserting it if it does not exist.
+ * <p>
+ * Unlike insert, this method always succeeds.
+ *
+ * @param key key of the row to update, or insert if missing.
+ * @param data data to store at this row.
+ * @throws OrmException the data store cannot process the request, for example
+ * due to a network connectivity problem.
+ */
public abstract void upsert(byte[] key, byte[] data) throws OrmException;
+ /**
+ * Delete the row stored under the given key.
+ * <p>
+ * If the row does not exist, this method must complete successfully anyway.
+ * The intent of the caller is to ensure the row does not exist when the
+ * method completes, and a row that did not exist satisfies that intent.
+ *
+ * @param key the key to delete.
+ * @throws OrmException the data store cannot perform the removal.
+ */
public abstract void delete(byte[] key) throws OrmException;
/**
* Atomically read and update a single row.
* <p>
- * Unlike the schema atomicUpdate, this method handles missing rows.
- * Implementations must be logically equivalent to the following, but
+ * Unlike schema's atomicUpdate() method, this method must handle missing
+ * rows. Implementations must be logically equivalent to the following, but
* performed atomically within the scope of the single row key:
*
* <pre>
* byte[] oldData = get(key);
* byte[] newData = update.update(oldData);
- * if (newData != null)
+ * if (newData != null) {
* upsert(key, newData);
- * else if (oldData != null) remove(key);
+ * } else if (oldData != null) {
+ * remove(key);
+ * }
* return data;
* </pre>
+ * <p>
+ * Secondary index row updates are assumed to never be part of the atomic
+ * update transaction. This is an intentional design decision to fit with many
+ * NoSQL product's limitations to support only single-row atomic updates.
+ * <p>
+ * The {@code update} method may be invoked multiple times before the
+ * operation is considered successful. This permits an implementation to
+ * perform an opportunistic update attempt, and retry the update if the same
+ * row was modified by another concurrent worker.
*
* @param key the row key to read, update and return.
- * @param update action to perform on the row's data element. May be passed in
- * null if the row doesn't exist, and should return the new row data,
- * or null to remove the row.
- * @return the return value of {@code update}.
+ * @param update action to perform on the row's data element. The action may
+ * be passed null if the row doesn't exist.
* @throws OrmException the database cannot perform the update.
*/
public abstract byte[] atomicUpdate(byte[] key, AtomicUpdate<byte[]> update)
throws OrmException;
- public void maybeFossilCollectIndexRow(long now, byte[] key, IndexRow r)
- throws OrmException {
- if (r.getTimestamp() + db.getMaxFossilAge() <= now) {
+ /**
+ * Check (and delete) an index row if its a fossil.
+ * <p>
+ * As index rows are written ahead of the main data row being written out,
+ * scans sometimes see an index row that does not match the data row. These
+ * are ignored for a short period ({@link GenericDatabase#getMaxFossilAge()})
+ * to allow the primary data row to eventually get written out. If however the
+ * writer never finished the update, these index rows are stale and need to be
+ * pruned. Any index row older than the fossil age is removed by this method.
+ *
+ * @param now timestamp when the current scan started.
+ * @param key the index row key.
+ * @param row the index row data.
+ */
+ public void maybeFossilCollectIndexRow(long now, byte[] key, IndexRow row) {
+ if (row.getTimestamp() + db.getMaxFossilAge() <= now) {
+ fossilCollectIndexRow(key, row);
+ }
+ }
+
+ /**
+ * Delete the given fossil index row.
+ * <p>
+ * This method is logically the same as {@link #delete(byte[])}, but its
+ * separated out to permit asynchronous delivery of the delete events since
+ * these are arriving during an index scan and are less time-critical than
+ * other delete operations.
+ * <p>
+ * The default implementation of this method calls {@link #delete(byte[])}.
+ *
+ * @param key index key to remove.
+ * @param row the index row data.
+ */
+ protected void fossilCollectIndexRow(byte[] key, IndexRow row) {
+ try {
delete(key);
+ } catch (OrmException e) {
+ // Ignore a fossil delete error.
}
}