/* 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) }
func TestIdempotent(t *testing.T) { g := Goblin(t) RegisterFailHandler(func(m string, _ ...int) { g.Fail(m) }) g.Describe("idempotent put", func() { g.Before(func() { db.Erase() db.Start() }) g.After(func() { db.End() }) g.It("should put a tree", func() { Expect(db.ReplaceTreeAt("/fruits/banana", map[string]interface{}{ "colour": "yellow", "hardness": "low", "_val": "a fruit.", }, false)).To(HavePrefix("1-")) Expect(db.GetValueAt("/fruits/banana")).To(BeEquivalentTo(`"a fruit."`)) Expect(db.GetValueAt("/fruits/banana/colour")).To(BeEquivalentTo(`"yellow"`)) }) g.It("should replace it with a totally different object with arrays", func() { rev, err := db.ReplaceTreeAt("", map[string]interface{}{ "what": "numbers", "numbers": []interface{}{"zero", "one", "two", "three"}, }, false) Expect(err).ToNot(HaveOccurred()) Expect(rev).To(HavePrefix("2-")) Expect(db.GetValueAt("/numbers/3")).To(BeEquivalentTo(`"three"`)) _, err = db.GetValueAt("/numbers") Expect(err).To(HaveOccurred()) Expect(db.GetTreeAt("")).To(Equal(map[string]interface{}{ "what": value("numbers"), "numbers": map[string]interface{}{ "0": value("zero"), "1": value("one"), "2": value("two"), "3": value("three"), }, })) _, err = db.GetValueAt("/fruits") Expect(err).To(HaveOccurred()) }) g.It("should replace it again with a totally different object", func() { Expect(db.ReplaceTreeAt("/fruits/orange", map[string]interface{}{ "colour": "orange", }, false)).To(HavePrefix("1-")) Expect(db.GetTreeAt("")).To(Equal(map[string]interface{}{ "what": value("numbers"), "numbers": map[string]interface{}{ "0": value("zero"), "1": value("one"), "2": value("two"), "3": value("three"), }, "fruits": map[string]interface{}{ "orange": map[string]interface{}{ "colour": value("orange"), }, }, })) }) g.It("should put an empty object", func() { rev, err := db.ReplaceTreeAt("/fruits/watermellon", map[string]interface{}{}, false) Expect(err).ToNot(HaveOccurred()) Expect(rev).To(HavePrefix("1-")) }) }) }
func TestSeqs(t *testing.T) { g := Goblin(t) RegisterFailHandler(func(m string, _ ...int) { g.Fail(m) }) g.Describe("seqs getting bumped", func() { g.Before(func() { db.Erase() db.Start() }) g.After(func() { db.End() }) /* we currently don't care a lot to the exact seqs here, we only care if the values are being increased and there are no conflicts. later maybe we'll have to optimize all this (and conseqüently change all these tests) so we don't get new the seq bumped for operations that do not result in an actual _rev being written (in DELETE and UNDELETE operations there are temporary _revs being created and then replaced by others with the same number prefix). */ g.It("should increase seqs when adding a new tree", func() { db.SaveTreeAt("", map[string]interface{}{ "x": "oiwaeuriasburis", }) Expect(db.GlobalUpdateSeq()).To(BeEquivalentTo(2)) Expect(db.LastSeqAt("")).To(BeNumerically(">", uint64(0))) Expect(db.LastSeqAt("/x")).To(BeNumerically("==", uint64(0))) }) g.It("should increase seqs when adding a new value", func() { db.SaveValueAt("/z", []byte("ihfiuewrhewoiruh")) Expect(db.GlobalUpdateSeq()).To(BeEquivalentTo(4)) Expect(db.LastSeqAt("")).To(BeNumerically(">", uint64(2))) Expect(db.LastSeqAt("/x")).To(BeNumerically("==", uint64(0))) }) g.It("should increase seqs when deleting a value", func() { db.DeleteAt("/x") Expect(db.GlobalUpdateSeq()).To(BeEquivalentTo(7)) Expect(db.LastSeqAt("")).To(BeNumerically(">", uint64(5))) Expect(db.LastSeqAt("/x")).To(BeNumerically("==", uint64(0))) Expect(db.LastSeqAt("/z")).To(BeNumerically("==", uint64(0))) }) g.It("should increase seqs when undeleting a value", func() { db.SaveValueAt("/x/xchild", []byte("skjfbslkfbskdf")) Expect(db.GlobalUpdateSeq()).To(BeEquivalentTo(10)) Expect(db.LastSeqAt("")).To(BeNumerically(">", uint64(7))) Expect(db.LastSeqAt("/x")).To(BeNumerically(">", uint64(7))) }) g.It("should increase seqs when making bizarre things", func() { db.ReplaceTreeAt("/x", map[string]interface{}{ "xchild": nil, "other": "saldkasndlksad", "_val": "askjdasnkdjasd", }, false) db.ReplaceTreeAt("/x/xchild", map[string]interface{}{ "ham": "sadljkasndlksad", "_val": "askjdasnkdjasd", }, false) Expect(db.GlobalUpdateSeq()).To(BeEquivalentTo(19)) }) }) }