github.com/go-kivik/kivik/v4@v4.3.2/x/memorydb/db.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 "bytes" 17 "context" 18 "errors" 19 "fmt" 20 "io" 21 "net/http" 22 "regexp" 23 "strings" 24 25 "github.com/go-kivik/kivik/v4" 26 "github.com/go-kivik/kivik/v4/driver" 27 ) 28 29 var notYetImplemented = statusError{status: http.StatusNotImplemented, error: errors.New("kivik: not yet implemented in memory driver")} 30 31 // database is an in-memory database representation. 32 type db struct { 33 *client 34 dbName string 35 db *database 36 } 37 38 func (d *db) Query(context.Context, string, string, driver.Options) (driver.Rows, error) { 39 // FIXME: Unimplemented 40 return nil, notYetImplemented 41 } 42 43 func (d *db) Get(ctx context.Context, docID string, options driver.Options) (*driver.Document, error) { 44 if exists, _ := d.client.DBExists(ctx, d.dbName, nil); !exists { 45 return nil, statusError{status: http.StatusPreconditionFailed, error: errors.New("database does not exist")} 46 } 47 if !d.db.docExists(docID) { 48 return nil, statusError{status: http.StatusNotFound, error: errors.New("missing")} 49 } 50 opts := map[string]interface{}{} 51 options.Apply(opts) 52 if rev, ok := opts["rev"].(string); ok { 53 if doc, found := d.db.getRevision(docID, rev); found { 54 return &driver.Document{ 55 Rev: rev, 56 Body: io.NopCloser(bytes.NewReader(doc.data)), 57 }, nil 58 } 59 return nil, statusError{status: http.StatusNotFound, error: errors.New("missing")} 60 } 61 last, _ := d.db.latestRevision(docID) 62 if last.Deleted { 63 return nil, statusError{status: http.StatusNotFound, error: errors.New("missing")} 64 } 65 return &driver.Document{ 66 Rev: fmt.Sprintf("%d-%s", last.ID, last.Rev), 67 Body: io.NopCloser(bytes.NewReader(last.data)), 68 }, nil 69 } 70 71 func (d *db) Put(ctx context.Context, docID string, doc interface{}, _ driver.Options) (rev string, err error) { 72 if exists, _ := d.client.DBExists(ctx, d.dbName, nil); !exists { 73 return "", statusError{status: http.StatusPreconditionFailed, error: errors.New("database does not exist")} 74 } 75 isLocal := strings.HasPrefix(docID, "_local/") 76 if !isLocal && docID[0] == '_' && !strings.HasPrefix(docID, "_design/") { 77 return "", statusError{status: http.StatusBadRequest, error: errors.New("only reserved document ids may start with underscore")} 78 } 79 couchDoc, err := toCouchDoc(doc) 80 if err != nil { 81 return "", err 82 } 83 couchDoc["_id"] = docID 84 // TODO: Add support for storing attachments. 85 delete(couchDoc, "_attachments") 86 87 if last, ok := d.db.latestRevision(docID); ok { 88 if !last.Deleted && !isLocal && couchDoc.Rev() != fmt.Sprintf("%d-%s", last.ID, last.Rev) { 89 return "", statusError{status: http.StatusConflict, error: errors.New("document update conflict")} 90 } 91 return d.db.addRevision(couchDoc), nil 92 } 93 94 if couchDoc.Rev() != "" { 95 // Rev should not be set for a new document 96 return "", statusError{status: http.StatusConflict, error: errors.New("document update conflict")} 97 } 98 return d.db.addRevision(couchDoc), nil 99 } 100 101 var revRE = regexp.MustCompile("^[0-9]+-[a-f0-9]{32}$") 102 103 func validRev(rev string) bool { 104 return revRE.MatchString(rev) 105 } 106 107 func (d *db) Delete(ctx context.Context, docID string, options driver.Options) (newRev string, err error) { 108 if exists, _ := d.client.DBExists(ctx, d.dbName, kivik.Params(nil)); !exists { 109 return "", statusError{status: http.StatusPreconditionFailed, error: errors.New("database does not exist")} 110 } 111 opts := map[string]interface{}{} 112 options.Apply(opts) 113 rev, _ := opts["rev"].(string) 114 if !strings.HasPrefix(docID, "_local/") && !validRev(rev) { 115 return "", statusError{status: http.StatusBadRequest, error: errors.New("invalid rev format")} 116 } 117 if !d.db.docExists(docID) { 118 return "", statusError{status: http.StatusNotFound, error: errors.New("missing")} 119 } 120 return d.Put(ctx, docID, map[string]interface{}{ 121 "_id": docID, 122 "_rev": rev, 123 "_deleted": true, 124 }, nil) 125 } 126 127 func (d *db) Stats(_ context.Context) (*driver.DBStats, error) { 128 return &driver.DBStats{ 129 Name: d.dbName, 130 // DocCount: 0, 131 // DeletedCount: 0, 132 // UpdateSeq: "", 133 // DiskSize: 0, 134 // ActiveSize: 0, 135 // ExternalSize: 0, 136 }, nil 137 } 138 139 func (c *client) Compact(_ context.Context) error { 140 // FIXME: Unimplemented 141 return notYetImplemented 142 } 143 144 func (d *db) CompactView(_ context.Context, _ string) error { 145 // FIXME: Unimplemented 146 return notYetImplemented 147 } 148 149 func (d *db) ViewCleanup(_ context.Context) error { 150 // FIXME: Unimplemented 151 return notYetImplemented 152 } 153 154 func (d *db) Changes(context.Context, driver.Options) (driver.Changes, error) { 155 // FIXME: Unimplemented 156 return nil, notYetImplemented 157 } 158 159 func (d *db) PutAttachment(context.Context, string, *driver.Attachment, driver.Options) (string, error) { 160 // FIXME: Unimplemented 161 return "", notYetImplemented 162 } 163 164 func (d *db) GetAttachment(context.Context, string, string, driver.Options) (*driver.Attachment, error) { 165 // FIXME: Unimplemented 166 return nil, notYetImplemented 167 } 168 169 func (d *db) DeleteAttachment(context.Context, string, string, driver.Options) (newRev string, err error) { 170 // FIXME: Unimplemented 171 return "", notYetImplemented 172 } 173 174 func (d *db) Close() error { return nil }