* ```.dec``` Number index
* ```.tok``` Array index
-Limitations/TODOs
+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
* Windows port
-EJDB - Embedded JSON Database engine
+[![EJDB](https://raw.github.com/Softmotions/ejdb/master/misc/ejdblogo3.png)](http://ejdb.org)
+
+
+Embedded JSON Database engine
====================================
It aims to be a fast [MongoDB](http://mongodb.org)-like library **which can be embedded into C/C++ and [NodeJS](http://nodejs.org/) applications under terms of LGPL license.**
*
* - Supported queries:
* - Simple matching of String OR Number OR Array value:
- * - {'json.field.path' : 'val', ...}
+ * - {'fpath' : 'val', ...}
* - $not Negate operation.
- * - {'json.field.path' : {'$not' : val}} //Field not equal to val
- * - {'json.field.path' : {'$not' : {'$begin' : prefix}}} //Field not begins with val
+ * - {'fpath' : {'$not' : val}} //Field not equal to val
+ * - {'fpath' : {'$not' : {'$begin' : prefix}}} //Field not begins with val
* - $begin String starts with prefix
- * - {'json.field.path' : {'$begin' : prefix}}
+ * - {'fpath' : {'$begin' : prefix}}
* - $gt, $gte (>, >=) and $lt, $lte for number types:
- * - {'json.field.path' : {'$gt' : number}, ...}
+ * - {'fpath' : {'$gt' : number}, ...}
* - $bt Between for number types:
- * - {'json.field.path' : {'$bt' : [num1, num2]}}
+ * - {'fpath' : {'$bt' : [num1, num2]}}
* - $in String OR Number OR Array val matches to value in specified array:
- * - {'json.field.path' : {'$in' : [val1, val2, val3]}}
+ * - {'fpath' : {'$in' : [val1, val2, val3]}}
* - $nin - Not IN
* - $strand String tokens OR String array val matches all tokens in specified array:
- * - {'json.field.path' : {'$strand' : [val1, val2, val3]}}
+ * - {'fpath' : {'$strand' : [val1, val2, val3]}}
* - $stror String tokens OR String array val matches any token in specified array:
- * - {'json.field.path' : {'$stror' : [val1, val2, val3]}}
+ * - {'fpath' : {'$stror' : [val1, val2, val3]}}
* - $exists Field existence matching:
- * - {'json.field.path' : {'$exists' : true|false}}
+ * - {'fpath' : {'$exists' : true|false}}
* - $icase Case insensitive string matching:
- * - {'json.field.path' : {'$icase' : 'val1'}} //icase 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`
* - 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' : {'json.field.path' : val1, 'json.field.pathN' : valN, ...}}
- * $pull - Atomically removes all occurrences of value from field, if field is an array.
- * - {.., '$pull' : {'json.field.path' : val1, 'json.field.pathN' : valN, ...}}
+ * 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.
* - $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
* [tcejdb/testejdb](https://github.com/Softmotions/ejdb/tree/master/tcejdb/testejdb)
Basic EJDB architecture
--------------------------------
+------------------------------------
**EJDB database files structure**
~~~~~~
* ```.dec``` Number index
* ```.tok``` Array index
-Limitations/TODOs
+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
* Windows port
+Related software
+------------------------------------
+[Connect session store backed by EJDB database](https://github.com/Softmotions/connect-session-ejdb)
const char *ipath, int ipathsz, void *op, int *vsz);
static char* _bsonfpathrowldr(TCLIST *tokens, const char *rowdata, int rowdatasz,
const char *fpath, int fpathsz, void *op, int *vsz);
-static void _ejdbclear(EJDB *jb);
static bool _createcoldb(const char *colname, EJDB *jb, EJCOLLOPTS *opts, TCTDB** res);
static bool _addcoldb0(const char *colname, EJDB *jb, EJCOLLOPTS *opts, EJCOLL **res);
static void _delcoldb(EJCOLL *cdb);
case JBEQONEEMATCH: return "only one $elemMatch allowed in the fieldpath"; //todo remove
case JBEQINCEXCL: return "$fields hint cannot mix include and exclude fields";
case JBEQACTKEY: return "action key in $do block can only be one of: $join";
+ case JBEMAXNUMCOLS: return "exceeded the maximum number of collections per database: 1024";
default: return tcerrmsg(ecode);
}
EJDB_EXPORT EJDB* ejdbnew(void) {
EJDB *jb;
- TCMALLOC(jb, sizeof (*jb));
- _ejdbclear(jb);
+ TCCALLOC(jb, 1, sizeof (*jb));
jb->metadb = tctdbnew();
tctdbsetmutex(jb->metadb);
tctdbsetcache(jb->metadb, 1024, 0, 0);
EJDB_EXPORT void ejdbdel(EJDB *jb) {
assert(jb && jb->metadb);
if (JBISOPEN(jb)) ejdbclose(jb);
- for (int i = 0; i < jb->cdbsnum; ++i) {
- _delcoldb(jb->cdbs + i);
+ for (int i = 0; i < EJDB_MAX_COLLECTIONS; ++i) {
+ if (jb->cdbs[i]) {
+ _delcoldb(jb->cdbs[i]);
+ TCFREE(jb->cdbs[i]);
+ jb->cdbs[i] = NULL;
+ }
}
- TCFREE(jb->cdbs);
jb->cdbsnum = 0;
if (jb->mmtx) {
pthread_rwlock_destroy(jb->mmtx);
EJDB_EXPORT bool ejdbclose(EJDB *jb) {
JBENSUREOPENLOCK(jb, true, false);
bool rv = true;
- for (int i = 0; i < jb->cdbsnum; ++i) {
- JBCLOCKMETHOD(jb->cdbs + i, true);
- if (!tctdbclose(jb->cdbs[i].tdb)) {
- rv = false;
+ for (int i = 0; i < EJDB_MAX_COLLECTIONS; ++i) {
+ if (jb->cdbs[i]) {
+ JBCLOCKMETHOD(jb->cdbs[i], true);
+ if (!tctdbclose(jb->cdbs[i]->tdb)) {
+ rv = false;
+ }
+ JBCUNLOCKMETHOD(jb->cdbs[i]);
}
- JBCUNLOCKMETHOD(jb->cdbs + i);
}
if (!tctdbclose(jb->metadb)) {
rv = false;
if (!rv) {
goto finish;
}
- TCMALLOC(jb->cdbs, 1);
jb->cdbsnum = 0;
TCTDB *mdb = jb->metadb;
rv = tctdbiterinit(mdb);
EJCOLL *coll = NULL;
JBENSUREOPENLOCK(jb, false, NULL);
TCLIST *ret = tclistnew2(jb->cdbsnum);
- for (int i = 0; i < jb->cdbsnum; ++i) {
- coll = jb->cdbs + i;
- assert(coll);
- TCLISTPUSH(ret, coll, sizeof (*coll));
+ for (int i = 0; i < EJDB_MAX_COLLECTIONS; ++i) {
+ coll = jb->cdbs[i];
+ if (coll) {
+ TCLISTPUSH(ret, coll, sizeof (*coll));
+ }
}
JBUNLOCKMETHOD(jb);
return ret;
if (!coll) {
goto finish;
}
- EJCOLL *cdbs = jb->cdbs;
- for (int i = 0; i < jb->cdbsnum; ++i) {
- coll = cdbs + i;
- if (!strcmp(colname, coll->cname)) {
- if (!JBCLOCKMETHOD(coll, true)) return false;
- tctdbout2(jb->metadb, colname);
- tctdbvanish(coll->tdb);
- TCLIST *paths = tclistnew2(10);
- tclistpush2(paths, coll->tdb->hdb->path);
- for (int j = 0; j < coll->tdb->inum; ++j) {
- TDBIDX *idx = coll->tdb->idxs + j;
- const char *ipath = tcbdbpath(idx->db);
- if (ipath) {
- tclistpush2(paths, ipath);
- }
- }
- tctdbclose(coll->tdb);
- if (unlinkfile) {
- for (int j = 0; j < TCLISTNUM(paths); ++j) {
- unlink(tclistval2(paths, j));
- }
- }
- tclistdel(paths);
- JBCUNLOCKMETHOD(coll);
- _delcoldb(coll);
- jb->cdbsnum--;
- memmove(cdbs + i, cdbs + i + 1, sizeof (*cdbs) * (jb->cdbsnum - i));
+ if (!JBCLOCKMETHOD(coll, true)) return false;
+ tctdbout2(jb->metadb, colname);
+ tctdbvanish(coll->tdb);
+ TCLIST *paths = tclistnew2(10);
+ tclistpush2(paths, coll->tdb->hdb->path);
+ for (int j = 0; j < coll->tdb->inum; ++j) {
+ TDBIDX *idx = coll->tdb->idxs + j;
+ const char *ipath = tcbdbpath(idx->db);
+ if (ipath) {
+ tclistpush2(paths, ipath);
+ }
+ }
+ tctdbclose(coll->tdb);
+ if (unlinkfile) {
+ for (int i = 0; i < TCLISTNUM(paths); ++i) {
+ unlink(tclistval2(paths, i));
+ }
+ }
+ tclistdel(paths);
+ jb->cdbsnum--;
+ for (int i = 0; i < EJDB_MAX_COLLECTIONS; ++i) {
+ if (jb->cdbs[i] == coll) {
+ jb->cdbs[i] = NULL;
break;
}
}
+ JBCUNLOCKMETHOD(coll);
+ _delcoldb(coll);
+ TCFREE(coll);
finish:
JBUNLOCKMETHOD(jb);
return rv;
assert(jb);
JBENSUREOPENLOCK(jb, true, false);
bool rv = true;
- EJCOLL *coll = NULL;
- for (int i = 0; i < jb->cdbsnum; ++i) {
- coll = jb->cdbs + i;
- assert(coll);
- rv = JBCLOCKMETHOD(coll, true);
- if (!rv) break;
- rv = tctdbsync(coll->tdb);
- JBCUNLOCKMETHOD(coll);
- if (!rv) break;
+ for (int i = 0; i < EJDB_MAX_COLLECTIONS; ++i) {
+ if (jb->cdbs[i]) {
+ rv = JBCLOCKMETHOD(jb->cdbs[i], true);
+ if (!rv) break;
+ rv = tctdbsync(jb->cdbs[i]->tdb);
+ JBCUNLOCKMETHOD(jb->cdbs[i]);
+ if (!rv) break;
+ }
}
JBUNLOCKMETHOD(jb);
return rv;
static EJCOLL* _getcoll(EJDB *jb, const char *colname) {
assert(colname);
- EJCOLL *coll = NULL;
- //check if collection exists
- for (int i = 0; i < jb->cdbsnum; ++i) {
- coll = jb->cdbs + i;
- assert(coll);
- if (!strcmp(colname, coll->cname)) {
- break;
- } else {
- coll = NULL;
+ for (int i = 0; i < EJDB_MAX_COLLECTIONS; ++i) {
+ if (jb->cdbs[i] && !strcmp(colname, jb->cdbs[i]->cname)) {
+ return jb->cdbs[i];
}
}
- return coll;
+ return NULL;
}
/* Set mutual exclusion control of a table database object for threading. */
}
static bool _addcoldb0(const char *cname, EJDB *jb, EJCOLLOPTS *opts, EJCOLL **res) {
+ int i;
bool rv = true;
TCTDB *cdb;
+
+ for (i = 0; i < EJDB_MAX_COLLECTIONS && jb->cdbs[i]; ++i);
+ if (i == EJDB_MAX_COLLECTIONS) {
+ _ejdbsetecode(jb, JBEMAXNUMCOLS, __FILE__, __LINE__, __func__);
+ return false;
+ }
rv = _createcoldb(cname, jb, opts, &cdb);
if (!rv) {
*res = NULL;
return rv;
}
- TCREALLOC(jb->cdbs, jb->cdbs, sizeof (jb->cdbs[0]) * (++jb->cdbsnum));
- EJCOLL *jcoll = jb->cdbs + jb->cdbsnum - 1;
+ EJCOLL *jcoll;
+ TCCALLOC(jcoll, 1, sizeof (*jcoll));
+ jb->cdbs[i] = jcoll;
jcoll->cname = tcstrdup(cname);
jcoll->cnamesz = strlen(cname);
jcoll->tdb = cdb;
jcoll->mmtx = NULL;
_ejdbcolsetmutex(jcoll);
*res = jcoll;
+ ++jb->cdbsnum;
return rv;
}
return rv;
}
-static void _ejdbclear(EJDB *jb) {
- assert(jb);
- jb->cdbs = NULL;
- jb->cdbsnum = 0;
- jb->metadb = NULL;
- jb->mmtx = NULL;
-}
-
/* Check whether a string includes all tokens in another string.*/
static bool _qrycondcheckstrand(const char *vbuf, const TCLIST *tokens) {
assert(vbuf && tokens);
JBEQUPDFAILED = 9010, /**< Updating failed. */
JBEQONEEMATCH = 9011, /**< Only one $elemMatch allowed in the fieldpath. */
JBEQINCEXCL = 9012, /**< $fields hint cannot mix include and exclude fields */
- JBEQACTKEY = 9013 /**< action key in $do block can only be one of: $join */
+ JBEQACTKEY = 9013, /**< action key in $do block can only be one of: $join */
+ JBEMAXNUMCOLS = 9014 /**< Exceeded the maximum number of collections per database */
};
enum { /** Database open modes */
atype == BSON_INT || atype == BSON_LONG || atype == BSON_DOUBLE || \
atype == BSON_ARRAY)
-struct EJDB {
- EJCOLL *cdbs; /*> Collection DBs for JSON collections. */
- int cdbsnum; /*> Count of collection DB. */
- TCTDB *metadb; /*> Metadata DB. */
- void *mmtx; /*> Mutex for method */
-};
+#define EJDB_MAX_COLLECTIONS 1024
+
struct EJCOLL { /**> EJDB Collection. */
char *cname; /**> Collection name. */
void *mmtx; /*> Mutex for method */
};
+struct EJDB {
+ EJCOLL * cdbs[EJDB_MAX_COLLECTIONS]; /*> Collection DBs for JSON collections. */
+ int cdbsnum; /*> Count of collection DB. */
+ TCTDB *metadb; /*> Metadata DB. */
+ void *mmtx; /*> Mutex for method */
+};
+
enum { /**> Query field flags */
// Comparison flags
EJCOMPGT = 1, /**> Comparison GT */
EJCONDADDSET = 1 << 12, /**> $addToSet Adds value to the array only if its not in the array already. */
EJCONDPULL = 1 << 13, /**> $pull Removes all occurrences of value from field, if field is an array */
EJCONDUPSERT = 1 << 14, /**> $upsert Upsert $set operation */
- EJCONDALL = 1 << 15, /**> 'All' modificator for $pull or $addToSet ($addToSetAll or $pullAll) */
+ EJCONDALL = 1 << 15, /**> 'All' modificator for $pull or $addToSet ($addToSetAll or $pullAll) */
EJCONDOIT = 1 << 16 /**> $do query field operation */
};
-
enum { /**> Query flags */
EJQINTERNAL = 1, /**> Internal query object used in _ejdbqryexecute */
EJQUPDATING = 1 << 1, /**> Query in updating mode */
- EJQDROPALL = 1 << 2, /**> Drop bson object if matched */
+ EJQDROPALL = 1 << 2, /**> Drop bson object if matched */
EJQONLYCOUNT = 1 << 3 /**> Only count mode */
};
//Temporal buffers used during query processing
TCXSTR *colbuf; /**> TCTDB current column buffer */
- TCXSTR *bsbuf; /**> current bson object */
+ TCXSTR *bsbuf; /**> current bson object */
};
#define JDBCOLBSON "$" /**> TCDB colname with BSON byte data */
tclistdel(q1res);
}
-
void testTicket43() {
+
+ EJCOLL *coll = ejdbcreatecoll(jb, "ticket43", NULL);
+ CU_ASSERT_PTR_NOT_NULL_FATAL(coll);
+
+ EJCOLL *rcoll = ejdbcreatecoll(jb, "ticket43_refs", NULL);
+ CU_ASSERT_PTR_NOT_NULL_FATAL(rcoll);
+
+ bson a1;
+ bson_oid_t oid;
+ bson_oid_t ref_oids[2];
+ char xoid[25];
+
+ bson_init(&a1);
+ bson_append_string(&a1, "name", "n1");
+ bson_finish(&a1);
+ CU_ASSERT_FALSE_FATAL(a1.err);
+ CU_ASSERT_TRUE_FATAL(ejdbsavebson(rcoll, &a1, &ref_oids[0]));
+ bson_destroy(&a1);
+
+ bson_init(&a1);
+ bson_append_string(&a1, "name", "n2");
+ bson_finish(&a1);
+ CU_ASSERT_FALSE_FATAL(a1.err);
+ CU_ASSERT_TRUE_FATAL(ejdbsavebson(rcoll, &a1, &ref_oids[1]));
+ bson_destroy(&a1);
+
+ bson_init(&a1);
+ bson_append_string(&a1, "name", "c1");
+ bson_oid_to_string(&ref_oids[0], xoid);
+ bson_append_string(&a1, "refs", xoid);
+ bson_finish(&a1);
+ CU_ASSERT_FALSE_FATAL(a1.err);
+ CU_ASSERT_TRUE_FATAL(ejdbsavebson(coll, &a1, &oid));
+ bson_destroy(&a1);
+
+ bson_init(&a1);
+ bson_append_string(&a1, "name", "c2");
+ bson_append_start_array(&a1, "arrefs");
+ bson_oid_to_string(&ref_oids[0], xoid);
+ bson_append_string(&a1, "0", xoid);
+ bson_append_oid(&a1, "1", &ref_oids[1]);
+ bson_append_finish_array(&a1);
+ bson_finish(&a1);
+ CU_ASSERT_FALSE_FATAL(a1.err);
+ CU_ASSERT_TRUE_FATAL(ejdbsavebson(coll, &a1, &oid));
+ //bson_print_raw(stderr, bson_dat, 0);
+ bson_destroy(&a1);
+
/*
Assuming fpath contains object id (or its string representation).
In query results fpath values will be replaced by loaded bson
{..., $do : {fpath : {$join : 'collectionname'}} }
Second form:
{..., $do : {fpath : {$join : {$ref : 'collectionname', $fields : {foo:1}} }} }
- */
+ */
bson bsq1;
bson_init_as_query(&bsq1);
bson_append_start_object(&bsq1, "$do");
- bson_append_start_object(&bsq1, "homeref");
+ bson_append_start_object(&bsq1, "refs");
bson_append_start_object(&bsq1, "$join");
- bson_append_string(&bsq1, "$join", "home");
+ bson_append_string(&bsq1, "$join", "ticket43_refcoll");
bson_append_finish_object(&bsq1);
bson_append_finish_object(&bsq1);
bson_append_finish_object(&bsq1);
EJQ *q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, NULL);
CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
ejdbquerydel(q1);
-
bson_destroy(&bsq1);
}