#38
authoradam <anton@adamansky.com>
Mon, 14 Jan 2013 11:33:36 +0000 (18:33 +0700)
committeradam <anton@adamansky.com>
Mon, 14 Jan 2013 11:33:36 +0000 (18:33 +0700)
node/nodejs
tcejdb/bson.c
tcejdb/bson.h
tcejdb/ejdb.c
tcejdb/ejdb_private.h
tcejdb/testejdb/t2.c

index 27ed8b0..c50c33e 160000 (submodule)
@@ -1 +1 @@
-Subproject commit 27ed8b05d1b0aed074d3363d901a206d007b28bc
+Subproject commit c50c33e9397d7a0a8717e8ce7530572907c054ad
index 3b1f637..2a91390 100644 (file)
@@ -25,6 +25,7 @@
 #include "bson.h"
 #include "encoding.h"
 #include "myconf.h"
+#include "tcutil.h"
 
 const int initialBufferSize = 128;
 
@@ -372,10 +373,6 @@ EJDB_EXPORT void bson_visit_fields(bson_iterator *it, bson_traverse_flags_t flag
     bson_visit_fields_impl(flags, pstack, 0, it, visitor, op);
 }
 
-
-
-
-
 static bson_type bson_find_fieldpath_value_impl(char* pstack, int curr, FFPCTX *ffpctx, bson_iterator *it) {
     int i;
     bson_type t;
@@ -1255,23 +1252,23 @@ EJDB_EXPORT int bson_numstrn(char *str, int maxbuf, int64_t i) {
     }
 }
 
-static bson_bool_t bson_isnumstr(const char *str, int len){
-  assert(str);
-  bool isnum = false;
-  while(len > 0 && *str > '\0' && *str <= ' '){
-    str++;
-    len--;
-  }
-  while(len > 0 && *str >= '0' && *str <= '9'){
-    isnum = true;
-    str++;
-    len--;
-  }
-  while(len > 0 && *str > '\0' && *str <= ' '){
-    str++;
-    len--;
-  }
-  return (isnum && (*str == '\0' || len == 0));
+static bson_bool_t bson_isnumstr(const char *str, int len) {
+    assert(str);
+    bool isnum = false;
+    while (len > 0 && *str > '\0' && *str <= ' ') {
+        str++;
+        len--;
+    }
+    while (len > 0 && *str >= '0' && *str <= '9') {
+        isnum = true;
+        str++;
+        len--;
+    }
+    while (len > 0 && *str > '\0' && *str <= ' ') {
+        str++;
+        len--;
+    }
+    return (isnum && (*str == '\0' || len == 0));
 }
 
 EJDB_EXPORT void bson_swap_endian64(void *outp, const void *inp) {
@@ -1614,13 +1611,13 @@ EJDB_EXPORT bson* bson_create_from_buffer2(bson *rv, const void* buf, int bufsz)
     return rv;
 }
 
-EJDB_EXPORT bool bson_find_merged_array_sets(const void *mbuf, const void *inbuf) {
+EJDB_EXPORT bool bson_find_merged_array_sets(const void *mbuf, const void *inbuf, bool expandall) {
     assert(mbuf && inbuf);
     bool found = false;
     bson_iterator it, it2;
     bson_type bt, bt2;
     bson_iterator_from_buffer(&it, mbuf);
-    while ((bt = bson_iterator_next(&it)) != BSON_EOO) {
+    while (!found && (bt = bson_iterator_next(&it)) != BSON_EOO) {
         bson_iterator_from_buffer(&it2, inbuf);
         bt2 = bson_find_fieldpath_value(bson_iterator_key(&it), &it2);
         if (bt2 != BSON_ARRAY) {
@@ -1628,15 +1625,26 @@ EJDB_EXPORT bool bson_find_merged_array_sets(const void *mbuf, const void *inbuf
         }
         bson_iterator sit;
         bson_iterator_subiterator(&it2, &sit);
-        while ((bt2 = bson_iterator_next(&sit)) != BSON_EOO) {
-            if (!bson_compare_it_current(&sit, &it)) {
-                found = true;
-                break;
+        while (!found && (bt2 = bson_iterator_next(&sit)) != BSON_EOO) {
+            if (expandall) {
+                if (bt2 != BSON_ARRAY) {
+                    continue;
+                }
+                bson_iterator sit2;
+                bson_iterator_subiterator(&sit, &sit2);
+                while ((bt2 = bson_iterator_next(&sit2)) != BSON_EOO) {
+                    if (!bson_compare_it_current(&sit, &it)) {
+                        found = true;
+                        break;
+                    }
+                }
+            } else {
+                if (!bson_compare_it_current(&sit, &it)) {
+                    found = true;
+                    break;
+                }
             }
         }
-        if (found) {
-            break;
-        }
     }
     return found;
 }
@@ -1654,7 +1662,7 @@ EJDB_EXPORT bool bson_find_unmerged_array_sets(const void *mbuf, const void *inb
             allfound = false;
             break;
         }
-        if (bt2 != BSON_ARRAY) { //some other field
+        if (bt2 != BSON_ARRAY) { //not an array field
             continue;
         }
         allfound = false;
@@ -1679,6 +1687,7 @@ typedef struct {
     int mfields;
     int ecode;
     bool duty;
+    bool expandall;
 } BSON_MASETS_CTX;
 
 static bson_visitor_cmd_t bson_merge_array_sets_pull_tf(const char *fpath, int fpathlen, const char *key, int keylen, const bson_iterator *it, bool after, void *op) {
@@ -1686,9 +1695,9 @@ static bson_visitor_cmd_t bson_merge_array_sets_pull_tf(const char *fpath, int f
     assert(ctx && ctx->mfields >= 0);
     bson_iterator mit;
     bson_type bt = bson_iterator_type(it);
-    if (bt != BSON_OBJECT && bt != BSON_ARRAY) { //simple primitive case
+    if (bt != BSON_OBJECT && bt != BSON_ARRAY) { //trivial case
         if (after) {
-            return BSON_VCMD_OK;
+            return (BSON_VCMD_OK);
         }
         bson_append_field_from_iterator(it, ctx->bsout);
         return (BSON_VCMD_SKIP_AFTER);
@@ -1696,7 +1705,7 @@ static bson_visitor_cmd_t bson_merge_array_sets_pull_tf(const char *fpath, int f
     if (bt == BSON_ARRAY) {
         bson_iterator_from_buffer(&mit, ctx->mbuf);
         bt = bson_find_fieldpath_value2(fpath, fpathlen, &mit);
-        if (bt == BSON_EOO) {
+        if (bt == BSON_EOO || (ctx->expandall && bt != BSON_ARRAY)) {
             bson_append_field_from_iterator(it, ctx->bsout);
             return (BSON_VCMD_SKIP_NESTED | BSON_VCMD_SKIP_AFTER);
         }
@@ -1708,13 +1717,26 @@ static bson_visitor_cmd_t bson_merge_array_sets_pull_tf(const char *fpath, int f
         bson_iterator_subiterator(it, &ait);
         bson_append_start_array(ctx->bsout, key);
         int c = 0;
+        bool found = false;
         while ((bt = bson_iterator_next(&ait)) != BSON_EOO) {
-            if (!bson_compare_it_current(&ait, &mit)) {
-                continue;
+            found = false;
+            if (ctx->expandall) {
+                bson_iterator mitsub;
+                bson_iterator_subiterator(&mit, &mitsub);
+                while ((bt = bson_iterator_next(&mitsub)) != BSON_EOO) {
+                    if (!bson_compare_it_current(&ait, &mitsub)) {
+                        found = true;
+                        break;
+                    }
+                }
+            } else {
+                found = !bson_compare_it_current(&ait, &mit);
+            }
+            if (!found) {
+                char kbuf[TCNUMBUFSIZ];
+                bson_numstrn(kbuf, TCNUMBUFSIZ, c++);
+                bson_append_field_from_iterator2(kbuf, &ait, ctx->bsout);
             }
-            char kbuf[TCNUMBUFSIZ];
-            bson_numstrn(kbuf, TCNUMBUFSIZ, c++);
-            bson_append_field_from_iterator2(kbuf, &ait, ctx->bsout);
         }
         bson_append_finish_array(ctx->bsout);
         return (BSON_VCMD_SKIP_NESTED | BSON_VCMD_SKIP_AFTER);
@@ -1734,9 +1756,9 @@ static bson_visitor_cmd_t bson_merge_array_sets_tf(const char *fpath, int fpathl
     bson_iterator mit;
     bson_type bt = bson_iterator_type(it);
 
-    if (bt != BSON_OBJECT && bt != BSON_ARRAY) { //simple primitive case
+    if (bt != BSON_OBJECT && bt != BSON_ARRAY) { //trivial case
         if (after) {
-            return BSON_VCMD_OK;
+            return (BSON_VCMD_OK);
         }
         bson_append_field_from_iterator(it, ctx->bsout);
         return (BSON_VCMD_SKIP_AFTER);
@@ -1744,7 +1766,7 @@ static bson_visitor_cmd_t bson_merge_array_sets_tf(const char *fpath, int fpathl
     if (bt == BSON_ARRAY) {
         bson_iterator_from_buffer(&mit, ctx->mbuf);
         bt = bson_find_fieldpath_value2(fpath, fpathlen, &mit);
-        if (bt == BSON_EOO) {
+        if (bt == BSON_EOO || (ctx->expandall && bt != BSON_ARRAY)) {
             bson_append_field_from_iterator(it, ctx->bsout);
             return (BSON_VCMD_SKIP_NESTED | BSON_VCMD_SKIP_AFTER);
         }
@@ -1757,18 +1779,44 @@ static bson_visitor_cmd_t bson_merge_array_sets_tf(const char *fpath, int fpathl
         bson_append_start_array(ctx->bsout, key);
         bool found = false;
         int c = 0;
-        while ((bt = bson_iterator_next(&ait)) != BSON_EOO) {
-            if (!found && !bson_compare_it_current(&ait, &mit)) {
-                found = true;
+        if (ctx->expandall) { //Set of array elements to add
+            while ((bt = bson_iterator_next(&ait)) != BSON_EOO) { //Flush current array
+                bson_append_field_from_iterator(&ait, ctx->bsout);
+                ++c;
+            }
+            //Iterate over set to add
+            bson_iterator mitsub;
+            bson_iterator_subiterator(&mit, &mitsub); //mit has BSON_ARRAY type
+            while ((bt = bson_iterator_next(&mitsub)) != BSON_EOO) {
+                found = false;
+                bson_iterator_subiterator(it, &ait); //Rewind main array iterator
+                while ((bt = bson_iterator_next(&ait)) != BSON_EOO) {
+                    if (!bson_compare_it_current(&ait, &mitsub)) {
+                        found = true;
+                        break;
+                    }
+                }
+                if (!found) { //Append missing element
+                    char kbuf[TCNUMBUFSIZ];
+                    bson_numstrn(kbuf, TCNUMBUFSIZ, c++);
+                    bson_append_field_from_iterator2(kbuf, &mitsub, ctx->bsout);
+                    ctx->duty = true;
+                }
+            }
+        } else { //Single element to add
+            while ((bt = bson_iterator_next(&ait)) != BSON_EOO) {
+                if (!found && !bson_compare_it_current(&ait, &mit)) {
+                    found = true;
+                }
+                bson_append_field_from_iterator(&ait, ctx->bsout);
+                ++c;
+            }
+            if (!found) { //uppend missing element into array
+                char kbuf[TCNUMBUFSIZ];
+                bson_numstrn(kbuf, TCNUMBUFSIZ, c);
+                bson_append_field_from_iterator2(kbuf, &mit, ctx->bsout);
+                ctx->duty = true;
             }
-            bson_append_field_from_iterator(&ait, ctx->bsout);
-            ++c;
-        }
-        if (!found) { //uppend missing element into array
-            char kbuf[TCNUMBUFSIZ];
-            bson_numstrn(kbuf, TCNUMBUFSIZ, c);
-            bson_append_field_from_iterator2(kbuf, &mit, ctx->bsout);
-            ctx->duty = true;
         }
         bson_append_finish_array(ctx->bsout);
         return (BSON_VCMD_SKIP_NESTED | BSON_VCMD_SKIP_AFTER);
@@ -1783,18 +1831,19 @@ static bson_visitor_cmd_t bson_merge_array_sets_tf(const char *fpath, int fpathl
 
 }
 
-EJDB_EXPORT int bson_merge_array_sets(const void *mbuf, const void *inbuf, bool pull, bson *bsout) {
+EJDB_EXPORT int bson_merge_array_sets(const void *mbuf, const void *inbuf, bool pull, bool expandall, bson *bsout) {
     assert(mbuf && inbuf && bsout);
     if (bsout->finished) {
         return BSON_ERROR;
     }
-    BSON_MASETS_CTX ctx;
-    ctx.bsout = bsout;
-    ctx.mbuf = mbuf;
-    ctx.mfields = 0;
-    ctx.duty = false;
-    ctx.ecode = BSON_OK;
-
+    BSON_MASETS_CTX ctx = {
+        .bsout = bsout,
+        .mbuf = mbuf,
+        .mfields = 0,
+        .duty = false,
+        .expandall = expandall,
+        .ecode = BSON_OK
+    };
     bson_type bt, bt2;
     bson_iterator it, it2;
     bson_iterator_from_buffer(&it, mbuf);
index 726ac25..98ddc23 100644 (file)
@@ -181,8 +181,6 @@ EJDB_EXPORT bson_type bson_find(bson_iterator *it, const bson *obj, const char *
 
 EJDB_EXPORT bson_type bson_find_from_buffer(bson_iterator *it, const char *buffer, const char *name);
 
-
-
 typedef struct { /**< Find field path context */
     const char* fpath;
     int fplen;
@@ -1142,8 +1140,8 @@ EJDB_EXPORT bson* bson_create_from_buffer(const void *buf, int bufsz);
 EJDB_EXPORT bson* bson_create_from_buffer2(bson *bs, const void *buf, int bufsz);
 
 EJDB_EXPORT bool bson_find_unmerged_array_sets(const void *mbuf, const void *inbuf);
-EJDB_EXPORT bool bson_find_merged_array_sets(const void *mbuf, const void *inbuf);
-EJDB_EXPORT int bson_merge_array_sets(const void *mbuf, const void *inbuf, bool pull, bson *bsout);
+EJDB_EXPORT bool bson_find_merged_array_sets(const void *mbuf, const void *inbuf, bool expandall);
+EJDB_EXPORT int bson_merge_array_sets(const void *mbuf, const void *inbuf, bool pull, bool expandall, bson *bsout);
 
 
 EJDB_EXTERN_C_END
index 6f24ae6..b508d3c 100644 (file)
@@ -634,7 +634,7 @@ EJDB_EXPORT TCLIST* ejdbqryexecute(EJCOLL *jcoll, const EJQ *q, uint32_t *count,
         _ejdbsetecode(jcoll->jb, TCEINVALID, __FILE__, __LINE__, __func__);
         return NULL;
     }
-    JBCLOCKMETHOD(jcoll, (q->flags & EJQUPDATING) ? true : false);    
+    JBCLOCKMETHOD(jcoll, (q->flags & EJQUPDATING) ? true : false);
     _ejdbsetecode(jcoll->jb, TCESUCCESS, __FILE__, __LINE__, __func__);
     if (ejdbecode(jcoll->jb) != TCESUCCESS) { //we are not in fatal state
         JBCUNLOCKMETHOD(jcoll);
@@ -1696,28 +1696,38 @@ static bool _qryupdate(EJCOLL *jcoll, const EJQ *ejq, void *bsbuf, int bsbufsz,
     bsout.dataSize = 0;
     bson_reset(&bsout);
 
-    const EJQF *setqf = NULL;
-    const EJQF *incqf = NULL;
-    const EJQF *addsetqf = NULL;
-    const EJQF *pullqf = NULL;
+    const EJQF *setqf = NULL; /*$set*/
+    const EJQF *incqf = NULL; /*$inc*/
+    const EJQF * addsetqf[2] = {NULL}; /*$addToSet, $addToSetAll*/
+    const EJQF * pullqf[2] = {NULL}; /*$pull, $pullAll*/
 
-    //$set, $inc, $addToSet, $pull operations
+    //$set, $inc, $addToSet, $addToSetAll, $pull, $pullAll operations
     for (int i = 0; i < TCLISTNUM(ejq->qobjlist); ++i) {
         const EJQF *qf = TCLISTVALPTR(ejq->qobjlist, i);
         if (qf->updateobj == NULL) {
             continue;
         }
-        if (!setqf && qf->flags & EJCONDSET) { //$set
+        if (qf->flags & EJCONDSET) { //$set
             setqf = qf;
-        } else if (!incqf && qf->flags & EJCONDINC) {
+            continue;
+        }
+        if (qf->flags & EJCONDINC) { //$inc
             incqf = qf;
-        } else if (!addsetqf && qf->flags & EJCONDADDSET) {
-            addsetqf = qf;
-        } else if (!pullqf && qf->flags & EJCONDPULL) {
-            pullqf = qf;
-        } else {
-            assert(0);
-            break;
+            continue;
+        }
+        if (qf->flags & EJCONDADDSET) { //$addToSet, $addToSetAll
+            if (qf->flags & EJCONDALL) {
+                addsetqf[1] = qf;
+            } else {
+                addsetqf[0] = qf;
+            }
+        }
+        if (qf->flags & EJCONDPULL) { //$pull, $pullAll
+            if (qf->flags & EJCONDALL) {
+                pullqf[1] = qf;
+            } else {
+                pullqf[0] = qf;
+            }
         }
     }
     if (setqf) { //$set
@@ -1770,14 +1780,18 @@ static bool _qryupdate(EJCOLL *jcoll, const EJQ *ejq, void *bsbuf, int bsbufsz,
                 update = true;
             }
         }
+
+        if (!rv) {
+            goto finish;
+        }
     }
-    if (!rv) {
-        goto finish;
-    }
 
-    if (pullqf) { //$pull
+
+    for (int i = 0; i < 2; ++i) { //$pull $pullAll
+        const EJQF *qf = pullqf[i];
+        if (!qf) continue;
         char* inbuf = (bsout.finished) ? bsout.data : bsbuf;
-        if (bson_find_merged_array_sets(bson_data(pullqf->updateobj), inbuf)) {
+        if (bson_find_merged_array_sets(bson_data(qf->updateobj), inbuf, (qf->flags & EJCONDALL))) {
             if (bsout.finished) {
                 //reinit `bsout`, `inbuf` already points to `bsout.data` and will be freed later
                 bson_init_size(&bsout, bson_size(&bsout));
@@ -1785,8 +1799,8 @@ static bool _qryupdate(EJCOLL *jcoll, const EJQ *ejq, void *bsbuf, int bsbufsz,
                 assert(bsout.data == NULL);
                 bson_init_size(&bsout, bsbufsz);
             }
-            //$pull merge
-            if (bson_merge_array_sets(bson_data(pullqf->updateobj), inbuf, true, &bsout)) {
+            //$pull $pullAll merge
+            if (bson_merge_array_sets(bson_data(qf->updateobj), inbuf, true, (qf->flags & EJCONDALL), &bsout)) {
                 rv = false;
                 _ejdbsetecode(jcoll->jb, JBEQUPDFAILED, __FILE__, __LINE__, __func__);
             }
@@ -1796,14 +1810,16 @@ static bool _qryupdate(EJCOLL *jcoll, const EJQ *ejq, void *bsbuf, int bsbufsz,
             bson_finish(&bsout);
             update = true;
         }
-    }
-    if (!rv) {
-        goto finish;
+        if (!rv) {
+            goto finish;
+        }
     }
 
-    if (addsetqf) { //$addToSet
+    for (int i = 0; i < 2; ++i) { //$addToSet $addToSetAll
+        const EJQF *qf = addsetqf[i];
+        if (!qf) continue;
         char* inbuf = (bsout.finished) ? bsout.data : bsbuf;
-        if (bson_find_unmerged_array_sets(bson_data(addsetqf->updateobj), inbuf)) {
+        if ((qf->flags & EJCONDALL) || bson_find_unmerged_array_sets(bson_data(qf->updateobj), inbuf)) {
             //Missing $addToSet element in some array field found
             if (bsout.finished) {
                 //reinit `bsout`, `inbuf` already points to `bsout.data` and will be freed later
@@ -1812,8 +1828,8 @@ static bool _qryupdate(EJCOLL *jcoll, const EJQ *ejq, void *bsbuf, int bsbufsz,
                 assert(bsout.data == NULL);
                 bson_init_size(&bsout, bsbufsz);
             }
-            //$addToSet merge
-            if (bson_merge_array_sets(bson_data(addsetqf->updateobj), inbuf, false, &bsout)) {
+            //$addToSet $addToSetAll merge
+            if (bson_merge_array_sets(bson_data(qf->updateobj), inbuf, false, (qf->flags & EJCONDALL), &bsout)) {
                 rv = false;
                 _ejdbsetecode(jcoll->jb, JBEQUPDFAILED, __FILE__, __LINE__, __func__);
             }
@@ -1823,12 +1839,12 @@ static bool _qryupdate(EJCOLL *jcoll, const EJQ *ejq, void *bsbuf, int bsbufsz,
             bson_finish(&bsout);
             update = true;
         }
+        if (!update || !rv) {
+            goto finish;
+        }
     }
 
-    if (!update || !rv) {
-        goto finish;
-    }
-    if (bsout.err) {
+    if (bsout.err) { //Resulting BSON is OK?
         rv = false;
         _ejdbsetecode(jcoll->jb, JBEQUPDFAILED, __FILE__, __LINE__, __func__);
         goto finish;
@@ -3217,8 +3233,10 @@ static int _parse_qobj_impl(EJDB *jb, EJQ *q, bson_iterator *it, TCLIST *qlist,
                     !strcmp("$inc", fkey) ||
                     !strcmp("$dropall", fkey) ||
                     !strcmp("$addToSet", fkey) ||
+                    !strcmp("$addToSetAll", fkey) ||
                     !strcmp("$upsert", fkey) ||
-                    !strcmp("$pull", fkey)) {
+                    !strcmp("$pull", fkey) ||
+                    !strcmp("$pullAll", fkey)) {
                 if (pqf) { //Top level ops
                     ret = JBEQERROR;
                     _ejdbsetecode(jb, ret, __FILE__, __LINE__, __func__);
@@ -3332,6 +3350,12 @@ static int _parse_qobj_impl(EJDB *jb, EJQ *q, bson_iterator *it, TCLIST *qlist,
                         } else if (!strcmp("$upsert", fkey)) {
                             qf.flags |= EJCONDSET;
                             qf.flags |= EJCONDUPSERT;
+                        } else if (!strcmp("$addToSetAll", fkey)) {
+                            qf.flags |= EJCONDADDSET;
+                            qf.flags |= EJCONDALL;
+                        } else if (!strcmp("$pullAll", fkey)) {
+                            qf.flags |= EJCONDPULL;
+                            qf.flags |= EJCONDALL;
                         }
                     }
                     if ((qf.flags & (EJCONDSET | EJCONDINC | EJCONDADDSET | EJCONDPULL))) {
@@ -3343,6 +3367,10 @@ static int _parse_qobj_impl(EJDB *jb, EJQ *q, bson_iterator *it, TCLIST *qlist,
                         bson_iterator sit;
                         bson_iterator_subiterator(it, &sit);
                         while ((sbt = bson_iterator_next(&sit)) != BSON_EOO) {
+                            if ((qf.flags & EJCONDALL) && sbt != BSON_ARRAY) {
+                                //$addToSetAll & $pullAll accepts arrays only as argument
+                                continue;
+                            }
                             bson_append_field_from_iterator(&sit, qf.updateobj);
                         }
                         bson_finish(qf.updateobj);
index eda3eca..452e211 100644 (file)
@@ -58,7 +58,8 @@ enum { /**> Query field flags */
     EJCONDINC = 1 << 11, /**> $inc Inc field update operation */
     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 */
+    EJCONDUPSERT = 1 << 14, /**> $upsert Upsert $set operation */
+    EJCONDALL  = 1 << 15 /**> 'All' modificator for $pull or $addToSet ($addToSetAll or $pullAll) */
 };
 
 
index affa3f1..932965d 100644 (file)
@@ -3366,8 +3366,14 @@ void test$addToSet() {
     bson_append_finish_object(&bsq1);
     bson_append_start_object(&bsq1, "$addToSet");
     bson_append_string(&bsq1, "personal.tags", "tag3");
-    bson_append_string(&bsq1, "labels", "red");
-    bson_append_finish_object(&bsq1);
+    bson_append_finish_object(&bsq1); //EOF $addToSet
+    bson_append_start_object(&bsq1, "$addToSetAll");
+    bson_append_start_array(&bsq1, "labels");
+    bson_append_string(&bsq1, "0", "red");
+    bson_append_string(&bsq1, "1", "black");
+    bson_append_string(&bsq1, "2", "green");
+    bson_append_finish_array(&bsq1);
+    bson_append_finish_object(&bsq1); //EOF $addToSetAll
     bson_finish(&bsq1);
     CU_ASSERT_FALSE_FATAL(bsq1.err);
 
@@ -3403,6 +3409,8 @@ void test$addToSet() {
         CU_ASSERT_FALSE(bson_compare_string("tag3", TCLISTVALPTR(q1res, i), "personal.tags.2"));
         CU_ASSERT_FALSE(bson_compare_string("green", TCLISTVALPTR(q1res, i), "labels.0"));
         CU_ASSERT_FALSE(bson_compare_string("red", TCLISTVALPTR(q1res, i), "labels.1"));
+        CU_ASSERT_FALSE(bson_compare_string("black", TCLISTVALPTR(q1res, i), "labels.2"));
+        CU_ASSERT_TRUE(bson_compare_string("green", TCLISTVALPTR(q1res, i), "labels.3"));
     }
 
     bson_destroy(&bsq1);