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