Tree [fd5313] master /
History



File Date Author Commit
.idea 2013-08-19 adam adam [ce71f2] Merge branch 'ticket91'
goejdb 2013-07-17 adam adam [8e879d] Merge remote-tracking branch 'origin/master' in...
jejdb 2013-07-24 Vyacheslav Tyutyunkov Vyacheslav Tyutyunkov [e2a5ff] #82
luaejdb 2013-07-18 adam adam [37dd4d] minors
misc 2012-11-17 adam adam [64e006] Merge branch 'master' into ticket9
nejdb 2013-08-28 adam adam [691197] #94
node 2013-08-19 adam adam [ce71f2] Merge branch 'ticket91'
pyejdb 2013-08-19 adam adam [853c69] Merge branch 'ticket91'
rbejdb 2013-09-03 adam adam [b27f79] Merge branch 'ticket96'
tcejdb 2013-09-03 adam adam [567615] v1.1.23
tools 2013-09-03 adam adam [fd5313] v1.1.23
var 2012-11-05 adam adam [de3067] Merge branch 'master' into sf
.gitignore 2013-08-19 adam adam [ce71f2] Merge branch 'ticket91'
.gitmodules 2012-11-05 adam adam [de3067] Merge branch 'master' into sf
.npmignore 2013-08-19 adam adam [ce71f2] Merge branch 'ticket91'
Changelog 2013-09-03 adam adam [567615] v1.1.23
LICENSE 2013-01-02 adam adam [424556] * Added `$upsert` opration (ticket #25)
Makefile 2013-08-19 adam adam [853c69] Merge branch 'ticket91'
README.md 2013-08-19 adam adam [853c69] Merge branch 'ticket91'
binding.gyp 2013-05-06 adam adam [b065a7] Merge remote-tracking branch 'origin/rubejdb'
ejdb.workspace 2013-08-19 adam adam [ce71f2] Merge branch 'ticket91'
package.json 2013-09-03 adam adam [fd5313] v1.1.23
tests.mk 2012-11-05 adam adam [de3067] Merge branch 'master' into sf

Read Me

EJDB

Embedded JSON Database engine

It aims to be a fast MongoDB-like library which can be embedded into C/C++, .Net, NodeJS, Python, Lua, Go, Java and Ruby applications under terms of LGPL license.

EJDB is the C library based on modified version of Tokyo Cabinet.

JSON representation of queries and data implemented with API based on C BSON

News

Features

  • LGPL license allows you to embed this library into proprietary software.
  • EJDB and TokyoCabinet API ported to Windows
  • MongoDB-like queries and overall philosophy.
  • Collection joins
  • Collection level write locking.
  • Collection level transactions.
  • Node.js/Python/Lua/Java/Ruby/.Net/Go bindings
  • [Adobe Air Native Extension (ANE) for EJDB] (https://github.com/thejustinwalsh/airejdb)
  • [Pike language binding] (https://github.com/hww3/pike_modules-ejdb)

Documentation

Community

EJDB NodeJS

One snippet intro

```JavaScript
var EJDB = require("ejdb");
//Open zoo DB
var jb = EJDB.open("zoo", EJDB.DEFAULT_OPEN_MODE | EJDB.JBOTRUNC);

var parrot1 = {
"name" : "Grenny",
"type" : "African Grey",
"male" : true,
"age" : 1,
"birthdate" : new Date(),
"likes" : ["green color", "night", "toys"],
"extra1" : null
};
var parrot2 = {
"name" : "Bounty",
"type" : "Cockatoo",
"male" : false,
"age" : 15,
"birthdate" : new Date(),
"likes" : ["sugar cane"]
};

jb.save("parrots", [parrot1, parrot2], function(err, oids) {
if (err) {
console.error(err);
return;
}
console.log("Grenny OID: " + parrot1["_id"]);
console.log("Bounty OID: " + parrot2["_id"]);

jb.find("parrots",
        {"likes" : "toys"},
        {"$orderby" : {"name" : 1}},
        function(err, cursor, count) {
            if (err) {
                console.error(err);
                return;
            }
            console.log("Found " + count + " parrots");
            while (cursor.next()) {
                console.log(cursor.field("name") + " likes toys!");
            }
            cursor.close(); //It's not mandatory to close cursor explicitly
            jb.close(); //Close the database
        });

});
```
EJDB NodeJS binding page

EJDB Python

One snippet intro

```python
import pyejdb
from datetime import datetime

Open database

ejdb = pyejdb.EJDB("zoo", pyejdb.DEFAULT_OPEN_MODE | pyejdb.JBOTRUNC)

parrot1 = {
"name": "Grenny",
"type": "African Grey",
"male": True,
"age": 1,
"birthdate": datetime.utcnow(),
"likes": ["green color", "night", "toys"],
"extra1": None
}
parrot2 = {
"name": "Bounty",
"type": "Cockatoo",
"male": False,
"age": 15,
"birthdate": datetime.utcnow(),
"likes": ["sugar cane"],
"extra1": None
}
ejdb.save("parrots2", parrot1, parrot2)

with ejdb.find("parrots2", {"likes" : "toys"},
hints={"$orderby" : [("name", 1)]}) as cur:
print("found %s parrots" % len(cur))
for p in cur:
print("%s likes toys!" % p["name"])

ejdb.close()
```
EJDB Python 2.7/3.x binding page

EJDB Lua

One snippet intro

```lua
local ejdb = require("ejdb")
local inspect = require("ejdb.inspect")
local Q = ejdb.Q

-- Used modes:
-- 'r' - read
-- 'w' - write
-- 'c' - create db if not exists
-- 't' - truncate existing db
local db = ejdb.open("zoo", "rwct")

-- Unordered lua table
local parrot1 = {
name = "Grenny",
type = "African Grey",
male = true,
age = 1,
birthhdate = ejdb.toDateNow(),
likes = { "green color", "night", "toys" },
extra1 = ejdb.toNull()
}

-- Preserve order of BSON keys
local parrot2 = Q();
parrot2:KV("name", "Bounty"):KV("type", "Cockatoo"):KV("male", false)
parrot2:KV("age", 15):KV("birthdate",
ejdb.toDate({ year = 2013, month = 1, day = 1, hour = 0, sec = 1 }))
parrot2:KV("likes", { "sugar cane" }):KV("extra1", ejdb.toNull())

--IF YOU WANT SOME DATA INSPECTIONS:
--print(ejdb.print_bson(parrot2:toBSON()))
--local obj = ejdb.from_bson(parrot2:toBSON())
--print(inspect(obj));

db:save("parrots2", parrot1)
db:save("parrots2", parrot2)

-- Below two equivalent queries:
-- Q1
local res, count, log =
db:find("parrots2", Q("likes", "toys"):OrderBy("name asc", "age desc"))
for i = 1, #res do -- iterate one
local ob = res:object(i)
print("" .. ob["name"] .. " likes toys #1")
end

-- Q2
local res, count, log =
db:find("parrots2", Q():F("likes"):Eq("toys"):OrderBy({ name = 1 }, { age = -1 }))
for i = 1, #res do -- iterate one
print("" .. res:field(i, "name") .. " likes toys #2")
end

-- Second way to iterate
for vobj, idx in res() do
print("" .. vobj["name"] .. " likes toys #3")
end

db:close()
```
EJDB Lua binding page

EJDB Go

One snippet intro

```go
package ejdbtutorial

import (
"fmt"
"github.com/mkilling/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()

}
```
EJDB Go binding page

EJDB Ruby

One snippet intro

```Ruby
require "rbejdb"

Open zoo DB

jb = EJDB.open("zoo", EJDB::DEFAULT_OPEN_MODE | EJDB::JBOTRUNC)

parrot1 = {
"name" => "Grenny",
"type" => "African Grey",
"male" => true,
"age" => 1,
"birthdate" => Time.now,
"likes" => ["green color", "night", "toys"],
"extra1" => nil
}
parrot2 = {
"name" => "Bounty",
"type" => "Cockatoo",
"male" => false,
"age" => 15,
"birthdate" => Time.now,
"likes" => ["sugar cane"],
"extra1" => nil
}

jb.save("parrots", parrot1, parrot2)
puts "Grenny OID: #{parrot1["_id"]}"
puts "Bounty OID: #{parrot2["_id"]}"

results = jb.find("parrots", {"likes" => "toys"}, {"$orderby" => {"name" => 1}})

puts "Found #{results.count} parrots"

results.each { |res|
puts "#{res['name']} likes toys!"
}

results.close #It's not mandatory to close cursor explicitly
jb.close #Close the database

```
EJDB Ruby binding page

EJDB Adobe AIR

One snippet intro

```as3
// Open the zoo DB
var db:EJDBDatabase = EJDB.open("zoo", EJDB.DEFAULT_OPEN_MODE | EJDB.JBOTRUNC) as EJDBDatabase;

var parrot1:Object = {
"name" : "Grenny",
"type" : "African Grey",
"male" : true,
"age" : 1,
"birthdate" : new Date(),
"likes" : ["green color", "night", "toys"],
"extra1" : null
};
var parrot2:Object = {
"name" : "Bounty",
"type" : "Cockatoo",
"male" : false,
"age" : 15,
"birthdate" : new Date(),
"likes" : ["sugar cane"]
};

var oids:Array = db.save("parrots", [parrot1, parrot2]);
trace("Grenny OID: " + parrot1._id);
trace("Bounty OID: " + parrot2._id);

var cursor:EJDBCursor = db.find("parrots",
{"likes" : "toys"},
[],
{"$orderby" : {"name" : 1}}
);

trace("Found " + cursor.length + " parrots");
while (cursor.next()) {
trace(cursor.field("name") + " likes toys!");
}

cursor.close(); // It IS mandatory to close cursor explicitly to free up resources
db.close(); // Close the database
```
[Adobe Air Native Extension (ANE) for EJDB] (https://github.com/thejustinwalsh/airejdb)

EJDB C Library

One snippet intro

```C

include <tcejdb ejdb.h="">

static EJDB *jb;

int main() {
jb = ejdbnew();
if (!ejdbopen(jb, "addressbook", JBOWRITER | JBOCREAT | JBOTRUNC)) {
return 1;
}
//Get or create collection 'contacts'
EJCOLL *coll = ejdbcreatecoll(jb, "contacts", NULL);

bson bsrec;
bson_oid_t oid;

//Insert one record:
//JSON: {'name' : 'Bruce', 'phone' : '333-222-333', 'age' : 58}
bson_init(&bsrec);
bson_append_string(&bsrec, "name", "Bruce");
bson_append_string(&bsrec, "phone", "333-222-333");
bson_append_int(&bsrec, "age", 58);
bson_finish(&bsrec);
//Save BSON
ejdbsavebson(coll, &bsrec, &oid);
fprintf(stderr, "\nSaved Bruce");
bson_destroy(&bsrec);

//Now execute query
//QUERY: {'name' : {'$begin' : 'Bru'}} //Name starts with 'Bru' string
bson bq1;
bson_init_as_query(&bq1);
bson_append_start_object(&bq1, "name");
bson_append_string(&bq1, "$begin", "Bru");
bson_append_finish_object(&bq1);
bson_finish(&bq1);

EJQ *q1 = ejdbcreatequery(jb, &bq1, NULL, 0, NULL);

uint32_t count;
TCLIST *res = ejdbqryexecute(coll, q1, &count, 0, NULL);
fprintf(stderr, "\n\nRecords found: %d\n", count);

//Now print the result set records
for (int i = 0; i < TCLISTNUM(res); ++i) {
    void *bsdata = TCLISTVALPTR(res, i);
    bson_print_raw(bsdata, 0);
}
fprintf(stderr, "\n");

//Dispose result set
tclistdel(res);

//Dispose query
ejdbquerydel(q1);
bson_destroy(&bq1);

//Close database
ejdbclose(jb);
ejdbdel(jb);
return 0;

}
```

You can save this code in csnippet.c And build:

sh gcc -std=c99 -Wall -pedantic -c -o csnippet.o csnippet.c gcc -o csnippet csnippet.o -ltcejdb

Building & Installation

Manual installation

Prerequisites

System libraries:

  • gcc
  • zlib-dev

Build and install

sh cd ./tcejdb ./configure --prefix=<installation prefix> && make && make check make install
library name: tcejdb (with pkgconfig)
main include header: <tcejdb/ejdb.h>

C API

EJDB API presented in ejdb.h C header file.

JSON processing API: bson.h

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.
 *      - $and, $or joining:
 *          -   {..., $and : [subq1, subq2, ...] }
 *          -   {..., $or  : [subq1, subq2, ...] }
 *          Example: {z : 33, $and : [ {$or : [{a : 1}, {b : 2}]}, {$or : [{c : 5}, {d : 7}]} ] }
 *
 *      - Mongodb $(projection) operator supported. (http://docs.mongodb.org/manual/reference/projection/positional/#proj._S_)
 *      - Mongodb positional $ update operator supported. (http://docs.mongodb.org/manual/reference/operator/positional/)
 *
 *
 *  - 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);

EJDB C Samples

You can find some code samples in:

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)

TODO

  • Collect collection index statistic