--- /dev/null
+EJDB Go Library
+==================================
+
+One snippet intro
+-----------------------------------
+
+~~~~~~
+package ejdbtutorial
+
+import (
+ "fmt"
+ "github.com/mkilling/ejdb/goejdb"
+ "labix.org/v2/mgo/bson"
+ "os"
+)
+
+func main() {
+ jb, err := goejdb.Open("addressbook", JBOWRITER | JBOCREAT | JBOTRUNC)
+ if err != nil {
+ os.Exit(1)
+ }
+ //Get or create collection 'contacts'
+ coll, _ := jb.CreateColl("contacts", nil)
+
+ //Insert one record:
+ //JSON: {'name' : 'Bruce', 'phone' : '333-222-333', 'age' : 58}
+ rec := map[string]interface{} {"name" : "Bruce", "phone" : "333-222-333", "age" : 58}
+ bsrec, _ := bson.Marshal(rec)
+ coll.SaveBson(bsrec)
+ fmt.Printf("\nSaved Bruce")
+
+ //Now execute query
+ res, _ := coll.Find(`{"name" : {"$begin" : "Bru"}}`) //Name starts with 'Bru' string
+ fmt.Printf("\n\nRecords found: %d\n", len(res))
+
+ //Now print the result set records
+ for _, bs := range res {
+ var m map[string]interface{}
+ bson.Unmarshal(bs, &m)
+ fmt.Println(m)
+ }
+
+ //Close database
+ jb.Close()
+}
+~~~~~~
+
+You can save this code in `ejdbtutorial.go` And build:
+
+
+```sh
+go build ejdbtutorial.go
+./ejdbtutorial
+```
+
+Building & Installation
+--------------------------------
+
+[Installing on Debian/Ubuntu](https://github.com/Softmotions/ejdb/wiki/Debian-Ubuntu-installation)
+
+Manual installation
+-------------------------------
+
+### Prerequisites
+**System libraries:**
+
+* Google Go
+* labix.org/v2/mgo/bson (go get labix.org/v2/mgo/bson)
+* tcejdb (see tcejdb/ in this repository)
+
+### Build and install
+
+~~~~~~
+ go get github.com/mkilling/ejdb/goejdb
+~~~~~~
+
+Queries
+---------------------------------
+
+~~~~~~
+/**
+ * Create query object.
+ * Sucessfully created queries must be destroyed with ejdbquerydel().
+ *
+ * EJDB queries inspired by MongoDB (mongodb.org) and follows same philosophy.
+ *
+ * - Supported queries:
+ * - Simple matching of String OR Number OR Array value:
+ * - {'fpath' : 'val', ...}
+ * - $not Negate operation.
+ * - {'fpath' : {'$not' : val}} //Field not equal to val
+ * - {'fpath' : {'$not' : {'$begin' : prefix}}} //Field not begins with val
+ * - $begin String starts with prefix
+ * - {'fpath' : {'$begin' : prefix}}
+ * - $gt, $gte (>, >=) and $lt, $lte for number types:
+ * - {'fpath' : {'$gt' : number}, ...}
+ * - $bt Between for number types:
+ * - {'fpath' : {'$bt' : [num1, num2]}}
+ * - $in String OR Number OR Array val matches to value in specified array:
+ * - {'fpath' : {'$in' : [val1, val2, val3]}}
+ * - $nin - Not IN
+ * - $strand String tokens OR String array val matches all tokens in specified array:
+ * - {'fpath' : {'$strand' : [val1, val2, val3]}}
+ * - $stror String tokens OR String array val matches any token in specified array:
+ * - {'fpath' : {'$stror' : [val1, val2, val3]}}
+ * - $exists Field existence matching:
+ * - {'fpath' : {'$exists' : true|false}}
+ * - $icase Case insensitive string matching:
+ * - {'fpath' : {'$icase' : 'val1'}} //icase matching
+ * Ignore case matching with '$in' operation:
+ * - {'name' : {'$icase' : {'$in' : ['tHéâtre - театр', 'heLLo WorlD']}}}
+ * For case insensitive matching you can create special index of type: `JBIDXISTR`
+ * - $elemMatch The $elemMatch operator matches more than one component within an array element.
+ * - { array: { $elemMatch: { value1 : 1, value2 : { $gt: 1 } } } }
+ * Restriction: only one $elemMatch allowed in context of one array field.
+ *
+ * - Queries can be used to update records:
+ * $set Field set operation.
+ * - {.., '$set' : {'field1' : val1, 'fieldN' : valN}}
+ * $upsert Atomic upsert. If matching records are found it will be '$set' operation,
+ * otherwise new record will be inserted with fields specified by argment object.
+ * - {.., '$upsert' : {'field1' : val1, 'fieldN' : valN}}
+ * $inc Increment operation. Only number types are supported.
+ * - {.., '$inc' : {'field1' : number, ..., 'field1' : number}
+ * $dropall In-place record removal operation.
+ * - {.., '$dropall' : true}
+ * $addToSet Atomically adds value to the array only if its not in the array already.
+ * If containing array is missing it will be created.
+ * - {.., '$addToSet' : {'fpath' : val1, 'fpathN' : valN, ...}}
+ * $addToSetAll Batch version if $addToSet
+ * - {.., '$addToSetAll' : {'fpath' : [array of values to add], ...}}
+ * $pull Atomically removes all occurrences of value from field, if field is an array.
+ * - {.., '$pull' : {'fpath' : val1, 'fpathN' : valN, ...}}
+ * $pullAll Batch version of $pull
+ * - {.., '$pullAll' : {'fpath' : [array of values to remove], ...}}
+ *
+ * NOTE: Negate operations: $not and $nin not using indexes
+ * so they can be slow in comparison to other matching operations.
+ *
+ * NOTE: Only one index can be used in search query operation.
+ *
+ * QUERY HINTS (specified by `hints` argument):
+ * - $max Maximum number in the result set
+ * - $skip Number of skipped results in the result set
+ * - $orderby Sorting order of query fields.
+ * - $fields Set subset of fetched fields
+ If a field presented in $orderby clause it will be forced to include in resulting records.
+ * Example:
+ * hints: {
+ * "$orderby" : { //ORDER BY field1 ASC, field2 DESC
+ * "field1" : 1,
+ * "field2" : -1
+ * },
+ * "$fields" : { //SELECT ONLY {_id, field1, field2}
+ * "field1" : 1,
+ * "field2" : 1
+ * }
+ * }
+ *
+ * Many query examples can be found in `testejdb/t2.c` test case.
+ *
+ * @param EJDB database handle.
+ * @param qobj Main BSON query object.
+ * @param orqobjs Array of additional OR query objects (joined with OR predicate).
+ * @param orqobjsnum Number of OR query objects.
+ * @param hints BSON object with query hints.
+ * @return On success return query handle. On error returns NULL.
+ */
+EJDB_EXPORT EJQ* ejdbcreatequery(EJDB *jb, bson *qobj, bson *orqobjs, int orqobjsnum, bson *hints);
+~~~~~~
+
+Basic EJDB architecture
+------------------------------------
+**EJDB database files structure**
+
+~~~~~~
+.
+├── <dbname>
+├── <dbname>_<collection1>
+├── ...
+├── <dbname>_<collectionN>
+└── <dbname>_<collectionN>_<fieldpath>.<index ext>
+~~~~~~
+
+Where
+
+* ```<dbname>``` - name of database. It is metadata DB.
+* ```<collectionN>``` - name of collection. Collection database.
+* ```<fieldpath>``` - JSON field path used in index
+* ```<index ext>``` - Collection index extension:
+ * ```.lex``` String index
+ * ```.dec``` Number index
+ * ```.tok``` Array index
+
+Limitations
+------------------------------------
+* One ejdb database can handle up to 1024 collections.
+* Indexes for objects in nested arrays currently not supported (#37)
+
+Related software
+------------------------------------
+[Connect session store backed by EJDB database](https://github.com/Softmotions/connect-session-ejdb)
+
+
--- /dev/null
+package goejdb
+
+// #cgo LDFLAGS: -ltcejdb
+// #include "../../../../../../tcejdb/ejdb.h"
+import "C"
+
+import "unsafe"
+
+const (
+ JBIDXDROP = C.JBIDXDROP
+ JBIDXDROPALL = C.JBIDXDROPALL
+ JBIDXOP = C.JBIDXOP
+ JBIDXREBLD = C.JBIDXREBLD
+ JBIDXNUM = C.JBIDXNUM
+ JBIDXSTR = C.JBIDXSTR
+ JBIDXARR = C.JBIDXARR
+ JBIDXISTR = C.JBIDXISTR
+)
+
+type EjColl struct {
+ ptr *[0]byte
+ ejdb *Ejdb
+}
+
+type EjCollOpts struct {
+ large bool
+ compressed bool
+ records int
+ cachedrecords int
+}
+
+func (coll *EjColl) save_c_bson(c_bson *C.bson) (string, *EjdbError) {
+ var c_oid C.bson_oid_t
+ C.ejdbsavebson(coll.ptr, c_bson, &c_oid)
+ return bson_oid_to_string(&c_oid), coll.ejdb.check_error()
+}
+
+func (coll *EjColl) SaveBson(bsdata []byte) (string, *EjdbError) {
+ c_bson := bson_from_byte_slice(bsdata)
+ defer C.bson_destroy(c_bson)
+ return coll.save_c_bson(c_bson)
+}
+
+func (coll *EjColl) SaveJson(j string) (string, *EjdbError) {
+ c_bson := bson_from_json(j)
+ defer C.bson_destroy(c_bson)
+ return coll.save_c_bson(c_bson)
+}
+
+// EJDB_EXPORT bool ejdbrmbson(EJCOLL *coll, bson_oid_t *oid);
+func (coll *EjColl) RmBson(oid string) bool {
+ c_oid := bson_oid_from_string(&oid)
+ ret := C.ejdbrmbson(coll.ptr, c_oid)
+ coll.ejdb.check_error()
+ return bool(ret)
+}
+
+// EJDB_EXPORT bson* ejdbloadbson(EJCOLL *coll, const bson_oid_t *oid);
+func (coll *EjColl) LoadBson(oid string) []byte {
+ c_oid := bson_oid_from_string(&oid)
+ bson := C.ejdbloadbson(coll.ptr, c_oid)
+ defer C.bson_del(bson)
+ coll.ejdb.check_error()
+
+ return bson_to_byte_slice(bson)
+}
+
+// EJDB_EXPORT EJQRESULT ejdbqryexecute(EJCOLL *jcoll, const EJQ *q, uint32_t *count, int qflags, TCXSTR *log);
+func (coll *EjColl) Find(query string, queries ...string) ([][]byte, *EjdbError) {
+ q, err := coll.ejdb.CreateQuery(query, queries...)
+ defer q.Del()
+ if err != nil {
+ return nil, err
+ } else {
+ return q.Execute(coll)
+ }
+}
+
+func (coll *EjColl) FindOne(query string, queries ...string) (*[]byte, *EjdbError) {
+ q, err := coll.ejdb.CreateQuery(query, queries...)
+ defer q.Del()
+ if err != nil {
+ return nil, err
+ } else {
+ return q.ExecuteOne(coll)
+ }
+}
+
+func (coll *EjColl) Count(query string, queries ...string) (int, *EjdbError) {
+ q, err := coll.ejdb.CreateQuery(query, queries...)
+ if err != nil {
+ return 0, err
+ }
+ defer q.Del()
+ return q.Count(coll)
+}
+
+// EJDB_EXPORT uint32_t ejdbupdate(EJCOLL *jcoll, bson *qobj, bson *orqobjs, int orqobjsnum, bson *hints, TCXSTR *log);
+func (coll *EjColl) Update(query string, queries ...string) (int, *EjdbError) {
+ query_bson := bson_from_json(query)
+ defer C.bson_destroy(query_bson)
+
+ orqueries := C.malloc((C.size_t)(unsafe.Sizeof(new(C.bson))) * C.size_t(len(queries)))
+ defer C.free(orqueries)
+ ptr_orqueries := (*[maxslice]*C.bson)(orqueries)
+ for i, q := range queries {
+ (*ptr_orqueries)[i] = bson_from_json(q)
+ defer C.bson_destroy((*ptr_orqueries)[i])
+ }
+
+ count := C.ejdbupdate(coll.ptr, query_bson, (*C.bson)(orqueries), C.int(len(queries)), nil, nil)
+ return int(count), coll.ejdb.check_error()
+}
+
+// EJDB_EXPORT bool ejdbsetindex(EJCOLL *coll, const char *ipath, int flags);
+func (coll *EjColl) SetIndex(ipath string, flags int) *EjdbError {
+ c_ipath := C.CString(ipath)
+ defer C.free(unsafe.Pointer(c_ipath))
+ res := C.ejdbsetindex(coll.ptr, c_ipath, C.int(flags))
+ if res {
+ return nil
+ } else {
+ return coll.ejdb.check_error()
+ }
+}
+
+// EJDB_EXPORT bool ejdbtranbegin(EJCOLL *coll);
+func (coll *EjColl) BeginTransaction() *EjdbError {
+ res := C.ejdbtranbegin(coll.ptr)
+ if res {
+ return nil
+ } else {
+ return coll.ejdb.check_error()
+ }
+}
+
+//EJDB_EXPORT bool ejdbtrancommit(EJCOLL *coll);
+func (coll *EjColl) CommitTransaction() *EjdbError {
+ res := C.ejdbtrancommit(coll.ptr)
+ if res {
+ return nil
+ } else {
+ return coll.ejdb.check_error()
+ }
+}
+
+// EJDB_EXPORT bool ejdbtranabort(EJCOLL *coll);
+func (coll *EjColl) AbortTransaction() *EjdbError {
+ res := C.ejdbtranabort(coll.ptr)
+ if res {
+ return nil
+ } else {
+ return coll.ejdb.check_error()
+ }
+}
+
+// EJDB_EXPORT bool ejdbtranstatus(EJCOLL *jcoll, bool *txactive);
+func (coll *EjColl) IsTransactionActive() bool {
+ var ret C.bool
+ C.ejdbtranstatus(coll.ptr, &ret)
+ return bool(ret)
+}
+
+// EJDB_EXPORT void ejdbqresultdispose(EJQRESULT qr);
+
+// EJDB_EXPORT bool ejdbsyncoll(EJCOLL *jcoll);
+func (coll *EjColl) Sync() (bool, *EjdbError) {
+ ret := C.ejdbsyncoll(coll.ptr)
+ if ret {
+ return bool(ret), nil
+ } else {
+ return bool(ret), coll.ejdb.check_error()
+ }
+}
--- /dev/null
+package goejdb
+
+// #cgo LDFLAGS: -ltcejdb
+// #include "../../../../../../tcejdb/ejdb.h"
+import "C"
+
+import (
+ "errors"
+ "fmt"
+ "unsafe"
+)
+
+const (
+ JBOREADER = C.JBOREADER
+ JBOWRITER = C.JBOWRITER
+ JBOCREAT = C.JBOCREAT
+ JBOTRUNC = C.JBOTRUNC
+ JBONOLCK = C.JBONOLCK
+ JBOLCKNB = C.JBOLCKNB
+ JBOTSYNC = C.JBOTSYNC
+)
+
+const maxslice = 1<<31 - 1
+
+type Ejdb struct {
+ ptr *[0]byte
+}
+
+type EjdbError struct {
+ ErrorCode int
+ error
+}
+
+func new_ejdb() *Ejdb {
+ ejdb := new(Ejdb)
+ ejdb.ptr = C.ejdbnew()
+ if ejdb.ptr == nil {
+ return nil
+ } else {
+ return ejdb
+ }
+}
+
+func Version() string {
+ cs := C.ejdbversion()
+ return C.GoString(cs)
+}
+
+func IsValidOidStr(oid string) bool {
+ c_oid := C.CString(oid)
+ res := C.ejdbisvalidoidstr(c_oid)
+ C.free(unsafe.Pointer(c_oid))
+
+ return bool(res)
+}
+
+func Open(path string, options int) (*Ejdb, *EjdbError) {
+ ejdb := new_ejdb()
+ if ejdb != nil {
+ c_path := C.CString(path)
+ defer C.free(unsafe.Pointer(c_path))
+ C.ejdbopen(ejdb.ptr, c_path, C.int(options))
+ }
+
+ return ejdb, ejdb.check_error()
+}
+
+func (ejdb *Ejdb) check_error() *EjdbError {
+ ecode := C.ejdbecode(ejdb.ptr)
+ if ecode == 0 {
+ return nil
+ }
+ c_msg := C.ejdberrmsg(ecode)
+ msg := C.GoString(c_msg)
+ return &EjdbError{int(ecode), errors.New(fmt.Sprintf("EJDB error: %v", msg))}
+}
+
+func (ejdb *Ejdb) IsOpen() bool {
+ ret := C.ejdbisopen(ejdb.ptr)
+ return bool(ret)
+}
+
+func (ejdb *Ejdb) Del() {
+ C.ejdbdel(ejdb.ptr)
+}
+
+func (ejdb *Ejdb) Close() *EjdbError {
+ C.ejdbclose(ejdb.ptr)
+ return ejdb.check_error()
+}
+
+func (ejdb *Ejdb) GetColl(colname string) (*EjColl, *EjdbError) {
+ c_colname := C.CString(colname)
+ defer C.free(unsafe.Pointer(c_colname))
+
+ ejcoll := new(EjColl)
+ ejcoll.ejdb = ejdb
+ ejcoll.ptr = C.ejdbgetcoll(ejdb.ptr, c_colname)
+
+ return ejcoll, ejdb.check_error()
+}
+
+func (ejdb *Ejdb) GetColls() ([]*EjColl, *EjdbError) {
+ ret := make([]*EjColl, 0)
+ lst := C.ejdbgetcolls(ejdb.ptr)
+ if lst == nil {
+ return ret, ejdb.check_error()
+ }
+
+ for i := int(lst.start); i < int(lst.start)+int(lst.num); i++ {
+ ptr := uintptr(unsafe.Pointer(lst.array)) + unsafe.Sizeof(C.TCLISTDATUM{})*uintptr(i)
+ datum := (*C.TCLISTDATUM)(unsafe.Pointer(ptr))
+ datum_ptr := unsafe.Pointer(datum.ptr)
+ ret = append(ret, &EjColl{(*[0]byte)(datum_ptr), ejdb})
+ }
+ return ret, nil
+}
+
+func (ejdb *Ejdb) CreateColl(colname string, opts *EjCollOpts) (*EjColl, *EjdbError) {
+ c_colname := C.CString(colname)
+ defer C.free(unsafe.Pointer(c_colname))
+
+ ret := new(EjColl)
+ ret.ejdb = ejdb
+
+ if opts != nil {
+ var c_opts C.EJCOLLOPTS
+ c_opts.large = C._Bool(opts.large)
+ c_opts.compressed = C._Bool(opts.large)
+ c_opts.records = C.int64_t(opts.records)
+ c_opts.cachedrecords = C.int(opts.cachedrecords)
+ ret.ptr = C.ejdbcreatecoll(ejdb.ptr, c_colname, &c_opts)
+ } else {
+ ret.ptr = C.ejdbcreatecoll(ejdb.ptr, c_colname, nil)
+ }
+
+ if ret.ptr != nil {
+ return ret, nil
+ } else {
+ return nil, ejdb.check_error()
+ }
+}
+
+func (ejdb *Ejdb) RmColl(colname string, unlinkfile bool) (bool, *EjdbError) {
+ c_colname := C.CString(colname)
+ defer C.free(unsafe.Pointer(c_colname))
+ res := C.ejdbrmcoll(ejdb.ptr, c_colname, C._Bool(unlinkfile))
+ if res {
+ return bool(res), nil
+ } else {
+ return bool(res), ejdb.check_error()
+ }
+}
+
+// EJDB_EXPORT bool ejdbsyncdb(EJDB *jb);
+func (ejdb *Ejdb) Sync() (bool, *EjdbError) {
+ ret := C.ejdbsyncdb(ejdb.ptr)
+ if ret {
+ return bool(ret), nil
+ } else {
+ return bool(ret), ejdb.check_error()
+ }
+}
+
+// EJDB_EXPORT bson* ejdbmeta(EJDB *jb);
+func (ejdb *Ejdb) Meta() ([]byte, *EjdbError) {
+ bson := C.ejdbmeta(ejdb.ptr)
+ err := ejdb.check_error()
+ if err != nil {
+ return make([]byte, 0), err
+ }
+ defer C.bson_del(bson)
+ return bson_to_byte_slice(bson), nil
+}
--- /dev/null
+package goejdb
+
+import (
+ "fmt"
+ "labix.org/v2/mgo/bson"
+ "sync"
+ "testing"
+ "os"
+)
+
+type TestType struct {
+ I int
+ S string
+ M map[string]string
+}
+
+func make_test_type() TestType {
+ return TestType{I: 5, S: "mystring", M: map[string]string{"1": "one", "2": "two"}}
+}
+
+var i int = 0
+var iMu sync.Mutex
+
+func open() *Ejdb {
+ iMu.Lock()
+ i++
+ ejdb, _ := Open(fmt.Sprintf("/tmp/my%d.ejdb", i), JBOWRITER|JBOCREAT|JBOTRUNC)
+ iMu.Unlock()
+ return ejdb
+}
+
+func TestVersion(t *testing.T) {
+ Version()
+}
+
+func TestIsValidOidStr(t *testing.T) {
+ if !IsValidOidStr("4fc62a0f4c114f273c000001") {
+ t.Errorf("4fc62a0f4c114f273c000001 not recognized as a valid oid str though it should be")
+ }
+
+ if IsValidOidStr("invalidoid") {
+ t.Errorf("invalidoid recognized as a valid oid str though it should not be")
+ }
+}
+
+func TestOpen(t *testing.T) {
+ ejdb := open()
+ if ejdb == nil || !ejdb.IsOpen() {
+ t.Errorf("Opening EJDB failed")
+ }
+}
+
+func TestDel(t *testing.T) {
+ ejdb := open()
+ ejdb.Del()
+}
+
+func TestClose(t *testing.T) {
+ ejdb := open()
+ ejdb.Close()
+ if ejdb.IsOpen() {
+ t.Errorf("Closing EJDB failed")
+ }
+}
+
+func TestCreateColl(t *testing.T) {
+ ejdb := open()
+ coll, err := ejdb.CreateColl("MyNewColl", nil)
+ if err != nil {
+ t.Errorf("CreateColl() failed with %v", err)
+ }
+ if coll == nil {
+ t.Errorf("CreateColl() returned nil")
+ }
+}
+
+func TestCreateCollWithOptions(t *testing.T) {
+ ejdb := open()
+ coll, err := ejdb.CreateColl("MyNewColl", &EjCollOpts{large: true, compressed: true, records: 1280000, cachedrecords: 0})
+ if err != nil {
+ t.Errorf("CreateColl() failed with %v", err)
+ }
+ if coll == nil {
+ t.Errorf("CreateColl() returned nil")
+ }
+}
+
+func TestGetColl(t *testing.T) {
+ ejdb := open()
+ ejdb.CreateColl("MyNewColl", nil)
+ coll, err := ejdb.GetColl("MyNewColl")
+ if err != nil {
+ t.Errorf("GetColl() failed with %v", err)
+ }
+ if coll == nil {
+ t.Errorf("GetColl() returned nil")
+ }
+}
+
+func TestGetColls(t *testing.T) {
+ ejdb := open()
+ ejdb.CreateColl("MyNewColl", nil)
+ ejdb.CreateColl("MyNewColl2", nil)
+
+ colls, err := ejdb.GetColls()
+ if err != nil {
+ t.Errorf("GetColls() failed with %v", err)
+ }
+ if len(colls) != 2 {
+ t.Errorf("expected GetColls() to return 2 collections, got %d", len(colls))
+ }
+}
+
+func TestRmColl(t *testing.T) {
+ ejdb := open()
+ ejdb.CreateColl("MyNewColl", nil)
+ ret, err := ejdb.RmColl("MyNewColl", true)
+ if err != nil {
+ t.Errorf("RmColl() failed with %v", err)
+ }
+ if ret == false {
+ t.Errorf("RmColl() returned false though collection did exist and it should have returned true")
+ }
+}
+
+func TestSaveBson(t *testing.T) {
+ bytes, _ := bson.Marshal(make_test_type())
+
+ ejdb := open()
+ coll, _ := ejdb.CreateColl("MyNewColl", nil)
+ oid, _ := coll.SaveBson(bytes)
+ if oid == "" {
+ t.Errorf("SaveBson returned empty string instead of valid BSON OID")
+ }
+}
+
+func TestLoadBson(t *testing.T) {
+ bytes, _ := bson.Marshal(make_test_type())
+
+ ejdb := open()
+ coll, _ := ejdb.CreateColl("MyNewColl", nil)
+ oid, _ := coll.SaveBson(bytes)
+
+ loaded := coll.LoadBson(oid)
+ var out TestType
+ bson.Unmarshal(loaded, &out)
+
+ if out.I != 5 {
+ t.Errorf("Unmarshalling int failed: expected 5 but was %v", out.I);
+ }
+ if out.S != "mystring" {
+ t.Errorf("Unmarshalling string failed: expected \"mystring\" but was %v", out.S);
+ }
+ if out.M["2"] != "two" {
+ t.Errorf("Unmarshalling map[string]string failed: expected entry [\"2\"] to be \"two\" but was %v", out.M["2"]);
+ }
+}
+
+func TestRmBson(t *testing.T) {
+ bytes, _ := bson.Marshal(make_test_type())
+
+ ejdb := open()
+ coll, _ := ejdb.CreateColl("MyNewColl", nil)
+ oid, _ := coll.SaveBson(bytes)
+ coll.RmBson(oid)
+}
+
+func TestSyncColl(t *testing.T) {
+ bytes, _ := bson.Marshal(make_test_type())
+
+ ejdb := open()
+ coll, _ := ejdb.CreateColl("MyNewColl", nil)
+ coll.SaveBson(bytes)
+ ret, err := coll.Sync()
+ if err != nil {
+ t.Errorf("Collection.Sync() failed with %v", err)
+ }
+ if ret == false {
+ t.Errorf("Collection.Sync() should have returned true but returned false")
+ }
+}
+
+func TestSyncEjdb(t *testing.T) {
+ ejdb := open()
+ ejdb.CreateColl("MyNewColl", nil)
+ ret, err := ejdb.Sync()
+ if err != nil {
+ t.Errorf("Ejdb.Sync() failed with %v", err)
+ }
+ if ret == false {
+ t.Errorf("Ejdb.Sync() should have returned true but returned false")
+ }
+}
+
+func TestEjdbMeta(t *testing.T) {
+ ejdb := open()
+ var m map[string]interface{}
+ bs, _ := ejdb.Meta()
+ bson.Unmarshal(bs, &m)
+ if m["file"] == nil {
+ t.Errorf("Metadata seems to be invalid, does not have an entry for [\"file\"]")
+ }
+}
+
+func TestFind(t *testing.T) {
+ bytes, _ := bson.Marshal(make_test_type())
+
+ ejdb := open()
+ coll, _ := ejdb.CreateColl("MyNewColl", nil)
+ coll.SaveBson(bytes)
+
+ res1, err := coll.Find(`{"s" : "mystring"}`)
+ if err != nil {
+ t.Errorf("Find() failed with %v", err)
+ }
+ if len(res1) != 1 {
+ fmt.Println(res1)
+ t.Errorf("Find() did not find the right amount of entries. Expected 1 but got %v", len(res1))
+ }
+}
+
+func TestFindShouldReturnEmptySliceOnNoResults(t *testing.T) {
+ ejdb := open()
+ coll, _ := ejdb.CreateColl("MyNewColl", nil)
+
+ res1, err := coll.Find(`{"s" : "mystring"}`)
+ if err != nil {
+ t.Errorf("Find() failed with %v", err)
+ }
+ if len(res1) != 0 {
+ fmt.Println(res1)
+ t.Errorf("Find() did not find the right amount of entries. Expected 0 but got %v", len(res1))
+ }
+}
+
+func TestCount(t *testing.T) {
+ bytes, _ := bson.Marshal(make_test_type())
+
+ ejdb := open()
+ coll, _ := ejdb.CreateColl("MyNewColl", nil)
+ coll.SaveBson(bytes)
+ coll.SaveBson(bytes)
+
+ ct, err := coll.Count(`{"s" : "mystring"}`)
+ if err != nil {
+ t.Errorf("Count() failed with %v", err)
+ }
+ if ct != 2 {
+ t.Errorf("Count() did not return the right result. Expected 2 but got %v", ct)
+ }
+}
+
+func TestFindOneShouldReturnResult(t *testing.T) {
+ bytes, _ := bson.Marshal(make_test_type())
+
+ ejdb := open()
+ coll, _ := ejdb.CreateColl("MyNewColl", nil)
+ coll.SaveBson(bytes)
+ coll.SaveBson(bytes)
+
+ ret, err := coll.FindOne(`{"s" : "mystring"}`)
+ if err != nil {
+ t.Errorf("FindOne() failed with %v", err)
+ }
+ if ret == nil {
+ t.Fatalf("FindOne() failed. Returned nil though there should be a result.")
+ }
+ var tt TestType
+ bson.Unmarshal(*ret, &tt)
+ if tt.S != "mystring" {
+ t.Errorf("FindOne() failed. Expected result to have entry [\"s\"] set to \"mystring\" but was \"%s\".", tt.S)
+ }
+}
+
+func TestFindOneShouldReturnNilOnNoResult(t *testing.T) {
+ ejdb := open()
+ coll, _ := ejdb.CreateColl("MyNewColl", nil)
+
+ ret, err := coll.FindOne(`{"s" : "mystring"}`)
+ if err != nil {
+ t.Errorf("FindOne() failed with %v", err)
+ }
+ if ret != nil {
+ t.Fatalf("FindOne() failed. Returned non-nil result though it should have found nothing and returned nil.")
+ }
+}
+
+func TestUpdate(t *testing.T) {
+ bytes, _ := bson.Marshal(make_test_type())
+
+ ejdb := open()
+ coll, _ := ejdb.CreateColl("MyNewColl", nil)
+ coll.SaveBson(bytes)
+ coll.SaveBson(bytes)
+
+ ct, err := coll.Update(`{"$inc": {"i": 5}}`)
+ if err != nil {
+ t.Errorf("Update() failed with %v", err)
+ }
+ if ct != 2 {
+ t.Errorf("Update() returned the wrong count. Expected 2 but got %d", ct)
+ }
+
+ // check the values after updating
+ res, _ := coll.Find(`{}`)
+ for _, r := range res {
+ var tt TestType
+ bson.Unmarshal(r, &tt)
+ if tt.I != 10 {
+ t.Errorf("Update() failed. Expected all \"I\" entries to have been updated to 10 but this one was %v", tt.I)
+ }
+ }
+}
+
+func TestSetIndex(t *testing.T) {
+ ejdb := open()
+ coll, _ := ejdb.CreateColl("MyNewColl", nil)
+ for i := 0; i < 10; i++ {
+ coll.SaveJson(fmt.Sprintf(`{"i": %d, "s": "number%d"`, i))
+ }
+
+ err1 := coll.SetIndex("i", JBIDXNUM)
+ if err1 != nil {
+ t.Errorf("SetIndex() with JBIDXNUM failed with %v", err1)
+ }
+ err2 := coll.SetIndex("s", JBIDXSTR)
+ if err2 != nil {
+ t.Errorf("SetIndex() with JBIDXSTR failed with %v", err2)
+ }
+}
+
+func TestTransactions(t *testing.T) {
+ bytes, _ := bson.Marshal(make_test_type())
+
+ ejdb := open()
+ coll, _ := ejdb.CreateColl("MyNewColl", nil)
+
+ coll.BeginTransaction()
+ coll.SaveBson(bytes)
+ coll.SaveBson(bytes)
+ coll.AbortTransaction()
+
+ ct, _ := coll.Count(`{}`)
+ if ct != 0 {
+ t.Errorf("transaction not aborted successfully")
+ }
+
+ if coll.IsTransactionActive() == true {
+ t.Errorf("IsTransactionActive() returned true though it should be false")
+ }
+
+ coll.BeginTransaction()
+ coll.SaveBson(bytes)
+ coll.SaveBson(bytes)
+
+ if coll.IsTransactionActive() == false {
+ t.Errorf("IsTransactionActive() returned false though it should be true")
+ }
+
+ coll.CommitTransaction()
+
+ ct2, _ := coll.Count(`{}`)
+ if ct2 != 2 {
+ t.Errorf("transaction not committed successfully")
+ }
+}
+
+func TestOneSnippetIntroFromReadme(t *testing.T) {
+ jb, err := Open("addressbook", JBOWRITER | JBOCREAT | JBOTRUNC)
+ if err != nil {
+ os.Exit(1)
+ }
+ //Get or create collection 'contacts'
+ coll, _ := jb.CreateColl("contacts", nil)
+
+ //Insert one record:
+ //JSON: {'name' : 'Bruce', 'phone' : '333-222-333', 'age' : 58}
+ rec := map[string]interface{} {"name" : "Bruce", "phone" : "333-222-333", "age" : 58}
+ bsrec, _ := bson.Marshal(rec)
+ coll.SaveBson(bsrec)
+ fmt.Printf("\nSaved Bruce")
+
+ //Now execute query
+ res, _ := coll.Find(`{"name" : {"$begin" : "Bru"}}`) //Name starts with 'Bru' string
+ fmt.Printf("\n\nRecords found: %d\n", len(res))
+
+ //Now print the result set records
+ for _, bs := range res {
+ var m map[string]interface{}
+ bson.Unmarshal(bs, &m)
+ fmt.Println(m)
+ }
+
+ //Close database
+ jb.Close()
+}
+
+// long-running benchmarks to identify possible memory leaks
+
+func BenchmarkSavingAndCounting(b *testing.B) {
+ ejdb, _ := Open("/tmp/mybench.ejdb", JBOWRITER|JBOCREAT|JBOTRUNC|JBOTSYNC)
+ coll, _ := ejdb.CreateColl("BenchmarkCollection", nil)
+ bytes, _ := bson.Marshal(make_test_type())
+
+ for i := 0; i < 100000; i++ {
+ coll.SaveBson(bytes)
+ }
+
+ coll.Count(`{"s" : "mystring"}`)
+
+ ejdb.Close()
+}
+
+func BenchmarkCounting(b *testing.B) {
+ ejdb, _ := Open("/tmp/mybench2.ejdb", JBOWRITER|JBOCREAT|JBOTRUNC|JBOTSYNC)
+ coll, _ := ejdb.CreateColl("BenchmarkCollection", nil)
+ bytes, _ := bson.Marshal(make_test_type())
+ coll.SaveBson(bytes)
+ coll.Count(`{"s" : "mystring"}`)
+ ejdb.Close()
+}
--- /dev/null
+package goejdb
+
+// #cgo LDFLAGS: -ltcejdb
+// #include "../../../../../../tcejdb/ejdb.h"
+import "C"
+
+import "unsafe"
+
+const (
+ JBQRYCOUNT = C.JBQRYCOUNT
+ JBQRYFINDONE = C.JBQRYFINDONE
+)
+
+type EjQuery struct {
+ ptr *[0]byte
+ ejdb *Ejdb
+}
+
+// EJDB_EXPORT EJQ* ejdbcreatequery(EJDB *jb, bson *qobj, bson *orqobjs, int orqobjsnum, bson *hints);
+func (ejdb *Ejdb) CreateQuery(query string, queries ...string) (*EjQuery, *EjdbError) {
+ query_bson := bson_from_json(query)
+ defer C.bson_destroy(query_bson)
+
+ orqueries_count := len(queries)
+ orqueries := C.malloc((C.size_t)(unsafe.Sizeof(new(C.bson))) * C.size_t(orqueries_count))
+ defer C.free(orqueries)
+ ptr_orqueries := (*[maxslice]*C.bson)(orqueries)
+ for i, q := range queries {
+ (*ptr_orqueries)[i] = bson_from_json(q)
+ defer C.bson_destroy((*ptr_orqueries)[i])
+ }
+
+ q := C.ejdbcreatequery(ejdb.ptr, query_bson, (*C.bson)(orqueries), C.int(len(queries)), nil)
+ if q == nil {
+ return nil, ejdb.check_error()
+ } else {
+ return &EjQuery{ptr: q, ejdb: ejdb}, nil
+ }
+}
+
+// EJDB_EXPORT EJQ* ejdbqueryhints(EJDB *jb, EJQ *q, const void *hintsbsdata);
+func (q *EjQuery) SetHints(hints string) *EjdbError {
+ bsdata := bson_from_json(hints).data
+ ret := C.ejdbqueryhints(q.ejdb.ptr, q.ptr, unsafe.Pointer(bsdata))
+ if ret == nil {
+ return q.ejdb.check_error()
+ } else {
+ return nil
+ }
+}
+
+func (q *EjQuery) Execute(coll *EjColl) ([][]byte, *EjdbError) {
+ // execute query
+ var count C.uint32_t
+ res := C.ejdbqryexecute(coll.ptr, q.ptr, &count, 0, nil)
+ defer C.ejdbqresultdispose(res)
+ err := coll.ejdb.check_error()
+
+ // return results
+ ret := make([][]byte, 0)
+ for i := 0; i < int(count); i++ {
+ var size C.int
+ bson_blob := C.ejdbqresultbsondata(res, C.int(i), &size)
+ ret = append(ret, ((*[maxslice]byte)(bson_blob))[:int(size)])
+ }
+
+ return ret, err
+}
+
+func (q *EjQuery) ExecuteOne(coll *EjColl) (*[]byte, *EjdbError) {
+ // execute query
+ var count C.uint32_t
+ res := C.ejdbqryexecute(coll.ptr, q.ptr, &count, JBQRYFINDONE, nil)
+ defer C.ejdbqresultdispose(res)
+ err := coll.ejdb.check_error()
+
+ // return results
+ if count == 0 {
+ return nil, err
+ } else {
+ var size C.int
+ bson_blob := C.ejdbqresultbsondata(res, 0, &size)
+ ret := ((*[maxslice]byte)(bson_blob))[:int(size)]
+ return &ret, err
+ }
+}
+
+func (q *EjQuery) Count(coll *EjColl) (int, *EjdbError) {
+ var count C.uint32_t
+ C.ejdbqryexecute(coll.ptr, q.ptr, &count, JBQRYCOUNT, nil)
+ err := coll.ejdb.check_error()
+ return int(count), err
+}
+
+// EJDB_EXPORT void ejdbquerydel(EJQ *q);
+func (q *EjQuery) Del() {
+ C.ejdbquerydel(q.ptr)
+}
--- /dev/null
+package goejdb
+
+// #cgo LDFLAGS: -ltcejdb
+// #include "../../../../../../tcejdb/ejdb.h"
+import "C"
+
+import (
+ "encoding/json"
+ "labix.org/v2/mgo/bson"
+ "unsafe"
+)
+
+func bson_oid_from_string(oid *string) *C.bson_oid_t {
+ if oid == nil {
+ return nil
+ }
+
+ c_oid := C.CString(*oid)
+ defer C.free(unsafe.Pointer(c_oid))
+
+ ret := new(C.bson_oid_t)
+ C.bson_oid_from_string(ret, c_oid)
+ return ret
+}
+
+func bson_oid_to_string(oid *C.bson_oid_t) string {
+ var c_str [25]C.char
+ char_ptr := (*C.char)(unsafe.Pointer(&c_str))
+ C.bson_oid_to_string(oid, char_ptr)
+ return C.GoString(char_ptr)
+}
+
+func bson_to_byte_slice(bson *C.bson) []byte {
+ size := int(C.bson_size(bson))
+ data := C.bson_data(bson)
+ ptr_data := (*[maxslice]byte)(unsafe.Pointer(data))
+ return (*ptr_data)[:size]
+}
+
+func bson_from_byte_slice(bsdata []byte) *C.bson {
+ c_bson := new(C.bson)
+ //C.bson_init(c_bson)
+
+ buff := C.malloc(C.size_t(len(bsdata)))
+ ptr_buff := (*[maxslice]byte)(unsafe.Pointer(buff))
+ for i := 0; i < len(bsdata); i++ {
+ (*ptr_buff)[i] = bsdata[i]
+ }
+
+ C.bson_init_finished_data(c_bson, (*C.char)(buff))
+ return c_bson
+}
+
+func bson_from_json(j string) *C.bson {
+ var m map[string]interface{}
+ json.Unmarshal(([]byte)(j), &m)
+ bytes, _ := bson.Marshal(&m)
+ return bson_from_byte_slice(bytes)
+}
+
+func bson_destroy(bson *C.bson) {
+ C.bson_destroy(bson)
+}
+
+func bson_del(bson *C.bson) {
+ C.bson_del(bson)
+}