github.com/go-kivik/kivik/v4@v4.3.2/pouchdb/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 //go:build js 14 15 package pouchdb 16 17 import ( 18 "context" 19 "encoding/json" 20 "io" 21 "net/http" 22 "strings" 23 24 "github.com/gopherjs/gopherjs/js" 25 26 "github.com/go-kivik/kivik/v4/driver" 27 internal "github.com/go-kivik/kivik/v4/int/errors" 28 "github.com/go-kivik/kivik/v4/pouchdb/bindings" 29 ) 30 31 var _ driver.Finder = &db{} 32 33 // buildIndex merges the ddoc and name into the index structure, as required 34 // by the PouchDB-find plugin. 35 func buildIndex(ddoc, name string, index interface{}) (*js.Object, error) { 36 i, err := bindings.Objectify(index) 37 if err != nil { 38 return nil, err 39 } 40 o := js.Global.Get("Object").New(i) 41 if ddoc != "" { 42 o.Set("ddoc", ddoc) 43 } 44 if name != "" { 45 o.Set("name", name) 46 } 47 return o, nil 48 } 49 50 func (d *db) CreateIndex(ctx context.Context, ddoc, name string, index interface{}, _ driver.Options) error { 51 indexObj, err := buildIndex(ddoc, name, index) 52 if err != nil { 53 return err 54 } 55 _, err = d.db.CreateIndex(ctx, indexObj) 56 return err 57 } 58 59 func (d *db) GetIndexes(ctx context.Context, _ driver.Options) (indexes []driver.Index, err error) { 60 defer bindings.RecoverError(&err) 61 result, err := d.db.GetIndexes(ctx) 62 if err != nil { 63 return nil, err 64 } 65 // This might not be the most efficient, but it's easy 66 var final struct { 67 Indexes []driver.Index `json:"indexes"` 68 } 69 err = json.Unmarshal([]byte(js.Global.Get("JSON").Call("stringify", result).String()), &final) 70 return final.Indexes, err 71 } 72 73 // findIndex attempts to find the requested index definition 74 func (d *db) findIndex(ctx context.Context, ddoc, name string) (interface{}, error) { 75 ddoc = "_design/" + strings.TrimPrefix(ddoc, "_design/") 76 indexes, err := d.GetIndexes(ctx, nil) 77 if err != nil { 78 return nil, err 79 } 80 for _, idx := range indexes { 81 if idx.Type == "special" { 82 continue 83 } 84 if idx.DesignDoc == ddoc && idx.Name == name { 85 return map[string]interface{}{ 86 "ddoc": idx.DesignDoc, 87 "name": idx.Name, 88 "type": idx.Type, 89 "def": idx.Definition, 90 }, nil 91 } 92 } 93 return nil, &internal.Error{Status: http.StatusNotFound, Message: "index does not exist"} 94 } 95 96 func (d *db) DeleteIndex(ctx context.Context, ddoc, name string, _ driver.Options) error { 97 index, err := d.findIndex(ctx, ddoc, name) 98 if err != nil { 99 return err 100 } 101 _, err = d.db.DeleteIndex(ctx, index) 102 return err 103 } 104 105 func (d *db) Find(ctx context.Context, query interface{}, _ driver.Options) (driver.Rows, error) { 106 result, err := d.db.Find(ctx, query) 107 if err != nil { 108 return nil, err 109 } 110 return &findRows{ 111 Object: result, 112 }, nil 113 } 114 115 type findRows struct { 116 *js.Object 117 } 118 119 var _ driver.Rows = &findRows{} 120 121 func (r *findRows) Offset() int64 { return 0 } 122 func (r *findRows) TotalRows() int64 { return 0 } 123 func (r *findRows) UpdateSeq() string { return "" } 124 func (r *findRows) Warning() string { 125 if w := r.Get("warning"); w != js.Undefined { 126 return w.String() 127 } 128 return "" 129 } 130 131 func (r *findRows) Close() error { 132 r.Delete("docs") // Free up memory used by any remaining rows 133 return nil 134 } 135 136 func (r *findRows) Next(row *driver.Row) (err error) { 137 defer bindings.RecoverError(&err) 138 if r.Get("docs") == js.Undefined || r.Get("docs").Length() == 0 { 139 return io.EOF 140 } 141 next := r.Get("docs").Call("shift") 142 row.Doc = strings.NewReader(jsJSON.Call("stringify", next).String()) 143 return nil 144 } 145 146 type queryPlan struct { 147 DBName string `json:"dbname"` 148 Index map[string]interface{} `json:"index"` 149 Selector map[string]interface{} `json:"selector"` 150 Options map[string]interface{} `json:"opts"` 151 Limit int64 `json:"limit"` 152 Skip int64 `json:"skip"` 153 Fields fields `json:"fields"` 154 Range map[string]interface{} `json:"range"` 155 } 156 157 type fields []interface{} 158 159 func (f *fields) UnmarshalJSON(data []byte) error { 160 if string(data) == `"all_fields"` { 161 return nil 162 } 163 var i []interface{} 164 if err := json.Unmarshal(data, &i); err != nil { 165 return err 166 } 167 newFields := make([]interface{}, len(i)) 168 copy(newFields, i) 169 *f = newFields 170 return nil 171 } 172 173 func (d *db) Explain(ctx context.Context, query interface{}, _ driver.Options) (*driver.QueryPlan, error) { 174 result, err := d.db.Explain(ctx, query) 175 if err != nil { 176 return nil, err 177 } 178 planJSON := js.Global.Get("JSON").Call("stringify", result).String() 179 var plan queryPlan 180 if err := json.Unmarshal([]byte(planJSON), &plan); err != nil { 181 return nil, err 182 } 183 return &driver.QueryPlan{ 184 DBName: plan.DBName, 185 Index: plan.Index, 186 Selector: plan.Selector, 187 Options: plan.Options, 188 Limit: plan.Limit, 189 Skip: plan.Skip, 190 Fields: plan.Fields, 191 Range: plan.Range, 192 }, nil 193 }