github.com/go-kivik/kivik/v4@v4.3.2/pouchdb/db.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 "bytes" 19 "context" 20 "encoding/json" 21 "io" 22 "net/http" 23 "sync/atomic" 24 25 "github.com/gopherjs/gopherjs/js" 26 27 "github.com/go-kivik/kivik/v4/driver" 28 internal "github.com/go-kivik/kivik/v4/int/errors" 29 "github.com/go-kivik/kivik/v4/pouchdb/bindings" 30 ) 31 32 type db struct { 33 db *bindings.DB 34 35 client *client 36 37 // these are set to 1 when compaction begins, and unset when the 38 // callback returns. 39 compacting uint32 40 viewCleanup uint32 41 } 42 43 var _ driver.DB = (*db)(nil) 44 45 func (d *db) AllDocs(ctx context.Context, options driver.Options) (driver.Rows, error) { 46 opts := map[string]interface{}{} 47 options.Apply(opts) 48 result, err := d.db.AllDocs(ctx, opts) 49 if err != nil { 50 return nil, err 51 } 52 return &rows{ 53 Object: result, 54 }, nil 55 } 56 57 func (d *db) Query(ctx context.Context, ddoc, view string, options driver.Options) (driver.Rows, error) { 58 opts := map[string]interface{}{} 59 options.Apply(opts) 60 result, err := d.db.Query(ctx, ddoc, view, opts) 61 if err != nil { 62 return nil, err 63 } 64 return &rows{ 65 Object: result, 66 }, nil 67 } 68 69 type document struct { 70 id string 71 rev string 72 body io.Reader 73 done bool 74 } 75 76 func (d *document) Next(row *driver.Row) error { 77 if d.done { 78 return io.EOF 79 } 80 d.done = true 81 row.ID = d.id 82 row.Rev = d.rev 83 row.Doc = d.body 84 return nil 85 } 86 87 func (d *document) Close() error { 88 d.done = true 89 return nil 90 } 91 92 func (d *document) UpdateSeq() string { return "" } 93 func (d *document) Offset() int64 { return 0 } 94 func (d *document) TotalRows() int64 { return 0 } 95 96 func (d *db) Get(ctx context.Context, docID string, options driver.Options) (*driver.Document, error) { 97 opts := map[string]interface{}{} 98 options.Apply(opts) 99 doc, rev, err := d.db.Get(ctx, docID, opts) 100 if err != nil { 101 return nil, err 102 } 103 return &driver.Document{ 104 Rev: rev, 105 Body: io.NopCloser(bytes.NewReader(doc)), 106 }, nil 107 } 108 109 func (d *db) CreateDoc(ctx context.Context, doc interface{}, options driver.Options) (docID, rev string, err error) { 110 jsonDoc, err := json.Marshal(doc) 111 if err != nil { 112 return "", "", err 113 } 114 jsDoc := js.Global.Get("JSON").Call("parse", string(jsonDoc)) 115 opts := map[string]interface{}{} 116 options.Apply(opts) 117 return d.db.Post(ctx, jsDoc, opts) 118 } 119 120 func (d *db) Put(ctx context.Context, docID string, doc interface{}, options driver.Options) (rev string, err error) { 121 jsonDoc, err := json.Marshal(doc) 122 if err != nil { 123 return "", err 124 } 125 jsDoc := js.Global.Get("JSON").Call("parse", string(jsonDoc)) 126 if id := jsDoc.Get("_id"); id != js.Undefined { 127 if id.String() != docID { 128 return "", &internal.Error{Status: http.StatusBadRequest, Message: "id argument must match _id field in document"} 129 } 130 } 131 opts := map[string]interface{}{} 132 options.Apply(opts) 133 jsDoc.Set("_id", docID) 134 return d.db.Put(ctx, jsDoc, opts) 135 } 136 137 func (d *db) Delete(ctx context.Context, docID string, options driver.Options) (newRev string, err error) { 138 opts := map[string]interface{}{} 139 options.Apply(opts) 140 rev, _ := opts["rev"].(string) 141 return d.db.Delete(ctx, docID, rev, opts) 142 } 143 144 func (d *db) Stats(ctx context.Context) (*driver.DBStats, error) { 145 i, err := d.db.Info(ctx) 146 return &driver.DBStats{ 147 Name: i.Name, 148 CompactRunning: atomic.LoadUint32(&d.compacting) == 1 || atomic.LoadUint32(&d.viewCleanup) == 1, 149 DocCount: i.DocCount, 150 UpdateSeq: i.UpdateSeq, 151 }, err 152 } 153 154 func (d *db) Compact(context.Context) error { 155 if atomic.LoadUint32(&d.compacting) == 1 { 156 return &internal.Error{Status: http.StatusTooManyRequests, Message: "kivik: compaction already running"} 157 } 158 atomic.StoreUint32(&d.compacting, 1) 159 defer atomic.StoreUint32(&d.compacting, 0) 160 return d.db.Compact() 161 } 162 163 // CompactView is unimplemented for PouchDB. 164 func (d *db) CompactView(context.Context, string) error { 165 return nil 166 } 167 168 func (d *db) ViewCleanup(context.Context) error { 169 if atomic.LoadUint32(&d.viewCleanup) == 1 { 170 return &internal.Error{Status: http.StatusTooManyRequests, Message: "kivik: view cleanup already running"} 171 } 172 atomic.StoreUint32(&d.viewCleanup, 1) 173 defer atomic.StoreUint32(&d.viewCleanup, 0) 174 return d.db.ViewCleanup() 175 } 176 177 func (d *db) Close() error { 178 return d.db.Close() 179 } 180 181 var _ driver.Purger = &db{} 182 183 func (d *db) Purge(ctx context.Context, docRevMap map[string][]string) (*driver.PurgeResult, error) { 184 result := &driver.PurgeResult{ 185 Purged: make(map[string][]string, len(docRevMap)), 186 } 187 for docID, revs := range docRevMap { 188 for _, rev := range revs { 189 delRevs, err := d.db.Purge(ctx, docID, rev) 190 if err != nil { 191 return result, err 192 } 193 if result.Purged[docID] == nil { 194 result.Purged[docID] = delRevs 195 } else { 196 result.Purged[docID] = append(result.Purged[docID], delRevs...) 197 } 198 } 199 } 200 return result, nil 201 }