Skip to content

Commit

Permalink
Merge pull request #1013 from go-kivik/sqliteFind
Browse files Browse the repository at this point in the history
Minimal _find support for sqlite backend
  • Loading branch information
flimzy authored Jul 17, 2024
2 parents 5be038d + 44f844a commit bb46ed0
Show file tree
Hide file tree
Showing 8 changed files with 194 additions and 5 deletions.
1 change: 1 addition & 0 deletions x/sqlite/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ type DB interface {
driver.LocalDocer
driver.DesignDocer
driver.DocCreator
driver.Finder
}

type testDB struct {
Expand Down
21 changes: 20 additions & 1 deletion x/sqlite/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ type db struct {
logger *log.Logger
}

var _ driver.DB = (*db)(nil)
var (
_ driver.DB = (*db)(nil)
_ driver.Finder = (*db)(nil)
)

func (c *client) newDB(name string) *db {
return &db{
Expand Down Expand Up @@ -69,3 +72,19 @@ 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) CreateIndex(context.Context, string, string, interface{}, driver.Options) error {
return nil
}

func (db) GetIndexes(context.Context, driver.Options) ([]driver.Index, error) {
return nil, nil
}

func (db) DeleteIndex(context.Context, string, string, driver.Options) error {
return nil
}

func (db) Explain(context.Context, interface{}, driver.Options) (*driver.QueryPlan, error) {
return nil, nil
}
59 changes: 59 additions & 0 deletions x/sqlite/find.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// 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://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.

package sqlite

import (
"context"
"encoding/json"
"net/http"

"github.com/go-kivik/kivik/v4/driver"
"github.com/go-kivik/kivik/v4/int/errors"
"github.com/go-kivik/kivik/v4/x/mango"
)

func parseQuery(query interface{}) (*mango.Selector, error) {
var selector []byte
switch t := query.(type) {
case string:
selector = []byte(t)
case []byte:
selector = t
case json.RawMessage:
selector = t
default:
var err error
selector, err = json.Marshal(query)
if err != nil {
return nil, err
}
}
var s mango.Selector
err := json.Unmarshal(selector, &s)
return &s, err
}

func (d *db) Find(ctx context.Context, query interface{}, options driver.Options) (driver.Rows, error) {
opts := newOpts(options)
vopts, err := opts.viewOptions(viewAllDocs)
if err != nil {
return nil, err
}
vopts.includeDocs = true

selector, err := parseQuery(query)
if err != nil {
return nil, &errors.Error{Status: http.StatusBadRequest, Err: err}
}
return d.queryBuiltinView(ctx, vopts, selector)
}
97 changes: 97 additions & 0 deletions x/sqlite/find_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// 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://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.

package sqlite

import (
"context"
"net/http"
"testing"

"gitlab.com/flimzy/testy"

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

func TestFind(t *testing.T) {
t.Parallel()
type test struct {
db *testDB
query any
options driver.Options
want []rowResult
wantStatus int
wantErr string
}

tests := testy.NewTable()
tests.Add("no docs in db", test{
want: nil,
})
tests.Add("query is invalid json", test{
query: "invalid json",
wantStatus: http.StatusBadRequest,
wantErr: "invalid character 'i' looking for beginning of value",
})
tests.Add("field equality", func(t *testing.T) interface{} {
d := newDB(t)
rev := d.tPut("foo", map[string]string{"foo": "bar"})
_ = d.tPut("bar", map[string]string{"bar": "baz"})

return test{
db: d,
query: map[string]interface{}{
"foo": "bar",
},
want: []rowResult{
{ID: "foo", Doc: `{"_id":"foo","_rev":"` + rev + `","foo":"bar"}`},
},
}
})

/*
TODO:
- Include _design_docs in results?
- Include _local_docs in results?
- limit
- skip
- fields
- use_index
- bookmark
- execution_stats
*/

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.Find(context.Background(), tt.query, opts)
if !testy.ErrorMatchesRE(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)
})
}
2 changes: 1 addition & 1 deletion x/sqlite/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.22.0
require (
github.com/cenkalti/backoff/v4 v4.3.0
github.com/dop251/goja v0.0.0-20240220182346-e401ed450204
github.com/go-kivik/kivik/v4 v4.2.4-0.20240716103146-b1a9ff9c0569
github.com/go-kivik/kivik/v4 v4.2.4-0.20240716145309-5be038df2f1e
github.com/google/go-cmp v0.6.0
github.com/google/uuid v1.6.0
github.com/mitchellh/mapstructure v1.5.0
Expand Down
4 changes: 2 additions & 2 deletions x/sqlite/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA
github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/go-kivik/kivik/v4 v4.2.4-0.20240716103146-b1a9ff9c0569 h1:ot3o/noHoCtye9GbG6l9GR9NfKRZRfkh6OCbclbal9g=
github.com/go-kivik/kivik/v4 v4.2.4-0.20240716103146-b1a9ff9c0569/go.mod h1:uPonn+OcrDYyZqPXZDTANaWPpmBWAIlpk6gEDnFnDpE=
github.com/go-kivik/kivik/v4 v4.2.4-0.20240716145309-5be038df2f1e h1:1V2uq+tL0mnfCr01Lg6LLgznVxHXK4+5MmYywxMluAs=
github.com/go-kivik/kivik/v4 v4.2.4-0.20240716145309-5be038df2f1e/go.mod h1:uPonn+OcrDYyZqPXZDTANaWPpmBWAIlpk6gEDnFnDpE=
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
Expand Down
2 changes: 1 addition & 1 deletion x/sqlite/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func (d *db) Query(ctx context.Context, ddoc, view string, options driver.Option
}

if isBuiltinView(ddoc) {
return d.queryBuiltinView(ctx, vopts)
return d.queryBuiltinView(ctx, vopts, nil)
}

// Normalize the ddoc and view values
Expand Down
13 changes: 13 additions & 0 deletions x/sqlite/views.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (

"github.com/go-kivik/kivik/v4/driver"
internal "github.com/go-kivik/kivik/v4/int/errors"
"github.com/go-kivik/kivik/v4/x/mango"
)

func endKeyOp(descending, inclusive bool) string {
Expand Down Expand Up @@ -77,6 +78,7 @@ func (d *db) DesignDocs(ctx context.Context, options driver.Options) (driver.Row
func (d *db) queryBuiltinView(
ctx context.Context,
vopts *viewOptions,
sel *mango.Selector,
) (driver.Rows, error) {
args := []interface{}{vopts.includeDocs, vopts.conflicts, vopts.updateSeq, vopts.attachments}

Expand Down Expand Up @@ -176,6 +178,7 @@ func (d *db) queryBuiltinView(
db: d,
rows: results,
updateSeq: meta.updateSeq,
selector: sel,
}, nil
}

Expand Down Expand Up @@ -233,6 +236,7 @@ type rows struct {
db *db
rows *sql.Rows
updateSeq string
selector *mango.Selector
}

var _ driver.Rows = (*rows)(nil)
Expand Down Expand Up @@ -315,6 +319,15 @@ func (r *rows) Next(row *driver.Row) error {
}
}
if full != nil {
if r.selector != nil {
// This means we're responding to a _find query, which requires
// filtering the results, and a different format.
if !r.selector.Match(full.toMap()) {
return r.Next(row)
}
row.Key = nil
row.Value = nil
}
row.Doc = full.toReader()
}
return nil
Expand Down

0 comments on commit bb46ed0

Please sign in to comment.