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-")) }) }) }
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) }) }