+++ /dev/null
-EJDB Go binding
-==================================
-
-One snippet intro
------------------------------------
-
-~~~~~~
-package ejdbtutorial
-
-import (
- "fmt"
- "github.com/mkilling/ejdb/goejdb"
- "labix.org/v2/mgo/bson"
- "os"
-)
-
-func main() {
- // Create a new database file and open it
- 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
+EJDB Go binding
+==================================
+
+The EJDB Go bindings can be found at [https://github.com/mkilling/goejdb](https://github.com/mkilling/goejdb)
+
+Installation
+-------------------------------
+
+### Prerequisites
+
+* Google Go
+* installed tcejdb (see ../tcejdb/ in this repository)
+
+### Install
+
+ go get github.com/mkilling/goejdb
\ No newline at end of file
+++ /dev/null
-package goejdb
-
-// #cgo LDFLAGS: -ltcejdb
-// #include "../../../../../../tcejdb/ejdb.h"
-import "C"
-
-import "unsafe"
-
-// Index modes, index types.
-const (
- // Drop index.
- JBIDXDROP = C.JBIDXDROP
- // Drop index for all types.
- JBIDXDROPALL = C.JBIDXDROPALL
- // Optimize indexes.
- JBIDXOP = C.JBIDXOP
- // Rebuild index.
- JBIDXREBLD = C.JBIDXREBLD
- // Number index.
- JBIDXNUM = C.JBIDXNUM
- // String index.*/
- JBIDXSTR = C.JBIDXSTR
- // Array token index.
- JBIDXARR = C.JBIDXARR
- // Case insensitive string index
- JBIDXISTR = C.JBIDXISTR
-)
-
-// An EJDB collection
-type EjColl struct {
- ptr *[0]byte
- ejdb *Ejdb
-}
-
-// EJDB collection tuning options
-type EjCollOpts struct {
- // Large collection. It can be larger than 2GB. Default false
- large bool
- // Collection records will be compressed with DEFLATE compression. Default: false
- compressed bool
- // Expected records number in the collection. Default: 128K
- records int
- // Maximum number of cached records. Default: 0
- 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)
-}
-
-// Execute a query specified by JSON strings query, queries and return the results as a slice of BSON objects
-// See the documentation of EjQuery for a description of the query format.
-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)
- }
-}
-
-// Execute a query specified by JSON strings query, queries and return only the first result as a BSON object
-// See the documentation of EjQuery for a description of the query format.
-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)
- }
-}
-
-// Execute a query specified by JSON strings query, queries and return the number of results, not the results themselves.
-// See the documentation of EjQuery for a description of the query format.
-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()
-}
-
-// Set index for JSON field in EJDB collection.
-//
-// - Available index types:
-// - `JBIDXSTR` String index for JSON string values.
-// - `JBIDXISTR` Case insensitive string index for JSON string values.
-// - `JBIDXNUM` Index for JSON number values.
-// - `JBIDXARR` Token index for JSON arrays and string values.
-//
-// - One JSON field can have several indexes for different types.
-//
-// - Available index operations:
-// - `JBIDXDROP` Drop index of specified type.
-// - Eg: flag = JBIDXDROP | JBIDXNUM (Drop number index)
-// - `JBIDXDROPALL` Drop index for all types.
-// - `JBIDXREBLD` Rebuild index of specified type.
-// - `JBIDXOP` Optimize index of specified type. (Optimize the B+ tree index file)
-//
-// Examples:
-// - Set index for JSON path `addressbook.number` for strings and numbers:
-// `ccoll.SetIndex("album.number", JBIDXSTR | JBIDXNUM)`
-// - Set index for array:
-// `ccoll.SetIndex("album.tags", JBIDXARR)`
-// - Rebuild previous index:
-// `ccoll.SetIndex("album.tags", JBIDXARR | JBIDXREBLD)`
-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()
- }
-}
-
-// Begin transaction for EJDB collection.
-func (coll *EjColl) BeginTransaction() *EjdbError {
- res := C.ejdbtranbegin(coll.ptr)
- if res {
- return nil
- } else {
- return coll.ejdb.check_error()
- }
-}
-
-// Commit transaction for EJDB collection.
-func (coll *EjColl) CommitTransaction() *EjdbError {
- res := C.ejdbtrancommit(coll.ptr)
- if res {
- return nil
- } else {
- return coll.ejdb.check_error()
- }
-}
-
-// Abort transaction for EJDB collection.
-func (coll *EjColl) AbortTransaction() *EjdbError {
- res := C.ejdbtranabort(coll.ptr)
- if res {
- return nil
- } else {
- return coll.ejdb.check_error()
- }
-}
-
-// Get current transaction status. Return true if a transaction is active, false otherwise.
-func (coll *EjColl) IsTransactionActive() bool {
- var ret C.bool
- C.ejdbtranstatus(coll.ptr, &ret)
- return bool(ret)
-}
-
-// Synchronize content of a EJDB collection database with the file on device. On success return true.
-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"
-)
-
-// Database open modes
-const (
- // Open as a reader.
- JBOREADER = C.JBOREADER
- // Open as a writer.
- JBOWRITER = C.JBOWRITER
- // Create if db file not exists.
- JBOCREAT = C.JBOCREAT
- // Truncate db on open.
- JBOTRUNC = C.JBOTRUNC
- // Open without locking.
- JBONOLCK = C.JBONOLCK
- // Lock without blocking.
- JBOLCKNB = C.JBOLCKNB
- // Synchronize every transaction.
- JBOTSYNC = C.JBOTSYNC
-)
-
-// Error codes
-const (
- // Invalid collection name.
- JBEINVALIDCOLNAME = C.JBEINVALIDCOLNAME
- // Invalid bson object.
- JBEINVALIDBSON = C.JBEINVALIDBSON
- // Invalid bson object id.
- JBEINVALIDBSONPK = C.JBEINVALIDBSONPK
- // Invalid query control field starting with '$'.
- JBEQINVALIDQCONTROL = C.JBEQINVALIDQCONTROL
- // $strand, $stror, $in, $nin, $bt keys requires not empty array value.
- JBEQINOPNOTARRAY = C.JBEQINOPNOTARRAY
- // Inconsistent database metadata.
- JBEMETANVALID = C.JBEMETANVALID
- // Invalid field path value.
- JBEFPATHINVALID = C.JBEFPATHINVALID
- // Invalid query regexp value.
- JBEQINVALIDQRX = C.JBEQINVALIDQRX
- // Result set sorting error.
- JBEQRSSORTING = C.JBEQRSSORTING
- // Query generic error.
- JBEQERROR = C.JBEQERROR
- // Updating failed.
- JBEQUPDFAILED = C.JBEQUPDFAILED
- // Only one $elemMatch allowed in the fieldpath.
- JBEQONEEMATCH = C.JBEQONEEMATCH
- // $fields hint cannot mix include and exclude fields
- JBEQINCEXCL = C.JBEQINCEXCL
- // action key in $do block can only be one of: $join
- JBEQACTKEY = C.JBEQACTKEY
- // Exceeded the maximum number of collections per database
- JBEMAXNUMCOLS = C.JBEMAXNUMCOLS
-)
-
-const maxslice = 1<<31 - 1
-
-// An EJDB database
-type Ejdb struct {
- ptr *[0]byte
-}
-
-type EjdbError struct {
- // Error code returned by EJDB
- ErrorCode int
- error
-}
-
-func new_ejdb() *Ejdb {
- ejdb := new(Ejdb)
- ejdb.ptr = C.ejdbnew()
- if ejdb.ptr == nil {
- return nil
- } else {
- return ejdb
- }
-}
-
-// Returns EJDB library version string. Eg: "1.1.13"
-func Version() string {
- cs := C.ejdbversion()
- return C.GoString(cs)
-}
-
-// Return true if passed `oid` string cat be converted to valid 12 bit BSON object identifier (OID).
-func IsValidOidStr(oid string) bool {
- c_oid := C.CString(oid)
- res := C.ejdbisvalidoidstr(c_oid)
- C.free(unsafe.Pointer(c_oid))
-
- return bool(res)
-}
-
-// Returns a new open EJDB database.
-// path is the path to the database file.
-// options specify the open mode bitmask flags.
-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))}
-}
-
-// Return true if database is in open state, false otherwise
-func (ejdb *Ejdb) IsOpen() bool {
- ret := C.ejdbisopen(ejdb.ptr)
- return bool(ret)
-}
-
-// Delete database object. If the database is not closed, it is closed implicitly.
-// Note that the deleted object and its derivatives can not be used anymore
-func (ejdb *Ejdb) Del() {
- C.ejdbdel(ejdb.ptr)
-}
-
-// Close a table database object. If a writer opens a database but does not close it appropriately, the database will be broken.
-// If successful return true, otherwise return false.
-func (ejdb *Ejdb) Close() *EjdbError {
- C.ejdbclose(ejdb.ptr)
- return ejdb.check_error()
-}
-
-// Retrieve collection handle for collection specified `collname`.
-// If collection with specified name does't exists it will return nil.
-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()
-}
-
-// Return a slice containing shallow copies of all collection handles (EjColl) currently open.
-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
-}
-
-// Same as GetColl() but automatically creates new collection if it doesn't exists.
-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()
- }
-}
-
-// Removes collections specified by `colname`.
-// If `unlinkfile` is true the collection db file and all of its index files will be removed.
-// If removal was successful return true, otherwise return false.
-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()
- }
-}
-
-// Synchronize entire EJDB database and all of its collections with storage.
-func (ejdb *Ejdb) Sync() (bool, *EjdbError) {
- ret := C.ejdbsyncdb(ejdb.ptr)
- if ret {
- return bool(ret), nil
- } else {
- return bool(ret), ejdb.check_error()
- }
-}
-
-// Gets description of EJDB database and its collections as a BSON object.
-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) {
- // Create a new database file and open it
- jb, err := Open("/tmp/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"
-
-// Query search mode flags in ejdbqryexecute()
-const (
- // Query only count(*)
- jbqrycount = C.JBQRYCOUNT
- // Fetch first record only
- jbqryfindone = C.JBQRYFINDONE
-)
-
-// An EJDB query
-type EjQuery struct {
- ptr *[0]byte
- ejdb *Ejdb
-}
-
-// Create query object.
-// Sucessfully created queries must be destroyed with Query.Del().
-//
-// 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], ...}}
-//
-// - Collection joins supported in the following form:
-//
-// {..., $do : {fpath : {$join : 'collectionname'}} }
-// Where 'fpath' value points to object's OIDs from 'collectionname'. Its value
-// can be OID, string representation of OID or array of this pointers.
-//
-// 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.
-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
- }
-}
-
-// Set query hints. `hints` is a JSON string
-// - $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
-// }
-// }
-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
- }
-}
-
-// Execute the query and return all results as a slice of BSON objects
-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
-}
-
-// Execute the query and return only the first result as a BSON object
-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
- }
-}
-
-// Execute the query and only return the number of results it returned, not the results themselves
-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
-}
-
-// Delete the query. This must be called in order to not leak memory.
-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)
-}