func TestLocalDocs(t *testing.T) { g := Goblin(t) RegisterFailHandler(func(m string, _ ...int) { g.Fail(m) }) g.Describe("local docs", func() { g.Before(func() { db.Erase() db.Start() }) g.After(func() { db.End() }) g.It("should save a _local doc", func() { Expect(db.SaveLocalDocAt("/banana/_local/w3hnks8hr", map[string]interface{}{ "j": 23, "history": []map[string]interface{}{ map[string]interface{}{"a": "b"}, }, })).To(Equal("0-1")) Expect(db.GetLocalDocJsonAt("/banana/_local/w3hnks8hr")).To(MatchJSON(`{ "_id": "_local/w3hnks8hr", "_rev": "0-1", "j": 23, "history": [{"a": "b"}] }`)) }) g.It("should update a _local doc", func() { Expect(db.SaveLocalDocAt("/banana/_local/w3hnks8hr", map[string]interface{}{ "j": 77, "history": []map[string]interface{}{ map[string]interface{}{"a": "b"}, map[string]interface{}{"c": "d"}, }, "_rev": "0-1", "_id": "_local/w3hnks8hr", })).To(Equal("0-2")) Expect(db.GetLocalDocJsonAt("/banana/_local/w3hnks8hr")).To(MatchJSON(`{ "_id": "_local/w3hnks8hr", "_rev": "0-2", "j": 77, "history": [{"a": "b"}, {"c": "d"}] }`)) }) g.It("should get the _local doc rev", func() { Expect(db.GetLocalDocRev("/banana/_local/w3hnks8hr")).To(BeEquivalentTo("0-2")) }) }) }
/* requests with raw string bodies: - curl -X PUT http://db/path/to/key/_val -d 'some value' and complete JSON objects - curl -X PUT http://db/path -d '{"to": {"_val": "nothing here", "key": "some value"}}' -H 'content-type: application/json' While setting the raw string body of a path will only update that path and do not change others, a full JSON request will replace all keys under the specified path. PUT is idempotent. The "_val" key is optional when setting, but can be used to set values right to the key to which they refer. It is sometimes needed, like in this example, here "path/to" had some children values to be set, but also needed a value of its own. Other use of the "_val" key is to set a value to null strictly, because setting the nude key to null will delete it instead of setting it. */ func Put(w http.ResponseWriter, r *http.Request) { ctx := getContext(r) if ctx.lastKey == "_security" { WriteSecurity(w, r) return } var err error var rev string if ctx.exists && ctx.currentRev != ctx.providedRev { log.WithFields(log.Fields{ "given": ctx.providedRev, "current": ctx.currentRev, }).Debug("rev mismatch.") res := responses.ConflictError() w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } if ctx.lastKey == "_val" || ctx.jsonBody == nil { // updating the value at only this specific path, as raw bytes rev, err = db.SaveValueAt(ctx.path, db.ToLevel(ctx.body)) } else if ctx.jsonBody != nil { // saving a JSON tree, as its structure demands, expanding to subpaths and so on if ctx.lastKey[0] == '_' { log.WithFields(log.Fields{ "path": ctx.path, "lastkey": ctx.lastKey, }).Debug("couldn't update special key.") res := responses.BadRequest("you can't update special keys") w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } if ctx.localDoc { rev, err = db.SaveLocalDocAt(ctx.path, ctx.jsonBody) } else { rev, err = db.ReplaceTreeAt(ctx.path, ctx.jsonBody, false) } } else { err = errors.New("value received at " + ctx.path + " is not JSON: " + string(ctx.body)) } if err != nil { log.Debug("couldn't save value: ", err) res := responses.UnknownError(err.Error()) w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } w.WriteHeader(201) w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(responses.Success{true, ctx.lastKey, rev}) }
// this method only exists for compatibility with PouchDB and should not be used elsewhere. func BulkDocs(w http.ResponseWriter, r *http.Request) { ctx := getContext(r) var idocs interface{} var iid interface{} var irev interface{} var id string var rev string var ok bool if idocs, ok = ctx.jsonBody["docs"]; !ok { res := responses.BadRequest("You're supposed to send an array of docs to input on the database, and you didn't.") w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } /* options */ new_edits := true if inew_edits, ok := ctx.jsonBody["new_edits"]; ok { new_edits = inew_edits.(bool) } path := db.CleanPath(ctx.path) docs := idocs.([]interface{}) res := make([]responses.BulkDocsResult, len(docs)) var err error if new_edits { /* procedure for normal document saving */ for i, idoc := range docs { var newrev string localDoc := false doc := idoc.(map[string]interface{}) if iid, ok = doc["_id"]; ok { id = iid.(string) if strings.HasPrefix(id, "_local/") { localDoc = true } if irev, ok = doc["_rev"]; ok { rev = irev.(string) } else { rev = "" } } else { id = db.Random(5) } if !localDoc { // procedure for normal documents // check rev matching: if iid != nil /* when iid is nil that means the doc had no _id, so we don't have to check. */ { currentRev := db.GetRev(path + "/" + db.EscapeKey(id)) if string(currentRev) != rev && !bytes.HasPrefix(currentRev, []byte{'0', '-'}) { // rev is not matching, don't input this document e := responses.ConflictError() res[i] = responses.BulkDocsResult{ Id: id, Error: e.Error, Reason: e.Reason, } continue } } // proceed to write the document. newrev, err = db.ReplaceTreeAt(path+"/"+db.EscapeKey(id), doc, false) if err != nil { e := responses.UnknownError() res[i] = responses.BulkDocsResult{ Id: id, Error: e.Error, Reason: e.Reason, } continue } } else { // procedure for local documents // check rev matching: currentRev := db.GetLocalDocRev(path + "/" + id) if currentRev != "0-0" && currentRev != rev { // rev is not matching, don't input this document e := responses.ConflictError() res[i] = responses.BulkDocsResult{ Id: id, Error: e.Error, Reason: e.Reason, } continue } // proceed to write the document. newrev, err = db.SaveLocalDocAt(path+"/"+id, doc) if err != nil { e := responses.UnknownError() res[i] = responses.BulkDocsResult{ Id: id, Error: e.Error, Reason: e.Reason, } continue } } res[i] = responses.BulkDocsResult{ Id: id, Ok: true, Rev: newrev, } } } else { /* procedure for replication-type _bulk_docs (new_edits=false)*/ for i, idoc := range docs { id = "" rev = "" doc := idoc.(map[string]interface{}) if iid, ok = doc["_id"]; ok { id = iid.(string) if irev, ok = doc["_rev"]; ok { rev = irev.(string) } } if id == "" || rev == "" { e := responses.UnknownError() res[i] = responses.BulkDocsResult{ Id: id, Error: e.Error, Reason: e.Reason, } continue } // proceed to write currentRev := db.GetRev(path + "/" + db.EscapeKey(id)) if rev < string(currentRev) { // will write only the rev if it is not the winning rev db.AcknowledgeRevFor(path+"/"+db.EscapeKey(id), rev) } else { // otherwise will write the whole doc normally (replacing the current) rev, err = db.ReplaceTreeAt(path+"/"+db.EscapeKey(id), doc, true) } // handle write error if err != nil { e := responses.UnknownError() res[i] = responses.BulkDocsResult{ Id: id, Error: e.Error, Reason: e.Reason, } continue } res[i] = responses.BulkDocsResult{ Id: id, Ok: true, Rev: rev, } } } w.WriteHeader(201) json.NewEncoder(w).Encode(res) }