-
Notifications
You must be signed in to change notification settings - Fork 44
/
find.go
194 lines (177 loc) · 5.79 KB
/
find.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
// 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 kivik
import (
"context"
"encoding/json"
"net/http"
"github.com/go-kivik/kivik/v4/driver"
"github.com/go-kivik/kivik/v4/int/errors"
)
// Find executes a query using the [_find interface]. The query must be a
// string, []byte, or [encoding/json.RawMessage] value, or JSON-marshalable to a
// valid valid query. The options are merged with the query, and will overwrite
// any values in the query.
//
// This arguments this method accepts will change in Kivik 5.x, to be more
// consistent with the rest of the Kivik API. See [issue #1014] for details.
//
// [_find interface]: https://docs.couchdb.org/en/stable/api/database/find.html
// [issue #1014]: https://github.com/go-kivik/kivik/issues/1014
func (db *DB) Find(ctx context.Context, query interface{}, options ...Option) *ResultSet {
if db.err != nil {
return &ResultSet{iter: errIterator(db.err)}
}
finder, ok := db.driverDB.(driver.Finder)
if !ok {
return &ResultSet{iter: errIterator(errFindNotImplemented)}
}
jsonQuery, err := toQuery(query, options...)
if err != nil {
return &ResultSet{iter: errIterator(err)}
}
endQuery, err := db.startQuery()
if err != nil {
return &ResultSet{iter: errIterator(err)}
}
rowsi, err := finder.Find(ctx, jsonQuery, multiOptions(options))
if err != nil {
endQuery()
return &ResultSet{iter: errIterator(err)}
}
return newResultSet(ctx, endQuery, rowsi)
}
// toQuery combines query and options into a final JSON query to be sent to the
// driver.
func toQuery(query interface{}, options ...Option) (json.RawMessage, error) {
var queryJSON []byte
switch t := query.(type) {
case string:
queryJSON = []byte(t)
case []byte:
queryJSON = t
case json.RawMessage:
queryJSON = t
default:
var err error
queryJSON, err = json.Marshal(query)
if err != nil {
return nil, &errors.Error{Status: http.StatusBadRequest, Err: err}
}
}
var queryObject map[string]interface{}
if err := json.Unmarshal(queryJSON, &queryObject); err != nil {
return nil, &errors.Error{Status: http.StatusBadRequest, Err: err}
}
opts := map[string]interface{}{}
multiOptions(options).Apply(opts)
for k, v := range opts {
queryObject[k] = v
}
return json.Marshal(queryObject)
}
// CreateIndex creates an index if it doesn't already exist. ddoc and name may
// be empty, in which case they will be auto-generated. index must be
// marshalable to a valid index object, as described in the [CouchDB documentation].
//
// [CouchDB documentation]: http://docs.couchdb.org/en/stable/api/database/find.html#db-index
func (db *DB) CreateIndex(ctx context.Context, ddoc, name string, index interface{}, options ...Option) error {
if db.err != nil {
return db.err
}
endQuery, err := db.startQuery()
if err != nil {
return err
}
defer endQuery()
if finder, ok := db.driverDB.(driver.Finder); ok {
return finder.CreateIndex(ctx, ddoc, name, index, multiOptions(options))
}
return errFindNotImplemented
}
// DeleteIndex deletes the requested index.
func (db *DB) DeleteIndex(ctx context.Context, ddoc, name string, options ...Option) error {
if db.err != nil {
return db.err
}
endQuery, err := db.startQuery()
if err != nil {
return err
}
defer endQuery()
if finder, ok := db.driverDB.(driver.Finder); ok {
return finder.DeleteIndex(ctx, ddoc, name, multiOptions(options))
}
return errFindNotImplemented
}
// Index is a MonboDB-style index definition.
type Index struct {
DesignDoc string `json:"ddoc,omitempty"`
Name string `json:"name"`
Type string `json:"type"`
Definition interface{} `json:"def"`
}
// GetIndexes returns the indexes defined on the current database.
func (db *DB) GetIndexes(ctx context.Context, options ...Option) ([]Index, error) {
if db.err != nil {
return nil, db.err
}
endQuery, err := db.startQuery()
if err != nil {
return nil, err
}
defer endQuery()
if finder, ok := db.driverDB.(driver.Finder); ok {
dIndexes, err := finder.GetIndexes(ctx, multiOptions(options))
indexes := make([]Index, len(dIndexes))
for i, index := range dIndexes {
indexes[i] = Index(index)
}
return indexes, err
}
return nil, errFindNotImplemented
}
// QueryPlan is the query execution plan for a query, as returned by
// [DB.Explain].
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 [DB.Find].
func (db *DB) Explain(ctx context.Context, query interface{}, options ...Option) (*QueryPlan, error) {
if db.err != nil {
return nil, db.err
}
if explainer, ok := db.driverDB.(driver.Finder); ok {
endQuery, err := db.startQuery()
if err != nil {
return nil, err
}
defer endQuery()
plan, err := explainer.Explain(ctx, query, multiOptions(options))
if err != nil {
return nil, err
}
qp := QueryPlan(*plan)
return &qp, nil
}
return nil, errFindNotImplemented
}