java/syncbase: Fix CollectionRowPattern to escape string patterns
We need to escape the strings because % and _ have special meaning for
CollectionRowPattern. It uses SQL LIKE syntax, and we use \ as an
escape character.
Similarly, we needed to rename the internal prefixes of userdata.
Change-Id: I3778d8239ad49500842e1b9090384e08c056371f
diff --git a/syncbase/build.gradle b/syncbase/build.gradle
index 484f3bd..1b90633 100644
--- a/syncbase/build.gradle
+++ b/syncbase/build.gradle
@@ -26,7 +26,7 @@
// You should update this after releasing a new version of the Syncbase API. See the
// list of published versions at: https://repo1.maven.org/maven2/io/v/syncbase
-version = '0.1.8'
+version = '0.1.9'
group = 'io.v'
def siteUrl = 'https://github.com/vanadium/java/syncbase'
diff --git a/syncbase/src/main/java/io/v/syncbase/Database.java b/syncbase/src/main/java/io/v/syncbase/Database.java
index 6232319..47b1034 100644
--- a/syncbase/src/main/java/io/v/syncbase/Database.java
+++ b/syncbase/src/main/java/io/v/syncbase/Database.java
@@ -396,14 +396,14 @@
final String name;
final String blessing;
final String row;
- final boolean showUserdataCollectionRow;
+ final boolean showUserdataInternalRow;
AddWatchChangeHandlerOptions(Builder builder) {
resumeMarker = builder.resumeMarker;
name = builder.name;
blessing = builder.blessing;
row = builder.row;
- showUserdataCollectionRow = builder.showUserdataCollectionRow;
+ showUserdataInternalRow = builder.showUserdataCollectionRow;
}
CollectionRowPattern getCollectionRowPattern() {
@@ -425,27 +425,23 @@
}
public Builder setCollectionNamePrefix(String prefix) {
- // TODO(alexfandrianto): Unsafe. The prefix was not escaped. Incorrect if it has a
- // trailing backslash.
- name = prefix + WILDCARD;
+ name = escapePattern(prefix) + WILDCARD;
return this;
}
public Builder setCollectionId(Id id) {
- name = id.getName();
- blessing = id.getBlessing();
+ name = escapePattern(id.getName());
+ blessing = escapePattern(id.getBlessing());
return this;
}
public Builder setRowKeyPrefix(String prefix) {
- // TODO(alexfandrianto): Unsafe. The prefix was not escaped. Incorrect if it has a
- // trailing backslash.
- row = prefix + WILDCARD;
+ row = escapePattern(prefix) + WILDCARD;
return this;
}
public Builder setRowKey(String rowKey) {
- row = rowKey;
+ row = escapePattern(rowKey);
return this;
}
@@ -454,6 +450,12 @@
return this;
}
+ private String escapePattern(String s) {
+ // Replace '\', '%', and '_' literals with a '\\', '\%', and '\_' respectively.
+ // The reason is that we use SQL LIKE syntax in Go.
+ return s.replaceAll("[\\\\%_]", "\\\\$0");
+ }
+
public AddWatchChangeHandlerOptions build() {
return new AddWatchChangeHandlerOptions(this);
}
@@ -511,12 +513,12 @@
public void onChange(io.v.syncbase.core.WatchChange coreWatchChange) {
boolean isRoot = coreWatchChange.entityType ==
io.v.syncbase.core.WatchChange.EntityType.ROOT;
- boolean isUserdataCollectionRow =
+ boolean isUserdataInternalRow =
coreWatchChange.entityType ==
io.v.syncbase.core.WatchChange.EntityType.ROW &&
coreWatchChange.collection.name.equals(Syncbase.USERDATA_NAME) &&
- coreWatchChange.row.startsWith(Syncbase.USERDATA_COLLECTION_PREFIX);
- if (!isRoot && (opts.showUserdataCollectionRow || !isUserdataCollectionRow)) {
+ coreWatchChange.row.startsWith(Syncbase.USERDATA_INTERNAL_PREFIX);
+ if (!isRoot && (opts.showUserdataInternalRow || !isUserdataInternalRow)) {
mBatch.add(new WatchChange(coreWatchChange));
}
if (!coreWatchChange.continued) {
diff --git a/syncbase/src/main/java/io/v/syncbase/Syncbase.java b/syncbase/src/main/java/io/v/syncbase/Syncbase.java
index 89d27f8..c8a586f 100644
--- a/syncbase/src/main/java/io/v/syncbase/Syncbase.java
+++ b/syncbase/src/main/java/io/v/syncbase/Syncbase.java
@@ -36,8 +36,8 @@
/**
* The "userdata" collection is a per-user collection (and associated syncgroup) for data that
* should automatically get synced across a given user's devices. It has the following schema:
- * - /syncgroups/{encodedSyncgroupId} -> null
- * - /ignoredInvites/{encodedSyncgroupId} -> null
+ * - internal__/syncgroups/{encodedSyncgroupId} -> null
+ * - internal__/ignoredInvites/{encodedSyncgroupId} -> null
*/
/**
@@ -219,11 +219,11 @@
static final String
TAG = "syncbase",
DIR_NAME = "syncbase",
- DB_NAME = "db";
+ DB_NAME = "db",
+ USERDATA_INTERNAL_PREFIX = "internal__/",
+ USERDATA_INTERNAL_SYNCGROUP_PREFIX = USERDATA_INTERNAL_PREFIX + "syncgroups";
- public static final String
- USERDATA_NAME = "userdata__",
- USERDATA_COLLECTION_PREFIX = "__collections/";
+ public static final String USERDATA_NAME = "userdata__";
private static Map selfAndCloud() throws SyncbaseException {
List<String> inList = sOpts.mCloudAdmin == null
@@ -401,10 +401,10 @@
if (watchChange.getCollectionId().getName().equals(USERDATA_NAME) &&
watchChange.getEntityType() == WatchChange.EntityType.ROW &&
watchChange.getChangeType() == WatchChange.ChangeType.PUT &&
- watchChange.getRowKey().startsWith(USERDATA_COLLECTION_PREFIX)) {
+ watchChange.getRowKey().startsWith(USERDATA_INTERNAL_SYNCGROUP_PREFIX)) {
try {
String encodedId = watchChange.getRowKey().
- substring(Syncbase.USERDATA_COLLECTION_PREFIX.length());
+ substring(Syncbase.USERDATA_INTERNAL_SYNCGROUP_PREFIX.length());
sDatabase.getSyncgroup(Id.decode(encodedId)).join();
} catch (VError vError) {
vError.printStackTrace();
@@ -417,7 +417,7 @@
static void addToUserdata(Id id) throws SyncbaseException {
sDatabase.getUserdataCollection().
- put(Syncbase.USERDATA_COLLECTION_PREFIX + id.encode(), true);
+ put(Syncbase.USERDATA_INTERNAL_SYNCGROUP_PREFIX + id.encode(), true);
}
/**
diff --git a/syncbase/src/main/java/io/v/syncbase/Syncgroup.java b/syncbase/src/main/java/io/v/syncbase/Syncgroup.java
index ea9ee01..a906059 100644
--- a/syncbase/src/main/java/io/v/syncbase/Syncgroup.java
+++ b/syncbase/src/main/java/io/v/syncbase/Syncgroup.java
@@ -167,25 +167,25 @@
public void updateAccessList(final AccessList delta, UpdateAccessListOptions opts)
throws SyncbaseException {
try {
-
// TODO(sadovsky): Make it so SyncgroupSpec can be updated as part of a batch?
VersionedSyncgroupSpec versionedSyncgroupSpec = mCoreSyncgroup.getSpec();
versionedSyncgroupSpec.syncgroupSpec.permissions = AccessList.applyDeltaForSyncgroup(
versionedSyncgroupSpec.syncgroupSpec.permissions, delta);
mCoreSyncgroup.setSpec(versionedSyncgroupSpec);
- // TODO(sadovsky): There's a race here - it's possible for a collection to get destroyed
- // after getSpec() but before db.getCollection().
- final List<io.v.syncbase.core.Id> collectionsIds =
- versionedSyncgroupSpec.syncgroupSpec.collections;
- mDatabase.runInBatch(new Database.BatchOperation() {
- @Override
- public void run(BatchDatabase db) throws SyncbaseException {
- for (io.v.syncbase.core.Id id : collectionsIds) {
- db.getCollection(new Id(id)).updateAccessList(delta);
+ if (!opts.syncgroupOnly) {
+ // TODO(sadovsky): There's a race here - it's possible for a collection to get
+ // destroyed after getSpec() but before db.getCollection().
+ final List<io.v.syncbase.core.Id> collectionsIds =
+ versionedSyncgroupSpec.syncgroupSpec.collections;
+ mDatabase.runInBatch(new Database.BatchOperation() {
+ @Override
+ public void run(BatchDatabase db) throws SyncbaseException {
+ for (io.v.syncbase.core.Id id : collectionsIds) {
+ db.getCollection(new Id(id)).updateAccessList(delta);
+ }
}
- }
- });
-
+ });
+ }
} catch (VError e) {
chainThrow("updating access list of syncgroup", getId().getName(), e);
}
diff --git a/syncbase/src/test/java/io/v/syncbase/DatabaseTest.java b/syncbase/src/test/java/io/v/syncbase/DatabaseTest.java
index ef21baa..b9b1490 100644
--- a/syncbase/src/test/java/io/v/syncbase/DatabaseTest.java
+++ b/syncbase/src/test/java/io/v/syncbase/DatabaseTest.java
@@ -18,6 +18,7 @@
import io.v.syncbase.exception.SyncbaseException;
import static io.v.syncbase.TestUtil.setUpDatabase;
+import static org.junit.Assert.assertEquals;
public class DatabaseTest {
@Rule
@@ -45,4 +46,73 @@
Syncbase.database().syncgroup("aSyncgroup", new ArrayList<Collection>());
}
+ @Test
+ public void testWatchChangeHandlerOptionsBuilder() {
+ Database.AddWatchChangeHandlerOptions opts;
+
+ // Wildcard and prefix tests.
+ opts = new Database.AddWatchChangeHandlerOptions.Builder()
+ .build();
+ assertEquals(opts.blessing, "%");
+ assertEquals(opts.name, "%");
+ assertEquals(opts.row, "%");
+
+ opts = new Database.AddWatchChangeHandlerOptions.Builder()
+ .setCollectionId(new Id("a", "b"))
+ .build();
+ assertEquals(opts.blessing, "a");
+ assertEquals(opts.name, "b");
+ assertEquals(opts.row, "%");
+
+ opts = new Database.AddWatchChangeHandlerOptions.Builder()
+ .setCollectionNamePrefix("c")
+ .build();
+ assertEquals(opts.blessing, "%");
+ assertEquals(opts.name, "c%");
+ assertEquals(opts.row, "%");
+
+ opts = new Database.AddWatchChangeHandlerOptions.Builder()
+ .setRowKey("d")
+ .build();
+ assertEquals(opts.blessing, "%");
+ assertEquals(opts.name, "%");
+ assertEquals(opts.row, "d");
+
+ opts = new Database.AddWatchChangeHandlerOptions.Builder()
+ .setRowKeyPrefix("e")
+ .build();
+ assertEquals(opts.blessing, "%");
+ assertEquals(opts.name, "%");
+ assertEquals(opts.row, "e%");
+
+ // Escaping tests. %, _ and \ are special characters.
+ opts = new Database.AddWatchChangeHandlerOptions.Builder()
+ .setCollectionId(new Id("%", "_"))
+ .build();
+ assertEquals(opts.blessing, "\\%");
+ assertEquals(opts.name, "\\_");
+ assertEquals(opts.row, "%");
+
+ opts = new Database.AddWatchChangeHandlerOptions.Builder()
+ .setCollectionNamePrefix("\\")
+ .build();
+ assertEquals(opts.blessing, "%");
+ assertEquals(opts.name, "\\\\%");
+ assertEquals(opts.row, "%");
+
+ opts = new Database.AddWatchChangeHandlerOptions.Builder()
+ .setRowKey("%%")
+ .build();
+ assertEquals(opts.blessing, "%");
+ assertEquals(opts.name, "%");
+ assertEquals(opts.row, "\\%\\%");
+
+ opts = new Database.AddWatchChangeHandlerOptions.Builder()
+ .setRowKeyPrefix("_\\_")
+ .build();
+ assertEquals(opts.blessing, "%");
+ assertEquals(opts.name, "%");
+ assertEquals(opts.row, "\\_\\\\\\_%");
+ }
+
}