diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | README.md | 143 | ||||
-rw-r--r-- | node/ejdb.js | 225 | ||||
-rw-r--r-- | node/samples/sample1.js | 47 | ||||
-rw-r--r-- | tcejdb/README | 74 | ||||
-rw-r--r-- | tcejdb/ejdb.c | 22 | ||||
-rw-r--r-- | tcejdb/ejdb.h | 5 | ||||
-rw-r--r-- | tcejdb/testejdb/t2.c | 121 |
9 files changed, 545 insertions, 96 deletions
@@ -47,4 +47,6 @@ /node/nbproject/private /build /var +/node/samples/zoo* + @@ -1,6 +1,6 @@ all: - cd tcejdb && ./configure + cd tcejdb && ./configure --disable-bzip make -C ./tcejdb clean: @@ -18,6 +18,78 @@ Features * Collection level transactions. * String token matching queries: ```$stror``` ```$strand``` +NodeJS binding +================================= + +One snippet intro +--------------------------------- +```JavaScript +var EJDB = require("ejdb"); +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 database + }); +}); +``` + +Installation +-------------------------------- +**System libraries:** + +* cunit +* zlib + +On Debian/Ubuntu linux you can install it as follows: + +```sh + sudo apt-get install libcunit1 libcunit1-dev zlib1g zlib1g-dev +``` + +**Installation from node package manager:** + ```npm install ejdb``` + + +EJDB C Library +================================== One snippet intro ----------------------------------- @@ -88,58 +160,32 @@ int main() { Building & Installation -================================ +-------------------------------- Prerequisites -------------------------------- -**Libraries:** +**System libraries:** * cunit * zlib -* bzip2 On Debian/Ubuntu linux you can install it as follows: ```sh - sudo apt-get install libcunit1 libcunit1-dev libbz2-1.0 libbz2-dev zlib1g zlib1g-dev + sudo apt-get install libcunit1 libcunit1-dev zlib1g zlib1g-dev ``` Building -------------------------------- ```sh - ./configure --prefix=<installation prefix> && make && make check + cd ./tcejdb + ./configure --disable-bzip --prefix=<installation prefix> && make && make check make install ``` * library name: **tcejdb** (with pkgconfig) * main include header: ```<tcejdb/ejdb.h>``` -Usage -=============================== - -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 - -API +C API --------------------------------- EJDB API presented in **ejdb.h** C header file. @@ -157,7 +203,7 @@ Queries * * - Supported queries: * - Simple matching of String OR Number OR Array value: - * - {'bson.field.path' : 'val', ...} + * - {'json.field.path' : '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 @@ -174,6 +220,8 @@ Queries * - {'json.field.path' : {'$strand' : [val1, val2, val3]}} * - $stror String tokens OR String array val matches any token in specified array: * - {'json.field.path' : {'$stror' : [val1, val2, val3]}} + * - $exists Field existence matching: + * - {'json.field.path' : {'$exists' : true|false}} * * NOTE: Negate operations: $not and $nin not using indexes * so they can be slow in comparison to other matching operations. @@ -208,11 +256,34 @@ Examples ------------------------------------ You can find some code samples in: -* /samples -* /testejdb test cases +* ./samples +* ./testejdb test cases + +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/TODOs ------------------------------------ +------------------------------------ * Case insensitive string indexes * Collect collection index statistic * Windows port diff --git a/node/ejdb.js b/node/ejdb.js index 3bc84ab..60a6f27 100644 --- a/node/ejdb.js +++ b/node/ejdb.js @@ -20,33 +20,69 @@ for (var k in ejdblib) { //Export constants } EJDB.DEFAULT_OPEN_MODE = DEFAULT_OPEN_MODE; +/** + * Open database. + * Returns database instance handle object . + * + * This is blocking function. + * + * @param {String} dbFile Database main file name + * @param {Number} [openMode] Bitmast of open modes: + * - `JBOREADER` Open as a reader. + * - `JBOWRITER` Open as a writer. + * - `JBOCREAT` Create db if it not exists + * - `JBOTRUNC` Truncate db. + * @returns {EJDB} EJDB database wrapper + */ + EJDB.open = function(dbFile, openMode) { return new EJDB(dbFile, openMode); }; +/** + * Close database. + * If database was not opened it does nothing. + * + * This is blocking function. + */ EJDB.prototype.close = function() { return this._impl.close(); }; +/** + * Check if database in opened state. + */ EJDB.prototype.isOpen = function() { return this._impl.isOpen(); }; +/** + * Automatically creates new collection if it does't exists. + * Optional collection options `copts` + * are applied only for newly created collection. + * For existing collections `copts` takes no effect. + * + * This is blocking function. + * + * @param {String} cname Name of collection. + * @param {Object} [copts] Collection options. + * @return {*} + */ EJDB.prototype.ensureCollection = function(cname, copts) { return this._impl.ensureCollection(cname, copts || {}); }; - /** + * Removes collection. + * * Call variations: * - rmCollection(cname) * - rmCollection(cname, cb) * - rmCollection(cname, prune, cb) * - * @param cname - * @param prune - * @param cb - * @return {*} + * @param {String} cname Name of collection. + * @param {Boolean} [prune=false] If true the collection data will erased from disk. + * @param {Function} [cb] Callback function with arguments: (error) */ EJDB.prototype.removeCollection = function(cname, prune, cb) { if (arguments.length == 2) { @@ -60,6 +96,23 @@ EJDB.prototype.removeCollection = function(cname, prune, cb) { return this._impl.removeCollection(cname, !!prune, cb); }; + +/** + * Save/update specified JSON objects in the collection. + * If collection with `cname` does not exists it will be created. + * + * Each persistent object has unique identifier (OID) placed in the `_id` property. + * If a saved object does not have this `_id` it will be autogenerated. + * To identify and update object it should contains `_id` property. + * + * Call variations: + * - save(cname, json object, [cb]) + * - save(cname, <Array of json objects>, [cb]) + * + * @param {String} cname Name of collection. + * @param {Array|Object} jsarr Signle JSON object or array of JSON objects to save + * @param {Function} [cb] Callback function with arguments: (error, {Array} of OIDs for saved objects) + */ EJDB.prototype.save = function(cname, jsarr, cb) { if (!jsarr) { return; @@ -87,28 +140,99 @@ EJDB.prototype.save = function(cname, jsarr, cb) { }); }; + +/** + * Loads JSON object identified OID from the collection. + * + * @param {String} cname Name of collection + * @param {String} oid Object identifier (OID) + * @param {Function} cb Callback function with arguments: (error, obj) + * `obj`: Retrieved JSON object or NULL if it is not found. + */ EJDB.prototype.load = function(cname, oid, cb) { return this._impl.load(cname, oid, cb); }; - +/** + * Removes JSON object from the collection. + * + * @param {String} cname Name of collection + * @param {String} oid Object identifier (OID) + * @param {Function} cb Callback function with arguments: (error) + */ EJDB.prototype.remove = function(cname, oid, cb) { return this._impl.remove(cname, oid, cb); }; /** + * Execute query on collection. * - * Call variations: + * EJDB queries inspired by MongoDB (mongodb.org) and follows same philosophy. + * + * - Supported queries: + * - Simple matching of String OR Number OR Array value: + * - {'json.field.path' : '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 + * - $begin String starts with prefix + * - {'json.field.path' : {'$begin' : prefix}} + * - $gt, $gte (>, >=) and $lt, $lte for number types: + * - {'json.field.path' : {'$gt' : number}, ...} + * - $bt Between for number types: + * - {'json.field.path' : {'$bt' : [num1, num2]}} + * - $in String OR Number OR Array val matches to value in specified array: + * - {'json.field.path' : {'$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]}} + * - $stror String tokens OR String array val matches any token in specified array: + * - {'json.field.path' : {'$stror' : [val1, val2, val3]}} + * - $exists Field existence matching: + * - {'json.field.path' : {'$exists' : true|false}} + * + * 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. + * Eg: ORDER BY field1 ASC, field2 DESC + * hints: { + * "$orderby" : { + * "field1" : 1, + * "field2" : -1 + * } + * } + * + * Many C API query examples can be found in `tcejdb/testejdb/t2.c` test case. + * * + * To traverse selected records cursor object is used: + * - Cursor#next() Move cursor to the next record and returns true if next record exists. + * - Cursor#hasNext() Returns true if cursor can be placed to the next record. + * - Cursor#field(name) Retrieve value of the specified field of the current JSON object record. + * - Cursor#object() Retrieve whole JSON object with all fields. + * - Cursor#reset() Reset cursor to its initial state. + * - Cursor#length Read-only property: Number of records placed into cursor. + * - Cursor#pos Read/Write property: You can set cursor position: 0 <= pos < length + * - Cursor#close() Closes cursor and free cursor resources. Cursor cant be used in closed state. + * + * Call variations of find(): * - find(cname, qobj, cb) * - find(cname, qobj, hints, cb) * - find(cname, qobj, qobjarr, cb) * - find(cname, qobj, qobjarr, hints, cb) * - * @param cname - * @param qobj - * @param orarr - * @param hints - * @param cb + * @param {String} cname Name of collection + * @param {Object} qobj Main JSON query object + * @param {Array} [orarr] Array of additional OR query objects (joined with OR predicate). + * @param {Object} [hints] JSON object with query hints. + * @param {Function} cb Callback function with arguments: (error, cursor, count) where: + * `cursor`: Cursor object to traverse records + * `count`: Total number of selected records */ EJDB.prototype.find = function(cname, qobj, orarr, hints, cb) { if (arguments.length == 4) { @@ -138,17 +262,27 @@ EJDB.prototype.find = function(cname, qobj, orarr, hints, cb) { } return this._impl.query(cname, [qobj].concat(orarr, hints), - (hints["onlycount"] ? ejdblib.JBQRYCOUNT : 0), + (hints["$onlycount"] ? ejdblib.JBQRYCOUNT : 0), cb); }; -/* - * Call variations: +/** + * Same as #find() but retrieves only one matching JSON object. + * + * Call variations of findOne(): * - findOne(cname, qobj, cb) * - findOne(cname, qobj, hints, cb) * - findOne(cname, qobj, qobjarr, cb) * - findOne(cname, qobj, qobjarr, hints, cb) + * + * @param {String} cname Name of collection + * @param {Object} qobj Main JSON query object + * @param {Array} [orarr] Array of additional OR query objects (joined with OR predicate). + * @param {Object} [hints] JSON object with query hints. + * @param {Function} cb Callback function with arguments: (error, obj) where: + * `obj`: Retrieved JSON object or NULL if it is not found. */ + EJDB.prototype.findOne = function(cname, qobj, orarr, hints, cb) { if (arguments.length == 4) { cb = hints; @@ -183,12 +317,71 @@ EJDB.prototype.findOne = function(cname, qobj, orarr, hints, cb) { return; } if (cursor.next()) { - cb(null, cursor.object()); + try { + cb(null, cursor.object()); + } finally { + cursor.close(); + } } else { cb(null, null); } }); }; +/** + * Convenient count(*) operation. + * + * Call variations of count(): + * - count(cname, qobj, cb) + * - count(cname, qobj, hints, cb) + * - count(cname, qobj, qobjarr, cb) + * - count(cname, qobj, qobjarr, hints, cb) + * + * @param {String} cname Name of collection + * @param {Object} qobj Main JSON query object + * @param {Array} [orarr] Array of additional OR query objects (joined with OR predicate). + * @param {Object} [hints] JSON object with query hints. + * @param {Function} cb Callback function with arguments: (error, count) where: + * `count`: Number of matching records. + */ +EJDB.prototype.count = function(cname, qobj, orarr, hints, cb) { + if (arguments.length == 4) { + cb = hints; + if (orarr && orarr.constructor === Array) { + hints = {}; + } else { + hints = orarr; + orarr = []; + } + } else if (arguments.length == 3) { + cb = orarr; + orarr = []; + hints = {}; + } + if (typeof cb !== "function") { + throw new Error("Callback 'cb' argument must be specified"); + } + if (typeof cname !== "string") { + throw new Error("Collection name 'cname' argument must be specified"); + } + if (!hints || typeof hints !== "object") { + hints = {}; + } + if (!qobj || typeof qobj !== "object") { + qobj = {}; + } + return this._impl.query(cname, + [qobj].concat(orarr, hints), + ejdblib.JBQRYCOUNT, + function(err, cursor, count) { + if (err) { + cb(err); + return; + } + cursor.close(); + cb(null, count); + }); +}; + module.exports = EJDB; diff --git a/node/samples/sample1.js b/node/samples/sample1.js new file mode 100644 index 0000000..a8a19ae --- /dev/null +++ b/node/samples/sample1.js @@ -0,0 +1,47 @@ +var EJDB = require("../ejdb"); +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 database + }); +}); + + diff --git a/tcejdb/README b/tcejdb/README index aa27f8f..60bc822 100644 --- a/tcejdb/README +++ b/tcejdb/README @@ -18,6 +18,8 @@ Features * Collection level transactions. * String token matching queries: ```$stror``` ```$strand``` +EJDB C Library +================================== One snippet intro ----------------------------------- @@ -88,58 +90,32 @@ int main() { Building & Installation -================================ +-------------------------------- Prerequisites -------------------------------- -**Libraries:** +**System libraries:** * cunit * zlib -* bzip2 On Debian/Ubuntu linux you can install it as follows: ```sh - sudo apt-get install libcunit1 libcunit1-dev libbz2-1.0 libbz2-dev zlib1g zlib1g-dev + sudo apt-get install libcunit1 libcunit1-dev zlib1g zlib1g-dev ``` Building -------------------------------- ```sh - ./configure --prefix=<installation prefix> && make && make check + cd ./tcejdb + ./configure --disable-bzip --prefix=<installation prefix> && make && make check make install ``` * library name: **tcejdb** (with pkgconfig) * main include header: ```<tcejdb/ejdb.h>``` -Usage -=============================== - -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 - -API +C API --------------------------------- EJDB API presented in **ejdb.h** C header file. @@ -157,7 +133,7 @@ Queries * * - Supported queries: * - Simple matching of String OR Number OR Array value: - * - {'bson.field.path' : 'val', ...} + * - {'json.field.path' : '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 @@ -174,6 +150,8 @@ Queries * - {'json.field.path' : {'$strand' : [val1, val2, val3]}} * - $stror String tokens OR String array val matches any token in specified array: * - {'json.field.path' : {'$stror' : [val1, val2, val3]}} + * - $exists Field existence matching: + * - {'json.field.path' : {'$exists' : true|false}} * * NOTE: Negate operations: $not and $nin not using indexes * so they can be slow in comparison to other matching operations. @@ -208,11 +186,35 @@ Examples ------------------------------------ You can find some code samples in: -* /samples -* /testejdb test cases +* ./samples +* ./testejdb test cases + +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/TODOs ------------------------------------ +------------------------------------ * Case insensitive string indexes * Collect collection index statistic * Windows port + diff --git a/tcejdb/ejdb.c b/tcejdb/ejdb.c index f0ca1d8..fee183d 100644 --- a/tcejdb/ejdb.c +++ b/tcejdb/ejdb.c @@ -2287,7 +2287,7 @@ static int _parse_qobj_impl(EJDB* jb, bson_iterator *it, TCMAP *qmap, TCLIST *pa } qf.fpath = tcstrjoin(pathStack, '.'); qf.fpathsz = strlen(qf.fpath); - tcmapputkeep(qmap, qf.fpath, strlen(qf.fpath), &qf, sizeof (qf)); + tcmapputkeep(qmap, qf.fpath, qf.fpathsz, &qf, sizeof (qf)); break; } else { bson_iterator sit; @@ -2321,7 +2321,7 @@ static int _parse_qobj_impl(EJDB* jb, bson_iterator *it, TCMAP *qmap, TCLIST *pa } else { qf.tcop = TDBQCSTREQ; } - tcmapputkeep(qmap, qf.fpath, strlen(qf.fpath), &qf, sizeof (qf)); + tcmapputkeep(qmap, qf.fpath, qf.fpathsz, &qf, sizeof (qf)); break; } case BSON_LONG: @@ -2353,7 +2353,7 @@ static int _parse_qobj_impl(EJDB* jb, bson_iterator *it, TCMAP *qmap, TCLIST *pa } else { qf.tcop = TDBQCNUMEQ; } - tcmapputkeep(qmap, qf.fpath, strlen(qf.fpath), &qf, sizeof (qf)); + tcmapputkeep(qmap, qf.fpath, qf.fpathsz, &qf, sizeof (qf)); break; } case BSON_REGEX: @@ -2382,7 +2382,7 @@ static int _parse_qobj_impl(EJDB* jb, bson_iterator *it, TCMAP *qmap, TCLIST *pa TCFREE(qf.expr); break; } - tcmapputkeep(qmap, qf.fpath, strlen(qf.fpath), &qf, sizeof (qf)); + tcmapputkeep(qmap, qf.fpath, qf.fpathsz, &qf, sizeof (qf)); break; } case BSON_NULL: @@ -2394,6 +2394,20 @@ static int _parse_qobj_impl(EJDB* jb, bson_iterator *it, TCMAP *qmap, TCLIST *pa qf.exprsz = 0; tcmapputkeep(qmap, qf.fpath, strlen(qf.fpath), &qf, sizeof (qf)); break; + + case BSON_BOOL: + if (isckey && !strcmp("$exists", fkey)) { + qf.tcop = TDBQCEXIST; + qf.fpath = tcstrjoin(pathStack, '.'); + qf.fpathsz = strlen(qf.fpath); + qf.expr = tcstrdup(""); //Empty string as expr + qf.exprsz = 0; + if (!bson_iterator_bool_raw(it)) { + qf.negate = !qf.negate; + } + tcmapputkeep(qmap, qf.fpath, qf.fpathsz, &qf, sizeof (qf)); + } + break; default: break; }; diff --git a/tcejdb/ejdb.h b/tcejdb/ejdb.h index 6aefa5e..243faef 100644 --- a/tcejdb/ejdb.h +++ b/tcejdb/ejdb.h @@ -146,6 +146,7 @@ EJDB_EXPORT EJCOLL* ejdbgetcoll(EJDB *jb, const char* colname); * @param colname Name of collection. * @param opts Options are applied only for newly created collection. * For existing collections it takes no effect. + * * @return Collection handle or NULL if error. */ EJDB_EXPORT EJCOLL* ejdbcreatecoll(EJDB *jb, const char* colname, EJCOLLOPTS *opts); @@ -197,7 +198,7 @@ EJDB_EXPORT bson* ejdbloadbson(EJCOLL* coll, const bson_oid_t* oid); * * - Supported queries: * - Simple matching of String OR Number OR Array value: - * - {'bson.field.path' : 'val', ...} + * - {'json.field.path' : '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 @@ -214,6 +215,8 @@ EJDB_EXPORT bson* ejdbloadbson(EJCOLL* coll, const bson_oid_t* oid); * - {'json.field.path' : {'$strand' : [val1, val2, val3]}} * - $stror String tokens OR String array val matches any token in specified array: * - {'json.field.path' : {'$stror' : [val1, val2, val3]}} + * - $exists Field existence matching: + * - {'json.field.path' : {'$exists' : true|false}} * * NOTE: Negate operations: $not and $nin not using indexes * so they can be slow in comparison to other matching operations. diff --git a/tcejdb/testejdb/t2.c b/tcejdb/testejdb/t2.c index d1aea95..11c27d8 100644 --- a/tcejdb/testejdb/t2.c +++ b/tcejdb/testejdb/t2.c @@ -74,7 +74,6 @@ void testAddData() { bson_append_string(&a1, "country", "Russian Federation"); bson_append_string(&a1, "zip", "630090"); bson_append_string(&a1, "street", "Pirogova"); - bson_append_int(&a1, "room", 335); bson_append_finish_object(&a1); bson_append_start_array(&a1, "labels"); bson_append_string(&a1, "0", "red"); @@ -2290,6 +2289,123 @@ void testQuery26() { //$not $nin ejdbquerydel(q1); } +void testQuery27() { //$exists + EJCOLL *contacts = ejdbcreatecoll(jb, "contacts", NULL); + CU_ASSERT_PTR_NOT_NULL_FATAL(contacts); + + //{'address.room' : {$exists : true}} + bson bsq1; + bson_init_as_query(&bsq1); + bson_append_start_object(&bsq1, "address.room"); + bson_append_bool(&bsq1, "$exists", true); + bson_append_finish_object(&bsq1); + bson_finish(&bsq1); + CU_ASSERT_FALSE_FATAL(bsq1.err); + + EJQ *q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, NULL); + CU_ASSERT_PTR_NOT_NULL_FATAL(q1); + + uint32_t count = 0; + TCXSTR *log = tcxstrnew(); + TCLIST *q1res = ejdbqrysearch(contacts, q1, &count, 0, log); + //fprintf(stderr, "%s", TCXSTRPTR(log)); + + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAX: 4294967295")); + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "SKIP: 0")); + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO")); + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'NONE'")); + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 0")); + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1")); + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "$OR QUERIES: 0")); + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: FALSE")); + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN")); + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 1")); + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS COUNT: 1")); + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: FALSE")); + CU_ASSERT_EQUAL(count, 1); + CU_ASSERT_TRUE(TCLISTNUM(q1res) == 1); + + bson_destroy(&bsq1); + tclistdel(q1res); + tcxstrdel(log); + ejdbquerydel(q1); + + + //{'address.room' : {$exists : true}} + bson_init_as_query(&bsq1); + bson_append_start_object(&bsq1, "address.room"); + bson_append_bool(&bsq1, "$exists", false); + bson_append_finish_object(&bsq1); + bson_finish(&bsq1); + CU_ASSERT_FALSE_FATAL(bsq1.err); + + q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, NULL); + CU_ASSERT_PTR_NOT_NULL_FATAL(q1); + + count = 0; + log = tcxstrnew(); + q1res = ejdbqrysearch(contacts, q1, &count, 0, log); + //fprintf(stderr, "%s", TCXSTRPTR(log)); + + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAX: 4294967295")); + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "SKIP: 0")); + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO")); + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'NONE'")); + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 0")); + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1")); + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "$OR QUERIES: 0")); + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: FALSE")); + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN")); + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 3")); + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS COUNT: 3")); + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: FALSE")); + CU_ASSERT_EQUAL(count, 3); + CU_ASSERT_TRUE(TCLISTNUM(q1res) == 3); + + bson_destroy(&bsq1); + tclistdel(q1res); + tcxstrdel(log); + ejdbquerydel(q1); + + //{'address.room' : {$not : {$exists : true}}} is equivalent to {'address.room' : {$exists : false}} + bson_init_as_query(&bsq1); + bson_append_start_object(&bsq1, "address.room"); + bson_append_start_object(&bsq1, "$not"); + bson_append_bool(&bsq1, "$exists", true); + bson_append_finish_object(&bsq1); + bson_append_finish_object(&bsq1); + bson_finish(&bsq1); + CU_ASSERT_FALSE_FATAL(bsq1.err); + + q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, NULL); + CU_ASSERT_PTR_NOT_NULL_FATAL(q1); + + count = 0; + log = tcxstrnew(); + q1res = ejdbqrysearch(contacts, q1, &count, 0, log); + //fprintf(stderr, "%s", TCXSTRPTR(log)); + + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAX: 4294967295")); + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "SKIP: 0")); + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO")); + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'NONE'")); + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 0")); + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1")); + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "$OR QUERIES: 0")); + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: FALSE")); + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN")); + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 3")); + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS COUNT: 3")); + CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: FALSE")); + CU_ASSERT_EQUAL(count, 3); + CU_ASSERT_TRUE(TCLISTNUM(q1res) == 3); + + bson_destroy(&bsq1); + tclistdel(q1res); + tcxstrdel(log); + ejdbquerydel(q1); +} + int main() { setlocale(LC_ALL, "en_US.UTF-8"); CU_pSuite pSuite = NULL; @@ -2333,7 +2449,8 @@ int main() { (NULL == CU_add_test(pSuite, "testQuery23", testQuery23)) || (NULL == CU_add_test(pSuite, "testQuery24", testQuery24)) || (NULL == CU_add_test(pSuite, "testQuery25", testQuery25)) || - (NULL == CU_add_test(pSuite, "testQuery26", testQuery26)) + (NULL == CU_add_test(pSuite, "testQuery26", testQuery26)) || + (NULL == CU_add_test(pSuite, "testQuery27", testQuery27)) ) { CU_cleanup_registry(); return CU_get_error(); |