github.com/go-kivik/kivik/v4@v4.3.2/x/memorydb/store.go (about) 1 // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 // use this file except in compliance with the License. You may obtain a copy of 3 // the License at 4 // 5 // http://www.apache.org/licenses/LICENSE-2.0 6 // 7 // Unless required by applicable law or agreed to in writing, software 8 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 // License for the specific language governing permissions and limitations under 11 // the License. 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 }