Skip to content

Commit

Permalink
Merge pull request #206 from flimzy/explain
Browse files Browse the repository at this point in the history
Add support for query plan explanations
  • Loading branch information
flimzy authored Oct 10, 2017
2 parents e83984d + 600de2c commit ace441a
Show file tree
Hide file tree
Showing 6 changed files with 131 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
24 changes: 23 additions & 1 deletion driver/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ type BulkDocer interface {
BulkDocs(ctx context.Context, docs []interface{}) (BulkResults, error)
}

// Finder is an optional interface which may be implemented by a database. The
// The Finder is an optional interface which may be implemented by a database. The
// Finder interface provides access to the new (in CouchDB 2.0) MongoDB-style
// query interface.
type Finder interface {
Expand All @@ -188,6 +188,28 @@ type Finder interface {
DeleteIndex(ctx context.Context, ddoc, name string) error
}

// QueryPlan is the response of an Explain query.
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 is the list of fields to be returned in the result set, or
// an empty list if all fields are to be returned.
Fields []interface{} `json:"fields"`
Range map[string]interface{} `json:"range"`
}

// The Explainer is an optional interface which provides access to the query
// explanation API supported by CouchDB 2.0 and newer, and PouchDB 6.3.4 and
// newer.
type Explainer interface {
Explain(ctx context.Context, query interface{}) (*QueryPlan, error)
}

// Index is a MonboDB-style index definition.
type Index struct {
DesignDoc string `json:"ddoc,omitempty"`
Expand Down
30 changes: 30 additions & 0 deletions find.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,33 @@ func (db *DB) GetIndexes(ctx context.Context) ([]Index, error) {
}
return nil, findNotImplemented
}

// QueryPlan is the query execution plan for a query, as returned by the Explain
// function.
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 is the list of fields to be returned in the result set, or
// an empty list if all fields are to be returned.
Fields []interface{} `json:"fields"`
Range map[string]interface{} `json:"range"`
}

// Explain returns the query plan for a given query. Explain takes the same
// arguments as Find.
func (db *DB) Explain(ctx context.Context, query interface{}) (*QueryPlan, error) {
if explainer, ok := db.driverDB.(driver.Explainer); ok {
plan, err := explainer.Explain(ctx, query)
if err != nil {
return nil, err
}
qp := QueryPlan(*plan)
return &qp, nil
}
return nil, errors.Status(StatusNotImplemented, "kivik: driver does not support explain")
}
49 changes: 49 additions & 0 deletions find_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package kivik

import (
"context"
"errors"
"testing"

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

func TestExplain(t *testing.T) {
tests := []struct {
name string
db driver.DB
expected *QueryPlan
status int
err string
}{
{
name: "non-finder",
db: &mockDB{},
status: StatusNotImplemented,
err: "kivik: driver does not support explain",
},
{
name: "explain error",
db: &mockExplainer{err: errors.New("explain error")},
status: StatusInternalServerError,
err: "explain error",
},
{
name: "success",
db: &mockExplainer{plan: &driver.QueryPlan{DBName: "foo"}},
expected: &QueryPlan{DBName: "foo"},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
db := &DB{driverDB: test.db}
result, err := db.Explain(context.Background(), nil)
testy.StatusError(t, test.err, test.status, err)
if d := diff.Interface(test.expected, result); d != nil {
t.Error(d)
}
})
}
}
3 changes: 3 additions & 0 deletions glide.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ import:
- package: github.com/go-kivik/kiviktest
subpackages:
- kt
testimport:
- package: github.com/flimzy/testy
version: ~0.0.1
25 changes: 25 additions & 0 deletions mock_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package kivik

import (
"context"

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

type mockDB struct {
driver.DB
}

var _ driver.DB = &mockDB{}

type mockExplainer struct {
driver.DB
plan *driver.QueryPlan
err error
}

var _ driver.Explainer = &mockExplainer{}

func (db *mockExplainer) Explain(_ context.Context, query interface{}) (*driver.QueryPlan, error) {
return db.plan, db.err
}

0 comments on commit ace441a

Please sign in to comment.