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 Unified Diffs Ignore Whitespace Patch

Changes to src/datasrc.c.

18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
..
39
40
41
42
43
44
45
46




47
48
49
50
51
52
53
...
354
355
356
357
358
359
360

361
362
363
364
365
366
367

368
369
370
371
372
373
374
...
403
404
405
406
407
408
409




















410
411
412
413
414
415
416
...
479
480
481
482
483
484
485






486
487
488
489
490
491
492
...
508
509
510
511
512
513
514





515
516
517
518
519
520
521
*/
#include "xjd1Int.h"


/*
** Called after statement parsing to initalize every DataSrc object.
*/
int xjd1DataSrcInit(DataSrc *p, Query *pQuery){
  int rc = XJD1_OK;
  p->pQuery = pQuery;
  switch( p->eDSType ){
    case TK_COMMA: {
      xjd1DataSrcInit(p->u.join.pLeft, pQuery);
      xjd1DataSrcInit(p->u.join.pRight, pQuery);
      break;
    }
    case TK_SELECT: {
      xjd1QueryInit(p->u.subq.q, pQuery->pStmt, 0);
      break;
    }
    case TK_ID: {
................................................................................
      char *zSql = sqlite3_mprintf("SELECT x FROM \"%w\"", p->u.tab.zName);
      sqlite3_prepare_v2(pQuery->pStmt->pConn->db, zSql, -1, 
                         &p->u.tab.pStmt, 0);
      sqlite3_free(zSql);
      break;
    }
    case TK_FLATTENOP: {
      xjd1DataSrcInit(p->u.flatten.pNext, pQuery);




      break;
    }
    case TK_NULL:                 /* Initializing a NULL DS is a no-op */
      assert( p->u.null.isDone==0 );
      break;
  }
  return rc;
................................................................................
        if( rc==XJD1_ROW ) {
          xjd1DataSrcRewind(p->u.join.pRight);
          rc = xjd1DataSrcStep(p->u.join.pRight);
        }
      }
      break;
    }

    case TK_SELECT: {
      xjd1JsonFree(p->pValue);
      p->pValue = 0;
      rc = xjd1QueryStep(p->u.subq.q);
      p->pValue = xjd1QueryDoc(p->u.subq.q, 0);
      break;
    }

    case TK_ID: {
      rc = sqlite3_step(p->u.tab.pStmt);
      xjd1JsonFree(p->pValue);
      p->pValue = 0;
      if( rc==SQLITE_ROW ){
        const char *zJson = (const char*)sqlite3_column_text(p->u.tab.pStmt, 0);
        p->pValue = xjd1JsonParse(zJson, -1);
................................................................................
      if( rc==XJD1_ROW ){
        JsonNode *pKey = 0;
        JsonNode *pValue = 0;
        JsonNode *pBase = p->u.flatten.pNext->pValue;
        flattenIterEntry(p->u.flatten.pIter, &pKey, &pValue);
        p->pValue = flattenedObject(pBase, pKey, pValue, p->u.flatten.pAs);
      }





















      break;
    }

    case TK_NULL: {
      rc = (p->u.null.isDone ? XJD1_DONE : XJD1_ROW);
      p->u.null.isDone = 1;
................................................................................
    case TK_SELECT: {
      xjd1QueryRewind(p->u.subq.q);
      break;
    }
    case TK_ID: {
      sqlite3_reset(p->u.tab.pStmt);
      break;






    }
    case TK_FLATTENOP: {
      xjd1DataSrcRewind(p->u.flatten.pNext);
      flattenIterFree(p->u.flatten.pIter);
      p->u.flatten.pIter = 0;
      break;
    }
................................................................................
    case TK_ID: {
      sqlite3_finalize(p->u.tab.pStmt);
      break;
    }
    case TK_FLATTENOP: {
      xjd1DataSrcClose(p->u.flatten.pNext);
      break;





    }
  }
  return XJD1_OK;
}

int xjd1DataSrcCount(DataSrc *p){
  int n = 1;







|




|
|







 







|
>
>
>
>







 







>







>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>
>
>
>
>
>







 







>
>
>
>
>







18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
..
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
...
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
...
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
...
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
...
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
*/
#include "xjd1Int.h"


/*
** Called after statement parsing to initalize every DataSrc object.
*/
int xjd1DataSrcInit(DataSrc *p, Query *pQuery, void *pOuterCtx){
  int rc = XJD1_OK;
  p->pQuery = pQuery;
  switch( p->eDSType ){
    case TK_COMMA: {
      xjd1DataSrcInit(p->u.join.pLeft, pQuery, pOuterCtx);
      xjd1DataSrcInit(p->u.join.pRight, pQuery, pOuterCtx);
      break;
    }
    case TK_SELECT: {
      xjd1QueryInit(p->u.subq.q, pQuery->pStmt, 0);
      break;
    }
    case TK_ID: {
................................................................................
      char *zSql = sqlite3_mprintf("SELECT x FROM \"%w\"", p->u.tab.zName);
      sqlite3_prepare_v2(pQuery->pStmt->pConn->db, zSql, -1, 
                         &p->u.tab.pStmt, 0);
      sqlite3_free(zSql);
      break;
    }
    case TK_FLATTENOP: {
      xjd1DataSrcInit(p->u.flatten.pNext, pQuery, pOuterCtx);
      break;
    }
    case TK_DOT: {
      rc = xjd1ExprInit(p->u.path.pPath, pQuery->pStmt, 0, 0, pOuterCtx);
      break;
    }
    case TK_NULL:                 /* Initializing a NULL DS is a no-op */
      assert( p->u.null.isDone==0 );
      break;
  }
  return rc;
................................................................................
        if( rc==XJD1_ROW ) {
          xjd1DataSrcRewind(p->u.join.pRight);
          rc = xjd1DataSrcStep(p->u.join.pRight);
        }
      }
      break;
    }

    case TK_SELECT: {
      xjd1JsonFree(p->pValue);
      p->pValue = 0;
      rc = xjd1QueryStep(p->u.subq.q);
      p->pValue = xjd1QueryDoc(p->u.subq.q, 0);
      break;
    }

    case TK_ID: {
      rc = sqlite3_step(p->u.tab.pStmt);
      xjd1JsonFree(p->pValue);
      p->pValue = 0;
      if( rc==SQLITE_ROW ){
        const char *zJson = (const char*)sqlite3_column_text(p->u.tab.pStmt, 0);
        p->pValue = xjd1JsonParse(zJson, -1);
................................................................................
      if( rc==XJD1_ROW ){
        JsonNode *pKey = 0;
        JsonNode *pValue = 0;
        JsonNode *pBase = p->u.flatten.pNext->pValue;
        flattenIterEntry(p->u.flatten.pIter, &pKey, &pValue);
        p->pValue = flattenedObject(pBase, pKey, pValue, p->u.flatten.pAs);
      }

      break;
    }

    case TK_DOT: {
      JsonNode *pArray = p->u.path.pArray;

      xjd1JsonFree(p->pValue);
      p->pValue = 0;

      if( pArray==0 ){
        pArray = p->u.path.pArray = xjd1ExprEval(p->u.path.pPath);
      }
      if( pArray 
       && pArray->eJType==XJD1_ARRAY 
       && p->u.path.iNext<pArray->u.ar.nElem
      ){
        rc = XJD1_ROW;
        p->pValue = xjd1JsonRef(pArray->u.ar.apElem[p->u.path.iNext++]);
      }

      break;
    }

    case TK_NULL: {
      rc = (p->u.null.isDone ? XJD1_DONE : XJD1_ROW);
      p->u.null.isDone = 1;
................................................................................
    case TK_SELECT: {
      xjd1QueryRewind(p->u.subq.q);
      break;
    }
    case TK_ID: {
      sqlite3_reset(p->u.tab.pStmt);
      break;
    }
    case TK_DOT: {
      xjd1JsonFree(p->u.path.pArray);
      p->u.path.pArray = 0;
      p->u.path.iNext = 0;
      break;
    }
    case TK_FLATTENOP: {
      xjd1DataSrcRewind(p->u.flatten.pNext);
      flattenIterFree(p->u.flatten.pIter);
      p->u.flatten.pIter = 0;
      break;
    }
................................................................................
    case TK_ID: {
      sqlite3_finalize(p->u.tab.pStmt);
      break;
    }
    case TK_FLATTENOP: {
      xjd1DataSrcClose(p->u.flatten.pNext);
      break;
    }
    case TK_DOT: {
      xjd1JsonFree(p->u.path.pArray);
      p->u.path.pArray = 0;
      break;
    }
  }
  return XJD1_OK;
}

int xjd1DataSrcCount(DataSrc *p){
  int n = 1;

Changes to src/expr.c.

92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110

111
112
113
114
115
116
117
118
119

120
121
122


123
124
125

126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156







157
158
159
160
161
162
163
...
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
      break;
    }
  }
  return rc;
}

static int exprResolve(Expr *p, ResolveCtx *pCtx){
  int eExpr = pCtx->eExpr;
  const char *zDoc;

  assert( eExpr>0 || (pCtx->pQuery==0 && pCtx->pStmt) );

  zDoc = p->u.id.zId;
  if( eExpr==0 ){
    Command *pCmd = pCtx->pStmt->pCmd;
    switch( pCmd->eCmdType ){
      case TK_DELETE:
        if( 0==strcmp(zDoc, pCmd->u.del.zName) ) return XJD1_OK;
        break;

      case TK_UPDATE:
        if( 0==strcmp(zDoc, pCmd->u.update.zName) ) return XJD1_OK;
        break;

      default:
        assert( 0 );
        break;
    }
  }else{

    ResolveCtx *pTest;
    for(pTest=pCtx; pTest; pTest=pTest->pParent){
      Query *pQuery = pTest->pQuery;


      int bFound = 0;
  
      assert( pQuery->eQType==TK_SELECT || eExpr==XJD1_EXPR_ORDERBY );

  
      /* Search the FROM clause. */
      if( pQuery->eQType==TK_SELECT && (
            eExpr==XJD1_EXPR_RESULT  || eExpr==XJD1_EXPR_WHERE ||
            eExpr==XJD1_EXPR_GROUPBY || eExpr==XJD1_EXPR_HAVING ||
            eExpr==XJD1_EXPR_ORDERBY
      )){
        int iDatasrc = xjd1DataSrcResolve(pQuery->u.simple.pFrom, zDoc);
        if( iDatasrc ){
          p->u.id.iDatasrc = iDatasrc;
          bFound = 1;
        }
      }
  
      /* Match against any 'AS' alias on the query result */
      if( bFound==0 && pQuery->zAs ){
        if( eExpr==XJD1_EXPR_ORDERBY 
         || eExpr==XJD1_EXPR_HAVING
         || (eExpr==XJD1_EXPR_WHERE && pQuery->u.simple.pAgg==0)
        ){
          if( 0==strcmp(zDoc, pQuery->zAs) ){
            bFound = 1;
          }
        }
      }
  
      if( bFound ){
        p->u.id.pQuery = pQuery;
        return XJD1_OK;
      }
    }







  }

  xjd1StmtError(pCtx->pStmt, XJD1_ERROR, "no such object: %s", zDoc);
  return XJD1_ERROR;
}

/*
................................................................................
      xjd1JsonFree(pJLeft);
      xjd1JsonFree(pJRight);
      if( pRes==0 ) pRes = nullJson();
      return pRes;
    }

    case TK_ID: {
      if( p->u.id.pDataSrc ){
        JsonNode *pVal = xjd1DataSrcRead(p->u.id.pDataSrc, 1);
        pRes = getProperty(pVal, p->u.id.zId);
        xjd1JsonFree(pVal);
        return pRes;
      }else if( p->pQuery ){
        return xjd1QueryDoc(p->u.id.pQuery, p->u.id.iDatasrc);
      }else{
        return xjd1StmtDoc(p->pStmt);
      }
    }

    /* The following two logical operators work in the same way as their
    ** javascript counterparts. i.e.
    **







|


<
<

<
<
|
|
|
|
>
|
|
|

<
<
<
<
<
>
|
|
|
>
>
|
|
|
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>
>
>
>
>
>
>







 







|
|
|
|
|
|
|
<







92
93
94
95
96
97
98
99
100
101


102


103
104
105
106
107
108
109
110
111





112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
...
541
542
543
544
545
546
547
548
549
550
551
552
553
554

555
556
557
558
559
560
561
      break;
    }
  }
  return rc;
}

static int exprResolve(Expr *p, ResolveCtx *pCtx){
  Command *pCmd = pCtx->pStmt->pCmd;
  const char *zDoc;



  zDoc = p->u.id.zId;


  switch( pCmd->eCmdType ){
    case TK_DELETE:
      if( 0==strcmp(zDoc, pCmd->u.del.zName) ) return XJD1_OK;
      break;

    case TK_UPDATE:
      if( 0==strcmp(zDoc, pCmd->u.update.zName) ) return XJD1_OK;
      break;






    case TK_SELECT: {
      ResolveCtx *pTest;
      for(pTest=pCtx; pTest; pTest=pTest->pParent){
        Query *pQuery = pTest->pQuery;
        if( pQuery ){
          int eExpr = pTest->eExpr;
          int bFound = 0;

          assert( pQuery->eQType==TK_SELECT || eExpr==XJD1_EXPR_ORDERBY );
          assert( eExpr>0 );

          /* Search the FROM clause. */
          if( pQuery->eQType==TK_SELECT && (
                eExpr==XJD1_EXPR_RESULT  || eExpr==XJD1_EXPR_WHERE ||
                eExpr==XJD1_EXPR_GROUPBY || eExpr==XJD1_EXPR_HAVING ||
                eExpr==XJD1_EXPR_ORDERBY
                )){
            int iDatasrc = xjd1DataSrcResolve(pQuery->u.simple.pFrom, zDoc);
            if( iDatasrc ){
              p->u.id.iDatasrc = iDatasrc;
              bFound = 1;
            }
          }

          /* Match against any 'AS' alias on the query result */
          if( bFound==0 && pQuery->zAs ){
            if( eExpr==XJD1_EXPR_ORDERBY 
                || eExpr==XJD1_EXPR_HAVING
                || (eExpr==XJD1_EXPR_WHERE && pQuery->u.simple.pAgg==0)
              ){
              if( 0==strcmp(zDoc, pQuery->zAs) ){
                bFound = 1;
              }
            }
          }

          if( bFound ){
            p->u.id.pQuery = pQuery;
            return XJD1_OK;
          }
        }
      }
      break;
    }

    default:
      assert( 0 );
      break;
  }

  xjd1StmtError(pCtx->pStmt, XJD1_ERROR, "no such object: %s", zDoc);
  return XJD1_ERROR;
}

/*
................................................................................
      xjd1JsonFree(pJLeft);
      xjd1JsonFree(pJRight);
      if( pRes==0 ) pRes = nullJson();
      return pRes;
    }

    case TK_ID: {
      if( p->u.id.pQuery ){
        assert( p->pStmt->pCmd->eCmdType==TK_SELECT );
        return xjd1QueryDoc(p->u.id.pQuery, p->u.id.iDatasrc);
      }else{
        assert( p->pStmt->pCmd->eCmdType==TK_DELETE
             || p->pStmt->pCmd->eCmdType==TK_UPDATE
        );

        return xjd1StmtDoc(p->pStmt);
      }
    }

    /* The following two logical operators work in the same way as their
    ** javascript counterparts. i.e.
    **

Changes to src/json.c.

650
651
652
653
654
655
656
657



658
659
660
661
662
663
664
        }
      }
      break;
    }
    case JSON_BEGIN_ARRAY: {
      int nAlloc = 0;
      tokenNext(pIn);
      if( tokenType(pIn)==JSON_END_ARRAY ) break;



      while( 1 ){
        if( pNew->u.ar.nElem>=nAlloc ){
          JsonNode **pNewArray;
          nAlloc = nAlloc*2 + 5;
          pNewArray = xjd1_realloc(pNew->u.ar.apElem,
                              sizeof(JsonNode*)*nAlloc);
          if( pNewArray==0 ) goto json_error;







|
>
>
>







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

Changes to src/parse.y.

449
450
451
452
453
454
455
456
457
458


459
460




461




462
463
464
465
466
467
468
...
516
517
518
519
520
521
522


523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
// A complete FROM clause.
//
%type from {DataSrc*}
%type fromlist {DataSrc*}
%type fromitem {DataSrc*}
%include {
  /* Create a new data source that is a named table */
  static DataSrc *tblDataSrc(Parse *p, Token *pTab, Token *pAs){
    DataSrc *pNew = xjd1PoolMallocZero(p->pPool, sizeof(*pNew));
    if( pNew ){


      pNew->eDSType = TK_ID;
      pNew->u.tab.zName = tokenStr(p, pTab);




      pNew->zAs = tokenStr(p, pAs);




    }
    return pNew;
  }

  /* Create a new data source that is a join */
  static DataSrc *joinDataSrc(Parse *p, DataSrc *pLeft, DataSrc *pRight){
    DataSrc *pNew = xjd1PoolMallocZero(p->pPool, sizeof(*pNew));
................................................................................
    return pNew;
  }
}
from(A) ::= .                                    {A = nullDataSrc(p);}
from(A) ::= FROM fromlist(X).                    {A = X;}
fromlist(A) ::= fromitem(X).                     {A = X;}
fromlist(A) ::= fromlist(X) COMMA fromitem(Y).   {A = joinDataSrc(p,X,Y);}


fromitem(A) ::= ID(X).                           {A = tblDataSrc(p,&X,0);}
fromitem(A) ::= ID(X) AS ID(Y).                  {A = tblDataSrc(p,&X,&Y);}
fromitem(A) ::= LP select(X) RP AS ID(Y).        {A = subqDataSrc(p,X,&Y);}

fromitem(A) ::= fromitem(W) FLATTENOP(X) LP eachexpr(Y) eachalias(Z) RP. {
  A = flattenDataSrc(p,W,&X,Y,Z);
}

%type eachalias {Expr*}
eachalias(A) ::= .                 {A=0;}
eachalias(A) ::= AS ID|STRING(Y).  {A=idExpr(p,&Y);}

%type eachexpr {Expr*}
eachexpr(A) ::= ID(Y).                           {A = idExpr(p, &Y);        }
eachexpr(A) ::= eachexpr(X) DOT ID(Y).           {A = lvalueExpr(p, X, &Y); }
eachexpr(A) ::= eachexpr(X) LB ID|STRING(Y) RB.  {A = lvalueExpr(p, X, &Y); }

%type groupby_opt {GroupByHaving}
groupby_opt(A) ::= .                            {A.pGroupBy=0; A.pHaving=0;}
groupby_opt(A) ::= GROUP BY exprlist(X).        {A.pGroupBy=X; A.pHaving=0;}
groupby_opt(A) ::= GROUP BY exprlist(X) HAVING expr(Y).
                                                {A.pGroupBy=X; A.pHaving=Y;}








|


>
>
|
|
>
>
>
>
|
>
>
>
>







 







>
>
|
|
<

|







|
|
|
|







449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
...
526
527
528
529
530
531
532
533
534
535
536

537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
// A complete FROM clause.
//
%type from {DataSrc*}
%type fromlist {DataSrc*}
%type fromitem {DataSrc*}
%include {
  /* Create a new data source that is a named table */
  static DataSrc *pathDataSrc(Parse *p, Expr *pPath, Token *pAs){
    DataSrc *pNew = xjd1PoolMallocZero(p->pPool, sizeof(*pNew));
    if( pNew ){
      if( pPath ){
        if( pPath->eType==TK_ID ){
          pNew->eDSType = TK_ID;
          pNew->u.tab.zName = pPath->u.id.zId;
        }else{
          pNew->eDSType = TK_DOT;
          pNew->u.path.pPath = pPath;
        }
        pNew->zAs = tokenStr(p, pAs);
      }else{
        pNew->eDSType = TK_ID;
        pNew->u.tab.zName = tokenStr(p,pAs);
      }
    }
    return pNew;
  }

  /* Create a new data source that is a join */
  static DataSrc *joinDataSrc(Parse *p, DataSrc *pLeft, DataSrc *pRight){
    DataSrc *pNew = xjd1PoolMallocZero(p->pPool, sizeof(*pNew));
................................................................................
    return pNew;
  }
}
from(A) ::= .                                    {A = nullDataSrc(p);}
from(A) ::= FROM fromlist(X).                    {A = X;}
fromlist(A) ::= fromitem(X).                     {A = X;}
fromlist(A) ::= fromlist(X) COMMA fromitem(Y).   {A = joinDataSrc(p,X,Y);}
fromitem(A) ::= LP select(X) RP AS ID(Y).        {A = subqDataSrc(p,X,&Y);}

fromitem(A) ::= ID(X).                           {A = pathDataSrc(p,0,&X);}
fromitem(A) ::= path(X) AS ID(Y).                {A = pathDataSrc(p,X,&Y);}


fromitem(A) ::= fromitem(W) FLATTENOP(X) LP path(Y) eachalias(Z) RP. {
  A = flattenDataSrc(p,W,&X,Y,Z);
}

%type eachalias {Expr*}
eachalias(A) ::= .                 {A=0;}
eachalias(A) ::= AS ID|STRING(Y).  {A=idExpr(p,&Y);}

%type path {Expr*}
path(A) ::= ID(Y).                               {A = idExpr(p, &Y);        }
path(A) ::= path(X) DOT ID(Y).                   {A = lvalueExpr(p, X, &Y); }
path(A) ::= path(X) LB ID|STRING(Y) RB.          {A = lvalueExpr(p, X, &Y); }

%type groupby_opt {GroupByHaving}
groupby_opt(A) ::= .                            {A.pGroupBy=0; A.pHaving=0;}
groupby_opt(A) ::= GROUP BY exprlist(X).        {A.pGroupBy=X; A.pHaving=0;}
groupby_opt(A) ::= GROUP BY exprlist(X) HAVING expr(Y).
                                                {A.pGroupBy=X; A.pHaving=Y;}

Changes to src/query.c.

173
174
175
176
177
178
179
180
181
182
183
184
185

186

187
188
189
190
191
192
193
){
  int rc;
  if( p==0 ) return XJD1_OK;
  p->pStmt = pStmt;
  if( p->eQType==TK_SELECT ){
    rc = xjd1ExprInit(p->u.simple.pRes, pStmt, p, XJD1_EXPR_RESULT, pCtx);
    if( !rc ){
      rc = xjd1DataSrcInit(p->u.simple.pFrom, p);
    }
    if( !rc ){
      rc = xjd1ExprInit(p->u.simple.pWhere, pStmt, p, XJD1_EXPR_WHERE, pCtx);
    }
    if( !rc ){

      rc = xjd1ExprListInit(p->u.simple.pGroupBy, pStmt, p, XJD1_EXPR_GROUPBY, pCtx);

    }
    if( !rc ){
      rc = xjd1ExprInit(p->u.simple.pHaving, pStmt, p, XJD1_EXPR_HAVING, pCtx);
    }
    if( !rc && p->u.simple.pGroupBy ){ 
      rc = xjd1AggregateInit(pStmt, p, 0);
    }







|





>
|
>







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

Changes to src/xjd1Int.h.

182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
...
336
337
338
339
340
341
342





343
344
345
346
347
348
349
...
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
      Expr *pLeft;             /* Lvalue or id to the left */
      char *zId;               /* ID to the right */
    } lvalue;
    struct {                /* Identifiers */
      char *zId;               /* token value.  eClass=EXPR_TK */
      int iDatasrc;
      Query *pQuery;
      DataSrc *pDataSrc;       /* Read property from this datasource */
    } id;
    struct {                /* Function calls.  eClass=EXPR_FUNC */
      char *zFName;            /* Name of the function */
      ExprList *args;          /* List of arguments */
      Function *pFunction;     /* Function object */
      JsonNode **apArg;        /* Array to martial function arguments in */
      int iAgg;
................................................................................
      DataSrc *pRight;         /* Data source on the right */
    } join;
    struct {                /* For a named collection.  eDSType==TK_ID */
      char *zName;             /* The collection name */
      sqlite3_stmt *pStmt;     /* Cursor for reading content */
      int eofSeen;             /* True if at EOF */
    } tab;





    struct {                /* EACH() or FLATTEN().  eDSType==TK_FLATTENOP */
      DataSrc *pNext;          /* Data source to the left */
      char cOpName;            /* 'E' or 'F' for "EACH" or "FLATTEN" */
      Expr *pExpr;             /* Expression to flatten on */
      Expr *pAs;               /* AS path, if any */
      FlattenIter *pIter;      /* Iterator */
    } flatten;
................................................................................
void xjd1ContextUnref(xjd1_context*);

/******************************** conn.c *************************************/
void xjd1Unref(xjd1*);
void xjd1Error(xjd1*,int,const char*,...);

/******************************** datasrc.c **********************************/
int xjd1DataSrcInit(DataSrc*,Query*);
int xjd1DataSrcRewind(DataSrc*);
int xjd1DataSrcStep(DataSrc*);
int xjd1DataSrcClose(DataSrc*);
int xjd1DataSrcCount(DataSrc*);
JsonNode *xjd1DataSrcDoc(DataSrc*, const char*);
int xjd1DataSrcCount(DataSrc *);
JsonNode *xjd1DataSrcCacheRead(DataSrc *, JsonNode **, const char *zDocname);







<







 







>
>
>
>
>







 







|







182
183
184
185
186
187
188

189
190
191
192
193
194
195
...
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
...
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
      Expr *pLeft;             /* Lvalue or id to the left */
      char *zId;               /* ID to the right */
    } lvalue;
    struct {                /* Identifiers */
      char *zId;               /* token value.  eClass=EXPR_TK */
      int iDatasrc;
      Query *pQuery;

    } id;
    struct {                /* Function calls.  eClass=EXPR_FUNC */
      char *zFName;            /* Name of the function */
      ExprList *args;          /* List of arguments */
      Function *pFunction;     /* Function object */
      JsonNode **apArg;        /* Array to martial function arguments in */
      int iAgg;
................................................................................
      DataSrc *pRight;         /* Data source on the right */
    } join;
    struct {                /* For a named collection.  eDSType==TK_ID */
      char *zName;             /* The collection name */
      sqlite3_stmt *pStmt;     /* Cursor for reading content */
      int eofSeen;             /* True if at EOF */
    } tab;
    struct {                /* For a named collection.  eDSType==TK_ID */
      Expr *pPath;             /* Path to correlated variable */
      JsonNode *pArray;        /* Value to iterate through */
      int iNext;               /* Index of next value in pValue to return */
    } path;
    struct {                /* EACH() or FLATTEN().  eDSType==TK_FLATTENOP */
      DataSrc *pNext;          /* Data source to the left */
      char cOpName;            /* 'E' or 'F' for "EACH" or "FLATTEN" */
      Expr *pExpr;             /* Expression to flatten on */
      Expr *pAs;               /* AS path, if any */
      FlattenIter *pIter;      /* Iterator */
    } flatten;
................................................................................
void xjd1ContextUnref(xjd1_context*);

/******************************** conn.c *************************************/
void xjd1Unref(xjd1*);
void xjd1Error(xjd1*,int,const char*,...);

/******************************** datasrc.c **********************************/
int xjd1DataSrcInit(DataSrc*,Query*,void*);
int xjd1DataSrcRewind(DataSrc*);
int xjd1DataSrcStep(DataSrc*);
int xjd1DataSrcClose(DataSrc*);
int xjd1DataSrcCount(DataSrc*);
JsonNode *xjd1DataSrcDoc(DataSrc*, const char*);
int xjd1DataSrcCount(DataSrc *);
JsonNode *xjd1DataSrcCacheRead(DataSrc *, JsonNode **, const char *zDocname);

Changes to test/all.test.

5
6
7
8
9
10
11

.read base03.test
.read base04.test
.read base05.test
.read base06.test
.read base07.test
.read base08.test
.read base09.test








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

Added test/base10.test.



































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
-- Test using a reference to an outer query property as a variable.
--

.new t1.db
CREATE COLLECTION c1;
INSERT INTO c1 VALUE {docid:1, tags:["a", "b", "c"]};
INSERT INTO c1 VALUE {docid:2, tags:[     "b", "c"]};
INSERT INTO c1 VALUE {docid:3, tags:[          "c", "d", "e"]};
INSERT INTO c1 VALUE {docid:4, tags:[]};
INSERT INTO c1 VALUE {docid:5};

.testcase 0
SELECT FROM c1;
.json {docid:1, tags:["a", "b", "c"]}               \
      {docid:2, tags:[     "b", "c"]}               \
      {docid:3, tags:[          "c", "d", "e"]}     \
      {docid:4, tags:[]}                            \
      {docid:5} 

-- All documents with tag "b"
--
.testcase 1
SELECT c1.docid FROM c1 WHERE (SELECT 1 FROM c1.tags AS t WHERE t=="b");
.result 1 2

-- All documents with at least one tag that do not have a tag greater than "c"
--
.testcase 2
SELECT c1.docid FROM c1 WHERE (
  SELECT count()==0 FROM c1.tags AS t WHERE t>"c"
);
.result 1 2 4 5

-- All documents with more than two tags.
--
.testcase 3
SELECT c1.docid FROM c1 WHERE (SELECT count()>2 FROM c1.tags AS t);
.result 1 3

-- Each document and it's smallest tag.
--
.testcase 4
SELECT {docid: c1.docid, smallest: (SELECT min(t) FROM c1.tags AS t) } FROM c1;
.json {docid:1, smallest:"a"} {docid:2, smallest:"b"}    \
      {docid:3, smallest:"c"} {docid:4, smallest:null}   \
      {docid:5, smallest:null}