#13 done
authoradam <adamansky@gmail.com>
Mon, 24 Dec 2012 17:00:45 +0000 (00:00 +0700)
committeradam <adamansky@gmail.com>
Mon, 24 Dec 2012 17:00:45 +0000 (00:00 +0700)
Changelog
README.md
node/ejdb.js
package.json
tcejdb/configure
tcejdb/configure.ac
tcejdb/ejdb.c
tcejdb/ejdb.h
tcejdb/ejdb_private.h
tcejdb/testejdb/t2.c

index e481ebb..084c92c 100644 (file)
--- a/Changelog
+++ b/Changelog
@@ -1,6 +1,7 @@
 2012-12-24 Anton Adamansky. <adamansky@gmail.com>
-    * Better array query matching (ticket: #13)
-    - Release 1.0.32
+    * Better array query matching
+    * $elemMatch support in queries (ticket: #13)
+    - Release 1.0.33
 
 2012-12-20 Anton Adamansky. <adamansky@gmail.com>
     * Initial version of EJDB CLI console
index b45b95b..2ab6737 100644 (file)
--- a/README.md
+++ b/README.md
@@ -290,7 +290,10 @@ EJDB queries inspired by MongoDB (mongodb.org) and follows same philosophy.
           -  {'json.field.path' : {'$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 type of string index.
+          For case insensitive matching you can create special type of string index.
+      - $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:
 
index e2db70a..66abc00 100644 (file)
@@ -286,6 +286,9 @@ function parseQueryArgs(args) {
  *          icase matching with '$in' operation:
  *          -    {'name' : {'$icase' : {'$in' : ['tHéâtre - театр', 'heLLo WorlD']}}}
  *          For case insensitive matching you can create special type of string index.
+ *      - $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:
  *
@@ -301,6 +304,7 @@ function parseQueryArgs(args) {
  *      $pull Atomically removes all occurrences of value from field, if field is an array.
  *          - {.., '$pull' : {'json.field.path' : val1, 'json.field.pathN' : valN, ...}}
  *
+ *
  *  NOTE: It is better to execute update queries with `$onlycount=true` hint flag
  *        or use the special `update()` method to avoid unnecessarily data fetching.
  *  NOTE: Negate operations: $not and $nin not using indexes
index f5ceeb5..4ad2647 100644 (file)
@@ -1,6 +1,6 @@
 {
     "name" : "ejdb",
-    "version" : "1.0.32",
+    "version" : "1.0.33",
     "main" : "node/ejdb.js",
     "homepage" : "http://ejdb.org",
     "description" : "EJDB - Embedded JSON Database engine",
index 7b5d600..1b72686 100755 (executable)
@@ -1,6 +1,6 @@
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for tcejdb 1.0.32.
+# Generated by GNU Autoconf 2.69 for tcejdb 1.0.33.
 #
 #
 # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc.
@@ -577,8 +577,8 @@ MAKEFLAGS=
 # Identity of this package.
 PACKAGE_NAME='tcejdb'
 PACKAGE_TARNAME='tcejdb'
-PACKAGE_VERSION='1.0.32'
-PACKAGE_STRING='tcejdb 1.0.32'
+PACKAGE_VERSION='1.0.33'
+PACKAGE_STRING='tcejdb 1.0.33'
 PACKAGE_BUGREPORT=''
 PACKAGE_URL=''
 
@@ -1257,7 +1257,7 @@ if test "$ac_init_help" = "long"; then
   # Omit some internal or obsolete options to make the list less imposing.
   # This message is too long to be a string in the A/UX 3.1 sh.
   cat <<_ACEOF
-\`configure' configures tcejdb 1.0.32 to adapt to many kinds of systems.
+\`configure' configures tcejdb 1.0.33 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1318,7 +1318,7 @@ fi
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of tcejdb 1.0.32:";;
+     short | recursive ) echo "Configuration of tcejdb 1.0.33:";;
    esac
   cat <<\_ACEOF
 
@@ -1424,7 +1424,7 @@ fi
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-tcejdb configure 1.0.32
+tcejdb configure 1.0.33
 generated by GNU Autoconf 2.69
 
 Copyright (C) 2012 Free Software Foundation, Inc.
@@ -1722,7 +1722,7 @@ cat >config.log <<_ACEOF
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by tcejdb $as_me 1.0.32, which was
+It was created by tcejdb $as_me 1.0.33, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   $ $0 $@
@@ -4870,7 +4870,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 ac_log="
-This file was extended by tcejdb $as_me 1.0.32, which was
+This file was extended by tcejdb $as_me 1.0.33, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -4923,7 +4923,7 @@ _ACEOF
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
 ac_cs_version="\\
-tcejdb config.status 1.0.32
+tcejdb config.status 1.0.33
 configured by $0, generated by GNU Autoconf 2.69,
   with options \\"\$ac_cs_config\\"
 
index a6c32fb..5de2430 100644 (file)
@@ -11,7 +11,7 @@ test -n "$CPPFLAGS" && MYCPPFLAGS="$CPPFLAGS $MYCPPFLAGS"
 test -n "$LDFLAGS" && MYLDFLAGS="$LDFLAGS $MYLDFLAGS"
 
 # Package name
-AC_INIT(tcejdb, 1.0.32)
+AC_INIT(tcejdb, 1.0.33)
 
 # Package information
 MYLIBVER=9
index 7b6535c..5d1afbc 100644 (file)
@@ -128,6 +128,7 @@ EJDB_EXPORT const char* ejdberrmsg(int ecode) {
         case JBEQERROR: return "query generic error";
         case JBEQUPDFAILED: return "bson record update failed";
         case JBEINVALIDBSONPK: return "invalid bson _id field";
+        case JBEQONEEMATCH: return "only one $elemMatch allowed in the fieldpath"; //todo remove 
         default: return tcerrmsg(ecode);
     }
 }
@@ -1281,7 +1282,7 @@ static bool _qrybsvalmatch(const EJQF *qf, bson_iterator *it, bool expandarrays)
 #undef _FETCHSTRFVAL
 }
 
-static bool _qrybsrecurrmatch(const EJQF *qf, FFPCTX *ffpctx, int arrpos) {
+static bool _qrybsrecurrmatch(const EJQF *qf, FFPCTX *ffpctx, int currpos) {
     assert(qf && ffpctx && ffpctx->stopnestedarr);
     bson_type bt = bson_find_fieldpath_value3(ffpctx);
     if (bt == BSON_ARRAY && ffpctx->stopos < ffpctx->fplen) { //a bit of complicated code  in this case =)
@@ -1291,7 +1292,7 @@ static bool _qrybsrecurrmatch(const EJQF *qf, FFPCTX *ffpctx, int arrpos) {
         ffpctx->fplen = ffpctx->fplen - ffpctx->stopos;
         assert(ffpctx->fplen > 0);
         ffpctx->fpath = ffpctx->fpath + ffpctx->stopos;
-        arrpos += ffpctx->stopos; //adjust cumulative field position
+        currpos += ffpctx->stopos; //adjust cumulative field position
         bson_iterator sit;
         bson_iterator_subiterator(ffpctx->input, &sit);
         while ((bt = bson_iterator_next(&sit)) != BSON_EOO) {
@@ -1299,27 +1300,27 @@ static bool _qrybsrecurrmatch(const EJQF *qf, FFPCTX *ffpctx, int arrpos) {
                 bson_iterator sit2;
                 bson_iterator_subiterator(&sit, &sit2);
                 ffpctx->input = &sit2;
-                if (_qrybsrecurrmatch(qf, ffpctx, arrpos)) {
+                if (_qrybsrecurrmatch(qf, ffpctx, currpos)) {
                     bool ret = true;
-                    if (qf->matchgrp > 0) { //$elemMatch matching group exists
+                    if (qf->elmatchgrp > 0 && qf->elmatchpos == currpos) { //$elemMatch matching group exists at right place
                         for (int i = TCLISTNUM(qf->q->qobjlist) - 1; i >= 0; --i) {
                             EJQF *eqf = TCLISTVALPTR(qf->q->qobjlist, i);
-                            if (eqf == qf || (eqf->matchgrp != qf->matchgrp) || (eqf->mflags & EJFEXCLUDED)) {
+                            if (eqf == qf || (eqf->mflags & EJFEXCLUDED) || eqf->elmatchgrp != qf->elmatchgrp) {
                                 continue;
                             }
                             eqf->mflags |= EJFEXCLUDED;
                             bson_iterator_subiterator(&sit, &sit2);
                             FFPCTX nffpctx = *ffpctx;
-                            nffpctx.fplen = eqf->fpathsz - arrpos;
+                            nffpctx.fplen = eqf->fpathsz - eqf->elmatchpos;
                             if (nffpctx.fplen <= 0) { //should never happen if query construction is correct
                                 assert(false);
                                 ret = false;
                                 break;
                             }
-                            nffpctx.fpath = eqf->fpath + arrpos;
+                            nffpctx.fpath = eqf->fpath + eqf->elmatchpos;
                             nffpctx.input = &sit2;
                             nffpctx.stopos = 0;
-                            if (!_qrybsrecurrmatch(eqf, &nffpctx, arrpos)) {
+                            if (!_qrybsrecurrmatch(eqf, &nffpctx, eqf->elmatchpos)) {
                                 ret = false;
                                 break;
                             }
@@ -1525,7 +1526,9 @@ static void _qryfieldup(const EJQF *src, EJQF *target, uint32_t qflags) {
     target->order = src->order;
     target->orderseq = src->orderseq;
     target->tcop = src->tcop;
-    target->matchgrp = src->matchgrp;
+    target->elmatchgrp = src->elmatchgrp;
+    target->elmatchpos = src->elmatchpos;
+
     if (src->expr) {
         TCMEMDUP(target->expr, src->expr, src->exprsz);
         target->exprsz = src->exprsz;
@@ -3135,7 +3138,7 @@ static char* _fetch_bson_str_array2(EJDB *jb, bson_iterator *it, bson_type *type
 //
 //} _PQOBJCTX ;
 
-static int _parse_qobj_impl(EJDB *jb, EJQ *q, bson_iterator *it, TCLIST *qlist, TCLIST *pathStack, EJQF *pqf, int mgrp) {
+static int _parse_qobj_impl(EJDB *jb, EJQ *q, bson_iterator *it, TCLIST *qlist, TCLIST *pathStack, EJQF *pqf, int elmatchgrp) {
     assert(it && qlist && pathStack);
     int ret = 0;
     bson_type ftype;
@@ -3143,12 +3146,13 @@ static int _parse_qobj_impl(EJDB *jb, EJQ *q, bson_iterator *it, TCLIST *qlist,
     while ((ftype = bson_iterator_next(it)) != BSON_EOO) {
 
         const char *fkey = bson_iterator_key(it);
-        bool isckey = (*fkey == '$'); //Key is a control key: $in, $nin, $not, $all
+        bool isckey = (*fkey == '$'); //Key is a control key: $in, $nin, $not, $all, ...
         EJQF qf;
         memset(&qf, 0, sizeof (qf));
         qf.q = q;
         if (pqf) {
-            qf.matchgrp = pqf->matchgrp;
+            qf.elmatchgrp = pqf->elmatchgrp;
+            qf.elmatchpos = pqf->elmatchpos;
         }
 
         if (!isckey) {
@@ -3249,7 +3253,7 @@ static int _parse_qobj_impl(EJDB *jb, EJQ *q, bson_iterator *it, TCLIST *qlist,
                 } else {
                     bson_iterator sit;
                     bson_iterator_subiterator(it, &sit);
-                    ret = _parse_qobj_impl(jb, q, &sit, qlist, pathStack, &qf, mgrp);
+                    ret = _parse_qobj_impl(jb, q, &sit, qlist, pathStack, &qf, elmatchgrp);
                     break;
                 }
             }
@@ -3291,12 +3295,19 @@ static int _parse_qobj_impl(EJDB *jb, EJQ *q, bson_iterator *it, TCLIST *qlist,
                         break;
                     }
                     if (!strcmp("$elemMatch", fkey)) {
-                        qf.matchgrp = ++mgrp;
+                        if (qf.elmatchgrp) { //only one $elemMatch allowed in query field
+                            ret = JBEQERROR;
+                            break;
+                        }
+                        qf.elmatchgrp = ++elmatchgrp;
+                        char *fpath = tcstrjoin(pathStack, '.');
+                        qf.elmatchpos = strlen(fpath) + 1; //+ 1 to skip next dot '.'
+                        free(fpath);
                     }
                 }
                 bson_iterator sit;
                 bson_iterator_subiterator(it, &sit);
-                ret = _parse_qobj_impl(jb, q, &sit, qlist, pathStack, &qf, mgrp);
+                ret = _parse_qobj_impl(jb, q, &sit, qlist, pathStack, &qf, elmatchgrp);
                 break;
             }
             case BSON_OID:
index 9a18a54..b9c87a0 100644 (file)
@@ -44,7 +44,8 @@ enum { /** Error codes */
     JBEQINVALIDQRX = 9007, /**< Invalid query regexp value. */
     JBEQRSSORTING = 9008, /**< Result set sorting error. */
     JBEQERROR = 9009, /**< Query generic error. */
-    JBEQUPDFAILED = 9010 /**< Updating failed. */
+    JBEQUPDFAILED = 9010, /**< Updating failed. */
+    JBEQONEEMATCH = 9011 /**< Only one $elemMatch allowed in the fieldpath. */
 };
 
 enum { /** Database open modes */
@@ -249,6 +250,9 @@ EJDB_EXPORT bson* ejdbloadbson(EJCOLL *coll, const bson_oid_t *oid);
  *          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:
  *
index 0156088..efac00b 100644 (file)
@@ -87,7 +87,8 @@ struct EJQF { /**> Matching field and status */
     const TDBIDX *idx; /**> Column index for this field if exists */
     bson *idxmeta; /**> Index metainfo */
     bson *updateobj; /**> Update bson object for $set and $inc operations */
-    int matchgrp; /**> $elemMatch group number */
+    int elmatchgrp; /**> $elemMatch group id */
+    int elmatchpos; /**> $elemMatch fieldpath position */
     EJDB *jb; /**> Reference to the EJDB during query processing */
     EJQ *q; /**> Query object field embedded into */
 };
index 7ec564c..121765e 100644 (file)
@@ -65,7 +65,7 @@ void testAddData() {
     bson_append_string(&a1, "foo3", "bar3");
     bson_append_finish_object(&a1);
     bson_append_start_object(&a1, "1");
-    bson_append_string(&a1, "foo", "bar1");
+    bson_append_string(&a1, "foo", "bar");
     bson_append_string(&a1, "foo2", "bar3");
     bson_append_finish_object(&a1);
     bson_append_int(&a1, "2", 333);
@@ -3490,7 +3490,7 @@ void testFindInComplexArray() {
 
     //Check matching positional element
     bson_init_as_query(&bsq1);
-    bson_append_string(&bsq1, "complexarr.1.foo", "bar1");
+    bson_append_string(&bsq1, "complexarr.1.foo", "bar");
     bson_finish(&bsq1);
     q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, NULL);
     CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
@@ -3500,7 +3500,7 @@ void testFindInComplexArray() {
     //fprintf(stderr, "\n%s", TCXSTRPTR(log));
     CU_ASSERT_EQUAL(count, 1);
     for (int i = 0; i < TCLISTNUM(q1res); ++i) {
-        CU_ASSERT_FALSE(bson_compare_string("bar1", TCLISTVALPTR(q1res, i), "complexarr.1.foo"));
+        CU_ASSERT_FALSE(bson_compare_string("bar", TCLISTVALPTR(q1res, i), "complexarr.1.foo"));
     }
 
     bson_destroy(&bsq1);
@@ -3601,9 +3601,8 @@ void testFindInComplexArray() {
     ejdbquerydel(q1);
 }
 
-
 void test$elemMatch() {
-    //{complexarr : {$elemMatch : {foo : 'bar', foo2 : 'bar3'}}}
+    //{complexarr : {$elemMatch : {foo : 'bar', foo2 : 'bar2', foo3 : 'bar3'}}}
     EJCOLL *coll = ejdbcreatecoll(jb, "contacts", NULL);
     CU_ASSERT_PTR_NOT_NULL_FATAL(coll);
     bson bsq1;
@@ -3624,8 +3623,52 @@ void test$elemMatch() {
     TCXSTR *log = tcxstrnew();
     TCLIST *q1res = ejdbqryexecute(coll, q1, &count, 0, log);
     CU_ASSERT_PTR_NOT_NULL_FATAL(q1res);
-    fprintf(stderr, "%s", TCXSTRPTR(log));
+    CU_ASSERT_EQUAL(count, 1);
+    //fprintf(stderr, "%s", TCXSTRPTR(log));
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        CU_ASSERT_FALSE(bson_compare_string("bar", TCLISTVALPTR(q1res, i), "complexarr.0.foo"));
+        CU_ASSERT_FALSE(bson_compare_string("bar2", TCLISTVALPTR(q1res, i), "complexarr.0.foo2"));
+        CU_ASSERT_FALSE(bson_compare_string("bar3", TCLISTVALPTR(q1res, i), "complexarr.0.foo3"));
+    }
+    bson_destroy(&bsq1);
+    tclistdel(q1res);
+    tcxstrdel(log);
+    ejdbquerydel(q1);
 
+
+    bson_init_as_query(&bsq1);
+    bson_append_start_object(&bsq1, "complexarr");
+    bson_append_start_object(&bsq1, "$elemMatch");
+    bson_append_string(&bsq1, "foo", "bar");
+    bson_append_string(&bsq1, "foo2", "bar3");
+    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);
+    log = tcxstrnew();
+    q1res = ejdbqryexecute(coll, q1, &count, 0, log);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1res);
+    //fprintf(stderr, "%s", TCXSTRPTR(log));
+    CU_ASSERT_EQUAL(count, 0);
+    bson_destroy(&bsq1);
+    tclistdel(q1res);
+    tcxstrdel(log);
+    ejdbquerydel(q1);
+
+    bson_init_as_query(&bsq1);
+    bson_append_string(&bsq1, "complexarr.foo", "bar");
+    bson_append_string(&bsq1, "complexarr.foo2", "bar3");
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+    q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+    log = tcxstrnew();
+    q1res = ejdbqryexecute(coll, q1, &count, 0, log);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1res);
+    //fprintf(stderr, "%s", TCXSTRPTR(log));
+    CU_ASSERT_EQUAL(count, 1);
     bson_destroy(&bsq1);
     tclistdel(q1res);
     tcxstrdel(log);