#43 #44
authoradam <anton@adamansky.com>
Wed, 16 Jan 2013 08:44:38 +0000 (15:44 +0700)
committeradam <anton@adamansky.com>
Wed, 16 Jan 2013 08:44:38 +0000 (15:44 +0700)
README.md
tcejdb/README
tcejdb/ejdb.c
tcejdb/ejdb.h
tcejdb/ejdb_private.h
tcejdb/testejdb/t2.c

index d9b0a6c..876dcad 100644 (file)
--- a/README.md
+++ b/README.md
@@ -806,7 +806,12 @@ Where
     * ```.dec``` Number index
     * ```.tok``` Array index
 
-Limitations/TODOs
+Limitations
+------------------------------------
+* One ejdb database can handle up to 1024 collections.
+* Indexes for objects in nested arrays currently not supported (#37)
+
+TODO
 ------------------------------------
 * Collect collection index statistic
 * Windows port
index b8204ff..dc7a143 100644 (file)
@@ -1,4 +1,7 @@
-EJDB - Embedded JSON Database engine
+[![EJDB](https://raw.github.com/Softmotions/ejdb/master/misc/ejdblogo3.png)](http://ejdb.org)
+
+
+Embedded JSON Database engine
 ====================================
 
 It aims to be a fast [MongoDB](http://mongodb.org)-like library **which can be embedded into C/C++ and [NodeJS](http://nodejs.org/) applications under terms of LGPL license.**
@@ -157,27 +160,27 @@ Queries
  *
  *  - Supported queries:
  *      - Simple matching of String OR Number OR Array value:
- *          -   {'json.field.path' : 'val', ...}
+ *          -   {'fpath' : '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
+ *          -   {'fpath' : {'$not' : val}} //Field not equal to val
+ *          -   {'fpath' : {'$not' : {'$begin' : prefix}}} //Field not begins with val
  *      - $begin String starts with prefix
- *          -   {'json.field.path' : {'$begin' : prefix}}
+ *          -   {'fpath' : {'$begin' : prefix}}
  *      - $gt, $gte (>, >=) and $lt, $lte for number types:
- *          -   {'json.field.path' : {'$gt' : number}, ...}
+ *          -   {'fpath' : {'$gt' : number}, ...}
  *      - $bt Between for number types:
- *          -   {'json.field.path' : {'$bt' : [num1, num2]}}
+ *          -   {'fpath' : {'$bt' : [num1, num2]}}
  *      - $in String OR Number OR Array val matches to value in specified array:
- *          -   {'json.field.path' : {'$in' : [val1, val2, val3]}}
+ *          -   {'fpath' : {'$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]}}
+ *          -   {'fpath' : {'$strand' : [val1, val2, val3]}}
  *      - $stror String tokens OR String array val matches any token in specified array:
- *          -   {'json.field.path' : {'$stror' : [val1, val2, val3]}}
+ *          -   {'fpath' : {'$stror' : [val1, val2, val3]}}
  *      - $exists Field existence matching:
- *          -   {'json.field.path' : {'$exists' : true|false}}
+ *          -   {'fpath' : {'$exists' : true|false}}
  *      - $icase Case insensitive string matching:
- *          -    {'json.field.path' : {'$icase' : 'val1'}} //icase matching
+ *          -    {'fpath' : {'$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 index of type: `JBIDXISTR`
@@ -188,15 +191,22 @@ Queries
  *  - Queries can be used to update records:
  *       $set Field set operation.
  *           - {.., '$set' : {'field1' : val1, 'fieldN' : valN}}
+ *       $upsert Atomic upsert. If matching records are found it will be '$set' operation,
+ *              otherwise new record will be inserted with fields specified by argment object.
+ *           - {.., '$upsert' : {'field1' : val1, 'fieldN' : valN}}
  *       $inc Increment operation. Only number types are supported.
  *           - {.., '$inc' : {'field1' : number, ...,  'field1' : number}
  *       $dropall In-place record removal operation.
  *           - {.., '$dropall' : true}
  *       $addToSet Atomically adds value to the array only if its not in the array already.
- *                   If containing array is missing it will be created.
- *           - {.., '$addToSet' : {'json.field.path' : val1, 'json.field.pathN' : valN, ...}}
- *       $pull - Atomically removes all occurrences of value from field, if field is an array.
- *           - {.., '$pull' : {'json.field.path' : val1, 'json.field.pathN' : valN, ...}}
+ *                    If containing array is missing it will be created.
+ *           - {.., '$addToSet' : {'fpath' : val1, 'fpathN' : valN, ...}}
+ *       $addToSetAll Batch version if $addToSet
+ *           - {.., '$addToSetAll' : {'fpath' : [array of values to add], ...}}
+ *       $pull Atomically removes all occurrences of value from field, if field is an array.
+ *           - {.., '$pull' : {'fpath' : val1, 'fpathN' : valN, ...}}
+ *       $pullAll Batch version of $pull
+ *           - {.., '$pullAll' : {'fpath' : [array of values to remove], ...}}
  *
  *  NOTE: Negate operations: $not and $nin not using indexes
  *  so they can be slow in comparison to other matching operations.
@@ -208,6 +218,7 @@ Queries
  *      - $skip Number of skipped results in the result set
  *      - $orderby Sorting order of query fields.
  *      - $fields Set subset of fetched fields
+            If a field presented in $orderby clause it will be forced to include in resulting records.
  *          Example:
  *          hints:    {
  *                      "$orderby" : { //ORDER BY field1 ASC, field2 DESC
@@ -240,7 +251,7 @@ You can find some code samples in:
 * [tcejdb/testejdb](https://github.com/Softmotions/ejdb/tree/master/tcejdb/testejdb)
 
 Basic EJDB architecture
--------------------------------
+------------------------------------
 **EJDB database files structure**
 
 ~~~~~~
@@ -262,8 +273,16 @@ Where
     * ```.dec``` Number index
     * ```.tok``` Array index
 
-Limitations/TODOs
+Limitations
+------------------------------------
+* One ejdb database can handle up to 1024 collections.
+* Indexes for objects in nested arrays currently not supported (#37)
+
+TODO
 ------------------------------------
 * Collect collection index statistic
 * Windows port
 
+Related software
+------------------------------------
+[Connect session store backed by EJDB database](https://github.com/Softmotions/connect-session-ejdb)
index f5a8772..85bd30c 100644 (file)
@@ -74,7 +74,6 @@ static char* _bsonipathrowldr(TCLIST *tokens, const char *pkbuf, int pksz, const
         const char *ipath, int ipathsz, void *op, int *vsz);
 static char* _bsonfpathrowldr(TCLIST *tokens, const char *rowdata, int rowdatasz,
         const char *fpath, int fpathsz, void *op, int *vsz);
-static void _ejdbclear(EJDB *jb);
 static bool _createcoldb(const char *colname, EJDB *jb, EJCOLLOPTS *opts, TCTDB** res);
 static bool _addcoldb0(const char *colname, EJDB *jb, EJCOLLOPTS *opts, EJCOLL **res);
 static void _delcoldb(EJCOLL *cdb);
@@ -135,6 +134,7 @@ EJDB_EXPORT const char* ejdberrmsg(int ecode) {
         case JBEQONEEMATCH: return "only one $elemMatch allowed in the fieldpath"; //todo remove
         case JBEQINCEXCL: return "$fields hint cannot mix include and exclude fields";
         case JBEQACTKEY: return "action key in $do block can only be one of: $join";
+        case JBEMAXNUMCOLS: return "exceeded the maximum number of collections per database: 1024";
 
         default: return tcerrmsg(ecode);
     }
@@ -159,8 +159,7 @@ EJDB_EXPORT int ejdbecode(EJDB *jb) {
 
 EJDB_EXPORT EJDB* ejdbnew(void) {
     EJDB *jb;
-    TCMALLOC(jb, sizeof (*jb));
-    _ejdbclear(jb);
+    TCCALLOC(jb, 1, sizeof (*jb));
     jb->metadb = tctdbnew();
     tctdbsetmutex(jb->metadb);
     tctdbsetcache(jb->metadb, 1024, 0, 0);
@@ -178,10 +177,13 @@ EJDB_EXPORT EJDB* ejdbnew(void) {
 EJDB_EXPORT void ejdbdel(EJDB *jb) {
     assert(jb && jb->metadb);
     if (JBISOPEN(jb)) ejdbclose(jb);
-    for (int i = 0; i < jb->cdbsnum; ++i) {
-        _delcoldb(jb->cdbs + i);
+    for (int i = 0; i < EJDB_MAX_COLLECTIONS; ++i) {
+        if (jb->cdbs[i]) {
+            _delcoldb(jb->cdbs[i]);
+            TCFREE(jb->cdbs[i]);
+            jb->cdbs[i] = NULL;
+        }
     }
-    TCFREE(jb->cdbs);
     jb->cdbsnum = 0;
     if (jb->mmtx) {
         pthread_rwlock_destroy(jb->mmtx);
@@ -194,12 +196,14 @@ EJDB_EXPORT void ejdbdel(EJDB *jb) {
 EJDB_EXPORT bool ejdbclose(EJDB *jb) {
     JBENSUREOPENLOCK(jb, true, false);
     bool rv = true;
-    for (int i = 0; i < jb->cdbsnum; ++i) {
-        JBCLOCKMETHOD(jb->cdbs + i, true);
-        if (!tctdbclose(jb->cdbs[i].tdb)) {
-            rv = false;
+    for (int i = 0; i < EJDB_MAX_COLLECTIONS; ++i) {
+        if (jb->cdbs[i]) {
+            JBCLOCKMETHOD(jb->cdbs[i], true);
+            if (!tctdbclose(jb->cdbs[i]->tdb)) {
+                rv = false;
+            }
+            JBCUNLOCKMETHOD(jb->cdbs[i]);
         }
-        JBCUNLOCKMETHOD(jb->cdbs + i);
     }
     if (!tctdbclose(jb->metadb)) {
         rv = false;
@@ -225,7 +229,6 @@ EJDB_EXPORT bool ejdbopen(EJDB *jb, const char *path, int mode) {
     if (!rv) {
         goto finish;
     }
-    TCMALLOC(jb->cdbs, 1);
     jb->cdbsnum = 0;
     TCTDB *mdb = jb->metadb;
     rv = tctdbiterinit(mdb);
@@ -259,10 +262,11 @@ EJDB_EXPORT TCLIST* ejdbgetcolls(EJDB *jb) {
     EJCOLL *coll = NULL;
     JBENSUREOPENLOCK(jb, false, NULL);
     TCLIST *ret = tclistnew2(jb->cdbsnum);
-    for (int i = 0; i < jb->cdbsnum; ++i) {
-        coll = jb->cdbs + i;
-        assert(coll);
-        TCLISTPUSH(ret, coll, sizeof (*coll));
+    for (int i = 0; i < EJDB_MAX_COLLECTIONS; ++i) {
+        coll = jb->cdbs[i];
+        if (coll) {
+            TCLISTPUSH(ret, coll, sizeof (*coll));
+        }
     }
     JBUNLOCKMETHOD(jb);
     return ret;
@@ -305,36 +309,35 @@ EJDB_EXPORT bool ejdbrmcoll(EJDB *jb, const char *colname, bool unlinkfile) {
     if (!coll) {
         goto finish;
     }
-    EJCOLL *cdbs = jb->cdbs;
-    for (int i = 0; i < jb->cdbsnum; ++i) {
-        coll = cdbs + i;
-        if (!strcmp(colname, coll->cname)) {
-            if (!JBCLOCKMETHOD(coll, true)) return false;
-            tctdbout2(jb->metadb, colname);
-            tctdbvanish(coll->tdb);
-            TCLIST *paths = tclistnew2(10);
-            tclistpush2(paths, coll->tdb->hdb->path);
-            for (int j = 0; j < coll->tdb->inum; ++j) {
-                TDBIDX *idx = coll->tdb->idxs + j;
-                const char *ipath = tcbdbpath(idx->db);
-                if (ipath) {
-                    tclistpush2(paths, ipath);
-                }
-            }
-            tctdbclose(coll->tdb);
-            if (unlinkfile) {
-                for (int j = 0; j < TCLISTNUM(paths); ++j) {
-                    unlink(tclistval2(paths, j));
-                }
-            }
-            tclistdel(paths);
-            JBCUNLOCKMETHOD(coll);
-            _delcoldb(coll);
-            jb->cdbsnum--;
-            memmove(cdbs + i, cdbs + i + 1, sizeof (*cdbs) * (jb->cdbsnum - i));
+    if (!JBCLOCKMETHOD(coll, true)) return false;
+    tctdbout2(jb->metadb, colname);
+    tctdbvanish(coll->tdb);
+    TCLIST *paths = tclistnew2(10);
+    tclistpush2(paths, coll->tdb->hdb->path);
+    for (int j = 0; j < coll->tdb->inum; ++j) {
+        TDBIDX *idx = coll->tdb->idxs + j;
+        const char *ipath = tcbdbpath(idx->db);
+        if (ipath) {
+            tclistpush2(paths, ipath);
+        }
+    }
+    tctdbclose(coll->tdb);
+    if (unlinkfile) {
+        for (int i = 0; i < TCLISTNUM(paths); ++i) {
+            unlink(tclistval2(paths, i));
+        }
+    }
+    tclistdel(paths);
+    jb->cdbsnum--;
+    for (int i = 0; i < EJDB_MAX_COLLECTIONS; ++i) {
+        if (jb->cdbs[i] == coll) {
+            jb->cdbs[i] = NULL;
             break;
         }
     }
+    JBCUNLOCKMETHOD(coll);
+    _delcoldb(coll);
+    TCFREE(coll);
 finish:
     JBUNLOCKMETHOD(jb);
     return rv;
@@ -669,15 +672,14 @@ EJDB_EXPORT bool ejdbsyncdb(EJDB *jb) {
     assert(jb);
     JBENSUREOPENLOCK(jb, true, false);
     bool rv = true;
-    EJCOLL *coll = NULL;
-    for (int i = 0; i < jb->cdbsnum; ++i) {
-        coll = jb->cdbs + i;
-        assert(coll);
-        rv = JBCLOCKMETHOD(coll, true);
-        if (!rv) break;
-        rv = tctdbsync(coll->tdb);
-        JBCUNLOCKMETHOD(coll);
-        if (!rv) break;
+    for (int i = 0; i < EJDB_MAX_COLLECTIONS; ++i) {
+        if (jb->cdbs[i]) {
+            rv = JBCLOCKMETHOD(jb->cdbs[i], true);
+            if (!rv) break;
+            rv = tctdbsync(jb->cdbs[i]->tdb);
+            JBCUNLOCKMETHOD(jb->cdbs[i]);
+            if (!rv) break;
+        }
     }
     JBUNLOCKMETHOD(jb);
     return rv;
@@ -772,18 +774,12 @@ static void _ejdbsetecode(EJDB *jb, int ecode, const char *filename, int line, c
 
 static EJCOLL* _getcoll(EJDB *jb, const char *colname) {
     assert(colname);
-    EJCOLL *coll = NULL;
-    //check if collection exists
-    for (int i = 0; i < jb->cdbsnum; ++i) {
-        coll = jb->cdbs + i;
-        assert(coll);
-        if (!strcmp(colname, coll->cname)) {
-            break;
-        } else {
-            coll = NULL;
+    for (int i = 0; i < EJDB_MAX_COLLECTIONS; ++i) {
+        if (jb->cdbs[i] && !strcmp(colname, jb->cdbs[i]->cname)) {
+            return jb->cdbs[i];
         }
     }
-    return coll;
+    return NULL;
 }
 
 /* Set mutual exclusion control of a table database object for threading. */
@@ -4024,15 +4020,23 @@ static void _delcoldb(EJCOLL *jcoll) {
 }
 
 static bool _addcoldb0(const char *cname, EJDB *jb, EJCOLLOPTS *opts, EJCOLL **res) {
+    int i;
     bool rv = true;
     TCTDB *cdb;
+
+    for (i = 0; i < EJDB_MAX_COLLECTIONS && jb->cdbs[i]; ++i);
+    if (i == EJDB_MAX_COLLECTIONS) {
+        _ejdbsetecode(jb, JBEMAXNUMCOLS, __FILE__, __LINE__, __func__);
+        return false;
+    }
     rv = _createcoldb(cname, jb, opts, &cdb);
     if (!rv) {
         *res = NULL;
         return rv;
     }
-    TCREALLOC(jb->cdbs, jb->cdbs, sizeof (jb->cdbs[0]) * (++jb->cdbsnum));
-    EJCOLL *jcoll = jb->cdbs + jb->cdbsnum - 1;
+    EJCOLL *jcoll;
+    TCCALLOC(jcoll, 1, sizeof (*jcoll));
+    jb->cdbs[i] = jcoll;
     jcoll->cname = tcstrdup(cname);
     jcoll->cnamesz = strlen(cname);
     jcoll->tdb = cdb;
@@ -4040,6 +4044,7 @@ static bool _addcoldb0(const char *cname, EJDB *jb, EJCOLLOPTS *opts, EJCOLL **r
     jcoll->mmtx = NULL;
     _ejdbcolsetmutex(jcoll);
     *res = jcoll;
+    ++jb->cdbsnum;
     return rv;
 }
 
@@ -4081,14 +4086,6 @@ static bool _createcoldb(const char *colname, EJDB *jb, EJCOLLOPTS *opts, TCTDB
     return rv;
 }
 
-static void _ejdbclear(EJDB *jb) {
-    assert(jb);
-    jb->cdbs = NULL;
-    jb->cdbsnum = 0;
-    jb->metadb = NULL;
-    jb->mmtx = NULL;
-}
-
 /* Check whether a string includes all tokens in another string.*/
 static bool _qrycondcheckstrand(const char *vbuf, const TCLIST *tokens) {
     assert(vbuf && tokens);
index 92174fa..7f5bccf 100644 (file)
@@ -47,7 +47,8 @@ enum { /** Error codes */
     JBEQUPDFAILED = 9010, /**< Updating failed. */
     JBEQONEEMATCH = 9011, /**< Only one $elemMatch allowed in the fieldpath. */
     JBEQINCEXCL = 9012, /**< $fields hint cannot mix include and exclude fields */
-    JBEQACTKEY = 9013 /**< action key in $do block can only be one of: $join */
+    JBEQACTKEY = 9013, /**< action key in $do block can only be one of: $join */
+    JBEMAXNUMCOLS = 9014 /**< Exceeded the maximum number of collections per database */
 };
 
 enum { /** Database open modes */
index f1beb7b..65ba4b3 100644 (file)
@@ -24,12 +24,8 @@ EJDB_EXTERN_C_START
                                           atype == BSON_INT || atype == BSON_LONG || atype == BSON_DOUBLE || \
                                           atype == BSON_ARRAY)
 
-struct EJDB {
-    EJCOLL *cdbs; /*> Collection DBs for JSON collections. */
-    int cdbsnum; /*> Count of collection DB. */
-    TCTDB *metadb; /*> Metadata DB. */
-    void *mmtx; /*> Mutex for method */
-};
+#define EJDB_MAX_COLLECTIONS 1024
+
 
 struct EJCOLL { /**> EJDB Collection. */
     char *cname; /**> Collection name. */
@@ -39,6 +35,13 @@ struct EJCOLL { /**> EJDB Collection. */
     void *mmtx; /*> Mutex for method */
 };
 
+struct EJDB {
+    EJCOLL * cdbs[EJDB_MAX_COLLECTIONS]; /*> Collection DBs for JSON collections. */
+    int cdbsnum; /*> Count of collection DB. */
+    TCTDB *metadb; /*> Metadata DB. */
+    void *mmtx; /*> Mutex for method */
+};
+
 enum { /**> Query field flags */
     // Comparison flags
     EJCOMPGT = 1, /**> Comparison GT */
@@ -59,15 +62,14 @@ enum { /**> Query field flags */
     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 */
-    EJCONDALL  = 1 << 15, /**> 'All' modificator for $pull or $addToSet ($addToSetAll or $pullAll) */
+    EJCONDALL = 1 << 15, /**> 'All' modificator for $pull or $addToSet ($addToSetAll or $pullAll) */
     EJCONDOIT = 1 << 16 /**> $do query field operation */
 };
 
-
 enum { /**> Query flags */
     EJQINTERNAL = 1, /**> Internal query object used in _ejdbqryexecute */
     EJQUPDATING = 1 << 1, /**> Query in updating mode */
-    EJQDROPALL =  1 << 2, /**> Drop bson object if matched */
+    EJQDROPALL = 1 << 2, /**> Drop bson object if matched */
     EJQONLYCOUNT = 1 << 3 /**> Only count mode */
 };
 
@@ -110,7 +112,7 @@ struct EJQ { /**> Query object. */
 
     //Temporal buffers used during query processing
     TCXSTR *colbuf; /**> TCTDB current column buffer */
-    TCXSTR *bsbuf;  /**> current bson object */
+    TCXSTR *bsbuf; /**> current bson object */
 };
 
 #define JDBCOLBSON "$"  /**> TCDB colname with BSON byte data */
index 33c9989..4ea59c7 100644 (file)
@@ -4049,8 +4049,55 @@ void testTicket38() {
     tclistdel(q1res);
 }
 
-
 void testTicket43() {
+
+    EJCOLL *coll = ejdbcreatecoll(jb, "ticket43", NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(coll);
+
+    EJCOLL *rcoll = ejdbcreatecoll(jb, "ticket43_refs", NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(rcoll);
+
+    bson a1;
+    bson_oid_t oid;
+    bson_oid_t ref_oids[2];
+    char xoid[25];
+
+    bson_init(&a1);
+    bson_append_string(&a1, "name", "n1");
+    bson_finish(&a1);
+    CU_ASSERT_FALSE_FATAL(a1.err);
+    CU_ASSERT_TRUE_FATAL(ejdbsavebson(rcoll, &a1, &ref_oids[0]));
+    bson_destroy(&a1);
+
+    bson_init(&a1);
+    bson_append_string(&a1, "name", "n2");
+    bson_finish(&a1);
+    CU_ASSERT_FALSE_FATAL(a1.err);
+    CU_ASSERT_TRUE_FATAL(ejdbsavebson(rcoll, &a1, &ref_oids[1]));
+    bson_destroy(&a1);
+
+    bson_init(&a1);
+    bson_append_string(&a1, "name", "c1");
+    bson_oid_to_string(&ref_oids[0], xoid);
+    bson_append_string(&a1, "refs", xoid);
+    bson_finish(&a1);
+    CU_ASSERT_FALSE_FATAL(a1.err);
+    CU_ASSERT_TRUE_FATAL(ejdbsavebson(coll, &a1, &oid));
+    bson_destroy(&a1);
+
+    bson_init(&a1);
+    bson_append_string(&a1, "name", "c2");
+    bson_append_start_array(&a1, "arrefs");
+    bson_oid_to_string(&ref_oids[0], xoid);
+    bson_append_string(&a1, "0", xoid);
+    bson_append_oid(&a1, "1", &ref_oids[1]);
+    bson_append_finish_array(&a1);
+    bson_finish(&a1);
+    CU_ASSERT_FALSE_FATAL(a1.err);
+    CU_ASSERT_TRUE_FATAL(ejdbsavebson(coll, &a1, &oid));
+    //bson_print_raw(stderr, bson_dat, 0);
+    bson_destroy(&a1);
+
     /*
      Assuming fpath contains object id (or its string representation).
      In query results fpath values will be replaced by loaded bson
@@ -4059,14 +4106,14 @@ void testTicket43() {
      {..., $do : {fpath : {$join : 'collectionname'}} }
      Second form:
      {..., $do : {fpath : {$join : {$ref : 'collectionname', $fields : {foo:1}} }} }
-    */
+     */
 
     bson bsq1;
     bson_init_as_query(&bsq1);
     bson_append_start_object(&bsq1, "$do");
-    bson_append_start_object(&bsq1, "homeref");
+    bson_append_start_object(&bsq1, "refs");
     bson_append_start_object(&bsq1, "$join");
-    bson_append_string(&bsq1, "$join", "home");
+    bson_append_string(&bsq1, "$join", "ticket43_refcoll");
     bson_append_finish_object(&bsq1);
     bson_append_finish_object(&bsq1);
     bson_append_finish_object(&bsq1);
@@ -4077,7 +4124,6 @@ void testTicket43() {
     EJQ *q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, NULL);
     CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
     ejdbquerydel(q1);
-
     bson_destroy(&bsq1);
 }