github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/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 // Config holds the parameters used to connect to MongoDB. 50 type Config struct { 51 Server string // Required. Defaults to "localhost" in ConfigFromJSON. 52 Database string // Required. 53 User string // Optional, unless the server was configured with auth on. 54 Password string // Optional, unless the server was configured with auth on. 55 } 56 57 // ConfigFromJSON populates Config from cfg, and validates 58 // cfg. It returns an error if cfg fails to validate. 59 func ConfigFromJSON(cfg jsonconfig.Obj) (Config, error) { 60 conf := Config{ 61 Server: cfg.OptionalString("host", "localhost"), 62 Database: cfg.RequiredString("database"), 63 User: cfg.OptionalString("user", ""), 64 Password: cfg.OptionalString("password", ""), 65 } 66 if err := cfg.Validate(); err != nil { 67 return Config{}, err 68 } 69 return conf, nil 70 } 71 72 // NewKeyValue returns a KeyValue implementation on top of MongoDB. 73 func NewKeyValue(cfg Config) (sorted.KeyValue, error) { 74 ins := &instance{ 75 conf: cfg, 76 } 77 db, err := ins.getCollection() 78 if err != nil { 79 return nil, err 80 } 81 return &keyValue{db: db, session: ins.session}, nil 82 } 83 84 func newKeyValueFromJSONConfig(cfg jsonconfig.Obj) (sorted.KeyValue, error) { 85 conf, err := ConfigFromJSON(cfg) 86 if err != nil { 87 return nil, err 88 } 89 return NewKeyValue(conf) 90 } 91 92 // Implementation of Iterator 93 type iter struct { 94 res bson.M 95 *mgo.Iter 96 end []byte 97 } 98 99 func (it *iter) Next() bool { 100 if !it.Iter.Next(&it.res) { 101 return false 102 } 103 if len(it.end) > 0 && bytes.Compare(it.KeyBytes(), it.end) >= 0 { 104 return false 105 } 106 return true 107 } 108 109 func (it *iter) Key() string { 110 key, ok := (it.res[mgoKey]).(string) 111 if !ok { 112 return "" 113 } 114 return key 115 } 116 117 func (it *iter) KeyBytes() []byte { 118 // TODO(bradfitz,mpl): this is less efficient than the string way. we should 119 // do better here, somehow, like all the other KeyValue iterators. 120 // For now: 121 return []byte(it.Key()) 122 } 123 124 func (it *iter) Value() string { 125 value, ok := (it.res[mgoValue]).(string) 126 if !ok { 127 return "" 128 } 129 return value 130 } 131 132 func (it *iter) ValueBytes() []byte { 133 // TODO(bradfitz,mpl): this is less efficient than the string way. we should 134 // do better here, somehow, like all the other KeyValue iterators. 135 // For now: 136 return []byte(it.Value()) 137 } 138 139 func (it *iter) Close() error { 140 return it.Iter.Close() 141 } 142 143 // Implementation of KeyValue 144 type keyValue struct { 145 session *mgo.Session // so we can close it 146 mu sync.Mutex // guards db 147 db *mgo.Collection 148 } 149 150 func (kv *keyValue) Get(key string) (string, error) { 151 kv.mu.Lock() 152 defer kv.mu.Unlock() 153 res := bson.M{} 154 q := kv.db.Find(&bson.M{mgoKey: key}) 155 err := q.One(&res) 156 if err != nil { 157 if err == mgo.ErrNotFound { 158 return "", sorted.ErrNotFound 159 } else { 160 return "", err 161 } 162 } 163 return res[mgoValue].(string), err 164 } 165 166 func (kv *keyValue) Find(start, end string) sorted.Iterator { 167 kv.mu.Lock() 168 defer kv.mu.Unlock() 169 it := kv.db.Find(&bson.M{mgoKey: &bson.M{"$gte": start}}).Sort(mgoKey).Iter() 170 return &iter{res: bson.M{}, Iter: it, end: []byte(end)} 171 } 172 173 func (kv *keyValue) Set(key, value string) error { 174 kv.mu.Lock() 175 defer kv.mu.Unlock() 176 _, err := kv.db.Upsert(&bson.M{mgoKey: key}, &bson.M{mgoKey: key, mgoValue: value}) 177 return err 178 } 179 180 // Delete removes the document with the matching key. 181 func (kv *keyValue) Delete(key string) error { 182 kv.mu.Lock() 183 defer kv.mu.Unlock() 184 err := kv.db.Remove(&bson.M{mgoKey: key}) 185 if err == mgo.ErrNotFound { 186 return nil 187 } 188 return err 189 } 190 191 // Wipe removes all documents from the collection. 192 func (kv *keyValue) Wipe() error { 193 kv.mu.Lock() 194 defer kv.mu.Unlock() 195 _, err := kv.db.RemoveAll(nil) 196 return err 197 } 198 199 type batch interface { 200 Mutations() []sorted.Mutation 201 } 202 203 func (kv *keyValue) BeginBatch() sorted.BatchMutation { 204 return sorted.NewBatchMutation() 205 } 206 207 func (kv *keyValue) CommitBatch(bm sorted.BatchMutation) error { 208 b, ok := bm.(batch) 209 if !ok { 210 return errors.New("invalid batch type") 211 } 212 213 kv.mu.Lock() 214 defer kv.mu.Unlock() 215 for _, m := range b.Mutations() { 216 if m.IsDelete() { 217 if err := kv.db.Remove(bson.M{mgoKey: m.Key()}); err != nil { 218 return err 219 } 220 } else { 221 if _, err := kv.db.Upsert(&bson.M{mgoKey: m.Key()}, &bson.M{mgoKey: m.Key(), mgoValue: m.Value()}); err != nil { 222 return err 223 } 224 } 225 } 226 return nil 227 } 228 229 func (kv *keyValue) Close() error { 230 kv.session.Close() 231 return nil 232 } 233 234 // Ping tests if MongoDB on host can be dialed. 235 func Ping(host string, timeout time.Duration) bool { 236 return (&instance{conf: Config{Server: host}}).ping(timeout) 237 } 238 239 // instance helps with the low level details about 240 // the connection to MongoDB. 241 type instance struct { 242 conf Config 243 session *mgo.Session 244 } 245 246 func (ins *instance) url() string { 247 if ins.conf.User == "" || ins.conf.Password == "" { 248 return ins.conf.Server 249 } 250 return ins.conf.User + ":" + ins.conf.Password + "@" + ins.conf.Server + "/" + ins.conf.Database 251 } 252 253 // ping won't work with old (1.2) mongo servers. 254 func (ins *instance) ping(timeout time.Duration) bool { 255 session, err := mgo.DialWithTimeout(ins.url(), timeout) 256 if err != nil { 257 return false 258 } 259 defer session.Close() 260 session.SetSyncTimeout(timeout) 261 if err = session.Ping(); err != nil { 262 return false 263 } 264 return true 265 } 266 267 func (ins *instance) getConnection() (*mgo.Session, error) { 268 if ins.session != nil { 269 return ins.session, nil 270 } 271 // TODO(mpl): do some "client caching" as in mysql, to avoid systematically dialing? 272 session, err := mgo.Dial(ins.url()) 273 if err != nil { 274 return nil, err 275 } 276 session.SetMode(mgo.Monotonic, true) 277 session.SetSafe(&mgo.Safe{}) // so we get an ErrNotFound error when deleting an absent key 278 ins.session = session 279 return session, nil 280 } 281 282 // TODO(mpl): I'm only calling getCollection at the beginning, and 283 // keeping the collection around and reusing it everywhere, instead 284 // of calling getCollection everytime, because that's the easiest. 285 // But I can easily change that. Gustavo says it does not make 286 // much difference either way. 287 // Brad, what do you think? 288 func (ins *instance) getCollection() (*mgo.Collection, error) { 289 session, err := ins.getConnection() 290 if err != nil { 291 return nil, err 292 } 293 session.SetSafe(&mgo.Safe{}) 294 session.SetMode(mgo.Strong, true) 295 c := session.DB(ins.conf.Database).C(CollectionName) 296 return c, nil 297 }