syncbase: Add Exists rpc method to app, db, table, row.
Exists returns true if the object exists (has been created).
If the caller doesn't have Read permission on the object, Exists will
return false instead of an error because access errors imply existence
in the current implementation. We leak existence information in other
rpcs, we may want to clean it up.
Change-Id: I7f4300512dff16369932854f0c19e735e3518aa7
diff --git a/services/syncbase/server/app.go b/services/syncbase/server/app.go
index 48338f5..0c8958f 100644
--- a/services/syncbase/server/app.go
+++ b/services/syncbase/server/app.go
@@ -56,6 +56,13 @@
return a.s.deleteApp(ctx, call, a.name)
}
+func (a *app) Exists(ctx *context.T, call rpc.ServerCall) (bool, error) {
+ if !a.exists {
+ return false, nil
+ }
+ return util.ErrorToExists(util.GetWithAuth(ctx, call, a.s.st, a.stKey(), &appData{}))
+}
+
func (a *app) SetPermissions(ctx *context.T, call rpc.ServerCall, perms access.Permissions, version string) error {
if !a.exists {
return verror.New(verror.ErrNoExist, ctx, a.name)
diff --git a/services/syncbase/server/nosql/database.go b/services/syncbase/server/nosql/database.go
index 62ad371..cb97fb9 100644
--- a/services/syncbase/server/nosql/database.go
+++ b/services/syncbase/server/nosql/database.go
@@ -140,6 +140,13 @@
return d.a.DeleteNoSQLDatabase(ctx, call, d.name)
}
+func (d *databaseReq) Exists(ctx *context.T, call rpc.ServerCall) (bool, error) {
+ if !d.exists {
+ return false, nil
+ }
+ return util.ErrorToExists(util.GetWithAuth(ctx, call, d.st, d.stKey(), &databaseData{}))
+}
+
var rng *rand.Rand = rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
func (d *databaseReq) BeginBatch(ctx *context.T, call rpc.ServerCall, bo wire.BatchOptions) (string, error) {
diff --git a/services/syncbase/server/nosql/row.go b/services/syncbase/server/nosql/row.go
index 88c08c7..a562c6d 100644
--- a/services/syncbase/server/nosql/row.go
+++ b/services/syncbase/server/nosql/row.go
@@ -26,6 +26,11 @@
////////////////////////////////////////
// RPC methods
+func (r *rowReq) Exists(ctx *context.T, call rpc.ServerCall) (bool, error) {
+ _, err := r.Get(ctx, call)
+ return util.ErrorToExists(err)
+}
+
func (r *rowReq) Get(ctx *context.T, call rpc.ServerCall) ([]byte, error) {
impl := func(st store.StoreReader) ([]byte, error) {
return r.get(ctx, call, st)
diff --git a/services/syncbase/server/nosql/table.go b/services/syncbase/server/nosql/table.go
index 37e1fbe..2eb1f25 100644
--- a/services/syncbase/server/nosql/table.go
+++ b/services/syncbase/server/nosql/table.go
@@ -77,6 +77,10 @@
})
}
+func (t *tableReq) Exists(ctx *context.T, call rpc.ServerCall) (bool, error) {
+ return util.ErrorToExists(util.GetWithAuth(ctx, call, t.d.st, t.stKey(), &tableData{}))
+}
+
func (t *tableReq) DeleteRowRange(ctx *context.T, call rpc.ServerCall, start, limit []byte) error {
impl := func(st store.StoreReadWriter) error {
// Check for table-level access before doing a scan.
diff --git a/services/syncbase/server/util/store_util.go b/services/syncbase/server/util/store_util.go
index d62c222..38fa8ce 100644
--- a/services/syncbase/server/util/store_util.go
+++ b/services/syncbase/server/util/store_util.go
@@ -98,6 +98,24 @@
return Put(ctx, st, k, v)
}
+// Wraps a call to Get and returns true if Get found the object, false
+// otherwise, suppressing ErrNoExist. Access errors are suppressed as well
+// because they imply existence in some Get implementations.
+// TODO(ivanpi): Revisit once ACL specification is finalized.
+func ErrorToExists(err error) (bool, error) {
+ if err == nil {
+ return true, nil
+ }
+ switch verror.ErrorID(err) {
+ case verror.ErrNoExist.ID:
+ return false, nil
+ case verror.ErrNoAccess.ID, verror.ErrNoExistOrNoAccess.ID:
+ return false, nil
+ default:
+ return false, err
+ }
+}
+
type OpenOptions struct {
CreateIfMissing bool
ErrorIfExists bool