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
- {'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:
* 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:
*
* $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
{
"name" : "ejdb",
- "version" : "1.0.32",
+ "version" : "1.0.33",
"main" : "node/ejdb.js",
"homepage" : "http://ejdb.org",
"description" : "EJDB - Embedded JSON Database engine",
#! /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.
# 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=''
# 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]...
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
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.
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 $@
# 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
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\\"
test -n "$LDFLAGS" && MYLDFLAGS="$LDFLAGS $MYLDFLAGS"
# Package name
-AC_INIT(tcejdb, 1.0.32)
+AC_INIT(tcejdb, 1.0.33)
# Package information
MYLIBVER=9
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);
}
}
#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 =)
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) {
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;
}
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;
//
//} _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;
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) {
} 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;
}
}
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:
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 */
* 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:
*
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 */
};
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);
//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);
//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);
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;
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);