From 53fb3f0569217a755856d0af4f0884c6f80949b1 Mon Sep 17 00:00:00 2001 From: nobonobo Date: Tue, 26 Nov 2013 23:04:27 +0900 Subject: [PATCH] first commit --- .gitignore | 1 + .gitmodules | 3 + Makefile | 5 + README.md | 22 ++++ src | 1 + unqlite.go | 334 ++++++++++++++++++++++++++++++++++++++++++++++++ unqlite_test.go | 131 +++++++++++++++++++ 7 files changed, 497 insertions(+) create mode 100644 .gitmodules create mode 100644 Makefile create mode 160000 src create mode 100644 unqlite.go create mode 100644 unqlite_test.go diff --git a/.gitignore b/.gitignore index 0026861..8661ba5 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ _cgo_export.* _testmain.go *.exe +.DS_Store diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..badc3c0 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "src"] + path = src + url = https://github.com/unqlite/unqlite.git diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..80d77c6 --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +# for GNU make + +libunqlite.a: src/unqlite.c src/unqlite.h + gcc -c src/unqlite.c -I./src -DUNQLITE_ENABLE_THREADS=1 + ar rv libunqlite.a unqlite.o diff --git a/README.md b/README.md index c02b777..5eeba08 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,25 @@ unqlitego ========= UnQLite Binding for golang. + +Install +--------- + +```sh +go get github.com/nobonobo/unqlitego +cd $GOPATH/src/github.com/nobonobo/unqlitego +git submodule init +git submodule update +make +go install +``` + +Benchmark +---------- + +``` +BenchmarkFileStore 200000 9667 ns/op +BenchmarkFileFetch 500000 7928 ns/op +BenchmarkMemStore 500000 3824 ns/op +BenchmarkMemFetch 1000000 3448 ns/op +``` diff --git a/src b/src new file mode 160000 index 0000000..4802711 --- /dev/null +++ b/src @@ -0,0 +1 @@ +Subproject commit 480271123692090441b41c54d0433218d592da15 diff --git a/unqlite.go b/unqlite.go new file mode 100644 index 0000000..5a28f0e --- /dev/null +++ b/unqlite.go @@ -0,0 +1,334 @@ +package unqlitego + +/* +#cgo LDFLAGS: -L./ -lunqlite +#include "./src/unqlite.h" +#include +*/ +import "C" + +import ( + "errors" + "fmt" + "runtime" + "unsafe" +) + +// UnQLiteError ... standard error for this module +type UnQLiteError int + +func (e UnQLiteError) Error() string { + s := errString[e] + if s == "" { + return fmt.Sprintf("errno %d", int(e)) + } + return s +} + +const ( + // UnQLiteNoMemErr ... + UnQLiteNoMemErr UnQLiteError = UnQLiteError(C.UNQLITE_NOMEM) +) + +var errString = map[UnQLiteError]string{ + C.UNQLITE_NOMEM: "Out of memory", +} + +// Database ... +type Database struct { + handle *C.unqlite +} + +// Cursor ... +type Cursor struct { + parent *Database + handle *C.unqlite_kv_cursor +} + +func init() { + C.unqlite_lib_init() + if C.unqlite_lib_is_threadsafe() != 1 { + panic("unqlite library was not compiled for thread-safe option UNQLITE_ENABLE_THREADS=1") + } +} + +// NewDatabase ... +func NewDatabase(filename string) (db *Database, err error) { + db = &Database{} + name := C.CString(filename) + defer C.free(unsafe.Pointer(name)) + res := C.unqlite_open(&db.handle, name, C.UNQLITE_OPEN_CREATE) + if res != C.UNQLITE_OK { + err = UnQLiteError(res) + } + if db.handle != nil { + runtime.SetFinalizer(db, (*Database).Close) + } + return +} + +// Close ... +func (db *Database) Close() (err error) { + if db.handle != nil { + res := C.unqlite_close(db.handle) + if res != C.UNQLITE_OK { + err = UnQLiteError(res) + } + db.handle = nil + } + return +} + +// Config ... +func (db *Database) Config(operate int, args ...interface{}) (err error) { + err = errors.New("not implemented") + return +} + +// Store ... +func (db *Database) Store(key, value []byte) (err error) { + res := C.unqlite_kv_store(db.handle, + unsafe.Pointer(&key[0]), C.int(len(key)), + unsafe.Pointer(&value[0]), C.unqlite_int64(len(value))) + if res == C.UNQLITE_OK { + return nil + } + return UnQLiteError(res) +} + +// Append ... +func (db *Database) Append(key, value []byte) (err error) { + res := C.unqlite_kv_append(db.handle, + unsafe.Pointer(&key[0]), C.int(len(key)), + unsafe.Pointer(&value[0]), C.unqlite_int64(len(value))) + if res != C.UNQLITE_OK { + err = UnQLiteError(res) + } + return +} + +// Fetch ... +func (db *Database) Fetch(key []byte) (value []byte, err error) { + var n C.unqlite_int64 + res := C.unqlite_kv_fetch(db.handle, unsafe.Pointer(&key[0]), C.int(len(key)), nil, &n) + if res != C.UNQLITE_OK { + err = UnQLiteError(res) + return + } + value = make([]byte, int(n)) + res = C.unqlite_kv_fetch(db.handle, unsafe.Pointer(&key[0]), C.int(len(key)), unsafe.Pointer(&value[0]), &n) + if res != C.UNQLITE_OK { + err = UnQLiteError(res) + } + return +} + +// Delete ... +func (db *Database) Delete(key []byte) (err error) { + res := C.unqlite_kv_delete(db.handle, unsafe.Pointer(&key[0]), C.int(len(key))) + if res != C.UNQLITE_OK { + err = UnQLiteError(res) + } + return +} + +// Begin ... +func (db *Database) Begin() (err error) { + res := C.unqlite_begin(db.handle) + if res != C.UNQLITE_OK { + err = UnQLiteError(res) + } + return +} + +// Commit ... +func (db *Database) Commit() (err error) { + res := C.unqlite_commit(db.handle) + if res != C.UNQLITE_OK { + err = UnQLiteError(res) + } + return +} + +// Rollback ... +func (db *Database) Rollback() (err error) { + res := C.unqlite_rollback(db.handle) + if res != C.UNQLITE_OK { + err = UnQLiteError(res) + } + return +} + +// NewCursor ... +func (db *Database) NewCursor() (cursor *Cursor, err error) { + cursor = &Cursor{parent: db} + res := C.unqlite_kv_cursor_init(db.handle, &cursor.handle) + if res != C.UNQLITE_OK { + err = UnQLiteError(res) + } + runtime.SetFinalizer(cursor, (*Cursor).Close) + return +} + +// Close ... +func (curs *Cursor) Close() (err error) { + if curs.parent.handle != nil && curs.handle != nil { + res := C.unqlite_kv_cursor_release(curs.parent.handle, curs.handle) + if res != C.UNQLITE_OK { + err = UnQLiteError(res) + } + curs.handle = nil + } + return +} + +// Seek ... +func (curs *Cursor) Seek(key []byte) (err error) { + res := C.unqlite_kv_cursor_seek(curs.handle, unsafe.Pointer(&key[0]), C.int(len(key)), C.UNQLITE_CURSOR_MATCH_EXACT) + if res != C.UNQLITE_OK { + err = UnQLiteError(res) + } + return +} + +// SeekLE ... +func (curs *Cursor) SeekLE(key []byte) (err error) { + res := C.unqlite_kv_cursor_seek(curs.handle, unsafe.Pointer(&key[0]), C.int(len(key)), C.UNQLITE_CURSOR_MATCH_LE) + if res != C.UNQLITE_OK { + err = UnQLiteError(res) + } + return +} + +// SeekGE ... +func (curs *Cursor) SeekGE(key []byte) (err error) { + res := C.unqlite_kv_cursor_seek(curs.handle, unsafe.Pointer(&key[0]), C.int(len(key)), C.UNQLITE_CURSOR_MATCH_GE) + if res != C.UNQLITE_OK { + err = UnQLiteError(res) + } + return +} + +// First ... +func (curs *Cursor) First() (err error) { + res := C.unqlite_kv_cursor_first_entry(curs.handle) + if res != C.UNQLITE_OK { + err = UnQLiteError(res) + } + return +} + +// Last ... +func (curs *Cursor) Last() (err error) { + res := C.unqlite_kv_cursor_last_entry(curs.handle) + if res != C.UNQLITE_OK { + err = UnQLiteError(res) + } + return +} + +// IsValid ... +func (curs *Cursor) IsValid() (ok bool) { + return C.unqlite_kv_cursor_valid_entry(curs.handle) == 1 +} + +// Next ... +func (curs *Cursor) Next() (err error) { + res := C.unqlite_kv_cursor_next_entry(curs.handle) + if res != C.UNQLITE_OK { + err = UnQLiteError(res) + } + return +} + +// Prev ... +func (curs *Cursor) Prev() (err error) { + res := C.unqlite_kv_cursor_prev_entry(curs.handle) + if res != C.UNQLITE_OK { + err = UnQLiteError(res) + } + return +} + +// Delete ... +func (curs *Cursor) Delete() (err error) { + res := C.unqlite_kv_cursor_delete_entry(curs.handle) + if res != C.UNQLITE_OK { + err = UnQLiteError(res) + } + return +} + +// Reset ... +func (curs *Cursor) Reset() (err error) { + res := C.unqlite_kv_cursor_reset(curs.handle) + if res != C.UNQLITE_OK { + err = UnQLiteError(res) + } + return +} + +// Key ... +func (curs *Cursor) Key() (key []byte, err error) { + var n C.int + res := C.unqlite_kv_cursor_key(curs.handle, nil, &n) + if res != C.UNQLITE_OK { + err = UnQLiteError(res) + return + } + key = make([]byte, int(n)) + res = C.unqlite_kv_cursor_key(curs.handle, unsafe.Pointer(&key[0]), &n) + if res != C.UNQLITE_OK { + err = UnQLiteError(res) + } + return +} + +// Value ... +func (curs *Cursor) Value() (value []byte, err error) { + var n C.unqlite_int64 + res := C.unqlite_kv_cursor_data(curs.handle, nil, &n) + if res != C.UNQLITE_OK { + err = UnQLiteError(res) + return + } + value = make([]byte, int(n)) + res = C.unqlite_kv_cursor_data(curs.handle, unsafe.Pointer(&value[0]), &n) + if res != C.UNQLITE_OK { + err = UnQLiteError(res) + } + return +} + +/* TODO: implement + +// Database Engine Handle +int unqlite_config(unqlite *pDb,int nOp,...); + +// Key/Value (KV) Store Interfaces +int unqlite_kv_store_fmt(unqlite *pDb,const void *pKey,int nKeyLen,const char *zFormat,...); +int unqlite_kv_append_fmt(unqlite *pDb,const void *pKey,int nKeyLen,const char *zFormat,...); +int unqlite_kv_fetch_callback(unqlite *pDb,const void *pKey, + int nKeyLen,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData); +int unqlite_kv_config(unqlite *pDb,int iOp,...); + +// Cursor Iterator Interfaces +int unqlite_kv_cursor_key_callback(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData); +int unqlite_kv_cursor_data_callback(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData); + +// Utility interfaces +int unqlite_util_load_mmaped_file(const char *zFile,void **ppMap,unqlite_int64 *pFileSize); +int unqlite_util_release_mmaped_file(void *pMap,unqlite_int64 iFileSize); +int unqlite_util_random_string(unqlite *pDb,char *zBuf,unsigned int buf_size); +unsigned int unqlite_util_random_num(unqlite *pDb); + +// Global Library Management Interfaces +int unqlite_lib_config(int nConfigOp,...); +int unqlite_lib_init(void); +int unqlite_lib_shutdown(void); +int unqlite_lib_is_threadsafe(void); +const char * unqlite_lib_version(void); +const char * unqlite_lib_signature(void); +const char * unqlite_lib_ident(void); +const char * unqlite_lib_copyright(void); +*/ diff --git a/unqlite_test.go b/unqlite_test.go new file mode 100644 index 0000000..09aeba0 --- /dev/null +++ b/unqlite_test.go @@ -0,0 +1,131 @@ +package unqlitego + +import ( + "bytes" + "fmt" + . "github.com/r7kamura/gospel" + "io/ioutil" + "testing" +) + +func Testモジュール(t *testing.T) { + var db *Database + var src []byte + src = []byte("value") + + Describe(t, "正常系", func() { + Context("基本テスト", func() { + It("NewDatabase", func() { + f, err := ioutil.TempFile("", "sample.db") + if err != nil { + panic(err) + } + db, err = NewDatabase(f.Name()) + Expect(err).To(NotExist) + Expect(db).To(Exist) + }) + It("Database.Begin", func() { + err := db.Begin() + Expect(err).To(NotExist) + }) + It("Database.Store", func() { + err := db.Store([]byte("sample"), src) + Expect(err).To(NotExist) + }) + It("Database.Fetch", func() { + dst, err := db.Fetch([]byte("sample")) + Expect(err).To(NotExist) + Expect(bytes.Compare(src, dst)).To(Equal, 0) + }) + It("Database.Append", func() { + err1 := db.Append([]byte("sample"), []byte(" append")) + Expect(err1).To(NotExist) + dst, err2 := db.Fetch([]byte("sample")) + Expect(err2).To(NotExist) + Expect(bytes.Compare(append(src, []byte(" append")...), dst)).To(Equal, 0) + }) + It("Database.Commit", func() { + err := db.Commit() + Expect(err).To(NotExist) + }) + It("Database.Begin", func() { + err := db.Begin() + Expect(err).To(NotExist) + }) + It("Database.Delete", func() { + err1 := db.Delete([]byte("sample")) + Expect(err1).To(NotExist) + _, err2 := db.Fetch([]byte("sample")) + Expect(err2).To(Exist) + }) + It("Database.Rollback", func() { + err := db.Rollback() + Expect(err).To(NotExist) + value, err2 := db.Fetch([]byte("sample")) + Expect(err2).To(NotExist) + Expect(value).To(Exist) + }) + It("Database.NewCursor", func() { + cursor, err := db.NewCursor() + Expect(err).To(NotExist) + Expect(cursor).To(Exist) + err = cursor.Seek([]byte("sample")) + Expect(err).To(NotExist) + }) + It("Database.Close", func() { + err := db.Close() + Expect(err).To(NotExist) + }) + }) + }) +} + +func BenchmarkFileStore(b *testing.B) { + b.StopTimer() + f, err := ioutil.TempFile("", "sample.db") + if err != nil { + panic(err) + } + db, _ := NewDatabase(f.Name()) + b.StartTimer() + for i := 0; i < b.N; i++ { + db.Store([]byte(fmt.Sprintf("%d", i)), []byte("abcdefghijklmnopabcdefghijklmnopabcdefghijklmnopabcdefghijklmnop")) + } +} + +func BenchmarkFileFetch(b *testing.B) { + b.StopTimer() + f, err := ioutil.TempFile("", "sample.db") + if err != nil { + panic(err) + } + db, _ := NewDatabase(f.Name()) + for i := 0; i < b.N; i++ { + db.Store([]byte(fmt.Sprintf("%d", i)), []byte("abcdefghijklmnopabcdefghijklmnopabcdefghijklmnopabcdefghijklmnop")) + } + b.StartTimer() + for i := 0; i < b.N; i++ { + _, _ = db.Fetch([]byte(fmt.Sprintf("%d", i))) + } +} + +func BenchmarkMemStore(b *testing.B) { + b.StopTimer() + db, _ := NewDatabase("") + b.StartTimer() + for i := 0; i < b.N; i++ { + db.Store([]byte(fmt.Sprintf("%d", i)), []byte("abcdefghijklmnopabcdefghijklmnopabcdefghijklmnopabcdefghijklmnop")) + } +} + +func BenchmarkMemFetch(b *testing.B) { + b.StopTimer() + db, _ := NewDatabase("") + for i := 0; i < b.N; i++ { + db.Store([]byte(fmt.Sprintf("%d", i)), []byte("abcdefghijklmnopabcdefghijklmnopabcdefghijklmnopabcdefghijklmnop")) + } + b.StartTimer() + for i := 0; i < b.N; i++ { + _, _ = db.Fetch([]byte(fmt.Sprintf("%d", i))) + } +}