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  }