Skip to content

Commit

Permalink
Merge pull request #9 from go-kivik/explain
Browse files Browse the repository at this point in the history
Explain support
  • Loading branch information
flimzy authored Oct 12, 2017
2 parents ff6b849 + 0746d8c commit 46b88f5
Show file tree
Hide file tree
Showing 8 changed files with 223 additions and 1 deletion.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ env:
global:
- KIVIK_TEST_DSN_COUCH16="http://admin:abc123@localhost:6000/"
- KIVIK_TEST_DSN_COUCH20="http://admin:abc123@localhost:6001/"
- HOMEBREW_NO_AUTO_UPDATE=1
matrix:
- MODE=standard

Expand Down
52 changes: 52 additions & 0 deletions find.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,55 @@ func (d *db) Find(ctx context.Context, query interface{}) (driver.Rows, error) {
}
return newRows(resp.Body), nil
}

type queryPlan struct {
DBName string `json:"dbname"`
Index map[string]interface{} `json:"index"`
Selector map[string]interface{} `json:"selector"`
Options map[string]interface{} `json:"opts"`
Limit int64 `json:"limit"`
Skip int64 `json:"skip"`
Fields fields `json:"fields"`
Range map[string]interface{} `json:"range"`
}

type fields []interface{}

func (f *fields) UnmarshalJSON(data []byte) error {
if string(data) == `"all_fields"` {
return nil
}
var i []interface{}
if err := json.Unmarshal(data, &i); err != nil {
return err
}
newFields := make([]interface{}, len(i))
copy(newFields, i)
*f = newFields
return nil
}

func (d *db) Explain(ctx context.Context, query interface{}) (*driver.QueryPlan, error) {
if d.client.Compat == CompatCouch16 {
return nil, findNotImplemented
}
body, err := util.ToJSON(query)
if err != nil {
return nil, err
}
var plan queryPlan
_, err = d.Client.DoJSON(ctx, kivik.MethodPost, d.path("_explain", nil), &chttp.Options{Body: body}, &plan)
if err != nil {
return nil, err
}
return &driver.QueryPlan{
DBName: plan.DBName,
Index: plan.Index,
Selector: plan.Selector,
Options: plan.Options,
Limit: plan.Limit,
Skip: plan.Skip,
Fields: plan.Fields,
Range: plan.Range,
}, nil
}
123 changes: 123 additions & 0 deletions find_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package couchdb

import (
"context"
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"strings"
"testing"

"github.com/flimzy/diff"
"github.com/flimzy/kivik"
"github.com/flimzy/kivik/driver"
"github.com/flimzy/testy"
)

func TestExplain(t *testing.T) {
tests := []struct {
name string
db *db
query interface{}
expected *driver.QueryPlan
status int
err string
}{
{
name: "CouchDB 1.6",
db: &db{
client: &client{Compat: CompatCouch16},
},
status: kivik.StatusNotImplemented,
err: "kivik: Find interface not implemented prior to CouchDB 2.0.0",
},
{
name: "invalid query",
db: &db{client: &client{}},
query: make(chan int),
status: kivik.StatusInternalServerError,
err: "json: unsupported type: chan int",
},
{
name: "transport error",
db: newTestDB(nil, errors.New("xport error")),
status: kivik.StatusInternalServerError,
err: "Post http://example.com/testdb/_explain: xport error",
},
{
name: "db error",
db: newTestDB(&http.Response{
StatusCode: kivik.StatusNotFound,
Request: &http.Request{Method: http.MethodPost},
Body: ioutil.NopCloser(strings.NewReader("")),
}, nil),
status: kivik.StatusNotFound,
err: "Not Found",
},
{
name: "success",
db: newTestDB(&http.Response{
StatusCode: kivik.StatusOK,
Body: ioutil.NopCloser(strings.NewReader(`{"dbname":"foo"}`)),
}, nil),
expected: &driver.QueryPlan{DBName: "foo"},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
result, err := test.db.Explain(context.Background(), test.query)
testy.StatusError(t, test.err, test.status, err)
if d := diff.Interface(test.expected, result); d != nil {
t.Error(d)
}
})
}
}

func TestUnmarshalQueryPlan(t *testing.T) {
tests := []struct {
name string
input string
expected *queryPlan
err string
}{
{
name: "non-array",
input: `{"fields":{}}`,
err: "json: cannot unmarshal object into Go",
},
{
name: "all_fields",
input: `{"fields":"all_fields","dbname":"foo"}`,
expected: &queryPlan{DBName: "foo"},
},
{
name: "simple field list",
input: `{"fields":["foo","bar"],"dbname":"foo"}`,
expected: &queryPlan{Fields: []interface{}{"foo", "bar"}, DBName: "foo"},
},
{
name: "complex field list",
input: `{"dbname":"foo", "fields":[{"foo":"asc"},{"bar":"desc"}]}`,
expected: &queryPlan{DBName: "foo",
Fields: []interface{}{map[string]interface{}{"foo": "asc"},
map[string]interface{}{"bar": "desc"}}},
},
{
name: "invalid bare string",
input: `{"fields":"not_all_fields"}`,
err: "json: cannot unmarshal string into Go",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
result := new(queryPlan)
err := json.Unmarshal([]byte(test.input), &result)
testy.ErrorRE(t, test.err, err)
if d := diff.Interface(test.expected, result); d != nil {
t.Error(d)
}
})
}
}
4 changes: 3 additions & 1 deletion glide.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ ignore:
- github.com/gopherjs/gopherjs
import:
- package: github.com/flimzy/kivik
version: ^1.1.0
version: ^1.2.0
subpackages:
- driver
- driver/util
Expand All @@ -19,3 +19,5 @@ testImport:
- package: github.com/go-kivik/kiviktest
subpackages:
- kt
- package: github.com/flimzy/testy
version: ^0.0.2
30 changes: 30 additions & 0 deletions mock_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package couchdb

import (
"context"
"net/http"

"github.com/go-kivik/couchdb/chttp"
)

type dummyTransport struct {
response *http.Response
err error
}

var _ http.RoundTripper = &dummyTransport{}

func (t *dummyTransport) RoundTrip(_ *http.Request) (*http.Response, error) {
return t.response, t.err
}

func newTestDB(response *http.Response, err error) *db {
chttpClient, _ := chttp.New(context.Background(), "http://example.com/")
chttpClient.Client.Transport = &dummyTransport{response: response, err: err}
return &db{
dbName: "testdb",
client: &client{
Client: chttpClient,
},
}
}
6 changes: 6 additions & 0 deletions test/cloudant.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ func registerSuiteCloudant() {
"Find/RW/group/Admin/Warning.warning": "no matching index found, create an index to optimize query time",
"Find/RW/group/NoAuth/Warning.warning": "no matching index found, create an index to optimize query time",

"Explain.databases": []string{"chicken", "_duck"},
"Explain/Admin/chicken.status": kivik.StatusNotFound,
"Explain/Admin/_duck.status": kivik.StatusForbidden,
"Explain/NoAuth/chicken.status": kivik.StatusNotFound,
"Explain/NoAuth/_duck.status": kivik.StatusUnauthorized,

"Query/RW/group/NoAuth.status": kivik.StatusUnauthorized,

"DBExists.databases": []string{"_users", "chicken", "_duck"},
Expand Down
2 changes: 2 additions & 0 deletions test/couchdb16.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ func registerSuiteCouch16() {
"CreateIndex.status": kivik.StatusNotImplemented, // Couchdb 1.6 doesn't support the find interface
"GetIndexes.skip": true, // Couchdb 1.6 doesn't support the find interface
"DeleteIndex.skip": true, // Couchdb 1.6 doesn't support the find interface
"Explain.databases": []string{"_users"},
"Explain.status": kivik.StatusNotImplemented, // Couchdb 1.6 doesn't support the find interface

"DBExists.databases": []string{"_users", "chicken", "_duck"},
"DBExists/Admin/_users.exists": true,
Expand Down
6 changes: 6 additions & 0 deletions test/couchdb20.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ func registerSuiteCouch20() {
"Find/RW/group/Admin/Warning.warning": "no matching index found, create an index to optimize query time",
"Find/RW/group/NoAuth/Warning.warning": "no matching index found, create an index to optimize query time",

"Explain.databases": []string{"chicken", "_duck"},
"Explain/Admin/chicken.status": kivik.StatusNotFound,
"Explain/Admin/_duck.status": kivik.StatusNotFound,
"Explain/NoAuth/chicken.status": kivik.StatusNotFound,
"Explain/NoAuth/_duck.status": kivik.StatusUnauthorized,

"DBExists.databases": []string{"_users", "chicken", "_duck"},
"DBExists/Admin/_users.exists": true,
"DBExists/Admin/chicken.exists": false,
Expand Down

0 comments on commit 46b88f5

Please sign in to comment.