mirror of
https://github.com/jimeh/ozu.io.git
synced 2026-02-19 08:06:39 +00:00
Instead of exposing a key/value get/set style interface, design the interface around the actions that are actually required. This is a first step towards supporting adding a Store to support Google Cloud's Datastore.
185 lines
3.7 KiB
Go
185 lines
3.7 KiB
Go
package goleveldbstore
|
|
|
|
import (
|
|
"strconv"
|
|
|
|
"github.com/jimeh/ozu.io/storage"
|
|
"github.com/syndtr/goleveldb/leveldb"
|
|
)
|
|
|
|
const errLeveldbNotFound = "leveldb: not found"
|
|
|
|
// DefaultSequenceKey is used by NextSequence().
|
|
var DefaultSequenceKey = []byte("__SEQUENCE_ID__")
|
|
|
|
var uidKeyPrefix = []byte("!")
|
|
var urlKeyPrefix = []byte("#")
|
|
|
|
// New creates a new Store using given path to persist data.
|
|
func New(path string) (*Store, error) {
|
|
db, err := leveldb.OpenFile(path, nil)
|
|
if err != nil {
|
|
return &Store{}, err
|
|
}
|
|
|
|
store := Store{
|
|
DB: db,
|
|
SequenceKey: DefaultSequenceKey,
|
|
}
|
|
|
|
return &store, nil
|
|
}
|
|
|
|
// Store allows storing data into a goleveldb database.
|
|
type Store struct {
|
|
DB *leveldb.DB
|
|
SequenceKey []byte
|
|
}
|
|
|
|
// Close underlying goleveldb database.
|
|
func (s *Store) Close() error {
|
|
return s.DB.Close()
|
|
}
|
|
|
|
// Create a given Record.
|
|
func (s *Store) Create(uid []byte, url []byte) (*storage.Record, error) {
|
|
tx, err := s.DB.OpenTransaction()
|
|
if err != nil {
|
|
return &storage.Record{}, err
|
|
}
|
|
|
|
err = tx.Put(s.uidKey(uid), url, nil)
|
|
if err != nil {
|
|
return &storage.Record{}, err
|
|
}
|
|
|
|
err = tx.Put(s.urlKey(url), uid, nil)
|
|
if err != nil {
|
|
return &storage.Record{}, err
|
|
}
|
|
|
|
err = tx.Commit()
|
|
if err != nil {
|
|
return &storage.Record{}, err
|
|
}
|
|
|
|
return &storage.Record{UID: uid, URL: url}, nil
|
|
}
|
|
|
|
// FindByUID looks up records based on their UID.
|
|
func (s *Store) FindByUID(uid []byte) (*storage.Record, error) {
|
|
value, err := s.DB.Get(s.uidKey(uid), nil)
|
|
if err != nil {
|
|
if err.Error() == errLeveldbNotFound {
|
|
return &storage.Record{}, storage.ErrNotFound
|
|
}
|
|
return &storage.Record{}, err
|
|
}
|
|
|
|
return &storage.Record{UID: uid, URL: value}, nil
|
|
}
|
|
|
|
// FindByURL looks up records based on their URL.
|
|
func (s *Store) FindByURL(url []byte) (*storage.Record, error) {
|
|
value, err := s.DB.Get(s.urlKey(url), nil)
|
|
if err != nil {
|
|
if err.Error() == errLeveldbNotFound {
|
|
return &storage.Record{}, storage.ErrNotFound
|
|
}
|
|
return &storage.Record{}, err
|
|
}
|
|
|
|
return &storage.Record{UID: value, URL: url}, nil
|
|
}
|
|
|
|
// DeleteByUID deletes records based on their UID.
|
|
func (s *Store) DeleteByUID(uid []byte) (*storage.Record, error) {
|
|
record, err := s.FindByUID(uid)
|
|
if err != nil {
|
|
return &storage.Record{}, err
|
|
}
|
|
|
|
s.delete(record)
|
|
return record, nil
|
|
}
|
|
|
|
// DeleteByURL deletes records based on their URL.
|
|
func (s *Store) DeleteByURL(url []byte) (*storage.Record, error) {
|
|
record, err := s.FindByURL(url)
|
|
if err != nil {
|
|
return &storage.Record{}, err
|
|
}
|
|
|
|
err = s.delete(record)
|
|
if err != nil {
|
|
return &storage.Record{}, err
|
|
}
|
|
|
|
return record, nil
|
|
}
|
|
|
|
func (s *Store) delete(r *storage.Record) error {
|
|
tx, err := s.DB.OpenTransaction()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = tx.Delete(s.uidKey(r.UID), nil)
|
|
if err != nil && err.Error() == errLeveldbNotFound {
|
|
return err
|
|
}
|
|
|
|
err = tx.Delete(s.urlKey(r.URL), nil)
|
|
if err != nil && err.Error() == errLeveldbNotFound {
|
|
return err
|
|
}
|
|
|
|
return tx.Commit()
|
|
}
|
|
|
|
func (s *Store) uidKey(uid []byte) []byte {
|
|
return append(uidKeyPrefix, uid...)
|
|
}
|
|
|
|
func (s *Store) urlKey(url []byte) []byte {
|
|
return append(urlKeyPrefix, url...)
|
|
}
|
|
|
|
// NextSequence returns a auto-incrementing int.
|
|
func (s *Store) NextSequence() (int, error) {
|
|
return s.incr(s.SequenceKey)
|
|
}
|
|
|
|
// Incr increments a given key (must be numeric-like value)
|
|
func (s *Store) incr(key []byte) (int, error) {
|
|
tx, err := s.DB.OpenTransaction()
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
|
|
value, err := tx.Get(key, nil)
|
|
if value == nil {
|
|
value = []byte("0")
|
|
}
|
|
|
|
num, err := strconv.Atoi(string(value))
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
|
|
num++
|
|
value = []byte(strconv.Itoa(num))
|
|
|
|
err = tx.Put(key, value, nil)
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
|
|
err = tx.Commit()
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
|
|
return num, nil
|
|
}
|