UnQL

Check-in [f00265864e]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Allow correlated references to list variables in the outer query to be used as data sources in scalar sub-queries.
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: f00265864ec56024bd2590e84f1d8cf46283e635
User & Date: dan 2011-07-28 18:47:38
Context
2011-07-28
19:32
Avoid concatenating multiple "syntax error" messages together. check-in: 2dd4975e9c user: dan tags: trunk
18:47
Allow correlated references to list variables in the outer query to be used as data sources in scalar sub-queries. check-in: f00265864e user: dan tags: trunk
16:43
Add optional ASYNCHRONOUS and SYNCHRONOUS keywords in front of INSERT. check-in: 0030b61e78 user: drh tags: trunk
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to src/datasrc.c.

    18     18   */
    19     19   #include "xjd1Int.h"
    20     20   
    21     21   
    22     22   /*
    23     23   ** Called after statement parsing to initalize every DataSrc object.
    24     24   */
    25         -int xjd1DataSrcInit(DataSrc *p, Query *pQuery){
           25  +int xjd1DataSrcInit(DataSrc *p, Query *pQuery, void *pOuterCtx){
    26     26     int rc = XJD1_OK;
    27     27     p->pQuery = pQuery;
    28     28     switch( p->eDSType ){
    29     29       case TK_COMMA: {
    30         -      xjd1DataSrcInit(p->u.join.pLeft, pQuery);
    31         -      xjd1DataSrcInit(p->u.join.pRight, pQuery);
           30  +      xjd1DataSrcInit(p->u.join.pLeft, pQuery, pOuterCtx);
           31  +      xjd1DataSrcInit(p->u.join.pRight, pQuery, pOuterCtx);
    32     32         break;
    33     33       }
    34     34       case TK_SELECT: {
    35     35         xjd1QueryInit(p->u.subq.q, pQuery->pStmt, 0);
    36     36         break;
    37     37       }
    38     38       case TK_ID: {
................................................................................
    39     39         char *zSql = sqlite3_mprintf("SELECT x FROM \"%w\"", p->u.tab.zName);
    40     40         sqlite3_prepare_v2(pQuery->pStmt->pConn->db, zSql, -1, 
    41     41                            &p->u.tab.pStmt, 0);
    42     42         sqlite3_free(zSql);
    43     43         break;
    44     44       }
    45     45       case TK_FLATTENOP: {
    46         -      xjd1DataSrcInit(p->u.flatten.pNext, pQuery);
           46  +      xjd1DataSrcInit(p->u.flatten.pNext, pQuery, pOuterCtx);
           47  +      break;
           48  +    }
           49  +    case TK_DOT: {
           50  +      rc = xjd1ExprInit(p->u.path.pPath, pQuery->pStmt, 0, 0, pOuterCtx);
    47     51         break;
    48     52       }
    49     53       case TK_NULL:                 /* Initializing a NULL DS is a no-op */
    50     54         assert( p->u.null.isDone==0 );
    51     55         break;
    52     56     }
    53     57     return rc;
................................................................................
   354    358           if( rc==XJD1_ROW ) {
   355    359             xjd1DataSrcRewind(p->u.join.pRight);
   356    360             rc = xjd1DataSrcStep(p->u.join.pRight);
   357    361           }
   358    362         }
   359    363         break;
   360    364       }
          365  +
   361    366       case TK_SELECT: {
   362    367         xjd1JsonFree(p->pValue);
   363    368         p->pValue = 0;
   364    369         rc = xjd1QueryStep(p->u.subq.q);
   365    370         p->pValue = xjd1QueryDoc(p->u.subq.q, 0);
   366    371         break;
   367    372       }
          373  +
   368    374       case TK_ID: {
   369    375         rc = sqlite3_step(p->u.tab.pStmt);
   370    376         xjd1JsonFree(p->pValue);
   371    377         p->pValue = 0;
   372    378         if( rc==SQLITE_ROW ){
   373    379           const char *zJson = (const char*)sqlite3_column_text(p->u.tab.pStmt, 0);
   374    380           p->pValue = xjd1JsonParse(zJson, -1);
................................................................................
   403    409         if( rc==XJD1_ROW ){
   404    410           JsonNode *pKey = 0;
   405    411           JsonNode *pValue = 0;
   406    412           JsonNode *pBase = p->u.flatten.pNext->pValue;
   407    413           flattenIterEntry(p->u.flatten.pIter, &pKey, &pValue);
   408    414           p->pValue = flattenedObject(pBase, pKey, pValue, p->u.flatten.pAs);
   409    415         }
          416  +
          417  +      break;
          418  +    }
          419  +
          420  +    case TK_DOT: {
          421  +      JsonNode *pArray = p->u.path.pArray;
          422  +
          423  +      xjd1JsonFree(p->pValue);
          424  +      p->pValue = 0;
          425  +
          426  +      if( pArray==0 ){
          427  +        pArray = p->u.path.pArray = xjd1ExprEval(p->u.path.pPath);
          428  +      }
          429  +      if( pArray 
          430  +       && pArray->eJType==XJD1_ARRAY 
          431  +       && p->u.path.iNext<pArray->u.ar.nElem
          432  +      ){
          433  +        rc = XJD1_ROW;
          434  +        p->pValue = xjd1JsonRef(pArray->u.ar.apElem[p->u.path.iNext++]);
          435  +      }
   410    436   
   411    437         break;
   412    438       }
   413    439   
   414    440       case TK_NULL: {
   415    441         rc = (p->u.null.isDone ? XJD1_DONE : XJD1_ROW);
   416    442         p->u.null.isDone = 1;
................................................................................
   479    505       case TK_SELECT: {
   480    506         xjd1QueryRewind(p->u.subq.q);
   481    507         break;
   482    508       }
   483    509       case TK_ID: {
   484    510         sqlite3_reset(p->u.tab.pStmt);
   485    511         break;
          512  +    }
          513  +    case TK_DOT: {
          514  +      xjd1JsonFree(p->u.path.pArray);
          515  +      p->u.path.pArray = 0;
          516  +      p->u.path.iNext = 0;
          517  +      break;
   486    518       }
   487    519       case TK_FLATTENOP: {
   488    520         xjd1DataSrcRewind(p->u.flatten.pNext);
   489    521         flattenIterFree(p->u.flatten.pIter);
   490    522         p->u.flatten.pIter = 0;
   491    523         break;
   492    524       }
................................................................................
   508    540       case TK_ID: {
   509    541         sqlite3_finalize(p->u.tab.pStmt);
   510    542         break;
   511    543       }
   512    544       case TK_FLATTENOP: {
   513    545         xjd1DataSrcClose(p->u.flatten.pNext);
   514    546         break;
          547  +    }
          548  +    case TK_DOT: {
          549  +      xjd1JsonFree(p->u.path.pArray);
          550  +      p->u.path.pArray = 0;
          551  +      break;
   515    552       }
   516    553     }
   517    554     return XJD1_OK;
   518    555   }
   519    556   
   520    557   int xjd1DataSrcCount(DataSrc *p){
   521    558     int n = 1;

Changes to src/expr.c.

    92     92         break;
    93     93       }
    94     94     }
    95     95     return rc;
    96     96   }
    97     97   
    98     98   static int exprResolve(Expr *p, ResolveCtx *pCtx){
    99         -  int eExpr = pCtx->eExpr;
           99  +  Command *pCmd = pCtx->pStmt->pCmd;
   100    100     const char *zDoc;
   101    101   
   102         -  assert( eExpr>0 || (pCtx->pQuery==0 && pCtx->pStmt) );
   103         -
   104    102     zDoc = p->u.id.zId;
   105         -  if( eExpr==0 ){
   106         -    Command *pCmd = pCtx->pStmt->pCmd;
   107         -    switch( pCmd->eCmdType ){
   108         -      case TK_DELETE:
   109         -        if( 0==strcmp(zDoc, pCmd->u.del.zName) ) return XJD1_OK;
   110         -        break;
   111         -      case TK_UPDATE:
   112         -        if( 0==strcmp(zDoc, pCmd->u.update.zName) ) return XJD1_OK;
   113         -        break;
   114         -
   115         -      default:
   116         -        assert( 0 );
   117         -        break;
   118         -    }
   119         -  }else{
   120         -    ResolveCtx *pTest;
   121         -    for(pTest=pCtx; pTest; pTest=pTest->pParent){
   122         -      Query *pQuery = pTest->pQuery;
   123         -      int bFound = 0;
   124         -  
   125         -      assert( pQuery->eQType==TK_SELECT || eExpr==XJD1_EXPR_ORDERBY );
   126         -  
   127         -      /* Search the FROM clause. */
   128         -      if( pQuery->eQType==TK_SELECT && (
   129         -            eExpr==XJD1_EXPR_RESULT  || eExpr==XJD1_EXPR_WHERE ||
   130         -            eExpr==XJD1_EXPR_GROUPBY || eExpr==XJD1_EXPR_HAVING ||
   131         -            eExpr==XJD1_EXPR_ORDERBY
   132         -      )){
   133         -        int iDatasrc = xjd1DataSrcResolve(pQuery->u.simple.pFrom, zDoc);
   134         -        if( iDatasrc ){
   135         -          p->u.id.iDatasrc = iDatasrc;
   136         -          bFound = 1;
   137         -        }
   138         -      }
   139         -  
   140         -      /* Match against any 'AS' alias on the query result */
   141         -      if( bFound==0 && pQuery->zAs ){
   142         -        if( eExpr==XJD1_EXPR_ORDERBY 
   143         -         || eExpr==XJD1_EXPR_HAVING
   144         -         || (eExpr==XJD1_EXPR_WHERE && pQuery->u.simple.pAgg==0)
   145         -        ){
   146         -          if( 0==strcmp(zDoc, pQuery->zAs) ){
   147         -            bFound = 1;
          103  +  switch( pCmd->eCmdType ){
          104  +    case TK_DELETE:
          105  +      if( 0==strcmp(zDoc, pCmd->u.del.zName) ) return XJD1_OK;
          106  +      break;
          107  +
          108  +    case TK_UPDATE:
          109  +      if( 0==strcmp(zDoc, pCmd->u.update.zName) ) return XJD1_OK;
          110  +      break;
          111  +
          112  +    case TK_SELECT: {
          113  +      ResolveCtx *pTest;
          114  +      for(pTest=pCtx; pTest; pTest=pTest->pParent){
          115  +        Query *pQuery = pTest->pQuery;
          116  +        if( pQuery ){
          117  +          int eExpr = pTest->eExpr;
          118  +          int bFound = 0;
          119  +
          120  +          assert( pQuery->eQType==TK_SELECT || eExpr==XJD1_EXPR_ORDERBY );
          121  +          assert( eExpr>0 );
          122  +
          123  +          /* Search the FROM clause. */
          124  +          if( pQuery->eQType==TK_SELECT && (
          125  +                eExpr==XJD1_EXPR_RESULT  || eExpr==XJD1_EXPR_WHERE ||
          126  +                eExpr==XJD1_EXPR_GROUPBY || eExpr==XJD1_EXPR_HAVING ||
          127  +                eExpr==XJD1_EXPR_ORDERBY
          128  +                )){
          129  +            int iDatasrc = xjd1DataSrcResolve(pQuery->u.simple.pFrom, zDoc);
          130  +            if( iDatasrc ){
          131  +              p->u.id.iDatasrc = iDatasrc;
          132  +              bFound = 1;
          133  +            }
          134  +          }
          135  +
          136  +          /* Match against any 'AS' alias on the query result */
          137  +          if( bFound==0 && pQuery->zAs ){
          138  +            if( eExpr==XJD1_EXPR_ORDERBY 
          139  +                || eExpr==XJD1_EXPR_HAVING
          140  +                || (eExpr==XJD1_EXPR_WHERE && pQuery->u.simple.pAgg==0)
          141  +              ){
          142  +              if( 0==strcmp(zDoc, pQuery->zAs) ){
          143  +                bFound = 1;
          144  +              }
          145  +            }
          146  +          }
          147  +
          148  +          if( bFound ){
          149  +            p->u.id.pQuery = pQuery;
          150  +            return XJD1_OK;
   148    151             }
   149    152           }
   150    153         }
   151         -  
   152         -      if( bFound ){
   153         -        p->u.id.pQuery = pQuery;
   154         -        return XJD1_OK;
   155         -      }
          154  +      break;
   156    155       }
          156  +
          157  +    default:
          158  +      assert( 0 );
          159  +      break;
   157    160     }
   158    161   
   159    162     xjd1StmtError(pCtx->pStmt, XJD1_ERROR, "no such object: %s", zDoc);
   160    163     return XJD1_ERROR;
   161    164   }
   162    165   
   163    166   /*
................................................................................
   538    541         xjd1JsonFree(pJLeft);
   539    542         xjd1JsonFree(pJRight);
   540    543         if( pRes==0 ) pRes = nullJson();
   541    544         return pRes;
   542    545       }
   543    546   
   544    547       case TK_ID: {
   545         -      if( p->u.id.pDataSrc ){
   546         -        JsonNode *pVal = xjd1DataSrcRead(p->u.id.pDataSrc, 1);
   547         -        pRes = getProperty(pVal, p->u.id.zId);
   548         -        xjd1JsonFree(pVal);
   549         -        return pRes;
   550         -      }else if( p->pQuery ){
          548  +      if( p->u.id.pQuery ){
          549  +        assert( p->pStmt->pCmd->eCmdType==TK_SELECT );
   551    550           return xjd1QueryDoc(p->u.id.pQuery, p->u.id.iDatasrc);
   552    551         }else{
          552  +        assert( p->pStmt->pCmd->eCmdType==TK_DELETE
          553  +             || p->pStmt->pCmd->eCmdType==TK_UPDATE
          554  +        );
   553    555           return xjd1StmtDoc(p->pStmt);
   554    556         }
   555    557       }
   556    558   
   557    559       /* The following two logical operators work in the same way as their
   558    560       ** javascript counterparts. i.e.
   559    561       **

Changes to src/json.c.

   650    650           }
   651    651         }
   652    652         break;
   653    653       }
   654    654       case JSON_BEGIN_ARRAY: {
   655    655         int nAlloc = 0;
   656    656         tokenNext(pIn);
   657         -      if( tokenType(pIn)==JSON_END_ARRAY ) break;
          657  +      if( tokenType(pIn)==JSON_END_ARRAY ){
          658  +        tokenNext(pIn);
          659  +        break;
          660  +      }
   658    661         while( 1 ){
   659    662           if( pNew->u.ar.nElem>=nAlloc ){
   660    663             JsonNode **pNewArray;
   661    664             nAlloc = nAlloc*2 + 5;
   662    665             pNewArray = xjd1_realloc(pNew->u.ar.apElem,
   663    666                                 sizeof(JsonNode*)*nAlloc);
   664    667             if( pNewArray==0 ) goto json_error;

Changes to src/parse.y.

   449    449   // A complete FROM clause.
   450    450   //
   451    451   %type from {DataSrc*}
   452    452   %type fromlist {DataSrc*}
   453    453   %type fromitem {DataSrc*}
   454    454   %include {
   455    455     /* Create a new data source that is a named table */
   456         -  static DataSrc *tblDataSrc(Parse *p, Token *pTab, Token *pAs){
          456  +  static DataSrc *pathDataSrc(Parse *p, Expr *pPath, Token *pAs){
   457    457       DataSrc *pNew = xjd1PoolMallocZero(p->pPool, sizeof(*pNew));
   458    458       if( pNew ){
   459         -      pNew->eDSType = TK_ID;
   460         -      pNew->u.tab.zName = tokenStr(p, pTab);
   461         -      pNew->zAs = tokenStr(p, pAs);
          459  +      if( pPath ){
          460  +        if( pPath->eType==TK_ID ){
          461  +          pNew->eDSType = TK_ID;
          462  +          pNew->u.tab.zName = pPath->u.id.zId;
          463  +        }else{
          464  +          pNew->eDSType = TK_DOT;
          465  +          pNew->u.path.pPath = pPath;
          466  +        }
          467  +        pNew->zAs = tokenStr(p, pAs);
          468  +      }else{
          469  +        pNew->eDSType = TK_ID;
          470  +        pNew->u.tab.zName = tokenStr(p,pAs);
          471  +      }
   462    472       }
   463    473       return pNew;
   464    474     }
   465    475   
   466    476     /* Create a new data source that is a join */
   467    477     static DataSrc *joinDataSrc(Parse *p, DataSrc *pLeft, DataSrc *pRight){
   468    478       DataSrc *pNew = xjd1PoolMallocZero(p->pPool, sizeof(*pNew));
................................................................................
   516    526       return pNew;
   517    527     }
   518    528   }
   519    529   from(A) ::= .                                    {A = nullDataSrc(p);}
   520    530   from(A) ::= FROM fromlist(X).                    {A = X;}
   521    531   fromlist(A) ::= fromitem(X).                     {A = X;}
   522    532   fromlist(A) ::= fromlist(X) COMMA fromitem(Y).   {A = joinDataSrc(p,X,Y);}
   523         -fromitem(A) ::= ID(X).                           {A = tblDataSrc(p,&X,0);}
   524         -fromitem(A) ::= ID(X) AS ID(Y).                  {A = tblDataSrc(p,&X,&Y);}
   525    533   fromitem(A) ::= LP select(X) RP AS ID(Y).        {A = subqDataSrc(p,X,&Y);}
   526    534   
   527         -fromitem(A) ::= fromitem(W) FLATTENOP(X) LP eachexpr(Y) eachalias(Z) RP. {
          535  +fromitem(A) ::= ID(X).                           {A = pathDataSrc(p,0,&X);}
          536  +fromitem(A) ::= path(X) AS ID(Y).                {A = pathDataSrc(p,X,&Y);}
          537  +
          538  +fromitem(A) ::= fromitem(W) FLATTENOP(X) LP path(Y) eachalias(Z) RP. {
   528    539     A = flattenDataSrc(p,W,&X,Y,Z);
   529    540   }
   530    541   
   531    542   %type eachalias {Expr*}
   532    543   eachalias(A) ::= .                 {A=0;}
   533    544   eachalias(A) ::= AS ID|STRING(Y).  {A=idExpr(p,&Y);}
   534    545   
   535         -%type eachexpr {Expr*}
   536         -eachexpr(A) ::= ID(Y).                           {A = idExpr(p, &Y);        }
   537         -eachexpr(A) ::= eachexpr(X) DOT ID(Y).           {A = lvalueExpr(p, X, &Y); }
   538         -eachexpr(A) ::= eachexpr(X) LB ID|STRING(Y) RB.  {A = lvalueExpr(p, X, &Y); }
          546  +%type path {Expr*}
          547  +path(A) ::= ID(Y).                               {A = idExpr(p, &Y);        }
          548  +path(A) ::= path(X) DOT ID(Y).                   {A = lvalueExpr(p, X, &Y); }
          549  +path(A) ::= path(X) LB ID|STRING(Y) RB.          {A = lvalueExpr(p, X, &Y); }
   539    550   
   540    551   %type groupby_opt {GroupByHaving}
   541    552   groupby_opt(A) ::= .                            {A.pGroupBy=0; A.pHaving=0;}
   542    553   groupby_opt(A) ::= GROUP BY exprlist(X).        {A.pGroupBy=X; A.pHaving=0;}
   543    554   groupby_opt(A) ::= GROUP BY exprlist(X) HAVING expr(Y).
   544    555                                                   {A.pGroupBy=X; A.pHaving=Y;}
   545    556   

Changes to src/query.c.

   173    173   ){
   174    174     int rc;
   175    175     if( p==0 ) return XJD1_OK;
   176    176     p->pStmt = pStmt;
   177    177     if( p->eQType==TK_SELECT ){
   178    178       rc = xjd1ExprInit(p->u.simple.pRes, pStmt, p, XJD1_EXPR_RESULT, pCtx);
   179    179       if( !rc ){
   180         -      rc = xjd1DataSrcInit(p->u.simple.pFrom, p);
          180  +      rc = xjd1DataSrcInit(p->u.simple.pFrom, p, pCtx);
   181    181       }
   182    182       if( !rc ){
   183    183         rc = xjd1ExprInit(p->u.simple.pWhere, pStmt, p, XJD1_EXPR_WHERE, pCtx);
   184    184       }
   185    185       if( !rc ){
   186         -      rc = xjd1ExprListInit(p->u.simple.pGroupBy, pStmt, p, XJD1_EXPR_GROUPBY, pCtx);
          186  +      rc = xjd1ExprListInit(
          187  +          p->u.simple.pGroupBy, pStmt, p, XJD1_EXPR_GROUPBY, pCtx
          188  +      );
   187    189       }
   188    190       if( !rc ){
   189    191         rc = xjd1ExprInit(p->u.simple.pHaving, pStmt, p, XJD1_EXPR_HAVING, pCtx);
   190    192       }
   191    193       if( !rc && p->u.simple.pGroupBy ){ 
   192    194         rc = xjd1AggregateInit(pStmt, p, 0);
   193    195       }

Changes to src/xjd1Int.h.

   182    182         Expr *pLeft;             /* Lvalue or id to the left */
   183    183         char *zId;               /* ID to the right */
   184    184       } lvalue;
   185    185       struct {                /* Identifiers */
   186    186         char *zId;               /* token value.  eClass=EXPR_TK */
   187    187         int iDatasrc;
   188    188         Query *pQuery;
   189         -      DataSrc *pDataSrc;       /* Read property from this datasource */
   190    189       } id;
   191    190       struct {                /* Function calls.  eClass=EXPR_FUNC */
   192    191         char *zFName;            /* Name of the function */
   193    192         ExprList *args;          /* List of arguments */
   194    193         Function *pFunction;     /* Function object */
   195    194         JsonNode **apArg;        /* Array to martial function arguments in */
   196    195         int iAgg;
................................................................................
   336    335         DataSrc *pRight;         /* Data source on the right */
   337    336       } join;
   338    337       struct {                /* For a named collection.  eDSType==TK_ID */
   339    338         char *zName;             /* The collection name */
   340    339         sqlite3_stmt *pStmt;     /* Cursor for reading content */
   341    340         int eofSeen;             /* True if at EOF */
   342    341       } tab;
          342  +    struct {                /* For a named collection.  eDSType==TK_ID */
          343  +      Expr *pPath;             /* Path to correlated variable */
          344  +      JsonNode *pArray;        /* Value to iterate through */
          345  +      int iNext;               /* Index of next value in pValue to return */
          346  +    } path;
   343    347       struct {                /* EACH() or FLATTEN().  eDSType==TK_FLATTENOP */
   344    348         DataSrc *pNext;          /* Data source to the left */
   345    349         char cOpName;            /* 'E' or 'F' for "EACH" or "FLATTEN" */
   346    350         Expr *pExpr;             /* Expression to flatten on */
   347    351         Expr *pAs;               /* AS path, if any */
   348    352         FlattenIter *pIter;      /* Iterator */
   349    353       } flatten;
................................................................................
   396    400   void xjd1ContextUnref(xjd1_context*);
   397    401   
   398    402   /******************************** conn.c *************************************/
   399    403   void xjd1Unref(xjd1*);
   400    404   void xjd1Error(xjd1*,int,const char*,...);
   401    405   
   402    406   /******************************** datasrc.c **********************************/
   403         -int xjd1DataSrcInit(DataSrc*,Query*);
          407  +int xjd1DataSrcInit(DataSrc*,Query*,void*);
   404    408   int xjd1DataSrcRewind(DataSrc*);
   405    409   int xjd1DataSrcStep(DataSrc*);
   406    410   int xjd1DataSrcClose(DataSrc*);
   407    411   int xjd1DataSrcCount(DataSrc*);
   408    412   JsonNode *xjd1DataSrcDoc(DataSrc*, const char*);
   409    413   int xjd1DataSrcCount(DataSrc *);
   410    414   JsonNode *xjd1DataSrcCacheRead(DataSrc *, JsonNode **, const char *zDocname);

Changes to test/all.test.

     5      5   .read base03.test
     6      6   .read base04.test
     7      7   .read base05.test
     8      8   .read base06.test
     9      9   .read base07.test
    10     10   .read base08.test
    11     11   .read base09.test
           12  +.read base10.test

Added test/base10.test.

            1  +-- Test using a reference to an outer query property as a variable.
            2  +--
            3  +
            4  +.new t1.db
            5  +CREATE COLLECTION c1;
            6  +INSERT INTO c1 VALUE {docid:1, tags:["a", "b", "c"]};
            7  +INSERT INTO c1 VALUE {docid:2, tags:[     "b", "c"]};
            8  +INSERT INTO c1 VALUE {docid:3, tags:[          "c", "d", "e"]};
            9  +INSERT INTO c1 VALUE {docid:4, tags:[]};
           10  +INSERT INTO c1 VALUE {docid:5};
           11  +
           12  +.testcase 0
           13  +SELECT FROM c1;
           14  +.json {docid:1, tags:["a", "b", "c"]}               \
           15  +      {docid:2, tags:[     "b", "c"]}               \
           16  +      {docid:3, tags:[          "c", "d", "e"]}     \
           17  +      {docid:4, tags:[]}                            \
           18  +      {docid:5} 
           19  +
           20  +-- All documents with tag "b"
           21  +--
           22  +.testcase 1
           23  +SELECT c1.docid FROM c1 WHERE (SELECT 1 FROM c1.tags AS t WHERE t=="b");
           24  +.result 1 2
           25  +
           26  +-- All documents with at least one tag that do not have a tag greater than "c"
           27  +--
           28  +.testcase 2
           29  +SELECT c1.docid FROM c1 WHERE (
           30  +  SELECT count()==0 FROM c1.tags AS t WHERE t>"c"
           31  +);
           32  +.result 1 2 4 5
           33  +
           34  +-- All documents with more than two tags.
           35  +--
           36  +.testcase 3
           37  +SELECT c1.docid FROM c1 WHERE (SELECT count()>2 FROM c1.tags AS t);
           38  +.result 1 3
           39  +
           40  +-- Each document and it's smallest tag.
           41  +--
           42  +.testcase 4
           43  +SELECT {docid: c1.docid, smallest: (SELECT min(t) FROM c1.tags AS t) } FROM c1;
           44  +.json {docid:1, smallest:"a"} {docid:2, smallest:"b"}    \
           45  +      {docid:3, smallest:"c"} {docid:4, smallest:null}   \
           46  +      {docid:5, smallest:null}
           47  +
           48  +
           49  +