syncbase: naming change follow-ups

MultiPart: 2/7
Change-Id: Ie9140bb5dce61b9a00f9beeb2bf5a757c571b243
diff --git a/services/syncbase/server/app.go b/services/syncbase/server/app.go
index a2b1c4f..37b9152 100644
--- a/services/syncbase/server/app.go
+++ b/services/syncbase/server/app.go
@@ -89,11 +89,11 @@
 	}
 	// Check perms.
 	sn := a.s.st.NewSnapshot()
+	defer sn.Abort()
 	if err := util.GetWithAuth(ctx, call, sn, a.stKey(), &appData{}); err != nil {
-		sn.Abort()
 		return err
 	}
-	return util.GlobChildren(ctx, call, matcher, sn, sn.Abort, util.JoinKeyParts(util.DbInfoPrefix, a.name))
+	return util.GlobChildren(ctx, call, matcher, sn, util.JoinKeyParts(util.DbInfoPrefix, a.name))
 }
 
 ////////////////////////////////////////
diff --git a/services/syncbase/server/dispatcher.go b/services/syncbase/server/dispatcher.go
index a5b9bba..6cf2359 100644
--- a/services/syncbase/server/dispatcher.go
+++ b/services/syncbase/server/dispatcher.go
@@ -33,8 +33,6 @@
 var auth security.Authorizer = security.AllowEveryone()
 
 func (disp *dispatcher) Lookup(ctx *context.T, suffix string) (interface{}, security.Authorizer, error) {
-	// TODO(sadovsky): Can we drop this TrimPrefix?
-	suffix = strings.TrimPrefix(suffix, "/")
 	if len(suffix) == 0 {
 		return wire.ServiceServer(disp.s), auth, nil
 	}
diff --git a/services/syncbase/server/nosql/database.go b/services/syncbase/server/nosql/database.go
index 40da19e..c8afa10 100644
--- a/services/syncbase/server/nosql/database.go
+++ b/services/syncbase/server/nosql/database.go
@@ -17,6 +17,7 @@
 	wire "v.io/v23/services/syncbase/nosql"
 	"v.io/v23/syncbase/nosql/query"
 	"v.io/v23/syncbase/nosql/query/exec"
+	pubutil "v.io/v23/syncbase/util"
 	"v.io/v23/vdl"
 	"v.io/v23/verror"
 	"v.io/v23/vom"
@@ -320,22 +321,53 @@
 	if !d.exists {
 		return verror.New(verror.ErrNoExist, ctx, d.name)
 	}
-	impl := func(sntx store.SnapshotOrTransaction, closeSntx func() error) error {
+	impl := func(sntx store.SnapshotOrTransaction) error {
 		// Check perms.
-		sn := d.st.NewSnapshot()
-		if err := util.GetWithAuth(ctx, call, sn, d.stKey(), &databaseData{}); err != nil {
-			sn.Abort()
+		if err := util.GetWithAuth(ctx, call, sntx, d.stKey(), &databaseData{}); err != nil {
 			return err
 		}
-		return util.GlobChildren(ctx, call, matcher, sn, closeSntx, util.TablePrefix)
+		return util.GlobChildren(ctx, call, matcher, sntx, util.TablePrefix)
 	}
 	if d.batchId != nil {
-		return impl(d.batchReader(), func() error {
-			return nil
-		})
+		return impl(d.batchReader())
 	} else {
 		sn := d.st.NewSnapshot()
-		return impl(sn, sn.Abort)
+		defer sn.Abort()
+		return impl(sn)
+	}
+}
+
+// See comment in v.io/v23/services/syncbase/nosql/service.vdl for why we can't
+// implement ListTables using Glob.
+func (d *databaseReq) ListTables(ctx *context.T, call rpc.ServerCall) ([]string, error) {
+	if !d.exists {
+		return nil, verror.New(verror.ErrNoExist, ctx, d.name)
+	}
+	impl := func(sntx store.SnapshotOrTransaction) ([]string, error) {
+		// Check perms.
+		if err := util.GetWithAuth(ctx, call, sntx, d.stKey(), &databaseData{}); err != nil {
+			return nil, err
+		}
+		it := sntx.Scan(util.ScanPrefixArgs(util.TablePrefix, ""))
+		key := []byte{}
+		res := []string{}
+		for it.Advance() {
+			key = it.Key(key)
+			parts := util.SplitKeyParts(string(key))
+			// For explanation of Escape(), see comment in server/nosql/dispatcher.go.
+			res = append(res, pubutil.Escape(parts[len(parts)-1]))
+		}
+		if err := it.Err(); err != nil {
+			return nil, err
+		}
+		return res, nil
+	}
+	if d.batchId != nil {
+		return impl(d.batchReader())
+	} else {
+		sntx := d.st.NewSnapshot()
+		defer sntx.Abort()
+		return impl(sntx)
 	}
 }
 
diff --git a/services/syncbase/server/nosql/dispatcher.go b/services/syncbase/server/nosql/dispatcher.go
index 87ff495..022a86f 100644
--- a/services/syncbase/server/nosql/dispatcher.go
+++ b/services/syncbase/server/nosql/dispatcher.go
@@ -50,8 +50,6 @@
 // restricted to be valid identifiers, such that neither "<appName>:<dbName>"
 // nor "<tableName>:<rowKey>" can be ambiguous.
 func (disp *dispatcher) Lookup(ctx *context.T, suffix string) (interface{}, security.Authorizer, error) {
-	// TODO(sadovsky): Can we drop this TrimPrefix?
-	suffix = strings.TrimPrefix(suffix, "/")
 	parts := strings.SplitN(suffix, "/", 3) // db, table, row
 
 	// Note, the slice returned by strings.SplitN is guaranteed to contain at
diff --git a/services/syncbase/server/nosql/table.go b/services/syncbase/server/nosql/table.go
index 7da77fa..d5d4c10 100644
--- a/services/syncbase/server/nosql/table.go
+++ b/services/syncbase/server/nosql/table.go
@@ -117,9 +117,9 @@
 	if t.d.batchId != nil {
 		return impl(t.d.batchReader())
 	} else {
-		sntx := t.d.st.NewSnapshot()
-		defer sntx.Abort()
-		return impl(sntx)
+		sn := t.d.st.NewSnapshot()
+		defer sn.Abort()
+		return impl(sn)
 	}
 }
 
@@ -365,21 +365,19 @@
 }
 
 func (t *tableReq) GlobChildren__(ctx *context.T, call rpc.GlobChildrenServerCall, matcher *glob.Element) error {
-	impl := func(sntx store.SnapshotOrTransaction, closeSntx func() error) error {
+	impl := func(sntx store.SnapshotOrTransaction) error {
 		// Check perms.
 		if err := t.checkAccess(ctx, call, sntx, ""); err != nil {
-			closeSntx()
 			return err
 		}
-		return util.GlobChildren(ctx, call, matcher, sntx, closeSntx, util.JoinKeyParts(util.RowPrefix, t.name))
+		return util.GlobChildren(ctx, call, matcher, sntx, util.JoinKeyParts(util.RowPrefix, t.name))
 	}
 	if t.d.batchId != nil {
-		return impl(t.d.batchReader(), func() error {
-			return nil
-		})
+		return impl(t.d.batchReader())
 	} else {
 		sn := t.d.st.NewSnapshot()
-		return impl(sn, sn.Abort)
+		defer sn.Abort()
+		return impl(sn)
 	}
 }
 
diff --git a/services/syncbase/server/service.go b/services/syncbase/server/service.go
index 2ddf195..1677a39 100644
--- a/services/syncbase/server/service.go
+++ b/services/syncbase/server/service.go
@@ -161,11 +161,11 @@
 func (s *service) GlobChildren__(ctx *context.T, call rpc.GlobChildrenServerCall, matcher *glob.Element) error {
 	// Check perms.
 	sn := s.st.NewSnapshot()
+	defer sn.Abort()
 	if err := util.GetWithAuth(ctx, call, sn, s.stKey(), &serviceData{}); err != nil {
-		sn.Abort()
 		return err
 	}
-	return util.GlobChildren(ctx, call, matcher, sn, sn.Abort, util.AppPrefix)
+	return util.GlobChildren(ctx, call, matcher, sn, util.AppPrefix)
 }
 
 ////////////////////////////////////////
diff --git a/services/syncbase/server/util/glob_children.go b/services/syncbase/server/util/glob_children.go
index e874163..13007e4 100644
--- a/services/syncbase/server/util/glob_children.go
+++ b/services/syncbase/server/util/glob_children.go
@@ -10,27 +10,31 @@
 	"v.io/v23/naming"
 	"v.io/v23/rpc"
 	pubutil "v.io/v23/syncbase/util"
+	"v.io/v23/verror"
 	"v.io/x/ref/services/syncbase/store"
 )
 
 // Note, Syncbase handles Glob requests by implementing GlobChildren__ at each
 // level (service, app, database, table).
 
-// GlobChildren performs a glob. It calls closeSntx to close sntx.
-func GlobChildren(ctx *context.T, call rpc.GlobChildrenServerCall, matcher *glob.Element, sntx store.SnapshotOrTransaction, closeSntx func() error, stKeyPrefix string) error {
+// GlobChildren implements glob over the Syncbase namespace.
+func GlobChildren(ctx *context.T, call rpc.GlobChildrenServerCall, matcher *glob.Element, sntx store.SnapshotOrTransaction, stKeyPrefix string) error {
 	prefix, _ := matcher.FixedPrefix()
-	it := sntx.Scan(ScanPrefixArgs(stKeyPrefix, prefix))
-	defer closeSntx()
+	unescPrefix, ok := pubutil.Unescape(prefix)
+	if !ok {
+		return verror.New(verror.ErrBadArg, ctx, prefix)
+	}
+	it := sntx.Scan(ScanPrefixArgs(stKeyPrefix, unescPrefix))
 	key := []byte{}
 	for it.Advance() {
 		key = it.Key(key)
 		parts := SplitKeyParts(string(key))
-		name := parts[len(parts)-1]
+		// For explanation of Escape(), see comment in server/nosql/dispatcher.go.
+		name := pubutil.Escape(parts[len(parts)-1])
 		if matcher.Match(name) {
 			// TODO(rogulenko): Check for resolve access. (For table glob, this means
 			// checking prefix perms.)
-			// For explanation of Escape(), see comment in server/nosql/dispatcher.go.
-			if err := call.SendStream().Send(naming.GlobChildrenReplyName{Value: pubutil.Escape(name)}); err != nil {
+			if err := call.SendStream().Send(naming.GlobChildrenReplyName{Value: name}); err != nil {
 				return err
 			}
 		}
diff --git a/services/syncbase/testutil/layer.go b/services/syncbase/testutil/layer.go
index 755aae9..7c4e6b0 100644
--- a/services/syncbase/testutil/layer.go
+++ b/services/syncbase/testutil/layer.go
@@ -7,6 +7,7 @@
 import (
 	"fmt"
 	"reflect"
+	"sort"
 	"testing"
 
 	"v.io/v23/context"
@@ -103,16 +104,13 @@
 	for _, name := range notOkNames {
 		if err := parent.Child(name).Create(ctx, nil); err == nil {
 			t.Fatalf("Create(%q) should have failed: %v", name, err)
+		} else if name != "" && verror.ErrorID(err) != wire.ErrInvalidName.ID {
 			// TODO(sadovsky): Currently for name "" we cannot check for
 			// ErrInvalidName, since parent.Child("") dispatches on the parent
 			// component. Create will still fail, but for a different reason. If/when
 			// we add client-side name validation, we'll be able to check for
 			// ErrInvalidName specifically.
-			// TODO(sadovsky): Will dropping the "TrimPrefix" calls in our server-side
-			// dispatchers fix this?
-			if name != "" && verror.ErrorID(err) != wire.ErrInvalidName.ID {
-				t.Fatalf("Create(%q) should have failed with ErrInvalidName: %v", name, err)
-			}
+			t.Fatalf("Create(%q) should have failed with ErrInvalidName: %v", name, err)
 		}
 	}
 }
@@ -199,43 +197,36 @@
 	assertExists(t, ctx, self, "self", false)
 }
 
-func TestListChildren(t *testing.T, ctx *context.T, i interface{}) {
+func TestListChildren(t *testing.T, ctx *context.T, i interface{}, names []string) {
 	self := makeLayer(i)
 
 	var got, want []string
 	var err error
 
-	got, err = self.ListChildren(ctx)
-	want = []string{}
-	if err != nil {
-		t.Fatalf("self.ListChildren() failed: %v", err)
-	}
-	if !reflect.DeepEqual([]string(got), want) {
-		t.Fatalf("Lists do not match: got %v, want %v", got, want)
-	}
+	sortedNames := make([]string, len(names))
+	copy(sortedNames, names)
+	sort.Strings(sortedNames)
+	names = sortedNames
 
-	if err := self.Child("y").Create(ctx, nil); err != nil {
-		t.Fatalf("y.Create() failed: %v", err)
-	}
-	got, err = self.ListChildren(ctx)
-	want = []string{"y"}
-	if err != nil {
-		t.Fatalf("self.ListChildren() failed: %v", err)
-	}
-	if !reflect.DeepEqual(got, want) {
-		t.Fatalf("Lists do not match: got %v, want %v", got, want)
-	}
-
-	if err := self.Child("x").Create(ctx, nil); err != nil {
-		t.Fatalf("x.Create() failed: %v", err)
-	}
-	got, err = self.ListChildren(ctx)
-	want = []string{"x", "y"}
-	if err != nil {
-		t.Fatalf("self.ListChildren() failed: %v", err)
-	}
-	if !reflect.DeepEqual(got, want) {
-		t.Fatalf("Lists do not match: got %v, want %v", got, want)
+	for i := 0; i <= len(names); i++ {
+		got, err = self.ListChildren(ctx)
+		want = names[:i]
+		if err != nil {
+			t.Fatalf("self.ListChildren() failed: %v", err)
+		}
+		if got == nil {
+			got = []string{}
+		}
+		if !reflect.DeepEqual(got, want) {
+			t.Fatalf("Lists do not match: got %v, want %v", got, want)
+		}
+		if i == len(names) {
+			break
+		}
+		name := names[i]
+		if err := self.Child(name).Create(ctx, nil); err != nil {
+			t.Fatalf("Create(%q) failed: %v", name, err)
+		}
 	}
 }
 
@@ -340,6 +331,8 @@
 
 type layer interface {
 	util.AccessController
+	Name() string
+	FullName() string
 	Create(ctx *context.T, perms access.Permissions) error
 	Destroy(ctx *context.T) error
 	Exists(ctx *context.T) (bool, error)
@@ -351,6 +344,9 @@
 	syncbase.Service
 }
 
+func (s *service) Name() string {
+	panic(notAvailable)
+}
 func (s *service) Create(ctx *context.T, perms access.Permissions) error {
 	panic(notAvailable)
 }
@@ -411,6 +407,9 @@
 	nosql.Row
 }
 
+func (r *row) Name() string {
+	return r.Key()
+}
 func (r *row) Create(ctx *context.T, perms access.Permissions) error {
 	if perms != nil {
 		panic(fmt.Sprintf("bad perms: %v", perms))