github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/pkg/sorted/mongo/mongokv.go (about) 1 /* 2 Copyright 2013 The Camlistore Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package mongo provides an implementation of sorted.KeyValue 18 // using MongoDB. 19 package mongo 20 21 import ( 22 "bytes" 23 "errors" 24 "sync" 25 "time" 26 27 "camlistore.org/pkg/jsonconfig" 28 "camlistore.org/pkg/sorted" 29 30 "camlistore.org/third_party/labix.org/v2/mgo" 31 "camlistore.org/third_party/labix.org/v2/mgo/bson" 32 ) 33 34 // We explicitely separate the key and the value in a document, 35 // instead of simply storing as key:value, to avoid problems 36 // such as "." being an illegal char in a key name. Also because 37 // there is no way to do partial matching for key names (one can 38 // only check for their existence with bson.M{$exists: true}). 39 const ( 40 CollectionName = "keys" // MongoDB collection, equiv. to SQL table 41 mgoKey = "k" 42 mgoValue = "v" 43 ) 44 45 func init() { 46 sorted.RegisterKeyValue("mongo", newKeyValueFromJSONConfig) 47 } 48 49 func newKeyValueFromJSONConfig(cfg jsonconfig.Obj) (sorted.KeyValue, error) { 50 ins := &instance{ 51 server: cfg.OptionalString("host", "localhost"), 52 database: cfg.RequiredString("database"), 53 user: cfg.OptionalString("user", ""), 54 password: cfg.OptionalString("password", ""), 55 } 56 if err := cfg.Validate(); err != nil { 57 return nil, err 58 } 59 db, err := ins.getCollection() 60 if err != nil { 61 return nil, err 62 } 63 return &keyValue{db: db, session: ins.session}, nil 64 } 65 66 // Implementation of Iterator 67 type iter struct { 68 res bson.M 69 *mgo.Iter 70 end []byte 71 } 72 73 func (it *iter) Next() bool { 74 if !it.Iter.Next(&it.res) { 75 return false 76 } 77 if len(it.end) > 0 && bytes.Compare(it.KeyBytes(), it.end) >= 0 { 78 return false 79 } 80 return true 81 } 82 83 func (it *iter) Key() string { 84 key, ok := (it.res[mgoKey]).(string) 85 if !ok { 86 return "" 87 } 88 return key 89 } 90 91 func (it *iter) KeyBytes() []byte { 92 // TODO(bradfitz,mpl): this is less efficient than the string way. we should 93 // do better here, somehow, like all the other KeyValue iterators. 94 // For now: 95 return []byte(it.Key()) 96 } 97 98 func (it *iter) Value() string { 99 value, ok := (it.res[mgoValue]).(string) 100 if !ok { 101 return "" 102 } 103 return value 104 } 105 106 func (it *iter) ValueBytes() []byte { 107 // TODO(bradfitz,mpl): this is less efficient than the string way. we should 108 // do better here, somehow, like all the other KeyValue iterators. 109 // For now: 110 return []byte(it.Value()) 111 } 112 113 func (it *iter) Close() error { 114 return it.Iter.Close() 115 } 116 117 // Implementation of KeyValue 118 type keyValue struct { 119 session *mgo.Session // so we can close it 120 mu sync.Mutex // guards db 121 db *mgo.Collection 122 } 123 124 func (kv *keyValue) Get(key string) (string, error) { 125 kv.mu.Lock() 126 defer kv.mu.Unlock() 127 res := bson.M{} 128 q := kv.db.Find(&bson.M{mgoKey: key}) 129 err := q.One(&res) 130 if err != nil { 131 if err == mgo.ErrNotFound { 132 return "", sorted.ErrNotFound 133 } else { 134 return "", err 135 } 136 } 137 return res[mgoValue].(string), err 138 } 139 140 func (kv *keyValue) Find(start, end string) sorted.Iterator { 141 kv.mu.Lock() 142 defer kv.mu.Unlock() 143 it := kv.db.Find(&bson.M{mgoKey: &bson.M{"$gte": start}}).Sort(mgoKey).Iter() 144 return &iter{res: bson.M{}, Iter: it, end: []byte(end)} 145 } 146 147 func (kv *keyValue) Set(key, value string) error { 148 kv.mu.Lock() 149 defer kv.mu.Unlock() 150 _, err := kv.db.Upsert(&bson.M{mgoKey: key}, &bson.M{mgoKey: key, mgoValue: value}) 151 return err 152 } 153 154 // Delete removes the document with the matching key. 155 func (kv *keyValue) Delete(key string) error { 156 kv.mu.Lock() 157 defer kv.mu.Unlock() 158 err := kv.db.Remove(&bson.M{mgoKey: key}) 159 if err == mgo.ErrNotFound { 160 return nil 161 } 162 return err 163 } 164 165 // Wipe removes all documents from the collection. 166 func (kv *keyValue) Wipe() error { 167 kv.mu.Lock() 168 defer kv.mu.Unlock() 169 _, err := kv.db.RemoveAll(nil) 170 return err 171 } 172 173 type batch interface { 174 Mutations() []sorted.Mutation 175 } 176 177 func (kv *keyValue) BeginBatch() sorted.BatchMutation { 178 return sorted.NewBatchMutation() 179 } 180 181 func (kv *keyValue) CommitBatch(bm sorted.BatchMutation) error { 182 b, ok := bm.(batch) 183 if !ok { 184 return errors.New("invalid batch type") 185 } 186 187 kv.mu.Lock() 188 defer kv.mu.Unlock() 189 for _, m := range b.Mutations() { 190 if m.IsDelete() { 191 if err := kv.db.Remove(bson.M{mgoKey: m.Key()}); err != nil { 192 return err 193 } 194 } else { 195 if _, err := kv.db.Upsert(&bson.M{mgoKey: m.Key()}, &bson.M{mgoKey: m.Key(), mgoValue: m.Value()}); err != nil { 196 return err 197 } 198 } 199 } 200 return nil 201 } 202 203 func (kv *keyValue) Close() error { 204 kv.session.Close() 205 return nil 206 } 207 208 // Ping tests if MongoDB on host can be dialed. 209 func Ping(host string, timeout time.Duration) bool { 210 return (&instance{server: host}).ping(timeout) 211 } 212 213 // instance helps with the low level details about 214 // the connection to MongoDB. 215 type instance struct { 216 server string 217 database string 218 user string 219 password string 220 session *mgo.Session 221 } 222 223 func (ins *instance) url() string { 224 if ins.user == "" || ins.password == "" { 225 return ins.server 226 } 227 return ins.user + ":" + ins.password + "@" + ins.server + "/" + ins.database 228 } 229 230 // ping won't work with old (1.2) mongo servers. 231 func (ins *instance) ping(timeout time.Duration) bool { 232 session, err := mgo.DialWithTimeout(ins.url(), timeout) 233 if err != nil { 234 return false 235 } 236 defer session.Close() 237 session.SetSyncTimeout(timeout) 238 if err = session.Ping(); err != nil { 239 return false 240 } 241 return true 242 } 243 244 func (ins *instance) getConnection() (*mgo.Session, error) { 245 if ins.session != nil { 246 return ins.session, nil 247 } 248 // TODO(mpl): do some "client caching" as in mysql, to avoid systematically dialing? 249 session, err := mgo.Dial(ins.url()) 250 if err != nil { 251 return nil, err 252 } 253 session.SetMode(mgo.Monotonic, true) 254 session.SetSafe(&mgo.Safe{}) // so we get an ErrNotFound error when deleting an absent key 255 ins.session = session 256 return session, nil 257 } 258 259 // TODO(mpl): I'm only calling getCollection at the beginning, and 260 // keeping the collection around and reusing it everywhere, instead 261 // of calling getCollection everytime, because that's the easiest. 262 // But I can easily change that. Gustavo says it does not make 263 // much difference either way. 264 // Brad, what do you think? 265 func (ins *instance) getCollection() (*mgo.Collection, error) { 266 session, err := ins.getConnection() 267 if err != nil { 268 return nil, err 269 } 270 session.SetSafe(&mgo.Safe{}) 271 session.SetMode(mgo.Strong, true) 272 c := session.DB(ins.database).C(CollectionName) 273 return c, nil 274 }