// this method only exists for compatibility with PouchDB and should not be used elsewhere. func BulkGet(w http.ResponseWriter, r *http.Request) { ctx := getContext(r) var ireqs interface{} /* options */ revs := flag(r, "revs") var ok bool if ireqs, ok = ctx.jsonBody["docs"]; !ok { res := responses.BadRequest("You were supposed to request some docs specified by their ids, and you didn't.") w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } reqs := ireqs.([]interface{}) res := responses.BulkGet{ Results: make([]responses.BulkGetResult, len(reqs)), } for i, ireq := range reqs { req := ireq.(map[string]interface{}) res.Results[i] = responses.BulkGetResult{ Docs: make([]responses.DocOrError, 1), } iid, ok := req["id"] if !ok { err := responses.BadRequest("missing id") res.Results[i].Docs[0].Error = &err continue } id := iid.(string) res.Results[i].Id = id path := db.CleanPath(ctx.path) + "/" + db.EscapeKey(id) rev := string(db.GetRev(path)) doc, err := db.GetTreeAt(path) if err != nil { err := responses.NotFound() res.Results[i].Docs[0].Error = &err continue } doc["_id"] = id doc["_rev"] = rev delete(doc, "_val") // pouchdb doesn't like top-level keys starting with _ if revs { // magic method to fetch _revisions // docs["_revisions"] = ... } res.Results[i].Docs[0].Ok = &doc } w.Header().Add("Content-Type", "application/json") w.WriteHeader(200) json.NewEncoder(w).Encode(res) }
func TestBasics(t *testing.T) { g := Goblin(t) RegisterFailHandler(func(m string, _ ...int) { g.Fail(m) }) g.Describe("basics test", func() { g.Before(func() { db.Erase() db.Start() }) g.After(func() { db.End() }) g.It("should save a tree", func() { db.SaveTreeAt("/fruits/banana", map[string]interface{}{ "colour": "yellow", "hardness": "low", "_val": "a fruit.", }) Expect(db.GetValueAt("/fruits/banana")).To(BeEquivalentTo(`"a fruit."`)) Expect(db.GetValueAt("/fruits/banana/colour")).To(BeEquivalentTo(`"yellow"`)) Expect(db.GetValueAt("/fruits/banana/hardness")).To(BeEquivalentTo(`"low"`)) _, err := db.GetValueAt("/fruits") Expect(err).To(HaveOccurred()) Expect(db.GetTreeAt("/fruits")).To(Equal(map[string]interface{}{ "banana": map[string]interface{}{ "colour": value("yellow"), "hardness": value("low"), "_val": "a fruit.", }, })) }) g.It("should modify a subvalue of the tree", func() { db.SaveValueAt("/fruits/banana/colour", []byte(`"black-and-yellow"`)) Expect(db.GetValueAt("/fruits/banana/colour")).To(BeEquivalentTo(`"black-and-yellow"`)) Expect(db.GetValueAt("/fruits/banana/hardness")).To(BeEquivalentTo(`"low"`)) }) g.It("should add a value deeply nested in a tree that doesn't exists", func() { db.SaveValueAt("/fruits/mellon/season", []byte(`"spring"`)) Expect(db.GetValueAt("/fruits/mellon/season")).To(BeEquivalentTo(`"spring"`)) Expect(db.GetTreeAt("/fruits")).To(Equal(map[string]interface{}{ "banana": map[string]interface{}{ "colour": value("black-and-yellow"), "hardness": value("low"), "_val": "a fruit.", }, "mellon": map[string]interface{}{ "season": value("spring"), }, })) }) g.It("should add a tree deeply nested like the previous", func() { db.SaveTreeAt("/fruits/orange", map[string]interface{}{ "colour": "orange", "hardness": "medium", "_val": "name == colour", }) Expect(db.GetValueAt("/fruits/orange/colour")).To(BeEquivalentTo(`"orange"`)) Expect(db.GetValueAt("/fruits/orange")).To(BeEquivalentTo(`"name == colour"`)) Expect(db.GetTreeAt("/fruits/orange")).To(Equal(map[string]interface{}{ "_val": "name == colour", "colour": value("orange"), "hardness": value("medium"), })) }) g.It("should delete a key", func() { db.DeleteAt("/fruits/banana/colour") Expect(db.GetValueAt("/fruits/orange/colour")).To(BeEquivalentTo(`"orange"`)) _, err := db.GetValueAt("/fruits/banana/colour") Expect(db.GetValueAt("/fruits/banana/colour/_deleted")).To(BeEquivalentTo("")) Expect(err).To(HaveOccurred()) }) g.It("should delete a value when setting it to null with a tree", func() { db.SaveTreeAt("/fruits/mellon", map[string]interface{}{ "colour": "orange", "season": nil, }) Expect(db.GetValueAt("/fruits/mellon/colour")).To(BeEquivalentTo(`"orange"`)) _, err := db.GetValueAt("/fruits/mellon/season") Expect(err).To(HaveOccurred()) db.SaveTreeAt("/fruits", map[string]interface{}{ "mellon": nil, }) _, err = db.GetValueAt("/fruits/mellon/colour") Expect(err).To(HaveOccurred()) _, err = db.GetValueAt("/fruits/mellon") Expect(err).To(HaveOccurred()) }) g.It("should delete a tree", func() { db.DeleteAt("/fruits/banana") Expect(db.GetValueAt("/fruits/orange/colour")).To(BeEquivalentTo(`"orange"`)) _, err := db.GetValueAt("/fruits/banana/hardness") Expect(err).To(HaveOccurred()) rev, err := db.DeleteAt("/fruits") Expect(err).ToNot(HaveOccurred()) Expect(rev).To(HavePrefix("9-")) _, err = db.GetValueAt("/fruits") Expect(err).To(HaveOccurred()) Expect(db.GetValueAt("/fruits/orange/_deleted")).To(BeEquivalentTo("")) _, err = db.GetValueAt("/fruits/orange/colour") Expect(err).To(HaveOccurred()) _, err = db.GetValueAt("/fruits/banana/hardness") Expect(err).To(HaveOccurred()) }) g.It("should error when fetching an untouched tree path", func() { _, err := db.GetTreeAt("/nowhere") Expect(err).To(HaveOccurred()) }) g.It("should return when fetching a deleted tree path", func() { tree, err := db.GetTreeAt("/fruits/banana") Expect(err).ToNot(HaveOccurred()) empty := make(map[string]interface{}) Expect(tree).To(BeEquivalentTo(empty)) }) }) }
/* Currently _all_docs does not guarantee key order and should not be used for querying or anything. It is here just to provide PouchDB replication support. */ func AllDocs(w http.ResponseWriter, r *http.Request) { ctx := getContext(r) /* options */ include_docs := flag(r, "include_docs") jsonKeys := param(r, "keys") // startkey := param(r, "startkey") // endkey := param(r, "endkey") // descending := flag(r, "descending") // skip := param(r, "skip") // limit := param(r, "limit") path := db.CleanPath(ctx.path) tree, err := db.GetTreeAt(path) if err != nil { res := responses.UnknownError(err.Error()) w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } res := responses.AllDocs{ TotalRows: 0, Offset: 0, // temporary Rows: make([]responses.Row, 0), } if jsonKeys != "" { // in case "keys" was provided we can just pick the requested keys from our tree var keys []string err = json.Unmarshal([]byte(jsonKeys), &keys) if err != nil { res := responses.BadRequest() w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } for _, id := range keys { var doc interface{} var ok bool if doc, ok = tree[id]; !ok || strings.HasPrefix(id, "_") { res.Rows = append(res.Rows, responses.Row{ Key: id, Error: (responses.NotFound()).Reason, }) } rev := string(db.GetRev(path + "/" + db.EscapeKey(id))) row := responses.Row{ Id: id, Key: id, Value: map[string]interface{}{"rev": rev}, } if include_docs { row.Doc = doc.(map[string]interface{}) row.Doc["_id"] = id row.Doc["_rev"] = rev delete(row.Doc, "_val") // pouchdb doesn't like top-level keys starting with _ } res.Rows = append(res.Rows, row) res.TotalRows += 1 } } else { // otherwise iterate through the entire tree for id, doc := range tree { if strings.HasPrefix(id, "_") { continue } rev := string(db.GetRev(path + "/" + db.EscapeKey(id))) row := responses.Row{ Id: id, Key: id, Value: map[string]interface{}{"rev": rev}, } if include_docs { row.Doc = doc.(map[string]interface{}) row.Doc["_id"] = id row.Doc["_rev"] = rev delete(row.Doc, "_val") // pouchdb doesn't like top-level keys starting with _ } res.Rows = append(res.Rows, row) res.TotalRows += 1 } } w.Header().Add("Content-Type", "application/json") w.WriteHeader(200) json.NewEncoder(w).Encode(res) }
func Get(w http.ResponseWriter, r *http.Request) { ctx := getContext(r) /* here we handle special endpoints that, in CouchDB, refer to a database, but here are treated as documents (or better, subtrees) methods */ if ctx.wantsDatabaseInfo { DatabaseInfo(w, r) return } else if ctx.lastKey == "_changes" { Changes(w, r) return } else if ctx.lastKey == "_all_docs" { AllDocs(w, r) return } else if ctx.lastKey == "_security" { ReadSecurity(w, r) return } else if ctx.lastKey == "_missing_revs" { return } var response []byte var err error if !ctx.exists { res := responses.NotFound() w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } if ctx.localDoc { jsondoc, err := db.GetLocalDocJsonAt(ctx.path) if err != nil { // ctx.exists doesn't work for _local docs res := responses.NotFound() w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } w.WriteHeader(200) w.Header().Add("Content-Type", "application/json") w.Write(jsondoc) return } if ctx.wantsTree { var tree map[string]interface{} tree, err = db.GetTreeAt(ctx.path) if err == nil { tree["_id"] = ctx.lastKey tree["_rev"] = ctx.currentRev if flag(r, "revs") { var revs []string revs, err = db.RevsAt(ctx.path) if err == nil { var start int start, err = strconv.Atoi(strings.Split(revs[0], "-")[0]) revids := make([]string, len(revs)) for i, rev := range revs { revids[i] = strings.Split(rev, "-")[1] } tree["_revisions"] = responses.Revisions{ Start: start, Ids: revids, } } } if flag(r, "revs_info") { var revs []string revs, err = db.RevsAt(ctx.path) if err == nil { revsInfo := make([]responses.RevInfo, len(revs)) i := 0 for r := len(revs) - 1; r >= 0; r-- { revsInfo[i] = responses.RevInfo{ Rev: revs[r], Status: "missing", } i++ } revsInfo[0].Status = "available" tree["_revs_info"] = revsInfo } } response, err = json.Marshal(tree) } } else { if ctx.lastKey == "_rev" { response = []byte(ctx.currentRev) } else { response, err = db.GetValueAt(ctx.path) if err != nil { res := responses.NotFound() w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } } } if err != nil { res := responses.UnknownError(err.Error()) w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } w.WriteHeader(200) w.Header().Add("Content-Type", "application/json") w.Write(response) }
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 TestArrays(t *testing.T) { g := Goblin(t) RegisterFailHandler(func(m string, _ ...int) { g.Fail(m) }) g.Describe("array values", func() { g.Before(func() { db.Erase() db.Start() }) g.After(func() { db.End() }) g.It("should save a tree with a simple array", func() { rev, err := db.SaveTreeAt("", map[string]interface{}{ "numbers": []interface{}{"zero", "one", "two", "three"}, }) Expect(err).ToNot(HaveOccurred()) Expect(rev).To(HavePrefix("1-")) Expect(db.GetValueAt("/numbers/0")).To(BeEquivalentTo(`"zero"`)) Expect(db.GetValueAt("/numbers/3")).To(BeEquivalentTo(`"three"`)) _, err = db.GetValueAt("/numbers") Expect(err).To(HaveOccurred()) Expect(db.GetTreeAt("/numbers")).To(Equal(map[string]interface{}{ "0": value("zero"), "1": value("one"), "2": value("two"), "3": value("three"), })) }) g.It("should save a tree with a complex array", func() { rev, err := db.SaveTreeAt("", map[string]interface{}{ "letters": []interface{}{ map[string]interface{}{ "name": "á", "variations": []interface{}{"a", "A"}, }, map[string]interface{}{ "name": "bê", "variations": []interface{}{"b", "B"}, }, }, }) Expect(err).ToNot(HaveOccurred()) Expect(rev).To(HavePrefix("2-")) Expect(db.GetValueAt("/letters/0/name")).To(BeEquivalentTo(`"á"`)) Expect(db.GetValueAt("/letters/1/variations/1")).To(BeEquivalentTo(`"B"`)) _, err = db.GetValueAt("/letters/0/variations") Expect(err).To(HaveOccurred()) Expect(db.GetTreeAt("/letters")).To(Equal(map[string]interface{}{ "0": map[string]interface{}{ "name": value("á"), "variations": map[string]interface{}{ "0": value("a"), "1": value("A"), }, }, "1": map[string]interface{}{ "name": value("bê"), "variations": map[string]interface{}{ "0": value("b"), "1": value("B"), }, }, })) }) }) }