Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3.1.1 backport of allow /db/ if default collection is configured with other named collections (#6222) #6230

Merged
merged 1 commit into from
May 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 42 additions & 2 deletions rest/api_collections_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"testing"

"github.com/couchbase/gocb/v2"
sgbucket "github.com/couchbase/sg-bucket"
"github.com/couchbase/sync_gateway/auth"
"github.com/couchbase/sync_gateway/base"
"github.com/couchbase/sync_gateway/channels"
Expand Down Expand Up @@ -93,7 +94,7 @@ func TestCollectionsPutDocInKeyspace(t *testing.T) {
require.Contains(t, resp.Body.String(), test.keyspace)
// assert special case where /db/docID returns db._default._default
if test.keyspace == dbName {
require.Contains(t, resp.Body.String(), strings.Join([]string{dbName, base.DefaultScope, base.DefaultCollection}, base.ScopeCollectionSeparator))
require.Contains(t, resp.Body.String(), "keyspace db not found")
}
}
if test.expectedStatus == http.StatusCreated {
Expand Down Expand Up @@ -400,7 +401,6 @@ func TestMultiCollectionChannelAccess(t *testing.T) {
scopesConfig = GetCollectionsConfig(t, tb, 2)
dataStoreNames = GetDataStoreNamesFromScopesConfig(scopesConfig)

//collection3 := dataStoreNames[2].CollectionName()
scopesConfig[scope].Collections[collection1] = CollectionConfig{SyncFn: &c1SyncFunction}
scopesConfig[scope].Collections[collection2] = CollectionConfig{SyncFn: &c1SyncFunction}
scopesConfigString, err = json.Marshal(scopesConfig)
Expand Down Expand Up @@ -615,6 +615,7 @@ func TestCollectionsSGIndexQuery(t *testing.T) {
_, err := rt.WaitForChanges(1, "/{{.keyspace}}/_changes", username, false)
require.NoError(t, err)
}

func TestCollectionsPutDBInexistentCollection(t *testing.T) {
base.TestRequiresCollections(t)

Expand All @@ -637,6 +638,45 @@ func TestCollectionsPutDBInexistentCollection(t *testing.T) {
RequireStatus(t, resp, http.StatusForbidden)
}

func TestCollectionsPutDocInDefaultCollectionWithNamedCollections(t *testing.T) {
base.TestRequiresCollections(t)

if base.UnitTestUrlIsWalrus() {
t.Skip("This test only works against Couchbase Server")
}

tb := base.GetTestBucket(t)
defer tb.Close()

// create named collection in the default scope
const customCollectionName = "new_collection"
dBucket := tb.GetUnderlyingBucket().(sgbucket.DynamicDataStoreBucket)
require.NoError(t, dBucket.CreateDataStore(base.ScopeAndCollectionName{Scope: base.DefaultScope, Collection: customCollectionName}))
defer func() {
assert.NoError(t, dBucket.DropDataStore(base.ScopeAndCollectionName{Scope: base.DefaultScope, Collection: customCollectionName}))
}()

rtConfig := &RestTesterConfig{
CustomTestBucket: tb,
PersistentConfig: true,
}

rt := NewRestTester(t, rtConfig)
defer rt.Close()

resp := rt.SendAdminRequest("PUT", "/db1/", fmt.Sprintf(`{"bucket": "%s", "num_index_replicas":0, "scopes": {"_default": {"collections": {"_default": {}, "%s": {}}}}}`, tb.GetName(), customCollectionName))
RequireStatus(t, resp, http.StatusCreated)

resp = rt.SendAdminRequest("PUT", "/db1/doc1", `{"test": true}`)
AssertStatus(t, resp, http.StatusCreated)

resp = rt.SendAdminRequest("PUT", "/db1._default._default/doc2", `{"test": true}`)
AssertStatus(t, resp, http.StatusCreated)

resp = rt.SendAdminRequest("PUT", fmt.Sprintf("/db1._default.%s/doc3", customCollectionName), `{"test": true}`)
AssertStatus(t, resp, http.StatusCreated)
}

func TestCollectionsChangeConfigScope(t *testing.T) {
if base.UnitTestUrlIsWalrus() {
t.Skip("can not create new buckets and scopes in walrus")
Expand Down
16 changes: 7 additions & 9 deletions rest/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -452,31 +452,29 @@ func (h *handler) invoke(method handlerMethod, accessPermissions []Permission, r
if ks != "" {
ksNotFound := base.HTTPErrorf(http.StatusNotFound, "keyspace %s not found", ks)
if dbContext.Scopes != nil {
// If scopes are defined on the database but not in th an empty scope to refer to the one SG is running with, rather than falling back to _default
// endpoint like /db/doc where this matches /db._default._default/
// the check whether the _default._default actually exists on this database is performed below
if keyspaceScope == nil && keyspaceCollection == nil {
ksNotFound = base.HTTPErrorf(http.StatusNotFound, "keyspace %s.%s.%s not found", ks, base.DefaultScope, base.DefaultCollection)
keyspaceScope = base.StringPtr(base.DefaultScope)
keyspaceCollection = base.StringPtr(base.DefaultCollection)
}
// endpoint like /db.collectionName/doc where this matches /db.scopeName.collectionName/doc because there's a single scope defined
if keyspaceScope == nil {
if len(dbContext.Scopes) == 1 {
for scopeName, _ := range dbContext.Scopes {
keyspaceScope = base.StringPtr(scopeName)
}

} else {
keyspaceScope = base.StringPtr(base.DefaultScope)
// There are multiple scopes on a DatabaseContext. This isn't allowed in Sync Gateway but if the feature becomes available, it will be ambiguous which scope to match.
return ksNotFound
}
}
scope, foundScope := dbContext.Scopes[*keyspaceScope]
if !foundScope {
return ksNotFound
}

if keyspaceCollection == nil {
if len(scope.Collections) > 1 {
return ksNotFound
}
keyspaceCollection = base.StringPtr(base.DefaultCollection)
}
_, foundCollection := scope.Collections[*keyspaceCollection]
if !foundCollection {
return ksNotFound
Expand Down