예제 #1
0
func TestRevs(t *testing.T) {
	g := Goblin(t)
	RegisterFailHandler(func(m string, _ ...int) { g.Fail(m) })

	g.Describe("_rev", func() {

		g.Before(func() {
			db.Erase()
			db.Start()
		})

		g.After(func() {
			db.End()
		})

		g.It("should generate _rev for a single key", func() {
			savedrev, _ := db.SaveValueAt("/name", []byte(`"database of vehicles"`))
			gottenrev, _ := db.GetValueAt("/name/_rev")
			Expect(savedrev).To(BeEquivalentTo(gottenrev))
			Expect(gottenrev).To(HavePrefix("1-"))
		})

		g.It("should generate _rev for parent keys", func() {
			db.SaveValueAt("/vehicles/car/land", []byte("true"))
			db.SaveValueAt("/vehicles/carriage/land", []byte("true"))
			db.SaveValueAt("/vehicles/carriage/air", []byte("false"))
			Expect(db.GetValueAt("/vehicles/car/land/_rev")).To(HavePrefix("1-"))
			Expect(db.GetValueAt("/vehicles/car/_rev")).To(HavePrefix("1-"))
			Expect(db.GetValueAt("/vehicles/_rev")).To(HavePrefix("3-"))
			Expect(db.GetValueAt("/vehicles/carriage/land/_rev")).To(HavePrefix("1-"))
			Expect(db.GetValueAt("/vehicles/carriage/_rev")).To(HavePrefix("2-"))
		})

		g.It("should bump _rev for single keys", func() {
			db.SaveValueAt("/name", []byte(`"just a database of vehicles"`))
			Expect(db.GetValueAt("/name/_rev")).To(HavePrefix("2-"))
		})

		g.It("should bump _rev for parent keys", func() {
			db.SaveValueAt("/vehicles/car/water", []byte("false"))
			Expect(db.GetValueAt("/vehicles/car/land/_rev")).To(HavePrefix("1-"))
			Expect(db.GetValueAt("/vehicles/car/water/_rev")).To(HavePrefix("1-"))
			Expect(db.GetValueAt("/vehicles/car/_rev")).To(HavePrefix("2-"))
			Expect(db.GetValueAt("/vehicles/_rev")).To(HavePrefix("4-"))
			Expect(db.GetValueAt("/vehicles/carriage/land/_rev")).To(HavePrefix("1-"))
			Expect(db.GetValueAt("/vehicles/carriage/_rev")).To(HavePrefix("2-"))

			db.SaveValueAt("/vehicles/boat/water", []byte("true"))
			Expect(db.GetValueAt("/vehicles/car/land/_rev")).To(HavePrefix("1-"))
			Expect(db.GetValueAt("/vehicles/car/water/_rev")).To(HavePrefix("1-"))
			Expect(db.GetValueAt("/vehicles/boat/water/_rev")).To(HavePrefix("1-"))
			Expect(db.GetValueAt("/vehicles/car/_rev")).To(HavePrefix("2-"))
			Expect(db.GetValueAt("/vehicles/boat/_rev")).To(HavePrefix("1-"))
			Expect(db.GetValueAt("/vehicles/_rev")).To(HavePrefix("5-"))
		})

		g.It("on delete, should bump _rev for parents and sons", func() {
			db.DeleteAt("/vehicles/car")
			Expect(db.GetValueAt("/vehicles/car/land/_rev")).To(HavePrefix("2-"))
			Expect(db.GetValueAt("/vehicles/car/water/_rev")).To(HavePrefix("2-"))
			Expect(db.GetValueAt("/vehicles/car/_rev")).To(HavePrefix("3-"))
			Expect(db.GetValueAt("/vehicles/boat/water/_rev")).To(HavePrefix("1-"))
			Expect(db.GetValueAt("/vehicles/boat/_rev")).To(HavePrefix("1-"))
			Expect(db.GetValueAt("/vehicles/_rev")).To(HavePrefix("6-"))
			Expect(db.GetValueAt("/vehicles/carriage/land/_rev")).To(HavePrefix("1-"))
			Expect(db.GetValueAt("/vehicles/carriage/_rev")).To(HavePrefix("2-"))
		})

		g.It("should bump rev of all parents of affected keys", func() {
			db.SaveTreeAt("/vehicles/boat", map[string]interface{}{
				"water": true,
				"land":  false,
				"air":   false,
			})
			Expect(db.GetValueAt("/vehicles/car/land/_rev")).To(HavePrefix("2-"))
			Expect(db.GetValueAt("/vehicles/car/water/_rev")).To(HavePrefix("2-"))
			Expect(db.GetValueAt("/vehicles/car/_rev")).To(HavePrefix("3-"))
			Expect(db.GetValueAt("/vehicles/boat/water/_rev")).To(HavePrefix("2-"))
			Expect(db.GetValueAt("/vehicles/boat/land/_rev")).To(HavePrefix("1-"))
			Expect(db.GetValueAt("/vehicles/boat/air/_rev")).To(HavePrefix("1-"))
			Expect(db.GetValueAt("/vehicles/boat/_rev")).To(HavePrefix("2-"))
			Expect(db.GetValueAt("/vehicles/_rev")).To(HavePrefix("7-"))
		})

		g.It("doing it again to make sure", func() {
			db.SaveTreeAt("/vehicles", map[string]interface{}{
				"car": map[string]interface{}{
					"water": true,
				},
				"boat": map[string]interface{}{
					"air": true,
				},
			})
			Expect(db.GetValueAt("/vehicles/car/land/_rev")).To(HavePrefix("2-"))
			Expect(db.GetValueAt("/vehicles/car/water/_rev")).To(HavePrefix("3-"))
			Expect(db.GetValueAt("/vehicles/car/_rev")).To(HavePrefix("4-"))
			Expect(db.GetValueAt("/vehicles/boat/water/_rev")).To(HavePrefix("2-"))
			Expect(db.GetValueAt("/vehicles/boat/land/_rev")).To(HavePrefix("1-"))
			Expect(db.GetValueAt("/vehicles/boat/air/_rev")).To(HavePrefix("2-"))
			Expect(db.GetValueAt("/vehicles/boat/_rev")).To(HavePrefix("3-"))
			Expect(db.GetValueAt("/vehicles/_rev")).To(HavePrefix("8-"))
			Expect(db.GetValueAt("/vehicles/carriage/land/_rev")).To(HavePrefix("1-"))
			Expect(db.GetValueAt("/vehicles/carriage/_rev")).To(HavePrefix("2-"))
		})

		g.It("should bump the revs correctly when a tree operation involves deleting", func() {
			db.SaveTreeAt("/vehicles", map[string]interface{}{
				"carriage": map[string]interface{}{
					"space": false,
					"land":  nil,
				},
				"boat": nil,
			})
			Expect(db.GetValueAt("/vehicles/car/_rev")).To(HavePrefix("4-"))
			Expect(db.GetValueAt("/vehicles/boat/water/_rev")).To(HavePrefix("3-"))
			Expect(db.GetValueAt("/vehicles/boat/land/_rev")).To(HavePrefix("2-"))
			Expect(db.GetValueAt("/vehicles/boat/air/_rev")).To(HavePrefix("3-"))
			Expect(db.GetValueAt("/vehicles/boat/_rev")).To(HavePrefix("4-"))
			Expect(db.GetValueAt("/vehicles/_rev")).To(HavePrefix("9-"))
			Expect(db.GetValueAt("/vehicles/carriage/land/_rev")).To(HavePrefix("2-"))
			Expect(db.GetValueAt("/vehicles/carriage/_rev")).To(HavePrefix("3-"))
		})

		g.It("should bump revs of intermediate paths when modifying a deep field", func() {
			db.SaveValueAt("/vehicles/train/land/rail", []byte("true"))
			Expect(db.GetValueAt("/vehicles/_rev")).To(HavePrefix("10-"))
			Expect(db.GetValueAt("/vehicles/train/_rev")).To(HavePrefix("1-"))
			Expect(db.GetValueAt("/vehicles/train/land/_rev")).To(HavePrefix("1-"))
			Expect(db.GetValueAt("/vehicles/train/land/rail/_rev")).To(HavePrefix("1-"))

			db.DeleteAt("/vehicles/train")
			Expect(db.GetValueAt("/vehicles/_rev")).To(HavePrefix("11-"))
			Expect(db.GetValueAt("/vehicles/train/_rev")).To(HavePrefix("2-"))
			Expect(db.GetValueAt("/vehicles/train/land/_rev")).To(HavePrefix("2-"))
			Expect(db.GetValueAt("/vehicles/train/land/rail/_rev")).To(HavePrefix("2-"))

			db.SaveTreeAt("", map[string]interface{}{
				"vehicles": map[string]interface{}{
					"skate": map[string]interface{}{
						"air": map[string]interface{}{
							"carried": map[string]interface{}{
								"_val": true,
							},
						},
					},
				},
			})
			Expect(db.GetValueAt("/vehicles/_rev")).To(HavePrefix("12-"))
			Expect(db.GetValueAt("/vehicles/skate/_rev")).To(HavePrefix("1-"))
			Expect(db.GetValueAt("/vehicles/skate/air/_rev")).To(HavePrefix("1-"))
			Expect(db.GetValueAt("/vehicles/skate/air/carried/_rev")).To(HavePrefix("1-"))

			db.SaveTreeAt("", map[string]interface{}{
				"vehicles": map[string]interface{}{
					"skate": map[string]interface{}{
						"air": map[string]interface{}{
							"carried": nil,
						},
					},
				},
			})
			Expect(db.GetValueAt("/vehicles/_rev")).To(HavePrefix("13-"))
			Expect(db.GetValueAt("/vehicles/skate/_rev")).To(HavePrefix("2-"))
			Expect(db.GetValueAt("/vehicles/skate/air/_rev")).To(HavePrefix("2-"))
			Expect(db.GetValueAt("/vehicles/skate/air/carried/_rev")).To(HavePrefix("2-"))
		})

		g.It("should return rev", func() {
			sk, err := db.GetSpecialKeysAt("/vehicles/skate")
			Expect(err).ToNot(HaveOccurred())
			Expect(sk.Rev).To(HavePrefix("2-"))
		})
	})
}
예제 #2
0
파일: context.go 프로젝트: fiatjaf/summadb
func setCommonVariables(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ctx := getContext(r)

		/* when the path is in the format /nanana/nanana/_val
		   we reply with the single value for that path, otherwise
		   assume the whole tree is being requested. */
		ctx.path = r.URL.Path
		ctx.wantsTree = true
		ctx.localDoc = false
		ctx.wantsDatabaseInfo = false

		entityPath := ctx.path
		pathKeys := db.SplitKeys(ctx.path)
		nkeys := len(pathKeys)
		ctx.lastKey = pathKeys[nkeys-1]

		if nkeys > 1 && pathKeys[nkeys-2] == "_local" {
			// workaround for couchdb-like _local/blablabla docs
			// we use a different sublevel for local docs so we must
			// acknowledge that somehow.
			ctx.lastKey = db.UnescapeKey(db.JoinKeys(pathKeys[nkeys-2:]))
			ctx.localDoc = true
		} else if ctx.lastKey == "" {
			// this means the request was made with an ending slash (for example:
			// https://my.summadb.com/path/to/here/), so it wants couchdb-like information
			// for the referred sub-database, and not the document at the referred path.
			// to get information on the document at the referred path the request must be
			// made without the trailing slash (or with a special header, for the root document
			// and other scenarios in which the user does not have control over the presence
			// of the ending slash).
			ctx.wantsDatabaseInfo = true

			if ctx.path == "/" {
				ctx.path = ""
				ctx.lastKey = ""
				entityPath = ""
			}
		} else {
			if ctx.lastKey[0] == '_' {
				ctx.wantsTree = false
				entityPath = db.JoinKeys(pathKeys[:nkeys-1])
				if ctx.lastKey == "_val" {
					ctx.path = db.JoinKeys(pathKeys[:nkeys-1])
					ctx.lastKey = pathKeys[nkeys-1]
				}
			}
		}

		ctx.currentRev = ""
		ctx.deleted = false
		qrev := r.URL.Query().Get("rev")
		hrev := r.Header.Get("If-Match")
		drev := ""
		ctx.providedRev = hrev // will be "" if there's no header rev

		// fetching current rev
		if !ctx.localDoc {
			// procedure for normal documents

			specialKeys, err := db.GetSpecialKeysAt(entityPath)
			if err == nil {
				ctx.currentRev = specialKeys.Rev
				ctx.deleted = specialKeys.Deleted
			}

		} else {
			// procedure for local documents
			ctx.currentRev = db.GetLocalDocRev(ctx.path)
		}

		// body parsing
		if r.Method[0] == 'P' { // PUT, PATCH, POST
			/* filter body size */
			b, err := ioutil.ReadAll(io.LimitReader(r.Body, 1048576))
			ctx.body = b
			if err != nil {
				res := responses.BadRequest("request body too large")
				w.WriteHeader(res.Code)
				json.NewEncoder(w).Encode(res)
				return
			}

			if ctx.lastKey != "_val" {
				err = json.Unmarshal(ctx.body, &ctx.jsonBody)
				if err != nil {
					res := responses.BadRequest("invalid JSON sent as JSON")
					w.WriteHeader(res.Code)
					json.NewEncoder(w).Encode(res)
					return
				}
			}
		}

		revfail := false
		// rev checking
		if qrev != "" {
			if hrev != "" && qrev != hrev {
				revfail = true
			} else {
				ctx.providedRev = qrev
			}
		}
		drev = ""
		if drevi, ok := ctx.jsonBody["_rev"]; ok {
			drev = drevi.(string)
		}
		if drev != "" {
			if qrev != "" && qrev != drev {
				revfail = true
			} else if hrev != "" && hrev != drev {
				revfail = true
			} else if qrev != "" && hrev != "" && qrev != hrev {
				revfail = true
			} else {
				ctx.providedRev = drev
			}
		}

		if revfail {
			res := responses.BadRequest("different rev values were sent")
			w.WriteHeader(res.Code)
			json.NewEncoder(w).Encode(res)
			return
		}

		ctx.exists = ctx.currentRev != "" && !ctx.deleted
		context.Set(r, k, ctx)

		next.ServeHTTP(w, r)
	})
}