...
1
2
3
4
5
6
7
8
9
10
11
12
13 package memorydb
14
15 import (
16 "encoding/json"
17 "errors"
18 "fmt"
19 "math/rand"
20 "net/http"
21 "strings"
22 "sync"
23 "time"
24
25 "github.com/go-kivik/kivik/v4/driver"
26 )
27
28 type file struct {
29 ContentType string
30 Data []byte
31 }
32
33 type document struct {
34 revs []*revision
35 }
36
37 type revision struct {
38 data []byte
39 ID int64
40 Rev string
41 Deleted bool
42 Attachments map[string]file
43 }
44
45 type database struct {
46 mu sync.RWMutex
47 docs map[string]*document
48 deleted bool
49 security *driver.Security
50 }
51
52 var (
53 rnd *rand.Rand
54 rndMU = &sync.Mutex{}
55 )
56
57 func init() {
58 rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
59 }
60
61 func (d *database) getRevision(docID, rev string) (*revision, bool) {
62 d.mu.RLock()
63 defer d.mu.RUnlock()
64 doc, ok := d.docs[docID]
65 if !ok {
66 return nil, false
67 }
68 for _, r := range doc.revs {
69 if rev == fmt.Sprintf("%d-%s", r.ID, r.Rev) {
70 return r, true
71 }
72 }
73 return nil, false
74 }
75
76 func (d *database) docExists(docID string) bool {
77 d.mu.RLock()
78 defer d.mu.RUnlock()
79 _, ok := d.docs[docID]
80 return ok
81 }
82
83 func (d *database) latestRevision(docID string) (*revision, bool) {
84 d.mu.RLock()
85 defer d.mu.RUnlock()
86 doc, ok := d.docs[docID]
87 if ok {
88 last := doc.revs[len(doc.revs)-1]
89 return last, true
90 }
91 return nil, false
92 }
93
94 type couchDoc map[string]interface{}
95
96 func (d couchDoc) ID() string {
97 id, _ := d["_id"].(string)
98 return id
99 }
100
101 func (d couchDoc) Rev() string {
102 rev, _ := d["_rev"].(string)
103 return rev
104 }
105
106 func toCouchDoc(i interface{}) (couchDoc, error) {
107 if d, ok := i.(couchDoc); ok {
108 return d, nil
109 }
110 asJSON, err := json.Marshal(i)
111 if err != nil {
112 return nil, statusError{status: http.StatusBadRequest, error: err}
113 }
114 var m couchDoc
115 if e := json.Unmarshal(asJSON, &m); e != nil {
116 return nil, statusError{status: http.StatusInternalServerError, error: errors.New("THIS IS A BUG: failed to decode encoded document")}
117 }
118 return m, nil
119 }
120
121 func (d *database) addRevision(doc couchDoc) string {
122 d.mu.Lock()
123 defer d.mu.Unlock()
124 id, ok := doc["_id"].(string)
125 if !ok {
126 panic("_id missing or not a string")
127 }
128 isLocal := strings.HasPrefix(id, "_local/")
129 if d.docs[id] == nil {
130 d.docs[id] = &document{
131 revs: make([]*revision, 0, 1),
132 }
133 }
134 var revID int64
135 var revStr string
136 if isLocal {
137 revID = 1
138 revStr = "0"
139 } else {
140 l := len(d.docs[id].revs)
141 if l == 0 {
142 revID = 1
143 } else {
144 revID = d.docs[id].revs[l-1].ID + 1
145 }
146 revStr = randStr()
147 }
148 rev := fmt.Sprintf("%d-%s", revID, revStr)
149 doc["_rev"] = rev
150 data, err := json.Marshal(doc)
151 if err != nil {
152 panic(err)
153 }
154 deleted, _ := doc["_deleted"].(bool)
155 newRev := &revision{
156 data: data,
157 ID: revID,
158 Rev: revStr,
159 Deleted: deleted,
160 }
161 if isLocal {
162 d.docs[id].revs = []*revision{newRev}
163 } else {
164 d.docs[id].revs = append(d.docs[id].revs, newRev)
165 }
166 return rev
167 }
168
View as plain text