syncbase: Change id encoding in store keys to preserve sorting.

Changed id encoding in store keys from the wire encoding
encodeName("<blessing>,<name>") to "<blessing>\0<name>\0".
The new encoding preserves the natural sort order (lexicographically,
first by blessing, then by name).

The new encoding removes the need for sorting when scanning databases,
collections, and rows.

Syncgroups use a hash in keys instead; changed syncgroup sort routine
to sort by blessing first instead of name first, added test to confirm.

MultiPart: 1/3
Change-Id: I1dae34e5661879692b139125f82ecba8672633d0
diff --git a/services/syncbase/service.vdl b/services/syncbase/service.vdl
index 5560ecd..394cbac 100644
--- a/services/syncbase/service.vdl
+++ b/services/syncbase/service.vdl
@@ -49,7 +49,8 @@
 // RPC methods if the --dev flag is not set.
 
 // Service represents a Vanadium Syncbase service.
-// Service.Glob operates over Database ids, requiring Read on Service.
+// Service.Glob operates over Database ids, requiring Read on Service, returning
+// ids sorted by blessing, then by name.
 type Service interface {
 	// DevModeUpdateVClock updates various bits of Syncbase virtual clock and
 	// clock daemon state based on the specified options.
@@ -73,7 +74,8 @@
 
 // Database represents a set of Collections. Batches, queries, syncgroups, and
 // watch all operate at the Database level.
-// Database.Glob operates over Collection ids, requiring Read on Database.
+// Database.Glob operates over Collection ids, requiring Read on Database,
+// returning ids sorted by blessing, then by name.
 type Database interface {
 	// Create creates this Database. Permissions must be non-nil and include at
 	// least one admin.
@@ -95,8 +97,8 @@
 	// Otherwise, ErrNoExistOrNoAccess is returned.
 	Exists() (bool | error)
 
-	// ListCollections returns an unsorted list of all Collection ids that the
-	// caller is allowed to see.
+	// ListCollections returns a list of ids of all Collections in this Database.
+	// The list is sorted by blessing, then by name.
 	// This method exists on Database but not on Service because for the latter
 	// we can simply use glob, while for the former glob lists only Collections
 	// visible in a new snapshot of the Database, ignoring user batches.
@@ -191,7 +193,7 @@
 
 // Collection represents a set of Rows.
 // Collection.Glob operates over keys of Rows in the Collection, requiring Read
-// on Collection.
+// on Collection, returning keys in a lexicographically sorted order.
 type Collection interface {
 	// Create creates this Collection. Permissions must be non-nil and include at
 	// least one admin.
@@ -276,8 +278,8 @@
 // the parent of its syncgroups for permissions checking purposes.
 // TODO(hpucha): Add blessings to create/join and add a refresh method.
 type SyncgroupManager interface {
-	// ListSyncgroups returns the relative syncgroup ids of all syncgroups attached to
-	// this database.
+	// ListSyncgroups returns a list of ids of all syncgroups attached to this
+	// Database. The list is sorted by blessing, then by name.
 	//
 	// Requires: Read on Database.
 	ListSyncgroups() ([]Id | error) {access.Read}
diff --git a/services/syncbase/syncbase.vdl.go b/services/syncbase/syncbase.vdl.go
index 452f002..735806f 100644
--- a/services/syncbase/syncbase.vdl.go
+++ b/services/syncbase/syncbase.vdl.go
@@ -3225,7 +3225,8 @@
 // containing Service methods.
 //
 // Service represents a Vanadium Syncbase service.
-// Service.Glob operates over Database ids, requiring Read on Service.
+// Service.Glob operates over Database ids, requiring Read on Service, returning
+// ids sorted by blessing, then by name.
 type ServiceClientMethods interface {
 	// Object provides access control for Vanadium objects.
 	//
@@ -3316,7 +3317,8 @@
 // implements for Service.
 //
 // Service represents a Vanadium Syncbase service.
-// Service.Glob operates over Database ids, requiring Read on Service.
+// Service.Glob operates over Database ids, requiring Read on Service, returning
+// ids sorted by blessing, then by name.
 type ServiceServerMethods interface {
 	// Object provides access control for Vanadium objects.
 	//
@@ -3436,7 +3438,7 @@
 var descService = rpc.InterfaceDesc{
 	Name:    "Service",
 	PkgPath: "v.io/v23/services/syncbase",
-	Doc:     "// Service represents a Vanadium Syncbase service.\n// Service.Glob operates over Database ids, requiring Read on Service.",
+	Doc:     "// Service represents a Vanadium Syncbase service.\n// Service.Glob operates over Database ids, requiring Read on Service, returning\n// ids sorted by blessing, then by name.",
 	Embeds: []rpc.EmbedDesc{
 		{"Object", "v.io/v23/services/permissions", "// Object provides access control for Vanadium objects.\n//\n// Vanadium services implementing dynamic access control would typically embed\n// this interface and tag additional methods defined by the service with one of\n// Admin, Read, Write, Resolve etc. For example, the VDL definition of the\n// object would be:\n//\n//   package mypackage\n//\n//   import \"v.io/v23/security/access\"\n//   import \"v.io/v23/services/permissions\"\n//\n//   type MyObject interface {\n//     permissions.Object\n//     MyRead() (string, error) {access.Read}\n//     MyWrite(string) error    {access.Write}\n//   }\n//\n// If the set of pre-defined tags is insufficient, services may define their\n// own tag type and annotate all methods with this new type.\n//\n// Instead of embedding this Object interface, define SetPermissions and\n// GetPermissions in their own interface. Authorization policies will typically\n// respect annotations of a single type. For example, the VDL definition of an\n// object would be:\n//\n//  package mypackage\n//\n//  import \"v.io/v23/security/access\"\n//\n//  type MyTag string\n//\n//  const (\n//    Blue = MyTag(\"Blue\")\n//    Red  = MyTag(\"Red\")\n//  )\n//\n//  type MyObject interface {\n//    MyMethod() (string, error) {Blue}\n//\n//    // Allow clients to change access via the access.Object interface:\n//    SetPermissions(perms access.Permissions, version string) error         {Red}\n//    GetPermissions() (perms access.Permissions, version string, err error) {Blue}\n//  }"},
 	},
@@ -3809,8 +3811,8 @@
 // the parent of its syncgroups for permissions checking purposes.
 // TODO(hpucha): Add blessings to create/join and add a refresh method.
 type SyncgroupManagerClientMethods interface {
-	// ListSyncgroups returns the relative syncgroup ids of all syncgroups attached to
-	// this database.
+	// ListSyncgroups returns a list of ids of all syncgroups attached to this
+	// Database. The list is sorted by blessing, then by name.
 	//
 	// Requires: Read on Database.
 	ListSyncgroups(*context.T, ...rpc.CallOpt) ([]Id, error)
@@ -3947,8 +3949,8 @@
 // the parent of its syncgroups for permissions checking purposes.
 // TODO(hpucha): Add blessings to create/join and add a refresh method.
 type SyncgroupManagerServerMethods interface {
-	// ListSyncgroups returns the relative syncgroup ids of all syncgroups attached to
-	// this database.
+	// ListSyncgroups returns a list of ids of all syncgroups attached to this
+	// Database. The list is sorted by blessing, then by name.
 	//
 	// Requires: Read on Database.
 	ListSyncgroups(*context.T, rpc.ServerCall) ([]Id, error)
@@ -4108,7 +4110,7 @@
 	Methods: []rpc.MethodDesc{
 		{
 			Name: "ListSyncgroups",
-			Doc:  "// ListSyncgroups returns the relative syncgroup ids of all syncgroups attached to\n// this database.\n//\n// Requires: Read on Database.",
+			Doc:  "// ListSyncgroups returns a list of ids of all syncgroups attached to this\n// Database. The list is sorted by blessing, then by name.\n//\n// Requires: Read on Database.",
 			OutArgs: []rpc.ArgDesc{
 				{"", ``}, // []Id
 			},
@@ -5460,7 +5462,8 @@
 //
 // Database represents a set of Collections. Batches, queries, syncgroups, and
 // watch all operate at the Database level.
-// Database.Glob operates over Collection ids, requiring Read on Database.
+// Database.Glob operates over Collection ids, requiring Read on Database,
+// returning ids sorted by blessing, then by name.
 type DatabaseClientMethods interface {
 	// Object provides access control for Vanadium objects.
 	//
@@ -5576,8 +5579,8 @@
 	// Requires: at least one tag on Database, or Read or Write on Service.
 	// Otherwise, ErrNoExistOrNoAccess is returned.
 	Exists(*context.T, ...rpc.CallOpt) (bool, error)
-	// ListCollections returns an unsorted list of all Collection ids that the
-	// caller is allowed to see.
+	// ListCollections returns a list of ids of all Collections in this Database.
+	// The list is sorted by blessing, then by name.
 	// This method exists on Database but not on Service because for the latter
 	// we can simply use glob, while for the former glob lists only Collections
 	// visible in a new snapshot of the Database, ignoring user batches.
@@ -5788,7 +5791,8 @@
 //
 // Database represents a set of Collections. Batches, queries, syncgroups, and
 // watch all operate at the Database level.
-// Database.Glob operates over Collection ids, requiring Read on Database.
+// Database.Glob operates over Collection ids, requiring Read on Database,
+// returning ids sorted by blessing, then by name.
 type DatabaseServerMethods interface {
 	// Object provides access control for Vanadium objects.
 	//
@@ -5904,8 +5908,8 @@
 	// Requires: at least one tag on Database, or Read or Write on Service.
 	// Otherwise, ErrNoExistOrNoAccess is returned.
 	Exists(*context.T, rpc.ServerCall) (bool, error)
-	// ListCollections returns an unsorted list of all Collection ids that the
-	// caller is allowed to see.
+	// ListCollections returns a list of ids of all Collections in this Database.
+	// The list is sorted by blessing, then by name.
 	// This method exists on Database but not on Service because for the latter
 	// we can simply use glob, while for the former glob lists only Collections
 	// visible in a new snapshot of the Database, ignoring user batches.
@@ -6086,8 +6090,8 @@
 	// Requires: at least one tag on Database, or Read or Write on Service.
 	// Otherwise, ErrNoExistOrNoAccess is returned.
 	Exists(*context.T, rpc.ServerCall) (bool, error)
-	// ListCollections returns an unsorted list of all Collection ids that the
-	// caller is allowed to see.
+	// ListCollections returns a list of ids of all Collections in this Database.
+	// The list is sorted by blessing, then by name.
 	// This method exists on Database but not on Service because for the latter
 	// we can simply use glob, while for the former glob lists only Collections
 	// visible in a new snapshot of the Database, ignoring user batches.
@@ -6245,7 +6249,7 @@
 var descDatabase = rpc.InterfaceDesc{
 	Name:    "Database",
 	PkgPath: "v.io/v23/services/syncbase",
-	Doc:     "// Database represents a set of Collections. Batches, queries, syncgroups, and\n// watch all operate at the Database level.\n// Database.Glob operates over Collection ids, requiring Read on Database.",
+	Doc:     "// Database represents a set of Collections. Batches, queries, syncgroups, and\n// watch all operate at the Database level.\n// Database.Glob operates over Collection ids, requiring Read on Database,\n// returning ids sorted by blessing, then by name.",
 	Embeds: []rpc.EmbedDesc{
 		{"Object", "v.io/v23/services/permissions", "// Object provides access control for Vanadium objects.\n//\n// Vanadium services implementing dynamic access control would typically embed\n// this interface and tag additional methods defined by the service with one of\n// Admin, Read, Write, Resolve etc. For example, the VDL definition of the\n// object would be:\n//\n//   package mypackage\n//\n//   import \"v.io/v23/security/access\"\n//   import \"v.io/v23/services/permissions\"\n//\n//   type MyObject interface {\n//     permissions.Object\n//     MyRead() (string, error) {access.Read}\n//     MyWrite(string) error    {access.Write}\n//   }\n//\n// If the set of pre-defined tags is insufficient, services may define their\n// own tag type and annotate all methods with this new type.\n//\n// Instead of embedding this Object interface, define SetPermissions and\n// GetPermissions in their own interface. Authorization policies will typically\n// respect annotations of a single type. For example, the VDL definition of an\n// object would be:\n//\n//  package mypackage\n//\n//  import \"v.io/v23/security/access\"\n//\n//  type MyTag string\n//\n//  const (\n//    Blue = MyTag(\"Blue\")\n//    Red  = MyTag(\"Red\")\n//  )\n//\n//  type MyObject interface {\n//    MyMethod() (string, error) {Blue}\n//\n//    // Allow clients to change access via the access.Object interface:\n//    SetPermissions(perms access.Permissions, version string) error         {Red}\n//    GetPermissions() (perms access.Permissions, version string, err error) {Blue}\n//  }"},
 		{"DatabaseWatcher", "v.io/v23/services/syncbase", "// DatabaseWatcher allows a client to watch for updates to the database. For\n// each watch request, the client will receive a reliable stream of watch events\n// without re-ordering. Only rows and collections matching at least one of the\n// patterns are returned. Rows in collections with no Read access are also\n// filtered out.\n//\n// Watching is done by starting a streaming RPC. The RPC takes a ResumeMarker\n// argument that points to a particular place in the database event log. If an\n// empty ResumeMarker is provided, the WatchStream will begin with a Change\n// batch containing the initial state, always starting with an empty update for\n// the root entity. Otherwise, the WatchStream will contain only changes since\n// the provided ResumeMarker.\n// See watch.GlobWatcher for a detailed explanation of the behavior.\n//\n// The result stream consists of a never-ending sequence of Change messages\n// (until the call fails or is canceled). Each Change contains the Name field\n// with the Vanadium name of the watched entity relative to the database:\n// - \"<encCxId>/<rowKey>\" for row updates\n// - \"<encCxId>\" for collection updates\n// - \"\" for the initial root entity update\n// The Value field is a StoreChange.\n// If the client has no access to a row specified in a change, that change is\n// excluded from the result stream. Collection updates are always sent and can\n// be used to determine that access to a collection is denied, potentially\n// skipping rows.\n//\n// Note: A single Watch Change batch may contain changes from more than one\n// batch as originally committed on a remote Syncbase or obtained from conflict\n// resolution. However, changes from a single original batch will always appear\n// in the same Change batch."},
@@ -6278,7 +6282,7 @@
 		},
 		{
 			Name: "ListCollections",
-			Doc:  "// ListCollections returns an unsorted list of all Collection ids that the\n// caller is allowed to see.\n// This method exists on Database but not on Service because for the latter\n// we can simply use glob, while for the former glob lists only Collections\n// visible in a new snapshot of the Database, ignoring user batches.\n// (Note that the same issue is present in glob on Collection, where Scan can\n// be used instead if batch awareness is required.)\n// TODO(sadovsky): Maybe switch to streaming RPC.\n//\n// Requires: Read on Database.",
+			Doc:  "// ListCollections returns a list of ids of all Collections in this Database.\n// The list is sorted by blessing, then by name.\n// This method exists on Database but not on Service because for the latter\n// we can simply use glob, while for the former glob lists only Collections\n// visible in a new snapshot of the Database, ignoring user batches.\n// (Note that the same issue is present in glob on Collection, where Scan can\n// be used instead if batch awareness is required.)\n// TODO(sadovsky): Maybe switch to streaming RPC.\n//\n// Requires: Read on Database.",
 			InArgs: []rpc.ArgDesc{
 				{"bh", ``}, // BatchHandle
 			},
@@ -6381,7 +6385,7 @@
 //
 // Collection represents a set of Rows.
 // Collection.Glob operates over keys of Rows in the Collection, requiring Read
-// on Collection.
+// on Collection, returning keys in a lexicographically sorted order.
 type CollectionClientMethods interface {
 	// Create creates this Collection. Permissions must be non-nil and include at
 	// least one admin.
@@ -6554,7 +6558,7 @@
 //
 // Collection represents a set of Rows.
 // Collection.Glob operates over keys of Rows in the Collection, requiring Read
-// on Collection.
+// on Collection, returning keys in a lexicographically sorted order.
 type CollectionServerMethods interface {
 	// Create creates this Collection. Permissions must be non-nil and include at
 	// least one admin.
@@ -6719,7 +6723,7 @@
 var descCollection = rpc.InterfaceDesc{
 	Name:    "Collection",
 	PkgPath: "v.io/v23/services/syncbase",
-	Doc:     "// Collection represents a set of Rows.\n// Collection.Glob operates over keys of Rows in the Collection, requiring Read\n// on Collection.",
+	Doc:     "// Collection represents a set of Rows.\n// Collection.Glob operates over keys of Rows in the Collection, requiring Read\n// on Collection, returning keys in a lexicographically sorted order.",
 	Methods: []rpc.MethodDesc{
 		{
 			Name: "Create",
diff --git a/syncbase/client_test.go b/syncbase/client_test.go
index 622a04f..3787451 100644
--- a/syncbase/client_test.go
+++ b/syncbase/client_test.go
@@ -971,12 +971,12 @@
 
 	tu.CheckWatch(t, wstream1, []tu.WatchChangeTest{
 		tu.WatchChangeTestRootPut(nil),
-		tu.WatchChangeTestCollectionPut(cAFoobar.Id(), wire.AllCollectionTags, cxPerms, nil),
-		tu.WatchChangeTestRowPut(cAFoobar.Id(), "a", "value", nil),
-		tu.WatchChangeTestRowPut(cAFoobar.Id(), "abc", "value", nil),
 		tu.WatchChangeTestCollectionPut(cAFoo.Id(), wire.AllCollectionTags, cxPerms, nil),
 		tu.WatchChangeTestRowPut(cAFoo.Id(), "abcd", "value", nil),
-		tu.WatchChangeTestRowPut(cAFoo.Id(), "cd", "value", resumeMarkerInitial),
+		tu.WatchChangeTestRowPut(cAFoo.Id(), "cd", "value", nil),
+		tu.WatchChangeTestCollectionPut(cAFoobar.Id(), wire.AllCollectionTags, cxPerms, nil),
+		tu.WatchChangeTestRowPut(cAFoobar.Id(), "a", "value", nil),
+		tu.WatchChangeTestRowPut(cAFoobar.Id(), "abc", "value", resumeMarkerInitial),
 	})
 	tu.CheckWatch(t, wstream2, []tu.WatchChangeTest{
 		tu.WatchChangeTestRootPut(nil),
@@ -989,13 +989,13 @@
 		tu.WatchChangeTestCollectionPut(cBFoo.Id(), wire.AllCollectionTags, cxPerms, nil),
 		tu.WatchChangeTestRowPut(cBFoo.Id(), "ab", "value", nil),
 		tu.WatchChangeTestRowPut(cBFoo.Id(), "x\\yz", "value", nil),
-		tu.WatchChangeTestCollectionPut(cAFoobar.Id(), wire.AllCollectionTags, cxPerms, nil),
-		tu.WatchChangeTestRowPut(cAFoobar.Id(), "abc", "value", nil),
-		tu.WatchChangeTestRowPut(cAFoobar.Id(), "cd", "value", nil),
 		tu.WatchChangeTestCollectionPut(cAFoo.Id(), wire.AllCollectionTags, cxPerms, nil),
 		tu.WatchChangeTestRowPut(cAFoo.Id(), "abc", "value", nil),
 		tu.WatchChangeTestRowPut(cAFoo.Id(), "abcd", "value", nil),
-		tu.WatchChangeTestRowPut(cAFoo.Id(), "cd", "value", resumeMarkerInitial),
+		tu.WatchChangeTestRowPut(cAFoo.Id(), "cd", "value", nil),
+		tu.WatchChangeTestCollectionPut(cAFoobar.Id(), wire.AllCollectionTags, cxPerms, nil),
+		tu.WatchChangeTestRowPut(cAFoobar.Id(), "abc", "value", nil),
+		tu.WatchChangeTestRowPut(cAFoobar.Id(), "cd", "value", resumeMarkerInitial),
 	})
 
 	// More writes.
diff --git a/syncbase/database_batch.go b/syncbase/database_batch.go
index 6e860ea..8cfb31f 100644
--- a/syncbase/database_batch.go
+++ b/syncbase/database_batch.go
@@ -68,12 +68,7 @@
 func (d *databaseBatch) ListCollections(ctx *context.T) ([]wire.Id, error) {
 	// See comment in v.io/v23/services/syncbase/service.vdl for why we
 	// can't implement ListCollections using Glob (via util.ListChildren).
-	ids, err := d.c.ListCollections(ctx, d.bh)
-	if err != nil {
-		return nil, err
-	}
-	util.SortIds(ids)
-	return ids, nil
+	return d.c.ListCollections(ctx, d.bh)
 }
 
 // Exec implements DatabaseHandle.Exec.
diff --git a/syncbase/model.go b/syncbase/model.go
index 206e2ea..42db9ee 100644
--- a/syncbase/model.go
+++ b/syncbase/model.go
@@ -47,8 +47,8 @@
 	// DatabaseForId returns the Database with the given app blessing and name.
 	DatabaseForId(id wire.Id, schema *Schema) Database
 
-	// ListDatabases returns a list of all Database ids that the caller is allowed
-	// to see. The list is sorted by blessing, then by name.
+	// ListDatabases returns a list of ids of all Databases at this Service. The
+	// list is sorted by blessing, then by name.
 	ListDatabases(ctx *context.T) ([]wire.Id, error)
 
 	// SetPermissions and GetPermissions are included from the AccessController
@@ -74,8 +74,8 @@
 	// name.
 	CollectionForId(id wire.Id) Collection
 
-	// ListCollections returns a list of all Collection ids that the caller is
-	// allowed to see. The list is sorted by blessing, then by name.
+	// ListCollections returns a list of ids of all Collections in this Database.
+	// The list is sorted by blessing, then by name.
 	ListCollections(ctx *context.T) ([]wire.Id, error)
 
 	// Exec executes a syncQL query.
@@ -168,7 +168,8 @@
 	// SyncgroupForId returns the Syncgroup with the given user blessing and name.
 	SyncgroupForId(id wire.Id) Syncgroup
 
-	// ListSyncgroups returns all Syncgroups attached to this database.
+	// ListSyncgroups returns a list of ids of all syncgroups attached to this
+	// Database. The list is sorted by blessing, then by name.
 	ListSyncgroups(ctx *context.T) ([]wire.Id, error)
 
 	// CreateBlob creates a new blob and returns a handle to it.
diff --git a/syncbase/syncgroup_test.go b/syncbase/syncgroup_test.go
index bd77128..c776409 100644
--- a/syncbase/syncgroup_test.go
+++ b/syncbase/syncgroup_test.go
@@ -220,6 +220,45 @@
 	verifySyncgroupInfo(t, ctx, d, sgId, spec, 1)
 }
 
+// Tests that ListSyncgroups order is correct.
+func TestListSyncgroups(t *testing.T) {
+	ctx, sName, cleanup := tu.SetupOrDie(tu.DefaultPerms(access.AllTypicalTags(), "root:o:app:client"))
+	defer cleanup()
+	d := tu.CreateDatabase(t, ctx, syncbase.NewService(sName), "d", nil)
+	c := tu.CreateCollection(t, ctx, d, "c")
+
+	var wantGroups []wire.Id
+	verifySyncgroups(t, ctx, d, wantGroups, verror.ID(""))
+
+	spec := wire.SyncgroupSpec{
+		Perms:       tu.DefaultPerms(wire.AllSyncgroupTags, "root:o:app:client"),
+		Collections: []wire.Id{c.Id()},
+	}
+
+	sg1 := wire.Id{Blessing: "root:o:app:client", Name: "foo"}
+	createSyncgroup(t, ctx, d, sg1, spec, verror.ID(""))
+	sg2 := wire.Id{Blessing: "root", Name: "foobar"}
+	createSyncgroup(t, ctx, d, sg2, spec, verror.ID(""))
+	wantGroups = []wire.Id{sg2, sg1}
+	verifySyncgroups(t, ctx, d, wantGroups, verror.ID(""))
+
+	sg3 := wire.Id{Blessing: "root:o:app:client", Name: "foobar"}
+	createSyncgroup(t, ctx, d, sg3, spec, verror.ID(""))
+	sg4 := wire.Id{Blessing: "root:o:app", Name: "foobad"}
+	createSyncgroup(t, ctx, d, sg4, spec, verror.ID(""))
+	sg5 := wire.Id{Blessing: "root:o:app", Name: "foobaz"}
+	createSyncgroup(t, ctx, d, sg5, spec, verror.ID(""))
+	wantGroups = []wire.Id{sg2, sg4, sg5, sg1, sg3}
+	verifySyncgroups(t, ctx, d, wantGroups, verror.ID(""))
+
+	sg6 := wire.Id{Blessing: "root:o:app", Name: ":client,foobaz"}
+	createSyncgroup(t, ctx, d, sg6, spec, verror.ID(""))
+	sg7 := wire.Id{Blessing: "root", Name: ":o:app:client,foobaz"}
+	createSyncgroup(t, ctx, d, sg7, spec, verror.ID(""))
+	wantGroups = []wire.Id{sg7, sg2, sg6, sg4, sg5, sg1, sg3}
+	verifySyncgroups(t, ctx, d, wantGroups, verror.ID(""))
+}
+
 ///////////////////
 // Helpers.
 
diff --git a/syncbase/util/.api b/syncbase/util/.api
index 951fa94..c1e8c41 100644
--- a/syncbase/util/.api
+++ b/syncbase/util/.api
@@ -13,7 +13,6 @@
 pkg util, func PrefixRangeLimit(string) string
 pkg util, func PrefixRangeStart(string) string
 pkg util, func RowPrefixPattern(wire.Id, string) wire.CollectionRowPattern
-pkg util, func SortIds([]wire.Id)
 pkg util, func ValidateId(wire.Id) error
 pkg util, func ValidateRowKey(string) error
 pkg util, type AccessController interface { GetPermissions, SetPermissions }
diff --git a/syncbase/util/list_child_ids.go b/syncbase/util/list_child_ids.go
index cf4d5ad..527bf7a 100644
--- a/syncbase/util/list_child_ids.go
+++ b/syncbase/util/list_child_ids.go
@@ -5,7 +5,6 @@
 package util
 
 import (
-	"sort"
 	"strings"
 
 	"v.io/v23"
@@ -15,24 +14,8 @@
 	"v.io/v23/verror"
 )
 
-// byId implements sort.Interface for []wire.Id. Sorts by blessing, then name.
-type byId []wire.Id
-
-func (a byId) Len() int      { return len(a) }
-func (a byId) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
-func (a byId) Less(i, j int) bool {
-	if a[i].Blessing != a[j].Blessing {
-		return a[i].Blessing < a[j].Blessing
-	}
-	return a[i].Name < a[j].Name
-}
-
-// SortIds sorts a list of ids by blessing, then name.
-func SortIds(ids []wire.Id) {
-	sort.Sort(byId(ids))
-}
-
-// ListChildIds returns a sorted list of ids of all children of parentFullName.
+// ListChildIds returns a list of ids of all children of parentFullName. Glob is
+// assumed to return the child ids sorted by blessing, then by name.
 func ListChildIds(ctx *context.T, parentFullName string) ([]wire.Id, error) {
 	ns := v23.GetNamespace(ctx)
 	ch, err := ns.Glob(ctx, naming.Join(parentFullName, "*"))
@@ -58,6 +41,5 @@
 			return nil, v.Value.Error
 		}
 	}
-	SortIds(ids)
 	return ids, nil
 }