Skip to content

Commit

Permalink
Merge pull request #943 from go-kivik/LocalDocs
Browse files Browse the repository at this point in the history
x/sqlite: Add support for LocalDocs and DesignDocs methods
  • Loading branch information
flimzy committed Apr 16, 2024
2 parents ec42afe + 51dfa68 commit e2287f0
Show file tree
Hide file tree
Showing 7 changed files with 351 additions and 144 deletions.
2 changes: 2 additions & 0 deletions x/sqlite/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ type DB interface {
driver.OpenRever
driver.AttachmentMetaGetter
driver.RevGetter
driver.LocalDocer
driver.DesignDocer
}

type testDB struct {
Expand Down
8 changes: 0 additions & 8 deletions x/sqlite/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,3 @@ func (db) BulkDocs(context.Context, []interface{}, driver.Options) ([]driver.Bul
func (db) Copy(context.Context, string, string, driver.Options) (string, error) {
return "", nil
}

func (db) DesignDocs(context.Context, driver.Options) (driver.Rows, error) {
return nil, nil
}

func (db) LocalDocs(context.Context, driver.Options) (driver.Rows, error) {
return nil, nil
}
161 changes: 25 additions & 136 deletions x/sqlite/designdocs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,178 +17,67 @@ package sqlite

import (
"context"
"database/sql"
"net/http"
"regexp"
"testing"

"github.com/google/go-cmp/cmp"
"gitlab.com/flimzy/testy"

"github.com/go-kivik/kivik/v4"
"github.com/go-kivik/kivik/v4/driver"
"github.com/go-kivik/kivik/v4/internal/mock"
)

type ddoc struct {
ID string
Rev int
RevID string
Lang string
FuncType string
FuncName string
FuncBody string
AutoUpdate bool
}

func TestDBPut_designDocs(t *testing.T) {
func TestDBDesignDocs(t *testing.T) {
t.Parallel()
type test struct {
db *testDB
docID string
doc interface{}
options driver.Options
check func(*testing.T)
wantRev string
wantRevs []leaf
wantStatus int
wantErr string
wantAttachments []attachmentRow
wantDDocs []ddoc
db *testDB
options driver.Options
want []rowResult
wantStatus int
wantErr string
}
tests := testy.NewTable()
tests.Add("design doc with non-string language returns 400", test{
docID: "_design/foo",
doc: map[string]interface{}{
"language": 1234,
},
wantStatus: http.StatusBadRequest,
wantErr: "json: cannot unmarshal number into Go struct field designDocData.language of type string",
})
tests.Add("non-design doc with non-string language value is ok", test{
docID: "foo",
doc: map[string]interface{}{
"language": 1234,
},
wantRev: "1-.*",
wantRevs: []leaf{
{ID: "foo", Rev: 1},
},
tests.Add("no docs in db", test{
want: nil,
})
tests.Add("design doc with view function creates .Design entries and map table", func(t *testing.T) interface{} {
d := newDB(t)
tests.Add("returns only the design docs", func(t *testing.T) interface{} {
db := newDB(t)
_ = db.tPut("foo", map[string]string{"cat": "meow"})
rev2 := db.tPut("_design/bar", map[string]string{"dog": "woof"})
_ = db.tPut("_local/baz", map[string]string{"pig": "oink"})

return test{
db: d,
docID: "_design/foo",
doc: map[string]interface{}{
"language": "javascript",
"views": map[string]interface{}{
"bar": map[string]interface{}{
"map": "function(doc) { emit(doc._id, null); }",
},
},
},
wantRev: "1-.*",
wantRevs: []leaf{
{ID: "_design/foo", Rev: 1},
},
wantDDocs: []ddoc{
db: db,
want: []rowResult{
{
ID: "_design/foo",
Rev: 1,
Lang: "javascript",
FuncType: "map",
FuncName: "bar",
FuncBody: "function(doc) { emit(doc._id, null); }",
AutoUpdate: true,
ID: "_design/bar",
Rev: rev2,
Value: `{"value":{"rev":"` + rev2 + `"}}` + "\n",
},
},
check: func(t *testing.T) {
var viewCount int
err := d.underlying().QueryRow(`
SELECT COUNT(*)
FROM sqlite_master
WHERE type = 'table'
AND name LIKE 'foo_map_bar_%'
`).Scan(&viewCount)
if err != nil {
t.Fatal(err)
}
if viewCount != 1 {
t.Errorf("Found %d view tables, expected 1", viewCount)
}
},
}
})
/*
TODO:
- unsupported language? -- ignored?
*/

tests.Run(t, func(t *testing.T, tt test) {
t.Parallel()
dbc := tt.db
if dbc == nil {
dbc = newDB(t)
db := tt.db
if db == nil {
db = newDB(t)
}
opts := tt.options
if opts == nil {
opts = mock.NilOption
}
rev, err := dbc.Put(context.Background(), tt.docID, tt.doc, opts)
rows, err := db.DesignDocs(context.Background(), opts)
if !testy.ErrorMatches(tt.wantErr, err) {
t.Errorf("Unexpected error: %s", err)
}
if status := kivik.HTTPStatus(err); status != tt.wantStatus {
t.Errorf("Unexpected status: %d", status)
}
if tt.check != nil {
tt.check(t)
}
if err != nil {
return
}
if !regexp.MustCompile(tt.wantRev).MatchString(rev) {
t.Errorf("Unexpected rev: %s, want %s", rev, tt.wantRev)
}
checkLeaves(t, dbc.underlying(), tt.wantRevs)
checkAttachments(t, dbc.underlying(), tt.wantAttachments)
checkDDocs(t, dbc.underlying(), tt.wantDDocs)
})
}

func checkDDocs(t *testing.T, d *sql.DB, want []ddoc) {
t.Helper()
rows, err := d.Query(`
SELECT id, rev, rev_id, language, func_type, func_name, func_body, auto_update
FROM test_design
`)
if err != nil {
t.Fatal(err)
}
defer rows.Close()
var got []ddoc
for rows.Next() {
var d ddoc
if err := rows.Scan(&d.ID, &d.Rev, &d.RevID, &d.Lang, &d.FuncType, &d.FuncName, &d.FuncBody, &d.AutoUpdate); err != nil {
t.Fatal(err)
}
got = append(got, d)
}
if err := rows.Err(); err != nil {
t.Fatal(err)
}
for i, w := range want {
if i > len(got)-1 {
t.Errorf("Missing expected design doc: %+v", w)
break
}
// allow tests to omit RevID
if w.RevID == "" {
got[i].RevID = ""
}
}
if d := cmp.Diff(want, got); d != "" {
t.Errorf("Unexpected design docs:\n%s", d)
}
checkRows(t, rows, tt.want)
})
}
82 changes: 82 additions & 0 deletions x/sqlite/localdocs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// http:https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

//go:build !js
// +build !js

package sqlite

import (
"context"
"testing"

"gitlab.com/flimzy/testy"

"github.com/go-kivik/kivik/v4"
"github.com/go-kivik/kivik/v4/driver"
"github.com/go-kivik/kivik/v4/internal/mock"
)

func TestDBLocalDocs(t *testing.T) {
t.Parallel()
type test struct {
db *testDB
options driver.Options
want []rowResult
wantStatus int
wantErr string
}
tests := testy.NewTable()
tests.Add("no docs in db", test{
want: nil,
})
tests.Add("returns only the local docs", func(t *testing.T) interface{} {
db := newDB(t)
_ = db.tPut("foo", map[string]string{"cat": "meow"})
rev2 := db.tPut("_local/bar", map[string]string{"dog": "woof"})

return test{
db: db,
want: []rowResult{
{
ID: "_local/bar",
Rev: rev2,
Value: `{"value":{"rev":"` + rev2 + `"}}` + "\n",
},
},
}
})

tests.Run(t, func(t *testing.T, tt test) {
t.Parallel()
db := tt.db
if db == nil {
db = newDB(t)
}
opts := tt.options
if opts == nil {
opts = mock.NilOption
}
rows, err := db.LocalDocs(context.Background(), opts)
if !testy.ErrorMatches(tt.wantErr, err) {
t.Errorf("Unexpected error: %s", err)
}
if status := kivik.HTTPStatus(err); status != tt.wantStatus {
t.Errorf("Unexpected status: %d", status)
}
if err != nil {
return
}

checkRows(t, rows, tt.want)
})
}
Loading

0 comments on commit e2287f0

Please sign in to comment.