summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--Makefile2
-rw-r--r--README.md143
-rw-r--r--node/ejdb.js225
-rw-r--r--node/samples/sample1.js47
-rw-r--r--tcejdb/README74
-rw-r--r--tcejdb/ejdb.c22
-rw-r--r--tcejdb/ejdb.h5
-rw-r--r--tcejdb/testejdb/t2.c121
9 files changed, 545 insertions, 96 deletions
diff --git a/.gitignore b/.gitignore
index 992179d..0ffc6d3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -47,4 +47,6 @@
/node/nbproject/private
/build
/var
+/node/samples/zoo*
+
diff --git a/Makefile b/Makefile
index 2c71665..7cee82a 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
all:
- cd tcejdb && ./configure
+ cd tcejdb && ./configure --disable-bzip
make -C ./tcejdb
clean:
diff --git a/README.md b/README.md
index dff16bc..0790835 100644
--- a/README.md
+++ b/README.md
@@ -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();