syncbase/exec: Expose support for parameterized queries.
Allow Syncbase to accept parameterized queries, with '?' placeholders
in the where clause.
MultiPart: 3/5
Change-Id: I3474c07b3c49d6a493f60c77e58efd3dfdd14d24
diff --git a/lib/src/main/java/io/v/v23/syncbase/nosql/DatabaseCore.java b/lib/src/main/java/io/v/v23/syncbase/nosql/DatabaseCore.java
index 5be7120..dc810f6 100644
--- a/lib/src/main/java/io/v/v23/syncbase/nosql/DatabaseCore.java
+++ b/lib/src/main/java/io/v/v23/syncbase/nosql/DatabaseCore.java
@@ -10,6 +10,7 @@
import io.v.v23.services.watch.ResumeMarker;
import io.v.v23.vdl.VdlAny;
+import java.lang.reflect.Type;
import java.util.List;
import javax.annotation.CheckReturnValue;
@@ -77,6 +78,23 @@
ListenableFuture<QueryResults> exec(VContext context, String query);
/**
+ * Executes a SyncQL parameterized query. Same as {@link #exec(VContext, String)}, with the
+ * query supporting positional parameters. {@code paramValues} and {@code paramTypes} must each
+ * have one element corresponding to each '?' placeholder in the query.
+ *
+ * @param context Vanadium context
+ * @param query a SyncQL query
+ * @param paramValues SyncQL query parameters, one per '?' placeholder in the query
+ * @param paramTypes SyncQL query parameter types, one per parameter in paramValues
+ * @return a {@link ListenableFuture} whose result is a {@link QueryResults}
+ * object that allows the caller to iterate over arrays of values for each
+ * row that matches the query
+ */
+ @CheckReturnValue
+ ListenableFuture<QueryResults> exec(VContext context, String query,
+ List<Object> paramValues, List<Type> paramTypes);
+
+ /**
* Returns a new {@link ListenableFuture} whose result is the {@link ResumeMarker} that points
* to the current state of the database.
* <p>
diff --git a/lib/src/main/java/io/v/v23/syncbase/nosql/DatabaseImpl.java b/lib/src/main/java/io/v/v23/syncbase/nosql/DatabaseImpl.java
index bf2f04a..e1a7127 100644
--- a/lib/src/main/java/io/v/v23/syncbase/nosql/DatabaseImpl.java
+++ b/lib/src/main/java/io/v/v23/syncbase/nosql/DatabaseImpl.java
@@ -5,6 +5,7 @@
package io.v.v23.syncbase.nosql;
import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -35,8 +36,10 @@
import io.v.v23.vdl.VdlAny;
import io.v.v23.vdl.VdlOptional;
import io.v.v23.verror.VException;
+import io.v.v23.vom.VomUtil;
import java.io.Serializable;
+import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@@ -98,17 +101,27 @@
}
@Override
public ListenableFuture<QueryResults> exec(VContext ctx, String query) {
- final ClientRecvStream<List<VdlAny>, Void> stream =
- client.exec(ctx, getSchemaVersion(), query);
- return VFutures.withUserLandChecks(ctx, Futures.transform(stream.recv(),
- new AsyncFunction<List<VdlAny>, QueryResults>() {
- @Override
- public ListenableFuture<QueryResults> apply(List<VdlAny> columnNames)
- throws Exception {
- return Futures.immediateFuture(
- (QueryResults) new QueryResultsImpl(columnNames, stream));
- }
- }));
+ return this.execInternal(ctx, query, null);
+ }
+ @Override
+ public ListenableFuture<QueryResults> exec(VContext ctx, String query,
+ List<Object> paramValues, List<Type> paramTypes) {
+ Preconditions.checkNotNull(paramValues);
+ Preconditions.checkNotNull(paramTypes);
+ if (paramValues.size() != paramTypes.size()) {
+ throw new IllegalArgumentException("Length of paramValues and paramTypes is not equal");
+ }
+ List<VdlAny> params = new ArrayList<VdlAny>();
+ try {
+ Iterator<Object> v = paramValues.iterator();
+ Iterator<Type> t = paramTypes.iterator();
+ while (v.hasNext() && t.hasNext()) {
+ params.add(new VdlAny(VomUtil.valueOf(v.next(), t.next())));
+ }
+ } catch (VException e) {
+ return VFutures.withUserLandChecks(ctx, Futures.<QueryResults>immediateFailedFuture(e));
+ }
+ return this.execInternal(ctx, query, params);
}
// Implements AccessController interface.
@@ -292,6 +305,20 @@
watchChange.getContinued());
}
+ private ListenableFuture<QueryResults> execInternal(VContext ctx, String query, List<VdlAny> params) {
+ final ClientRecvStream<List<VdlAny>, Void> stream =
+ client.exec(ctx, getSchemaVersion(), query, params);
+ return VFutures.withUserLandChecks(ctx, Futures.transform(stream.recv(),
+ new AsyncFunction<List<VdlAny>, QueryResults>() {
+ @Override
+ public ListenableFuture<QueryResults> apply(List<VdlAny> columnNames)
+ throws Exception {
+ return Futures.immediateFuture(
+ (QueryResults) new QueryResultsImpl(columnNames, stream));
+ }
+ }));
+ }
+
private static List<String> splitInTwo(String str, String separator) {
Iterator<String> iter = Splitter.on(separator).limit(2).split(str).iterator();
return ImmutableList.of(
diff --git a/lib/src/test/java/io/v/v23/syncbase/SyncbaseTest.java b/lib/src/test/java/io/v/v23/syncbase/SyncbaseTest.java
index 4df616a..10ae922 100644
--- a/lib/src/test/java/io/v/v23/syncbase/SyncbaseTest.java
+++ b/lib/src/test/java/io/v/v23/syncbase/SyncbaseTest.java
@@ -49,6 +49,8 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
+import java.lang.reflect.Type;
+import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
@@ -226,6 +228,17 @@
ImmutableList.of(new VdlAny(String.class, "foo"), new VdlAny(Foo.class, foo))
);
}
+ {
+ DatabaseCore.QueryResults results = sync(db.exec(ctx,
+ "select k, v from " + TABLE_NAME + " where k = ? or v.I = ?",
+ Arrays.<Object>asList("baz", 4),
+ Arrays.<Type>asList(String.class, int.class)));
+ assertThat(results.columnNames()).containsExactly("k", "v");
+ assertThat(sync(InputChannels.asList(results))).containsExactly(
+ ImmutableList.of(new VdlAny(String.class, "baz"), new VdlAny(Baz.class, baz)),
+ ImmutableList.of(new VdlAny(String.class, "foo"), new VdlAny(Foo.class, foo))
+ );
+ }
}
public void testDatabaseWatch() throws Exception {