Java: first draft of the syncbase API
Change-Id: I75a1ae76ea6f754b20242d13a726ac65bb9bbfd0
diff --git a/lib/.gitignore b/lib/.gitignore
new file mode 100644
index 0000000..6e35645
--- /dev/null
+++ b/lib/.gitignore
@@ -0,0 +1,16 @@
+# Vanadium
+/.v23
+
+# Gradle
+.gradle/
+/build/
+.idea/
+/generated-src/
+
+# Eclipse
+.classpath
+.project
+.settings
+
+# IntelliJ
+lib.iml
diff --git a/lib/build.gradle b/lib/build.gradle
new file mode 100644
index 0000000..ccb6d77
--- /dev/null
+++ b/lib/build.gradle
@@ -0,0 +1,71 @@
+buildscript {
+ repositories {
+ mavenCentral()
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.2.3'
+ classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0'
+ }
+}
+
+apply plugin: 'java'
+
+repositories {
+ mavenCentral()
+
+ maven {
+ url "http://srdjan.mtv:8081/nexus/content/repositories/test_repo/"
+ }
+}
+
+dependencies {
+ compile 'io.v:vanadium:0.1-SNAPSHOT'
+ compile group: 'com.google.guava', name: 'guava', version: '18.0'
+ testCompile group: 'junit', name: 'junit', version: '4.12'
+ testCompile group: 'com.google.truth', name: 'truth', version: '0.25'
+}
+
+class VanadiumEnvironment {
+ File v23Root;
+ File v23Bin;
+
+ public static getVanadiumEnvironment() {
+ VanadiumEnvironment result = new VanadiumEnvironment()
+
+ if (!System.getenv().containsKey('V23_ROOT')) {
+ throw new InvalidUserDataException("V23_ROOT is not set. "
+ + "Please follow the Vanadium installation instructions at "
+ + "https://v.io/installation/index.html")
+ }
+
+ result.v23Root = new File(System.getenv()['V23_ROOT'])
+ result.v23Bin = new File(result.v23Root, ['devtools', 'bin', 'v23'].join(File.separator))
+ if (!result.v23Bin.exists() || !result.v23Bin.isFile() || !result.v23Bin.canExecute()) {
+ throw new InvalidUserDataException(
+ result.v23Bin.toString() + " does not exist or is not an executable file. "
+ + "Please follow the Vanadium installation instructions at "
+ + "https://v.io/installation/index.html")
+ }
+ return result
+ }
+}
+
+def v23Root = VanadiumEnvironment.getVanadiumEnvironment().v23Root.getAbsolutePath()
+def v23Bin = VanadiumEnvironment.getVanadiumEnvironment().v23Bin.getAbsolutePath()
+def vdlBin = [v23Root, 'release', 'go', 'bin', 'vdl'].join(File.separator)
+def vdlPath = [ [v23Root, 'roadmap', 'go', 'src'].join(File.separator),
+ [v23Root, 'release', 'go', 'src'].join(File.separator) ].join(":")
+
+sourceSets.main.java.srcDirs += 'generated-src/vdl'
+
+task buildVdlTool(type: Exec) {
+ commandLine v23Bin, 'go', 'install', 'v.io/x/ref/cmd/vdl'
+}
+
+task generateVdl(type: Exec, dependsOn: buildVdlTool) {
+ environment VDLPATH: vdlPath
+ commandLine vdlBin, 'generate', '--lang=java', '--java-out-dir=generated-src/vdl', 'all'
+}
+
+tasks.'compileJava'.dependsOn(generateVdl)
\ No newline at end of file
diff --git a/lib/gradle/wrapper/gradle-wrapper.jar b/lib/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..085a1cd
--- /dev/null
+++ b/lib/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/lib/gradle/wrapper/gradle-wrapper.properties b/lib/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..5787069
--- /dev/null
+++ b/lib/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu Jun 18 09:35:48 PDT 2015
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-bin.zip
diff --git a/lib/gradlew b/lib/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/lib/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/SyncbaseApp.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/SyncbaseApp.java
new file mode 100644
index 0000000..4d0be37
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/SyncbaseApp.java
@@ -0,0 +1,77 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.syncbase.v23.services.syncbase;
+
+import io.v.syncbase.v23.services.syncbase.nosql.Database;
+import io.v.syncbase.v23.services.syncbase.nosql.Schema;
+import io.v.syncbase.v23.services.syncbase.util.AccessController;
+import io.v.v23.context.VContext;
+import io.v.v23.security.access.Permissions;
+import io.v.v23.verror.VException;
+
+/**
+ * A handle for an app running as part of a {@link SyncbaseService}.
+ */
+public interface SyncbaseApp extends AccessController {
+ /**
+ * Returns the relative name of this app.
+ */
+ String name();
+
+ /**
+ * Returns the full (i.e., object) name of this app.
+ */
+ String fullName();
+
+ /**
+ * Returns {@code true} iff this app exists and the user has sufficient
+ * permissions to access it.
+ *
+ * @param ctx Vanadium context
+ * @return {@code true} iff this app exists and the user has sufficient
+ * permissions to access it
+ * @throws VException if the app's existence couldn't be determined
+ */
+ boolean exists(VContext ctx) throws VException;
+
+ /**
+ * Returns a handle for a NoSQL database with the provided name.
+ * <p>
+ * Note that this database may not yet exist and can be created using the
+ * {@link Database#create} method.
+ *
+ * @param relativeName name of the NoSQL database; must not contain slashes
+ * @param schema database schema; it can be {@code null} only if schema was never
+ * set for the database in the first place
+ * @return a handle for a NoSQL database with the provided name
+ */
+ Database getNoSqlDatabase(String relativeName, Schema schema);
+
+ /**
+ * Returns a list of all database names.
+ *
+ * @param ctx Vanadium context
+ * @throws VException if the list of database names couldn't be retrieved
+ */
+ String[] listDatabases(VContext ctx) throws VException;
+
+ /**
+ * Creates the app.
+ *
+ * @param ctx Vanadium context
+ * @param perms app permissions; if {@code null}, {@link SyncbaseService}'s
+ * permissions are used
+ * @throws VException if the app couldn't be created
+ */
+ void create(VContext ctx, Permissions perms) throws VException;
+
+ /**
+ * Deletes the app.
+ *
+ * @param ctx Vanadium context
+ * @throws VException if the app couldn't be deleted
+ */
+ void delete(VContext ctx) throws VException;
+}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/SyncbaseService.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/SyncbaseService.java
new file mode 100644
index 0000000..62d2bb5
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/SyncbaseService.java
@@ -0,0 +1,37 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+package io.v.syncbase.v23.services.syncbase;
+
+import io.v.v23.context.VContext;
+import io.v.syncbase.v23.services.syncbase.util.AccessController;
+import io.v.v23.verror.VException;
+
+/**
+ * The interface for a Vanadium Syncbase service.
+ */
+public interface SyncbaseService extends AccessController {
+ /**
+ * Returns the full (i.e., object) name of this service.
+ */
+ String fullName();
+
+ /**
+ * Returns the handle to an app with the given name.
+ * <p>
+ * Note that this app may not yet exist and can be created using the
+ * {@link SyncbaseApp#create} call.
+ *
+ * @param relativeName name of the given app. May not contain slashes
+ * @return the handle to an app with the given name
+ */
+ SyncbaseApp getApp(String relativeName);
+
+ /**
+ * Returns a list of all app names.
+ *
+ * @param ctx Vanadium context
+ * @throws VException if the list of app names couldn't be retrieved
+ */
+ String[] listApps(VContext ctx) throws VException;
+}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/BatchDatabase.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/BatchDatabase.java
new file mode 100644
index 0000000..c3cedbb
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/BatchDatabase.java
@@ -0,0 +1,33 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+package io.v.syncbase.v23.services.syncbase.nosql;
+
+import io.v.v23.context.VContext;
+import io.v.v23.verror.VException;
+
+/**
+ * A handle to a set of reads and writes to the database that should be considered an atomic unit.
+ * <p>
+ * See {@link Database#beginBatch Database.beginBatch()} for concurrency semantics.
+ */
+public interface BatchDatabase extends DatabaseCore {
+ /**
+ * Persists the pending changes to the database.
+ *
+ * @param ctx Vanadium context
+ * @throws VException if there was an error committing the changes
+ */
+ void commit(VContext ctx) throws VException;
+
+ /**
+ * Notifies the server that any pending changes can be discarded.
+ * <p>
+ * This method is not strictly required, but it may allow the server to release locks
+ * or other resources sooner than if it was not called.
+ *
+ * @param ctx Vanadium context
+ * @throws VException if there was an error discarding the changes
+ */
+ void abort(VContext ctx) throws VException;
+}
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Database.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Database.java
new file mode 100644
index 0000000..d1f2e2f
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Database.java
@@ -0,0 +1,128 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+package io.v.syncbase.v23.services.syncbase.nosql;
+
+import io.v.v23.context.VContext;
+import io.v.v23.security.access.Permissions;
+import io.v.syncbase.v23.services.syncbase.util.AccessController;
+import io.v.v23.verror.VException;
+
+/**
+ * A database interface, which is logically a collection of {@link Table}s.
+ */
+public interface Database extends DatabaseCore, AccessController {
+ /**
+ * Returns {@code true} iff this database exists and the user has sufficient
+ * permissions to access it.
+ *
+ * @param ctx Vanadium context
+ * @return {@code true} iff this database exists and the user has sufficient
+ * permissions to access it
+ * @throws VException if the database's existence couldn't be determined
+ */
+ boolean exists(VContext ctx) throws VException;
+
+ /**
+ * Creates this database.
+ *
+ * @param ctx Vanadium context
+ * @param perms database permissions; if {@code null},
+ * {@link io.v.syncbase.v23.services.syncbase.SyncbaseApp}'s
+ * permissions are used
+ * @throws VException if the database couldn't be created
+ */
+ void create(VContext ctx, Permissions perms) throws VException;
+
+ /**
+ * Deletes this database.
+ *
+ * @param ctx Vanadium context
+ * @throws VException if the database couldn't be deleted
+ */
+ void delete(VContext ctx) throws VException;
+
+ /**
+ * Creates the table with the given name.
+ *
+ * @param ctx Vanadium context
+ * @param relativeName relative name of the table; must not contain {@code /}
+ * @param perms table permissions; if {@code null}, {@link Database}'s
+ * permissions are used
+ * @throws VException if the table couldn't be created
+ */
+ void createTable(VContext ctx, String relativeName, Permissions perms) throws VException;
+
+ /**
+ * Deletes the table with the given name.
+ *
+ * @param ctx Vanadium context
+ * @throws VException if the table couldn't be deleted
+ */
+ void deleteTable(VContext ctx) throws VException;
+
+ /**
+ * Creates a new "batch", i.e., a handle to a set of reads and writes to the database that
+ * should be considered an atomic unit. Instead of calling this function directly, clients are
+ * encouraged to use the {@link NoSql#runInBatch NoSql.runInBatch()} helper function, which
+ * detects "concurrent batch" errors and handles retries internally.
+ * <p>
+ * Default concurrency semantics are as follows:
+ * <ul>
+ * <li> Reads (e.g. {@code get}s, {@code scan}s) inside a batch operate over a consistent
+ * snapshot taken during {@link #beginBatch beginBatch()}, and will see the effects of prior
+ * writes performed inside the batch.</li>
+ * <li> {@link BatchDatabase#commit commit()} may fail with {@link Errors#CONCURRENT_BATCH},
+ * indicating that after {@link #beginBatch beginBatch()} but before
+ * {@link BatchDatabase#commit commit()}, some concurrent routine wrote to a key that matches
+ * a key or row-range read inside this batch.</li>
+ * <li> Other methods will never fail with error {@link Errors#CONCURRENT_BATCH}, even if it
+ * is known that {@link BatchDatabase#commit commit()} will fail with this
+ * error.</li>
+ * </ul>
+ * <p>
+ * Once a batch has been committed or aborted, subsequent method calls will
+ * fail with no effect.
+ * <p>
+ * Concurrency semantics can be configured using BatchOptions.
+ *
+ * @param ctx Vanadium context
+ * @param opts batch options
+ * @return a handle to a set of reads and writes to the database that should be
+ * considered an atomic unit
+ * @throws VException if the batch couldn't be created
+ */
+ BatchDatabase beginBatch(VContext ctx, BatchOptions opts) throws VException;
+
+ /**
+ * Returns a handle to a database {@link SyncGroup} with the given name.
+ *
+ * @param name name of the synchronization group
+ */
+ SyncGroup getSyncGroup(String name);
+
+ /**
+ * Returns the global names of all {@link SyncGroup}s attached to this database.
+ *
+ * @param ctx Vanadium context
+ * @throws VException if the sync group names couldn't be retrieved
+ */
+ String[] listSyncGroupNames(VContext ctx) throws VException;
+
+ /**
+ * Compares the current schema version of the database with the schema version provided while
+ * creating this database handle. If the current database schema version is lower, then the
+ * {@link SchemaUpgrader} associated with the schema is called. If {@link SchemaUpgrader} is
+ * successful, this method stores the new schema metadata in database.
+ * <p>
+ * Note: this database handle may have been created with a {@code null} schema, in which case
+ * this method skips schema check and the caller is responsible for maintaining schema sanity.
+ *
+ * @param ctx Vanadium context
+ * @return {@code true} iff the database schema had to be upgraded, i.e., if the
+ * current database schema version was lower than the schema version with
+ * which the database was created
+ * @throws VException if there was an error upgrading the schema
+ */
+ boolean upgradeIfOutdated(VContext ctx) throws VException;
+}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/DatabaseCore.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/DatabaseCore.java
new file mode 100644
index 0000000..bf11ea6
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/DatabaseCore.java
@@ -0,0 +1,58 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+package io.v.syncbase.v23.services.syncbase.nosql;
+
+import io.v.v23.context.VContext;
+import io.v.v23.verror.VException;
+
+/**
+ * Base interface for {@link Database} and {@link BatchDatabase}, allowing clients to pass the
+ * handle to helper methods that are batch-agnostic.
+ */
+public interface DatabaseCore {
+ /**
+ * Returns the relative name of the database.
+ */
+ String name();
+
+ /**
+ * Returns the full (i.e., object) name of the database.
+ */
+ String fullName();
+
+ /**
+ * Returns the table with the given name.
+ * <p>
+ * Note that this table may not yet exist and can be created using the {@link Table#create}
+ * method.)
+ *
+ * @param relativeName name of the table; must not contain slashes
+ */
+ Table getTable(String relativeName);
+
+ /**
+ * Returns a list of all table names.
+ *
+ * @param ctx Vanadium context
+ * @return a list of all table names
+ * @throws VException if the list of table names couldn't be retrieved
+ */
+ String[] listTables(VContext ctx) throws VException;
+
+ /**
+ * Executes a SyncQL query, returning a {@link ResultStream} object that allows the caller to
+ * iterate over arrays of values for each row that matches the query.
+ * <p>
+ * It is legal to perform writes concurrently with {@link #exec exec()}. The returned stream reads
+ * from a consistent snapshot taken at the time of the method and will not reflect subsequent
+ * writes to keys not yet reached by the stream.
+ *
+ * @param ctx Vanadium context
+ * @param query a SyncQL query
+ * @return a {@link ResultStream} object that allows the caller to iterate over
+ * arrays of values for each row that matches the query
+ * @throws VException if there was an error executing the query
+ */
+ ResultStream exec(VContext ctx, String query) throws VException;
+}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/NoSql.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/NoSql.java
new file mode 100644
index 0000000..5fd7c62
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/NoSql.java
@@ -0,0 +1,82 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.syncbase.v23.services.syncbase.nosql;
+
+import io.v.v23.context.VContext;
+import io.v.v23.verror.VException;
+
+/**
+ * Various utility methods for the NoSql database.
+ */
+public class NoSql {
+ /**
+ * Creates a {@link RowRange} representing a single row.
+ */
+ public static RowRange singleRowRange(String row) {
+ return new RowRangeImpl(row);
+ }
+
+ /**
+ * Creates a {@link RowRange} represented by the provided {@code [start, limit)} parameters.
+ */
+ public static RowRange range(String start, String limit) {
+ return new RowRangeImpl(start, limit);
+ }
+
+ /**
+ * Creates {@link PrefixRange} with the provided prefix.
+ */
+ public static PrefixRange prefix(String prefix) {
+ return new PrefixRangeImpl(prefix);
+ }
+
+ /**
+ * Interface for a batch operation that is executed as part of {@link #runInBatch runInBatch()}.
+ */
+ public static interface BatchOperation {
+ /**
+ * Performs the batch operation.
+ *
+ * @param db batch database on which the operation is performed
+ * @throws VException if there was an error performing the operation; if thrown, the
+ * batch operation is aborted
+ */
+ void run(BatchDatabase db) throws VException;
+ }
+
+ /**
+ * Runs the given batch operation, managing retries and
+ * {@link BatchDatabase#commit commit()}/{@link BatchDatabase#abort abort()}s.
+ *
+ * @param ctx Vanadium context
+ * @param db database on which the batch operation is to be performed
+ * @param opts batch configuration
+ * @param op batch operation
+ * @throws VException if there was an error executing the given batch operation
+ */
+ public static void runInBatch(VContext ctx, Database db, BatchOptions opts, BatchOperation op)
+ throws VException {
+ for (int i = 0; i < 3; ++i) {
+ BatchDatabase batch = db.beginBatch(ctx, opts);
+ try {
+ op.run(batch);
+ } catch (VException e) {
+ batch.abort(ctx);
+ throw e;
+ }
+ try {
+ batch.commit(ctx);
+ return;
+ } catch (VException e) {
+ if (!e.getID().equals(Errors.CONCURRENT_BATCH)) {
+ throw e;
+ }
+ }
+ }
+ throw Errors.newConcurrentBatch(ctx);
+ }
+
+ private NoSql() {}
+}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/PrefixRange.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/PrefixRange.java
new file mode 100644
index 0000000..d550ab4
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/PrefixRange.java
@@ -0,0 +1,15 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.syncbase.v23.services.syncbase.nosql;
+
+/**
+ * Represents all rows with keys that have some prefix.
+ */
+public interface PrefixRange extends RowRange {
+ /**
+ * Returns the prefix shared by all the keys in the range.
+ */
+ String prefix();
+}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/PrefixRangeImpl.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/PrefixRangeImpl.java
new file mode 100644
index 0000000..00e5170
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/PrefixRangeImpl.java
@@ -0,0 +1,30 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.syncbase.v23.services.syncbase.nosql;
+
+import io.v.syncbase.v23.services.syncbase.util.Util;
+
+class PrefixRangeImpl implements PrefixRange {
+ private final String prefix;
+
+ PrefixRangeImpl(String prefix) {
+ this.prefix = prefix;
+ }
+
+ @Override
+ public String start() {
+ return Util.prefixRangeStart(prefix);
+ }
+
+ @Override
+ public String limit() {
+ return Util.prefixRangeLimit(prefix);
+ }
+
+ @Override
+ public String prefix() {
+ return this.prefix;
+ }
+}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/ResultStream.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/ResultStream.java
new file mode 100644
index 0000000..8ff2771
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/ResultStream.java
@@ -0,0 +1,29 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+package io.v.syncbase.v23.services.syncbase.nosql;
+
+import io.v.v23.vdl.VdlValue;
+import io.v.v23.verror.VException;
+
+/**
+ * An interface for iterating through rows resulting from a
+ * {@link DatabaseCore#exec DatabaseCore.exec()}.
+ */
+public interface ResultStream extends Stream {
+ /**
+ * Returns an array of column names that matched the query. The number of values returned
+ * by each call to {@link #result} will match the size of this array.
+ */
+ String[] columnNames();
+
+ /**
+ * Returns the result that was staged by {@link #advance}.
+ * <p>
+ * This method does not block.
+ *
+ * @throws VException if the value could not be decoded or if {@link #advance} returned
+ * {@code false} or was not called at all
+ */
+ VdlValue[] result() throws VException;
+}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Row.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Row.java
new file mode 100644
index 0000000..ba953bd
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Row.java
@@ -0,0 +1,56 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+package io.v.syncbase.v23.services.syncbase.nosql;
+
+import io.v.v23.context.VContext;
+import io.v.v23.verror.VException;
+
+/**
+ * A handle for a single row in a {@link Table}.
+ */
+public interface Row {
+ /**
+ * Returns the primary key for this row.
+ */
+ String key();
+
+ /**
+ * Returns the full (i.e., object) name of this row.
+ */
+ String fullName();
+
+ /**
+ * Returns {@code true} iff this row exists and the caller has permissions
+ * to access it.
+ *
+ * @param ctx Vanadium context
+ * @throws VException if the row's existence couldn't be determined
+ */
+ boolean exists(VContext ctx) throws VException;
+
+ /**
+ * Deletes this row.
+ *
+ * @param ctx Vanadium context
+ * @throws VException if the row couldn't be deleted
+ */
+ void delete(VContext ctx) throws VException;
+
+ /**
+ * Returns the value for this row.
+ *
+ * @param ctx Vanadium context
+ * @throws VException if the value couldn't be retrieved
+ */
+ Object get(VContext ctx) throws VException;
+
+ /**
+ * Writes the given value for this row.
+ *
+ * @param ctx Vanadium context
+ * @param value value to write
+ * @throws VException if the value couldn't be written
+ */
+ void put(VContext ctx, Object value) throws VException;
+}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/RowRange.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/RowRange.java
new file mode 100644
index 0000000..e1f9503
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/RowRange.java
@@ -0,0 +1,21 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.syncbase.v23.services.syncbase.nosql;
+
+/**
+ * Represents all rows with keys in {@code [start, limit)}. If limit is {@code ""}, all rows with
+ * keys ≥ {@code start} are included.
+ */
+public interface RowRange {
+ /**
+ * Returns the key that marks the start of the row range.
+ */
+ public String start();
+
+ /**
+ * Returns the key that marks the limit of the row range.
+ */
+ public String limit();
+}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/RowRangeImpl.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/RowRangeImpl.java
new file mode 100644
index 0000000..7363fc1
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/RowRangeImpl.java
@@ -0,0 +1,24 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.syncbase.v23.services.syncbase.nosql;
+
+class RowRangeImpl implements RowRange {
+ private final String start, limit;
+
+ RowRangeImpl(String start, String limit) {
+ this.start = start;
+ this.limit = limit;
+ }
+
+ RowRangeImpl(String row) {
+ this.start = this.limit = row;
+ }
+
+ @Override
+ public String start() { return this.start; }
+
+ @Override
+ public String limit() { return this.limit; }
+}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/ScanStream.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/ScanStream.java
new file mode 100644
index 0000000..54bcee8
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/ScanStream.java
@@ -0,0 +1,31 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+package io.v.syncbase.v23.services.syncbase.nosql;
+
+import io.v.v23.verror.VException;
+
+/**
+ * An interface for iterating through a collection of key/value pairs (obtained via
+ * {@link Table#scan Table.scan()}).
+ */
+public interface ScanStream extends Stream {
+ /**
+ * Returns the key of the element that was staged by {@link #advance}.
+ * <p>
+ * This method does not block.
+ *
+ * @throws VException if {@link #advance} returned {@code false} or was not called at all
+ */
+ String key() throws VException;
+
+ /**
+ * Returns the value of the element that was staged by {@link #advance}.
+ * <p>
+ * This method does not block.
+ *
+ * @throws VException if the value could not be decoded or if {@link #advance} returned
+ * {@code false} or was not called at all
+ */
+ Object value() throws VException;
+}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Schema.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Schema.java
new file mode 100644
index 0000000..9c036c7
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Schema.java
@@ -0,0 +1,41 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.syncbase.v23.services.syncbase.nosql;
+
+/**
+ * A database schema.
+ * <p>
+ * Each database has a schema associated with it which defines the current version of the
+ * database. When a new version of an app wishes to change its data in a way that it is not
+ * compatible with the old app's data, this app must change the schema version and provide relevant
+ * upgrade logic in the specified {@link SchemaUpgrader}. The conflict resolution rules are also
+ * associated with the schema version. Hence if the conflict resolution rules change then the schema
+ * version also must be bumped.
+ */
+public class Schema {
+ private final SchemaMetadata metadata;
+ private final SchemaUpgrader upgrader;
+
+ /**
+ * Creates a new database schema with the specified metadata and schema upgrader.
+ * <p>
+ * Note: {@link SchemaUpgrader} is purely local and is not persisted.
+ */
+ public Schema(SchemaMetadata metadata, SchemaUpgrader upgrader) {
+ this.metadata = metadata;
+ this.upgrader = upgrader;
+ }
+
+ /**
+ * Returns the metadata related to this schema.
+ */
+ public SchemaMetadata metadata() { return this.metadata; }
+
+ /**
+ * Returns the upgrade logic used for upgrading the schema when an app's schema version differs
+ * from the database's schema version.
+ */
+ public SchemaUpgrader upgrader() { return this.upgrader; }
+}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/SchemaUpgrader.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/SchemaUpgrader.java
new file mode 100644
index 0000000..72e45af
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/SchemaUpgrader.java
@@ -0,0 +1,25 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.syncbase.v23.services.syncbase.nosql;
+
+import io.v.v23.verror.VException;
+
+/**
+ * An interface that must be implemented by an app in order to upgrade the database schema from
+ * a lower version to a higher version.
+ */
+public interface SchemaUpgrader {
+ /**
+ * Updgrades database from an old to the new schema version.
+ * <p>
+ * This method must be idempotent.
+ *
+ * @param db database to be upgraded
+ * @param oldVersion old schema version
+ * @param newVersion new schema version
+ * @throws VException if the database couldn't be upgraded
+ */
+ void run(Database db, int oldVersion, int newVersion) throws VException;
+}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Stream.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Stream.java
new file mode 100644
index 0000000..15aa392
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Stream.java
@@ -0,0 +1,42 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+package io.v.syncbase.v23.services.syncbase.nosql;
+
+import io.v.v23.verror.VException;
+
+/**
+ * An interface for iterating through a collection of elements.
+ */
+public interface Stream {
+ /**
+ * Stages an element so that the client can retrieve it.
+ * <p>
+ * Returns {@code true} iff there is an element to retrieve.
+ * <p>
+ * The client must call {@link #advance} before retrieving the element.
+ * <p>
+ * The client must call {@link #cancel} if it does not iterate through all elements
+ * (i.e. until {@link #advance} returns {@code false}).
+ * <p>
+ * This method may block if an element is not immediately available.
+ *
+ * @return {@code true} iff there is an element to retrieve
+ * @throws VException if there was an error advancing the stream
+ */
+ boolean advance() throws VException;
+
+ /**
+ * Notifies the stream provider that it can stop producing elements. The client must call
+ * {@link #cancel} if it does not iterate through all elements (i.e. until {@link #advance})
+ * returns {@code false}).
+ * <p>
+ * This method is idempotent and can be called concurrently with a thread that is
+ * iterating via {@link #advance}.
+ * <p>
+ * This method causes {@link #advance} to subsequently return {@code false}.
+ * <p>
+ * This method does not block.
+ */
+ void cancel();
+}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/SyncGroup.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/SyncGroup.java
new file mode 100644
index 0000000..7c3071a
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/SyncGroup.java
@@ -0,0 +1,144 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+package io.v.syncbase.v23.services.syncbase.nosql;
+
+import java.util.Map;
+
+import io.v.v23.context.VContext;
+import io.v.v23.verror.VException;
+
+/**
+ * A handle for a {@link Database} synchronization group.
+ */
+public interface SyncGroup {
+ /**
+ * Returns {@code true} iff this sync group exists and the caller has at least {@code read}
+ * permissions on it.
+ *
+ * @param ctx Vanadium context
+ * @throws VException if the sync group's existence couldn't be determined
+ */
+ boolean exists(VContext ctx) throws VException;
+
+ /**
+ * Creates a new sync group with the given spec. Requires:
+ * <p>
+ * <ul>
+ * <li> client must have at least {@code read} access on the database,</li>
+ * <li> prefix ACL must exist at each sync group prefix,</li>
+ * <li> client must have at least {@code read} access on each of these prefix ACLs.</li>
+ * </ul>
+ *
+ * @param ctx Vanadium context
+ * @param spec sync group specification
+ * @param info creator's membership information
+ * @throws VException if the sync group couldn't be created
+ */
+ void create(VContext ctx, SyncGroupSpec spec, SyncGroupMemberInfo info) throws VException;
+
+ /**
+ * Joins a sync group. Requires:
+ * <p>
+ * <ul>
+ * <li> client must have at least {@code read} access on the database and on the sync group
+ * ACLs.</li>
+ * </ul>
+ *
+ * @param ctx Vanadium context
+ * @param info joiner's membership information
+ * @return sync group specification
+ * @throws VException if the sync group couldn't be joined
+ */
+ SyncGroupSpec join(VContext ctx, SyncGroupMemberInfo info) throws VException;
+
+ /**
+ * Leaves the sync group. Previously synced data will continue to be available. Requires:
+ * <p>
+ * <ul>
+ * <li> client must have at least {@code read} access on the database.</li>
+ * </ul>
+ *
+ * @param ctx Vanadium context
+ * @throws VException if the sync group couldn't be left
+ */
+ void leave(VContext ctx) throws VException;
+
+ /**
+ * Destroys the sync group. Previously synced data will continue to be available to all
+ * members. Requires:
+ * <p>
+ * <ul>
+ * <li> client must have at least {@code read} access on the database and an {@code admin}
+ * access on the sync group ACLs.</li>
+ * </ul>
+ *
+ * @param ctx Vanadium context
+ * @throws VException if the sync group couldn't be destroyed
+ */
+ void destroy(VContext ctx) throws VException;
+
+ /**
+ * Ejects a member from the sync group. The ejected member will not be able to sync further,
+ * but will retain any data it has already synced. Requires:
+ * <p>
+ * <ul>
+ * <li> client must have at least {@code read} access on the database and an {@code admin}
+ * access on the sync group ACLs.</li>
+ * </ul>
+ *
+ * @param ctx Vanadium context
+ * @param member member to be ejected
+ * @throws VException if the member couldn't be ejected
+ */
+ void eject(VContext ctx, String member) throws VException;
+
+ /**
+ * Returns the sync group specification, along with its version string. The version number
+ * allows for atomic read-modify-write of the specification (see {@link #setSpec setSpec()}).
+ * Requires:
+ * <p>
+ * <ul>
+ * <li> client must have at least {@code read} access on the database and on the sync group
+ * ACLs.</li>
+ * </ul>
+ *
+ * @param ctx Vanadium context
+ * @return sync group specification along with its version number. The returned
+ * map is guaranteed to be non-{@code null} and contain exactly one member
+ * @throws VException if the sync group specification couldn't be retrieved
+ */
+ Map<String, SyncGroupSpec> getSpec(VContext ctx) throws VException;
+
+ /**
+ * Sets the sync group specification. The version string may be either empty or the value of
+ * the previous {@link #getSpec getSpec()}, in which case {@link #setSpec setSpec()} will only
+ * succeed iff the current version matches the specified one.
+ * <p>
+ * <ul>
+ * <li> client must have at least {@code read} access on the database and an {@code admin}
+ * access on the sync group ACLs.</li>
+ * </ul>
+ *
+ * @param ctx Vanadium context
+ * @param spec the new sync group specification
+ * @param version expected version string of the sync group; if non-empty, this
+ * method will only succeed if the current sync group's version matches
+ * this value
+ * @throws VException if the sync group specification couldn't be set
+ */
+ void setSpec(VContext ctx, SyncGroupSpec spec, String version) throws VException;
+
+ /**
+ * Returns the information on membership of the sync group. Requires:
+ * <p>
+ * <ul>
+ * <li> client must have at least {@code read} access on the database and on the sync group
+ * ACLs.</li>
+ * </ul>
+ *
+ * @param ctx Vanadium context
+ * @throws VException [description]
+ */
+ Map<String, SyncGroupMemberInfo> getMembers(VContext ctx) throws VException;
+}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Table.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Table.java
new file mode 100644
index 0000000..ee89215
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Table.java
@@ -0,0 +1,131 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.syncbase.v23.services.syncbase.nosql;
+
+import io.v.v23.context.VContext;
+import io.v.v23.security.access.Permissions;
+import io.v.v23.verror.VException;
+
+/**
+ * Interface for a database table, i.e., a collection of {@link Row}s.
+ */
+public interface Table {
+ /**
+ * Returns the relative name of this table.
+ */
+ String name();
+
+ /**
+ * Returns the full (i.e., object) name of this table.
+ */
+ String fullName();
+
+ /**
+ * Returns {@code true} iff this table exists and the caller has sufficient permissions
+ * to access it.
+ *
+ * @param ctx Vanadium context
+ * @throws VException if the table's existence couldn't be determined
+ */
+ boolean exists(VContext ctx) throws VException;
+
+ /**
+ * Returns the row with the given primary key.
+ *
+ * @param key primary key of the row
+ */
+ Row getRow(String key);
+
+ /**
+ * Returns the value for the given primary key.
+ *
+ * @param ctx Vanadium context
+ * @param key the primary key for a row
+ * @throws VException if the value couldn't be retrieved or if its class doesn't match the
+ * provided class
+ */
+ Object get(VContext ctx, String key) throws VException;
+
+ /**
+ * Writes the value to the table under the provided primary key.
+ *
+ * @param ctx Vanadium context
+ * @param key primary key under which the value is to be written
+ * @param value value to be written
+ * @throws VException if the value couldn't be written
+ */
+ void put(VContext ctx, String key, Object value) throws VException;
+
+ /**
+ * Deletes all rows in the given half-open range {@code [start, limit)}. If {@code limit} is
+ * {@code ""}, all rows with keys ≥ {@code start} are deleted.
+ *
+ * @param ctx Vanadium context
+ * @param range range of rows to be deleted
+ * @throws VException if the rows couldn't be deleted
+ */
+ void delete(VContext ctx, RowRange range) throws VException;
+
+ /**
+ * Returns all rows in the given half-open range {@code [start, limit)}. If {@code limit}
+ * is {@code ""}, all rows with keys ≥ {@code start} are included.
+ * <p>
+ * It is legal to perform writes concurrently with {@link #scan scan()}. The returned stream
+ * reads from a consistent snapshot taken at the time of the method and will not reflect
+ * subsequent writes to keys not yet reached by the stream.
+ *
+ * See helpers {@link NoSql#prefix NoSql.prefix()}, {@link NoSql#range NoSql.range()},
+ * and {@link NoSql#singleRowRange NoSql.singleRowRange()}.
+ *
+ * @param ctx Vanadium context
+ * @param range range of rows to be read
+ * @return a {@link ScanStream} used for iterating over the snapshot of the provided rows
+ */
+ ScanStream scan(VContext ctx, RowRange range);
+
+ /**
+ * Returns an array of {@link PrefixPermissions} (i.e., {@code (prefix, perms)} pairs) for
+ * the row with the given key.
+ * <p>
+ * The array is sorted from longest prefix to shortest, so element zero is the one that
+ * applies to the row with the given key. The last element is always the prefix {@code ""}
+ * which represents the table's permissions -- the array will always have at least one element.
+ *
+ * @param ctx Vanadium context
+ * @param key key of the row whose permission prefixes are to be retrieved
+ * @return an array of prefix permissions for the given row
+ * @throws VException if the prefix permissions couldn't be retrieved
+ */
+ PrefixPermissions[] getPermissions(VContext ctx, String key) throws VException;
+
+ /**
+ * Sets the permissions for all current and future rows with the given prefix. If the prefix
+ * overlaps with an existing prefix, the longest prefix that matches a row applies.
+ * For example:
+ * <p><blockquote><pre>
+ * setPermissions(ctx, NoSql.prefix("a/b"), perms1);
+ * setPermissions(ctx, NoSql.prefix("a/b/c"), perms2);
+ * </pre></blockquote><p>
+ * The permissions for row {@code "a/b/1"} are {@code perms1}, and the permissions for row
+ * {@code "a/b/c/1"} are {@code perms2}.
+ *
+ * @param ctx Vanadium context
+ * @param prefix prefix to which to apply the new permissions
+ * @param perms permissions to apply
+ * @throws VException if the permissions couldn't be applied
+ */
+ void setPermissions(VContext ctx, PrefixRange prefix, Permissions perms) throws VException;
+
+ /**
+ * Deletes permissions for the specified prefix. Any rows covered by this prefix will use the
+ * next longest prefix's permissions. (See the array returned by
+ * {@link #getPermissions getPermissions()}).
+ *
+ * @param ctx Vanadium context
+ * @param prefix prefix for which the permissions are to be deleted
+ * @throws VException if the permissions couldn't be deleted
+ */
+ void deletePermissions(VContext ctx, PrefixRange prefix) throws VException;
+}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/util/AccessController.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/util/AccessController.java
new file mode 100644
index 0000000..2bb241b
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/util/AccessController.java
@@ -0,0 +1,44 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+package io.v.syncbase.v23.services.syncbase.util;
+
+import java.util.Map;
+
+import io.v.v23.context.VContext;
+import io.v.v23.security.access.Permissions;
+import io.v.v23.verror.VException;
+
+/**
+ * Provides access control for various syncbase objects.
+ */
+public interface AccessController {
+ /**
+ * Replaces the current permissions for an object.
+ * <p>
+ * For a detailed documentation, see
+ * {@link io.v.v23.services.permissions.ObjectClient#setPermissions}.
+ *
+ * @param ctx Vanadium context
+ * @param perms new permissions for the object
+ * @param version object's permissions version, which allows for optional, optimistic
+ * concurrency control. If non-empty, this value must come from
+ * {@link #getPermissions getPermissions()}. If empty,
+ * {@link #setPermissions setPermissions()} performs an unconditional update.
+ * @throws VException if the object permissions couldn't be updated
+ */
+ void setPermissions(VContext ctx, Permissions perms, String version) throws VException;
+
+ /**
+ * Returns the current permissions for an object.
+ * <p>
+ * For detailed documentation, see
+ * {@link io.v.v23.services.permissions.ObjectClient#getPermissions}.
+ *
+ * @param ctx Vanadium context
+ * @return object permissions along with its version number. The returned map
+ * is guaranteed to be non-{@code null} and contain exactly one member.
+ * @throws VException if the object permissions couldn't be retrieved
+ */
+ Map<String, Permissions> getPermissions(VContext ctx) throws VException;
+}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/util/Util.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/util/Util.java
new file mode 100644
index 0000000..2d5cf07
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/util/Util.java
@@ -0,0 +1,43 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.syncbase.v23.services.syncbase.util;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Various syncbase utility methods.
+ */
+public class Util {
+ /**
+ * Returns the start of the row range for the given prefix.
+ */
+ public static String prefixRangeStart(String prefix) {
+ return prefix;
+ }
+
+ /**
+ * Returns the limit of the row range for the given prefix.
+ */
+ public static String prefixRangeLimit(String prefix) {
+ // We convert a string to a byte[] array, which can be thought of as a base-256
+ // number. The code below effectively adds 1 to this number, then chops off any
+ // trailing 0x00 bytes. If the input string consists entirely of 0xFF, an empty string
+ // will be returned.
+ try {
+ byte[] bytes = prefix.getBytes("ISO8859-1");
+ int last = bytes.length - 1;
+ for (; last >= 0 && bytes[last] == (byte) 0xFF; --last);
+ if (last < 0) {
+ return "";
+ }
+ bytes[last] += 1;
+ return new String(bytes, 0, last + 1, "ISO8859-1");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("JVM must support ISO8859-1 char encoding", e);
+ }
+ }
+
+ private Util() {}
+}
diff --git a/lib/src/test/java/io/v/syncbase/v23/services/syncbase/util/UtilTest.java b/lib/src/test/java/io/v/syncbase/v23/services/syncbase/util/UtilTest.java
new file mode 100644
index 0000000..c5a2aeb
--- /dev/null
+++ b/lib/src/test/java/io/v/syncbase/v23/services/syncbase/util/UtilTest.java
@@ -0,0 +1,46 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.syncbase.v23.services.syncbase.util;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assert_;
+
+import java.util.Arrays;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the various {@link Util} methods.
+ */
+public class UtilTest extends TestCase {
+ public void testPrefixRange() {
+ List<String[]> tests = Arrays.asList(new String[][] {
+ {"", "", ""},
+ {"a", "a", "b"},
+ {"aa", "aa", "ab"},
+ {"\u00fe", "\u00fe", "\u00ff"},
+ {"a\u00fe", "a\u00fe", "a\u00ff"},
+ {"aa\u00fe", "aa\u00fe", "aa\u00ff"},
+ {"a\u00ff", "a\u00ff", "b"},
+ {"aa\u00ff", "aa\u00ff", "ab"},
+ {"a\u00ff\u00ff", "a\u00ff\u00ff", "b"},
+ {"aa\u00ff\u00ff", "aa\u00ff\u00ff", "ab"},
+ {"\u00ff", "\u00ff", ""},
+ {"\u00ff\u00ff", "\u00ff\u00ff", ""}
+ });
+ for (String[] test : tests) {
+ String prefix = test[0];
+ String start = test[1];
+ String limit = test[2];
+ String actualStart = Util.prefixRangeStart(prefix);
+ String actualLimit = Util.prefixRangeLimit(prefix);
+ assert_().withFailureMessage("Failed for prefix: " + prefix)
+ .that(actualStart).isEqualTo(start);
+ assert_().withFailureMessage("Failed for prefix: " + prefix)
+ .that(actualLimit).isEqualTo(limit);
+ }
+ }
+}