Java: first syncbase client implementation

Few notes:
  - for now, we add the "Type" parameter in get() and set()s,
    as we don't yet have the VOM support we need.
  - tests will be done in a separate CL.

Change-Id: I37a5f9ff36504e6721bc7624aec7158d3d24e65e
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/Syncbase.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/Syncbase.java
new file mode 100644
index 0000000..80af359
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/Syncbase.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;
+
+/**
+ * Various syncbase utility methods.
+ */
+public class Syncbase {
+    /**
+     * Returns a new handle to a syncbase service running at the given name.
+     *
+     * @param  fullName full (i.e., object) name of the syncbase service
+     */
+    public static SyncbaseService newService(String fullName) {
+        return new SyncbaseServiceImpl(fullName);
+    }
+
+    private Syncbase() {}
+}
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/SyncbaseAppImpl.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/SyncbaseAppImpl.java
new file mode 100644
index 0000000..50f428e
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/SyncbaseAppImpl.java
@@ -0,0 +1,69 @@
+// 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 java.util.Map;
+
+import com.google.common.collect.ImmutableMap;
+
+import io.v.impl.google.naming.NamingUtil;
+import io.v.syncbase.v23.services.syncbase.nosql.Database;
+import io.v.syncbase.v23.services.syncbase.nosql.NoSql;
+import io.v.syncbase.v23.services.syncbase.nosql.Schema;
+import io.v.syncbase.v23.services.syncbase.util.Util;
+import io.v.v23.context.VContext;
+import io.v.v23.security.access.Permissions;
+import io.v.v23.services.permissions.ObjectClient.GetPermissionsOut;
+import io.v.v23.verror.VException;
+
+class SyncbaseAppImpl implements SyncbaseApp {
+    private final String fullName;
+    private final String name;
+    private final AppClient client;
+
+    SyncbaseAppImpl(String parentFullName, String relativeName) {
+        this.fullName = NamingUtil.join(parentFullName, relativeName);
+        this.name = relativeName;
+        this.client = AppClientFactory.getAppClient(this.fullName);
+    }
+
+    @Override
+    public String name() {
+        return this.name;
+    }
+    @Override
+    public String fullName() {
+        return this.fullName;
+    }
+    @Override
+    public boolean exists(VContext ctx) throws VException {
+        return this.client.exists(ctx);
+    }
+    @Override
+    public Database getNoSqlDatabase(String relativeName, Schema schema) {
+        return NoSql.newDatabase(this.fullName, relativeName, schema);
+    }
+    @Override
+    public String[] listDatabases(VContext ctx) throws VException {
+        return Util.list(ctx, this.fullName);
+    }
+    @Override
+    public void create(VContext ctx, Permissions perms) throws VException {
+        this.client.create(ctx, perms);
+    }
+    @Override
+    public void delete(VContext ctx) throws VException {
+        this.client.delete(ctx);
+    }
+    @Override
+    public void setPermissions(VContext ctx, Permissions perms, String version) throws VException {
+        this.client.setPermissions(ctx, perms, version);
+    }
+    @Override
+    public Map<String, Permissions> getPermissions(VContext ctx) throws VException {
+        GetPermissionsOut perms = this.client.getPermissions(ctx);
+        return ImmutableMap.of(perms.version, perms.perms);
+    }
+}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/SyncbaseServiceImpl.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/SyncbaseServiceImpl.java
new file mode 100644
index 0000000..32bfb44
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/SyncbaseServiceImpl.java
@@ -0,0 +1,47 @@
+// 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 java.util.Map;
+
+import com.google.common.collect.ImmutableMap;
+
+import io.v.syncbase.v23.services.syncbase.util.Util;
+import io.v.v23.context.VContext;
+import io.v.v23.security.access.Permissions;
+import io.v.v23.services.permissions.ObjectClient.GetPermissionsOut;
+import io.v.v23.verror.VException;
+
+class SyncbaseServiceImpl implements SyncbaseService {
+    private final String fullName;
+    private final ServiceClient client;
+
+    SyncbaseServiceImpl(String fullName) {
+        this.fullName = fullName;
+        this.client = ServiceClientFactory.getServiceClient(fullName);
+    }
+
+    @Override
+    public String fullName() {
+        return this.fullName;
+    }
+    @Override
+    public SyncbaseApp getApp(String relativeName) {
+        return new SyncbaseAppImpl(this.fullName, relativeName);
+    }
+    @Override
+    public String[] listApps(VContext ctx) throws VException {
+        return Util.list(ctx, this.fullName);
+    }
+    @Override
+    public void setPermissions(VContext ctx, Permissions perms, String version) throws VException {
+        this.client.setPermissions(ctx, perms, version);
+    }
+    @Override
+    public Map<String, Permissions> getPermissions(VContext ctx) throws VException {
+        GetPermissionsOut perms = this.client.getPermissions(ctx);
+        return ImmutableMap.of(perms.version, perms.perms);
+    }
+}
\ No newline at end of file
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
index d1f2e2f..ec2aa98 100644
--- 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
@@ -56,10 +56,11 @@
     /**
      * Deletes the table with the given name.
      *
-     * @param  ctx        Vanadium context
-     * @throws VException if the table couldn't be deleted
+     * @param  ctx           Vanadium context
+     * @param  relativeName  relative name of the table; must not contain {@code /}
+     * @throws VException    if the table couldn't be deleted
      */
-    void deleteTable(VContext ctx) throws VException;
+    void deleteTable(VContext ctx, String relativeName) throws VException;
 
     /**
      * Creates a new "batch", i.e., a handle to a set of reads and writes to the database that
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
index bf11ea6..f4ad6fe 100644
--- 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
@@ -23,9 +23,6 @@
 
     /**
      * 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
      */
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/DatabaseImpl.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/DatabaseImpl.java
new file mode 100644
index 0000000..9e185de
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/DatabaseImpl.java
@@ -0,0 +1,200 @@
+// 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.io.EOFException;
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+import com.google.common.collect.ImmutableMap;
+
+import io.v.impl.google.naming.NamingUtil;
+import io.v.syncbase.v23.services.syncbase.nosql.Schema;
+import io.v.syncbase.v23.services.syncbase.util.Util;
+import io.v.v23.context.CancelableVContext;
+import io.v.v23.context.VContext;
+import io.v.v23.security.access.Permissions;
+import io.v.v23.services.permissions.ObjectClient.GetPermissionsOut;
+import io.v.v23.vdl.TypedClientStream;
+import io.v.v23.vdl.VdlAny;
+import io.v.v23.vdl.VdlOptional;
+import io.v.v23.verror.VException;
+
+class DatabaseImpl implements Database, BatchDatabase {
+    private final String parentFullName;
+    private final String fullName;
+    private final String name;
+    private final Schema schema;
+    private final DatabaseClient client;
+
+    DatabaseImpl(String parentFullName, String relativeName, Schema schema) {
+        this.parentFullName = parentFullName;
+        this.fullName = NamingUtil.join(parentFullName, relativeName);
+        this.name = relativeName;
+        this.schema = schema;
+        this.client = DatabaseClientFactory.getDatabaseClient(this.fullName);
+    }
+
+    // Implements DatabaseCore interface.
+    @Override
+    public String name() {
+        return this.name;
+    }
+    @Override
+    public String fullName() {
+        return this.fullName;
+    }
+    @Override
+    public Table getTable(String relativeName) {
+        return new TableImpl(this.fullName, relativeName, getSchemaVersion());
+    }
+    @Override
+    public String[] listTables(VContext ctx) throws VException {
+        return Util.list(ctx, this.fullName);
+    }
+    @Override
+    public ResultStream exec(VContext ctx, String query) throws VException {
+        CancelableVContext ctxC = ctx.withCancel();
+        TypedClientStream<Void, List<VdlAny>, Void> stream =
+                this.client.exec(ctxC, getSchemaVersion(), query);
+
+        // The first row contains column names, pull them off the stream.
+        List<VdlAny> row = null;
+        try {
+            row = stream.recv();
+        } catch (EOFException e) {
+            throw new VException("Got empty exec() stream for query: " + query);
+        }
+        String[] columnNames = new String[row.size()];
+        for (int i = 0; i < row.size(); ++i) {
+            Serializable elem = row.get(i).getElem();
+            if (elem instanceof String) {
+                columnNames[i] = (String) elem;
+            } else {
+                throw new VException("Expected first row in exec() stream to contain column " +
+                        "names (of type String), got type: " + elem.getClass());
+            }
+        }
+        return new ResultStreamImpl(ctxC, stream, columnNames);
+    }
+
+    // Implements AccessController interface.
+    @Override
+    public void setPermissions(VContext ctx, Permissions perms, String version) throws VException {
+        this.client.setPermissions(ctx, perms, version);
+    }
+    @Override
+    public Map<String, Permissions> getPermissions(VContext ctx) throws VException {
+        GetPermissionsOut perms = this.client.getPermissions(ctx);
+        return ImmutableMap.of(perms.version, perms.perms);
+    }
+
+    // Implements Database interface.
+    @Override
+    public boolean exists(VContext ctx) throws VException {
+        return this.client.exists(ctx, getSchemaVersion());
+    }
+    @Override
+    public void create(VContext ctx, Permissions perms) throws VException {
+        SchemaMetadata metadata = this.schema != null ? this.schema.getMetadata() : null;
+        this.client.create(ctx, VdlOptional.of(metadata), perms);
+    }
+    @Override
+    public void delete(VContext ctx) throws VException {
+        this.client.delete(ctx, getSchemaVersion());
+    }
+    @Override
+    public void createTable(VContext ctx, String relativeName, Permissions perms)
+            throws VException {
+        String tableFullName = NamingUtil.join(this.fullName, relativeName);
+        TableClient table = TableClientFactory.getTableClient(tableFullName);
+        table.create(ctx, getSchemaVersion(), perms);
+    }
+    @Override
+    public void deleteTable(VContext ctx, String relativeName) throws VException {
+        String tableFullName = NamingUtil.join(this.fullName, relativeName);
+        TableClient table = TableClientFactory.getTableClient(tableFullName);
+        table.delete(ctx, getSchemaVersion());
+    }
+    @Override
+    public BatchDatabase beginBatch(VContext ctx, BatchOptions opts) throws VException {
+        String relativeName = this.client.beginBatch(ctx, getSchemaVersion(), opts);
+        return new DatabaseImpl(this.parentFullName, relativeName, this.schema);
+    }
+    @Override
+    public SyncGroup getSyncGroup(String name) {
+        return new SyncGroupImpl(this.fullName, name);
+    }
+    @Override
+    public String[] listSyncGroupNames(VContext ctx) throws VException {
+        List<String> names = this.client.getSyncGroupNames(ctx);
+        return names.toArray(new String[names.size()]);
+    }
+    @Override
+    public boolean upgradeIfOutdated(VContext ctx) throws VException {
+        if (this.schema == null) {
+            throw new VException(io.v.v23.flow.Errors.BAD_STATE, ctx,
+                    "Schema or SchemaMetadata cannot be null. A valid Schema needs to be used " +
+                    "when creating a database handle.");
+        }
+        if (this.schema.getMetadata().getVersion() < 0) {
+            throw new VException(io.v.v23.flow.Errors.BAD_STATE, ctx,
+                    "Schema version cannot be less than zero.");
+        }
+        SchemaManager schemaManager = new SchemaManager(this.fullName);
+        SchemaMetadata currMetadata = null;
+        try {
+             currMetadata = schemaManager.getSchemaMetadata(ctx);
+        } catch (VException eGet) {
+            // If the client app did not set a schema as part of Database.Create(),
+            // getSchemaMetadata() will return Errors.NO_EXIST. If so we set the schema
+            // here.
+            if (!eGet.getID().equals(io.v.v23.verror.Errors.NO_EXIST)) {
+                throw eGet;
+            }
+            try {
+                schemaManager.setSchemaMetadata(ctx, this.schema.getMetadata());
+            } catch (VException eSet) {
+                if (!eSet.getID().equals(io.v.v23.verror.Errors.NO_EXIST)) {
+                    throw eSet;
+                }
+            }
+            return false;
+        }
+        if (currMetadata.getVersion() >= this.schema.getMetadata().getVersion()) {
+            return false;
+        }
+        // Call the Upgrader provided by the app to upgrade the schema.
+        //
+        // TODO(jlodhia): disable sync before running Upgrader and reenable
+        // once Upgrader is finished.
+        //
+        // TODO(jlodhia): prevent other processes (local/remote) from accessing
+        // the database while upgrade is in progress.
+        this.schema.getUpgrader().run(this,
+                currMetadata.getVersion(), this.schema.getMetadata().getVersion());
+
+        // Update the schema metadata in db to the latest version.
+        schemaManager.setSchemaMetadata(ctx, this.schema.getMetadata());
+        return true;
+    }
+
+    // Implements BatchDatabase.
+    @Override
+    public void commit(VContext ctx) throws VException {
+        this.client.commit(ctx, getSchemaVersion());
+    }
+    @Override
+    public void abort(VContext ctx) throws VException {
+        this.client.abort(ctx, getSchemaVersion());
+    }
+
+    private int getSchemaVersion() {
+        if (this.schema == null) {
+            return -1;
+        }
+        return this.schema.getMetadata().getVersion();
+    }
+}
\ 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
index 5fd7c62..4540823 100644
--- 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
@@ -11,24 +11,28 @@
  * Various utility methods for the NoSql database.
  */
 public class NoSql {
+    public static Database newDatabase(String parentFullName, String relativeName, Schema schema) {
+        return new DatabaseImpl(parentFullName, relativeName, schema);
+    }
+
     /**
      * Creates a {@link RowRange} representing a single row.
      */
-    public static RowRange singleRowRange(String row) {
+    public static RowRange newSingleRowRange(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) {
+    public static RowRange newRowRange(String start, String limit) {
         return new RowRangeImpl(start, limit);
     }
 
     /**
      * Creates {@link PrefixRange} with the provided prefix.
      */
-    public static PrefixRange prefix(String prefix) {
+    public static PrefixRange newPrefixRange(String prefix) {
         return new PrefixRangeImpl(prefix);
     }
 
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
index d550ab4..4745827 100644
--- 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
@@ -11,5 +11,5 @@
     /**
      * Returns the prefix shared by all the keys in the range.
      */
-    String prefix();
+    String getPrefix();
 }
\ 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
index 00e5170..80b9546 100644
--- 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
@@ -14,17 +14,17 @@
     }
 
     @Override
-    public String start() {
+    public String getStart() {
         return Util.prefixRangeStart(prefix);
     }
 
     @Override
-    public String limit() {
+    public String getLimit() {
         return Util.prefixRangeLimit(prefix);
     }
 
     @Override
-    public String prefix() {
+    public String getPrefix() {
         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
index 8ff2771..e106e36 100644
--- 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
@@ -1,29 +1,39 @@
 // 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.vdl.VdlAny;
 import io.v.v23.verror.VException;
 
 /**
  * An interface for iterating through rows resulting from a
  * {@link DatabaseCore#exec DatabaseCore.exec()}.
+ *
+ * The {@link java.util.Iterator} returned by the {@link java.lang.Iterable#iterator iterator}:
+ * <p><ul>
+ *     <li>can be created <strong>only</strong> once,
+ *     <li>does not support {@link java.util.Iterator#remove remove}</li>, and
+ *     <li>may throw {@link RuntimeException} if the underlying syncbase read throws
+ *         a {@link VException}.  The {@link RuntimeException#getCause cause} of the
+ *         {@link RuntimeException} will be the said {@link VException}.</li>
+ * </ul>
  */
-public interface ResultStream extends Stream {
+public interface ResultStream extends Iterable<VdlAny[]> {
     /**
-     * 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.
+     * Returns an array of column names that matched the query.  The size of the {@link VdlAny}
+     * array returned in every iteration will match the size of this array.
      */
     String[] columnNames();
 
     /**
-     * Returns the result that was staged by {@link #advance}.
+     * Notifies the stream provider that it can stop producing elements.  The client must call
+     * {@link #cancel} if it does not iterate through all the elements.
      * <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
+     * This method is idempotent and can be called concurrently with a thread that is iterating.
+     * <p>
+     * This method causes the iterator to (gracefully) terminate early.
      */
-    VdlValue[] result() throws VException;
+    void cancel() throws VException;
 }
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/ResultStreamImpl.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/ResultStreamImpl.java
new file mode 100644
index 0000000..bb33b9f
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/ResultStreamImpl.java
@@ -0,0 +1,69 @@
+// 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 com.google.common.collect.AbstractIterator;
+
+import java.io.EOFException;
+import java.util.Iterator;
+import java.util.List;
+
+import io.v.v23.context.CancelableVContext;
+import io.v.v23.vdl.TypedClientStream;
+import io.v.v23.vdl.VdlAny;
+import io.v.v23.verror.VException;
+
+class ResultStreamImpl implements ResultStream {
+    private final CancelableVContext ctxC;
+    private final TypedClientStream<Void, List<VdlAny>, Void> stream;
+    private final String[] columnNames;
+    private volatile boolean isCanceled;
+    private volatile boolean isCreated;
+
+    ResultStreamImpl(CancelableVContext ctxC, TypedClientStream<Void, List<VdlAny>, Void> stream,
+            String[] columnNames) {
+        this.ctxC = ctxC;
+        this.stream = stream;
+        this.columnNames = columnNames;
+        this.isCanceled = this.isCreated = false;
+    }
+    // Implements Iterable.
+    @Override
+    public synchronized Iterator<VdlAny[]> iterator() {
+        if (this.isCreated) {
+            throw new RuntimeException("Can only create one ResultStream iterator.");
+        }
+        this.isCreated = true;
+        return new AbstractIterator<VdlAny[]>() {
+            @Override
+            protected VdlAny[] computeNext() {
+                synchronized (ResultStreamImpl.this) {
+                    if (ResultStreamImpl.this.isCanceled) {  // client canceled the stream
+                        return endOfData();
+                    }
+                    try {
+                        List<VdlAny> result = ResultStreamImpl.this.stream.recv();
+                        return result.toArray(new VdlAny[result.size()]);
+                    } catch (EOFException e) {  // legitimate end of stream
+                        return endOfData();
+                    } catch (VException e) {
+                        throw new RuntimeException("Error retrieving next stream element.", e);
+                    }
+                }
+            }
+        };
+    }
+
+    // Implements ResultStream.
+    @Override
+    public String[] columnNames() {
+        return this.columnNames;
+    }
+    @Override
+    public synchronized void cancel() throws VException {
+        this.isCanceled = true;
+        this.stream.finish();
+    }
+}
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
index ba953bd..4ba462f 100644
--- 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
@@ -3,6 +3,8 @@
 // license that can be found in the LICENSE file.
 package io.v.syncbase.v23.services.syncbase.nosql;
 
+import java.lang.reflect.Type;
+
 import io.v.v23.context.VContext;
 import io.v.v23.verror.VException;
 
@@ -43,7 +45,7 @@
      * @param  ctx        Vanadium context
      * @throws VException if the value couldn't be retrieved
      */
-    Object get(VContext ctx) throws VException;
+    Object get(VContext ctx, Type type) throws VException;
 
     /**
      * Writes the given value for this row.
@@ -52,5 +54,5 @@
      * @param  value      value to write
      * @throws VException if the value couldn't be written
      */
-    void put(VContext ctx, Object value) throws VException;
+    void put(VContext ctx, Object value, Type type) throws VException;
 }
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/RowImpl.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/RowImpl.java
new file mode 100644
index 0000000..0451dd8
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/RowImpl.java
@@ -0,0 +1,53 @@
+// 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.lang.reflect.Type;
+
+import io.v.impl.google.naming.NamingUtil;
+import io.v.syncbase.v23.services.syncbase.util.Util;
+import io.v.v23.context.VContext;
+import io.v.v23.verror.VException;
+import io.v.v23.vom.VomUtil;
+
+class RowImpl implements Row {
+    private final String fullName;
+    private final String key;
+    private final int schemaVersion;
+    private final RowClient client;
+
+    RowImpl(String parentFullName, String key, int schemaVersion) {
+        this.fullName = NamingUtil.join(parentFullName, key);
+        this.key = key;
+        this.schemaVersion = schemaVersion;
+        this.client = RowClientFactory.getRowClient(this.fullName);
+    }
+    @Override
+    public String key() {
+        return this.key;
+    }
+    @Override
+    public String fullName() {
+        return this.fullName;
+    }
+    @Override
+    public boolean exists(VContext ctx) throws VException {
+        return this.client.exists(ctx, this.schemaVersion);
+    }
+    @Override
+    public void delete(VContext ctx) throws VException {
+        this.client.delete(ctx, this.schemaVersion);
+    }
+    @Override
+    public Object get(VContext ctx, Type type) throws VException {
+        byte[] data = this.client.get(ctx, this.schemaVersion);
+        return VomUtil.decode(data, type);
+    }
+    @Override
+    public void put(VContext ctx, Object value, Type type) throws VException {
+        byte[] data = VomUtil.encode(value, type);
+        this.client.put(ctx, this.schemaVersion, data);
+    }
+}
\ 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
index e1f9503..7de840d 100644
--- 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
@@ -12,10 +12,10 @@
     /**
      * Returns the key that marks the start of the row range.
      */
-    public String start();
+    public String getStart();
 
     /**
      * Returns the key that marks the limit of the row range.
      */
-    public String limit();
+    public String getLimit();
 }
\ 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
index 7363fc1..3359cf3 100644
--- 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
@@ -17,8 +17,8 @@
     }
 
     @Override
-    public String start() { return this.start; }
+    public String getStart() { return this.start; }
 
     @Override
-    public String limit() { return this.limit; }
+    public String getLimit() { 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
index 54bcee8..8a47fbe 100644
--- 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
@@ -8,24 +8,24 @@
 /**
  * An interface for iterating through a collection of key/value pairs (obtained via
  * {@link Table#scan Table.scan()}).
+ *
+ * The {@link java.util.Iterator} returned by the {@link java.lang.Iterable#iterator iterator}:
+ * <p><ul>
+ *     <li>can be created <strong>only</strong> once,
+ *     <li>does not support {@link java.util.Iterator#remove remove}</li>, and
+ *     <li>may throw {@link RuntimeException} if the underlying syncbase read throws
+ *         a {@link VException}.  The {@link RuntimeException#getCause cause} of the
+ *         {@link RuntimeException} will be the said {@link VException}.</li>
+ * </ul>
  */
-public interface ScanStream extends Stream {
+public interface ScanStream extends Iterable<KeyValue> {
     /**
-     * Returns the key of the element that was staged by {@link #advance}.
+     * Notifies the stream provider that it can stop producing elements.  The client must call
+     * {@link #cancel} if it does not iterate through all the elements.
      * <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}.
+     * This method is idempotent and can be called concurrently with a thread that is iterating.
      * <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
+     * This method causes the iterator to (gracefully) terminate early.
      */
-    Object value() throws VException;
+    void cancel() throws VException;
 }
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/ScanStreamImpl.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/ScanStreamImpl.java
new file mode 100644
index 0000000..77e7f93
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/ScanStreamImpl.java
@@ -0,0 +1,59 @@
+// 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 com.google.common.collect.AbstractIterator;
+
+import java.io.EOFException;
+import java.util.Iterator;
+
+import io.v.v23.context.CancelableVContext;
+import io.v.v23.vdl.TypedClientStream;
+import io.v.v23.verror.VException;
+
+class ScanStreamImpl implements ScanStream {
+    private final CancelableVContext ctxC;
+    private final TypedClientStream<Void, KeyValue, Void> stream;
+    private volatile boolean isCanceled;
+    private volatile boolean isCreated;
+
+    ScanStreamImpl(CancelableVContext ctxC, TypedClientStream<Void, KeyValue, Void> stream) {
+        this.ctxC = ctxC;
+        this.stream = stream;
+        this.isCanceled = this.isCreated = false;
+    }
+    // Implements Iterable.
+    @Override
+    public synchronized Iterator<KeyValue> iterator() {
+        if (this.isCreated) {
+            throw new RuntimeException("Can only create one ScanStream iterator.");
+        }
+        this.isCreated = true;
+        return new AbstractIterator<KeyValue>() {
+            @Override
+            protected KeyValue computeNext() {
+                synchronized (ScanStreamImpl.this) {
+                    if (ScanStreamImpl.this.isCanceled) {  // client canceled the stream
+                        return endOfData();
+                    }
+                    try {
+                        return ScanStreamImpl.this.stream.recv();
+                    } catch (EOFException e) {  // legitimate end of stream
+                        return endOfData();
+                    } catch (VException e) {
+                        throw new RuntimeException("Error retrieving next stream element.", e);
+                    }
+                }
+            }
+        };
+    }
+
+    // Implements ScanStream.
+    @Override
+    public synchronized void cancel() throws VException {
+        this.isCanceled = true;
+        this.stream.finish();
+    }
+}
\ 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
index 9c036c7..9665483 100644
--- 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
@@ -31,11 +31,11 @@
     /**
      * Returns the metadata related to this schema.
      */
-    public SchemaMetadata metadata() { return this.metadata; }
+    public SchemaMetadata getMetadata() { 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; }
+    public SchemaUpgrader getUpgrader() { return this.upgrader; }
 }
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/SchemaManager.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/SchemaManager.java
new file mode 100644
index 0000000..ed4590f
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/SchemaManager.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;
+
+import io.v.v23.context.VContext;
+import io.v.v23.verror.VException;
+
+class SchemaManager {
+    private final DatabaseClient client;
+
+    SchemaManager(String dbFullName) {
+        this.client = DatabaseClientFactory.getDatabaseClient(dbFullName);
+    }
+
+    SchemaMetadata getSchemaMetadata(VContext ctx) throws VException {
+        return this.client.getSchemaMetadata(ctx);
+    }
+
+    void setSchemaMetadata(VContext ctx, SchemaMetadata metadata) throws VException {
+        this.client.setSchemaMetadata(ctx, metadata);
+    }
+}
\ 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
deleted file mode 100644
index 15aa392..0000000
--- a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Stream.java
+++ /dev/null
@@ -1,42 +0,0 @@
-// 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
index 7c3071a..b9dfc90 100644
--- 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
@@ -13,15 +13,6 @@
  */
 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>
@@ -105,7 +96,7 @@
      *
      * @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
+     *                    map is guaranteed to be non-{@code null} and contain exactly one element
      * @throws VException if the sync group specification couldn't be retrieved
      */
     Map<String, SyncGroupSpec> getSpec(VContext ctx) throws VException;
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/SyncGroupImpl.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/SyncGroupImpl.java
new file mode 100644
index 0000000..373e820
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/SyncGroupImpl.java
@@ -0,0 +1,59 @@
+// 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 com.google.common.collect.ImmutableMap;
+
+import io.v.syncbase.v23.services.syncbase.nosql.Schema;
+import io.v.syncbase.v23.services.syncbase.nosql.SyncGroupManagerClient.GetSyncGroupSpecOut;
+import io.v.v23.context.VContext;
+import io.v.v23.verror.VException;
+
+class SyncGroupImpl implements SyncGroup {
+    private final String name;
+    private final String dbFullName;
+    private final DatabaseClient dbClient;
+
+    SyncGroupImpl(String dbFullName, String name) {
+        this.name = name;
+        this.dbFullName = dbFullName;
+        this.dbClient = DatabaseClientFactory.getDatabaseClient(dbFullName);
+    }
+    @Override
+    public void create(VContext ctx, SyncGroupSpec spec, SyncGroupMemberInfo info) throws VException {
+        this.dbClient.createSyncGroup(ctx, this.name, spec, info);
+    }
+    @Override
+    public SyncGroupSpec join(VContext ctx, SyncGroupMemberInfo info) throws VException {
+        return this.dbClient.joinSyncGroup(ctx, this.name, info);
+    }
+    @Override
+    public void leave(VContext ctx) throws VException {
+        this.dbClient.leaveSyncGroup(ctx, this.name);
+    }
+    @Override
+    public void destroy(VContext ctx) throws VException {
+        this.dbClient.destroySyncGroup(ctx, this.name);
+    }
+    @Override
+    public void eject(VContext ctx, String member) throws VException {
+        this.dbClient.ejectFromSyncGroup(ctx, this.name, member);
+    }
+    @Override
+    public Map<String, SyncGroupSpec> getSpec(VContext ctx) throws VException {
+        GetSyncGroupSpecOut spec = this.dbClient.getSyncGroupSpec(ctx, this.name);
+        return ImmutableMap.of(spec.version, spec.spec);
+    }
+    @Override
+    public void setSpec(VContext ctx, SyncGroupSpec spec, String version) throws VException {
+        this.dbClient.setSyncGroupSpec(ctx, this.name, spec, version);
+    }
+    @Override
+    public Map<String, SyncGroupMemberInfo> getMembers(VContext ctx) throws VException {
+        return this.dbClient.getSyncGroupMembers(ctx, this.name);
+    }
+}
\ 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
index ee89215..ee598a6 100644
--- 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
@@ -4,6 +4,8 @@
 
 package io.v.syncbase.v23.services.syncbase.nosql;
 
+import java.lang.reflect.Type;
+
 import io.v.v23.context.VContext;
 import io.v.v23.security.access.Permissions;
 import io.v.v23.verror.VException;
@@ -43,20 +45,22 @@
      *
      * @param  ctx        Vanadium context
      * @param  key        the primary key for a row
+     * @param  type       type of the value to be returned (needed for de-serialization)
      * @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;
+    Object get(VContext ctx, String key, Type type) 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  type       type of the value to be returned (needed for serialization)
      * @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;
+    void put(VContext ctx, String key, Object value, Type type) throws VException;
 
     /**
      * Deletes all rows in the given half-open range {@code [start, limit)}. If {@code limit} is
@@ -76,14 +80,17 @@
      * 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()}.
+     * See helpers {@link NoSql#newPrefixRange NoSql.newPrefixRange()},
+     * {@link NoSql#newRowRange NoSql.newRowRange()},
+     * and {@link NoSql#newSingleRowRange NoSql.newSingleRowRange()}.
      *
-     * @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
+     * @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
+     * @throws VException  if the scan stream couldn't be created
      */
-    ScanStream scan(VContext ctx, RowRange range);
+    ScanStream scan(VContext ctx, RowRange range) throws VException;
 
     /**
      * Returns an array of {@link PrefixPermissions} (i.e., {@code (prefix, perms)} pairs) for
@@ -92,6 +99,9 @@
      * 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.
+     * <p>
+     * TODO(spetrovic): Make a change to VDL so that PrefixPermissions.prefix decodes into a
+     * PrefixRange.
      *
      * @param  ctx        Vanadium context
      * @param  key        key of the row whose permission prefixes are to be retrieved
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/TableImpl.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/TableImpl.java
new file mode 100644
index 0000000..a52eb6e
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/TableImpl.java
@@ -0,0 +1,83 @@
+// 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.lang.reflect.Type;
+import java.util.List;
+
+import io.v.impl.google.naming.NamingUtil;
+import io.v.syncbase.v23.services.syncbase.util.Util;
+import io.v.v23.context.CancelableVContext;
+import io.v.v23.context.VContext;
+import io.v.v23.security.access.Permissions;
+import io.v.v23.vdl.TypedClientStream;
+import io.v.v23.verror.VException;
+
+class TableImpl implements Table {
+    private final String fullName;
+    private final String name;
+    private final int schemaVersion;
+    private final TableClient client;
+
+    TableImpl(String parentFullName, String relativeName, int schemaVersion) {
+        this.fullName = NamingUtil.join(parentFullName, relativeName);
+        this.name = relativeName;
+        this.schemaVersion = schemaVersion;
+        this.client = TableClientFactory.getTableClient(this.fullName);
+    }
+
+    @Override
+    public String name() {
+        return this.name;
+    }
+    @Override
+    public String fullName() {
+        return this.fullName;
+    }
+    @Override
+    public boolean exists(VContext ctx) throws VException {
+        return this.client.exists(ctx, this.schemaVersion);
+    }
+    @Override
+    public Row getRow(String key) {
+        return new RowImpl(this.fullName, key, this.schemaVersion);
+    }
+    @Override
+    public Object get(VContext ctx, String key, Type type) throws VException {
+        return getRow(key).get(ctx, type);
+    }
+    @Override
+    public void put(VContext ctx, String key, Object value, Type type) throws VException {
+        getRow(key).put(ctx, value, type);
+    }
+    @Override
+    public void delete(VContext ctx, RowRange range) throws VException {
+        this.client.deleteRowRange(ctx, this.schemaVersion,
+                Util.getBytes(range.getStart()), Util.getBytes(range.getLimit()));
+    }
+    @Override
+    public ScanStream scan(VContext ctx, RowRange range) throws VException {
+        CancelableVContext ctxC = ctx.withCancel();
+        TypedClientStream<Void, KeyValue, Void> stream = this.client.scan(ctxC, this.schemaVersion,
+                Util.getBytes(range.getStart()), Util.getBytes(range.getLimit()));
+        return new ScanStreamImpl(ctxC, stream);
+    }
+    @Override
+    public PrefixPermissions[] getPermissions(VContext ctx, String key) throws VException {
+        List<PrefixPermissions> perms = this.client.getPermissions(ctx, this.schemaVersion, key);
+        return perms.toArray(new PrefixPermissions[perms.size()]);
+    }
+    @Override
+    public void setPermissions(VContext ctx, PrefixRange prefix, Permissions perms)
+            throws VException {
+        this.client.setPermissions(ctx, this.schemaVersion, prefix.getPrefix(), perms);
+    }
+
+    @Override
+    public void deletePermissions(VContext ctx, PrefixRange prefix) throws VException {
+        this.client.deletePermissions(ctx, this.schemaVersion, prefix.getPrefix());
+    }
+}
+
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
index 2bb241b..35376ad 100644
--- 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
@@ -37,7 +37,7 @@
      *
      * @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.
+     *                    is guaranteed to be non-{@code null} and contain exactly one element
      * @throws VException if the object permissions couldn't be retrieved
      */
     Map<String, Permissions> getPermissions(VContext ctx) throws VException;
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
index 2d5cf07..9bb34f2 100644
--- 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
@@ -4,10 +4,25 @@
 
 package io.v.syncbase.v23.services.syncbase.util;
 
+import com.google.common.base.Charsets;
+
+import java.io.EOFException;
 import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+
+import io.v.impl.google.naming.NamingUtil;
+import io.v.v23.InputChannel;
+import io.v.v23.V;
+import io.v.v23.context.VContext;
+import io.v.v23.namespace.Namespace;
+import io.v.v23.naming.GlobReply;
+import io.v.v23.verror.VException;
 
 /**
- * Various syncbase utility methods.
+ * Various NoSQL utility methods.
  */
 public class Util {
     /**
@@ -39,5 +54,66 @@
         }
     }
 
+    /**
+     * Performs {@link Namespace#glob Namespace.glob("name/*")} and returns a
+     * sorted list of results.
+     *
+     * @param  ctx        Vanadium context
+     * @param  globName   name used for globbing
+     * @return            a sorted list of results of
+     *                    {@link Namespace#glob Namespace.glob("name/*")}
+     * @throws VException if a glob error occurred
+     */
+    public static String[] list(VContext ctx, String globName) throws VException {
+        Namespace n = V.getNamespace(ctx);
+        InputChannel<GlobReply> chan = n.glob(ctx, NamingUtil.join(globName, "*"));
+        ArrayList<String> names = new ArrayList<String>();
+        try {
+            for (GlobReply reply : chan) {
+                if (reply instanceof GlobReply.Entry) {
+                    String fullName = ((GlobReply.Entry) reply).getName();
+                    // NOTE(nlacasse): The names that come back from Glob are all
+                    // rooted.  We only want the last part of the name, so we must chop
+                    // off everything before the final '/'.  Since endpoints can
+                    // themselves contain slashes, we have to remove the endpoint from
+                    // the name first.
+                    String name = NamingUtil.splitAddressName(fullName).get(1);
+                    int idx = name.lastIndexOf('/');
+                    if (idx != -1) {
+                        name = name.substring(idx + 1, name.length());
+                    }
+                    names.add(name);
+                } else if (reply instanceof GlobReply.Error) {
+                    throw ((GlobReply.Error) reply).getElem().getError();
+                } else if (reply == null) {
+                    throw new VException("null glob() reply");
+                } else {
+                    throw new VException("Unrecognized glob() reply type: " + reply.getClass());
+                }
+            }
+        } catch (RuntimeException e) {  // error during iteration
+            throw (VException) e.getCause();
+        }
+        Collections.sort(names, Collator.getInstance());
+        return names.toArray(new String[names.size()]);
+    }
+
+    /**
+     * Returns the UTF-8 encoding of the provided string.
+     */
+    public static byte[] getBytes(String s) {
+        if (s == null) {
+            s = "";
+        }
+        return s.getBytes(Charsets.UTF_8);
+    }
+
+    /**
+     * Returns the UTF-8 decoded string.
+     */
+    public static String getString(byte[] bytes) {
+        return new String(bytes, Charsets.UTF_8);
+    }
+
     private Util() {}
 }
diff --git a/lib/src/test/java/io/v/syncbase/v23/services/syncbase/util/UtilPrefixRangeLimitTest.java b/lib/src/test/java/io/v/syncbase/v23/services/syncbase/util/UtilPrefixRangeLimitTest.java
new file mode 100644
index 0000000..fcc4e8d
--- /dev/null
+++ b/lib/src/test/java/io/v/syncbase/v23/services/syncbase/util/UtilPrefixRangeLimitTest.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.util;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static com.google.common.truth.Truth.assertThat;
+
+/**
+ * Unit tests for {@link Util#prefixRangeStart} and {@link Util#prefixRangeLimit}.
+ */
+@RunWith(Parameterized.class)
+public class UtilPrefixRangeLimitTest {
+    private final String prefix;
+    private final String expectedStart;
+    private final String expectedLimit;
+
+    @Parameterized.Parameters
+    public static Collection<Object[]> data() {
+        return Arrays.asList(new Object[][]{
+                {"", "", ""},
+                {"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", ""}
+        });
+    }
+
+    public UtilPrefixRangeLimitTest(String prefix, String start, String limit) {
+        this.prefix = prefix;
+        this.expectedStart = start;
+        this.expectedLimit = limit;
+    }
+
+    @Test
+    public void testPrefixRangeStart() {
+        assertThat(Util.prefixRangeStart(this.prefix)).isEqualTo(this.expectedStart);
+    }
+
+    @Test
+    public void testPrefixRangeLimit() {
+        assertThat(Util.prefixRangeLimit(this.prefix)).isEqualTo(this.expectedLimit);
+    }
+}
\ No newline at end of file
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
deleted file mode 100644
index c5a2aeb..0000000
--- a/lib/src/test/java/io/v/syncbase/v23/services/syncbase/util/UtilTest.java
+++ /dev/null
@@ -1,46 +0,0 @@
-// 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);
-        }
-    }
-}