Пример #1
0
// 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)
}
Пример #2
0
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))
		})
	})
}
Пример #3
0
/*
   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)
}
Пример #4
0
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)
}
Пример #5
0
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-"))
		})
	})
}
Пример #6
0
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"),
					},
				},
			}))
		})
	})
}