UnQL

Check-in [21ef096d9c]
Login

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

Overview
Comment:Allow FLATTEN/EACH on the properties of nested objects.
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 21ef096d9c8e010f51bdbb72ca341c6c29556fe3
User & Date: dan 2011-07-27 12:17:08
Context
2011-07-27
16:23
Add the ".json" command to shell.c. ".json" is like ".result", but removes excess white-space and quotes unquoted identifiers from the test result before comparing it with the library output. This allows us to make test files more readable. check-in: 1e6ab63d25 user: dan tags: trunk
12:17
Allow FLATTEN/EACH on the properties of nested objects. check-in: 21ef096d9c user: dan tags: trunk
2011-07-26
19:04
Add some support for FLATTEN. check-in: 269c12e5ee user: dan tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/datasrc.c.

39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
..
63
64
65
66
67
68
69




















































70
71
72
73
74
75
76
..
94
95
96
97
98
99
100
101




102






103
104
105
106
107
108
109



110
111
112
113
114
115
116
...
245
246
247
248
249
250
251
252
253
254
255

256
257
258
259
260
261
262
263



264
265
266
267
268
269
270
...
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330



331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
      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: {
      const char *zOp = (p->u.flatten.cOpName=='E' ? "EACH" : "FLATTEN");
      ExprItem *pItem = &p->u.flatten.pList->apEItem[0];
      xjd1DataSrcInit(p->u.flatten.pNext, pQuery);
      rc = xjd1FlattenExprInit(pItem->pExpr,p->u.flatten.pNext,&pItem->zAs,zOp);
      break;
    }
    case TK_NULL:                 /* Initializing a NULL DS is a no-op */
      assert( p->u.null.isDone==0 );
      break;
  }
  return rc;
................................................................................
  return pRet;
}

static JsonNode *newStringValue(const char *z){
  JsonNode *pRet = xjd1JsonNew(0);
  pRet->eJType = XJD1_STRING;
  pRet->u.z = xjd1PoolDup(0, z, -1);




















































  return pRet;
}

/*
** An iterator of this type is used by FLATTEN and EACH datasources. It
** is allocated, accessed and deleted only by the functions:
**
................................................................................

/*
** Allocate a new iterator to iterate through value pVal. If parameter 
** isRecursive is true, then the iteration descends into any contained structs
** or arrays (for the FLATTEN operator). Otherwise, the iteration is not
** recursive (used by the EACH operator).
*/
static FlattenIter *flattenIterNew(JsonNode *pVal, int isRecursive){




  FlattenIter *pNew;






  pNew = (FlattenIter *)xjd1MallocZero(sizeof(FlattenIter));
  if( pNew ){
    pNew->nAlloc = 1;
    pNew->nIter = 1;
    pNew->isRecursive = isRecursive;
    pNew->aIter[0].pVal = xjd1JsonRef(pVal);
  }



  return pNew;
}

static int flattenIterEntry(
  FlattenIter *pIter,             /* Iterator handle */
  JsonNode **ppKey,               /* OUT: Current key value */
  JsonNode **ppVal                /* OUT: Current payload value */
................................................................................
** This function decrements the ref-count of arguments pKey and pValue. But
** not pBase.
*/
static JsonNode *flattenedObject(
  JsonNode *pBase,                /* Base object */
  JsonNode *pKey,
  JsonNode *pValue,
  const char *zAs
){
  JsonNode *pRet;                 /* Value to return */
  JsonNode *pKV;                  /* New value for property zAs */


  pKV = xjd1JsonNew(0);
  pKV->eJType = XJD1_STRUCT;
  xjd1JsonInsert(pKV, "k", pKey);
  xjd1JsonInsert(pKV, "v", pValue);

  pRet = xjd1JsonEdit(xjd1JsonRef(pBase));
  xjd1JsonInsert(pRet, zAs, pKV);



  assert( pRet->nRef==1 && pRet!=pBase );
  return pRet;
}

/*
** Advance a data source to the next row. Return XJD1_DONE if the data 
** source is at EOF or XJD1_ROW if the step results in a row of content 
................................................................................
        p->u.tab.eofSeen = 1;
        rc = XJD1_DONE;
      }
      break;
    }

    case TK_FLATTENOP: {
      ExprItem *pItem = &p->u.flatten.pList->apEItem[0];
      xjd1JsonFree(p->pValue);
      p->pValue = 0;

      while( XJD1_ROW!=(rc = flattenIterNext(&p->u.flatten.pIter)) ){
        flattenIterFree(p->u.flatten.pIter);
        p->u.flatten.pIter = 0;

        rc = xjd1DataSrcStep(p->u.flatten.pNext);
        if( rc!=XJD1_ROW ){
          break;
        }else{
          JsonNode *pVal = xjd1ExprEval(pItem->pExpr);



          p->u.flatten.pIter = flattenIterNew(pVal, p->u.flatten.cOpName=='F');
        }
      }

      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, pItem->zAs);
      }

      break;
    }

    case TK_NULL: {
      rc = (p->u.null.isDone ? XJD1_DONE : XJD1_ROW);







<
<

<







 







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







 







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







 







|



>







|
>
>
>







 







<











|
>
>
>
|








<
|







39
40
41
42
43
44
45


46

47
48
49
50
51
52
53
..
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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
...
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
...
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
...
377
378
379
380
381
382
383

384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407

408
409
410
411
412
413
414
415
      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;
................................................................................
  return pRet;
}

static JsonNode *newStringValue(const char *z){
  JsonNode *pRet = xjd1JsonNew(0);
  pRet->eJType = XJD1_STRING;
  pRet->u.z = xjd1PoolDup(0, z, -1);
  return pRet;
}

/*
** Argument pPath is a an expression consisting entirely of TK_ID 
** and TK_DOT noddes. e.g. "a.b.c.d". If value pVal contains the
** identified property, a pointer to the JsonStructElem that contains
** it is returned. 
**
** If bCreate is not true and object pVal does not contain the specified
** property, NULL is returned. Or, if bCreate is true and pVal contains all
** but the rightmost component of the path, a new element is added to the 
** object and a pointer to it returned.
*/
static JsonStructElem *findStructElem(JsonNode *pVal, Expr *pPath, int bCreate){
  JsonStructElem *pRet = 0;       /* Return value */

  assert( bCreate==0 || pVal->nRef==1 );
  assert( pPath->eType==TK_ID || pPath->eType==TK_DOT );

  if( pVal && pVal->eJType==XJD1_STRUCT ){
    JsonNode *p = 0;
    const char *zAs;
    if( pPath->eType==TK_DOT ){
      JsonStructElem *pElem;
      pElem = findStructElem(pVal, pPath->u.lvalue.pLeft, bCreate);
      if( pElem ){
        p = pElem->pValue;
        zAs = pPath->u.lvalue.zId;
      }
    }else{
      p = pVal;
      zAs = pPath->u.id.zId;
    }

    if( p && p->eJType==XJD1_STRUCT ){
      for(pRet=p->u.st.pFirst; pRet; pRet=pRet->pNext){
        if( 0==strcmp(pRet->zLabel, zAs) ) break;
      }
      if( pRet==0 && bCreate ){
        pRet = xjd1MallocZero(sizeof(*pRet));
        pRet->zLabel = xjd1PoolDup(0, zAs, -1);
        if( p->u.st.pLast ){
          p->u.st.pLast->pNext = pRet;
        }else{
          p->u.st.pFirst = pRet;
        }
        p->u.st.pLast = pRet;
      }
    }
  }

  return pRet;
}

/*
** An iterator of this type is used by FLATTEN and EACH datasources. It
** is allocated, accessed and deleted only by the functions:
**
................................................................................

/*
** Allocate a new iterator to iterate through value pVal. If parameter 
** isRecursive is true, then the iteration descends into any contained structs
** or arrays (for the FLATTEN operator). Otherwise, the iteration is not
** recursive (used by the EACH operator).
*/
static FlattenIter *flattenIterNew(
  JsonNode *pBase,                /* Base object */
  Expr *pPath,                    /* Path to flatten or each on */
  int isRecursive                 /* True for FLATTEN, false for EACH */
){
  FlattenIter *pNew = 0;          /* New iterator object */
  JsonStructElem *pElem;

  pElem = findStructElem(pBase, pPath, 0);
  if( pElem ){
    JsonNode *pVal = pElem->pValue;
    if( pVal->eJType==XJD1_STRUCT || pVal->eJType==XJD1_ARRAY ){
      pNew = (FlattenIter *)xjd1MallocZero(sizeof(FlattenIter));
      if( pNew ){
        pNew->nAlloc = 1;
        pNew->nIter = 1;
        pNew->isRecursive = isRecursive;
        pNew->aIter[0].pVal = xjd1JsonRef(pVal);
      }
    }
  }

  return pNew;
}

static int flattenIterEntry(
  FlattenIter *pIter,             /* Iterator handle */
  JsonNode **ppKey,               /* OUT: Current key value */
  JsonNode **ppVal                /* OUT: Current payload value */
................................................................................
** This function decrements the ref-count of arguments pKey and pValue. But
** not pBase.
*/
static JsonNode *flattenedObject(
  JsonNode *pBase,                /* Base object */
  JsonNode *pKey,
  JsonNode *pValue,
  Expr *pPath
){
  JsonNode *pRet;                 /* Value to return */
  JsonNode *pKV;                  /* New value for property zAs */
  JsonStructElem *pElem;

  pKV = xjd1JsonNew(0);
  pKV->eJType = XJD1_STRUCT;
  xjd1JsonInsert(pKV, "k", pKey);
  xjd1JsonInsert(pKV, "v", pValue);

  pRet = xjd1JsonEdit(xjd1JsonRef(pBase));
  pElem = findStructElem(pRet, pPath, 1);
  xjd1JsonFree(pElem->pValue);
  pElem->pValue = pKV;
  
  assert( pRet->nRef==1 && pRet!=pBase );
  return pRet;
}

/*
** Advance a data source to the next row. Return XJD1_DONE if the data 
** source is at EOF or XJD1_ROW if the step results in a row of content 
................................................................................
        p->u.tab.eofSeen = 1;
        rc = XJD1_DONE;
      }
      break;
    }

    case TK_FLATTENOP: {

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

      while( XJD1_ROW!=(rc = flattenIterNext(&p->u.flatten.pIter)) ){
        flattenIterFree(p->u.flatten.pIter);
        p->u.flatten.pIter = 0;

        rc = xjd1DataSrcStep(p->u.flatten.pNext);
        if( rc!=XJD1_ROW ){
          break;
        }else{
          int isRecursive = (p->u.flatten.cOpName=='F');
          JsonNode *pBase = p->u.flatten.pNext->pValue;
          Expr *pPath = p->u.flatten.pExpr;

          p->u.flatten.pIter = flattenIterNew(pBase, pPath, isRecursive);
        }
      }

      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);

Changes to src/expr.c.

208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
  sCtx.pStmt = pStmt;
  sCtx.pQuery = pQuery;
  sCtx.eExpr = eExpr;
  sCtx.pParent = (ResolveCtx *)pCtx;
  return walkExpr(p, walkInitCallback, (void *)&sCtx);
}

typedef struct FlattenCtx FlattenCtx;
struct FlattenCtx {
  DataSrc *pSrc;
  xjd1_stmt *pStmt;
  char *zAs;
  const char *zOp;
};

static int walkInitFlattenCallback(Expr *p, void *pArg){
  FlattenCtx *pCtx = (FlattenCtx *)pArg;
  int rc = XJD1_OK;
  switch( p->eType ){
    case TK_ID:
      p->u.id.pDataSrc = pCtx->pSrc;
      pCtx->zAs = p->u.id.zId;
      break;

    default: {
      const char *zErrMsg = "error in %s expression";
      xjd1StmtError(pCtx->pStmt, XJD1_ERROR, zErrMsg, pCtx->zOp);
      rc = XJD1_ERROR;
      break;
    }
  }
  return rc;
}

int xjd1FlattenExprInit(Expr *p, DataSrc *pSrc, char **pzAs, const char *zOp){
  int rc;
  FlattenCtx sCtx;
  sCtx.pSrc = pSrc;
  sCtx.pStmt = pSrc->pQuery->pStmt;
  sCtx.zAs = 0;
  sCtx.zOp = zOp;
  rc = walkExpr(p, walkInitFlattenCallback, (void *)&sCtx);
  if( *pzAs==0 ) *pzAs = sCtx.zAs;
  return rc;
}

/*
** Initialize a list of expression in preparation for evaluation of a
** statement.
*/
int xjd1ExprListInit(
  ExprList *p,                    /* List of expressions to initialize */
  xjd1_stmt *pStmt,               /* Statement expressions belong to */







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







208
209
210
211
212
213
214







































215
216
217
218
219
220
221
  sCtx.pStmt = pStmt;
  sCtx.pQuery = pQuery;
  sCtx.eExpr = eExpr;
  sCtx.pParent = (ResolveCtx *)pCtx;
  return walkExpr(p, walkInitCallback, (void *)&sCtx);
}








































/*
** Initialize a list of expression in preparation for evaluation of a
** statement.
*/
int xjd1ExprListInit(
  ExprList *p,                    /* List of expressions to initialize */
  xjd1_stmt *pStmt,               /* Statement expressions belong to */

Changes to src/parse.y.

483
484
485
486
487
488
489
490

491
492
493
494
495
496
497

498
499
500
501
502
503
504
...
514
515
516
517
518
519
520

521
522
523





524
525
526



527
528
529
530
531
532
533
  }

  /* Create a new data source that is a FLATTEN or EACH operator */
  static DataSrc *flattenDataSrc(
    Parse *p,
    DataSrc *pLeft,
    Token *pOp,
    ExprList *pArgs

  ){
    DataSrc *pNew = xjd1PoolMallocZero(p->pPool, sizeof(*pNew));
    if( pNew ){
      pNew->eDSType = TK_FLATTENOP;
      pNew->u.flatten.pNext = pLeft;
      pNew->u.flatten.cOpName = pOp->z[0];
      pNew->u.flatten.pList = pArgs;

    }
    return pNew;
  }

  /* Create a new data source that represents an empty FROM clause.
  ** This is used for queries of the form "SELECT <expr>". It returns a
  ** single object with no properties.  
................................................................................
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(X) FLATTENOP(Y) LP eachexpr_list(Z) RP.
                                                 {A = flattenDataSrc(p,X,&Y,Z);}






%type eachexpr_list {ExprList*}
eachexpr_list(A) ::= lvalue(Y).                  {A = apndExpr(p,0,Y,0);}
eachexpr_list(A) ::= lvalue(Y) AS ID(Z).         {A = apndExpr(p,0,Y,&Z);}




%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;}








|
>






|
>







 







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







483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
...
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
  }

  /* Create a new data source that is a FLATTEN or EACH operator */
  static DataSrc *flattenDataSrc(
    Parse *p,
    DataSrc *pLeft,
    Token *pOp,
    Expr *pPath,
    Expr *pAs
  ){
    DataSrc *pNew = xjd1PoolMallocZero(p->pPool, sizeof(*pNew));
    if( pNew ){
      pNew->eDSType = TK_FLATTENOP;
      pNew->u.flatten.pNext = pLeft;
      pNew->u.flatten.cOpName = pOp->z[0];
      pNew->u.flatten.pExpr = pPath;
      pNew->u.flatten.pAs = (pAs ? pAs : pPath);
    }
    return pNew;
  }

  /* Create a new data source that represents an empty FROM clause.
  ** This is used for queries of the form "SELECT <expr>". It returns a
  ** single object with no properties.  
................................................................................
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;}

Changes to src/tokenize.c.

374
375
376
377
378
379
380




381
382
383
384
385
386
387
...
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
      *tokenType = keywordCode((char*)z, i);
      return i;
    }
  }
  *tokenType = TK_ILLEGAL;
  return 1;
}





/*
** Run the parser on the given SQL string.  The parser structure is
** passed in.  An SQLITE_ status code is returned.  If an error occurs
** then an and attempt is made to write an error message into 
** memory obtained from xjd1_malloc() and to make *pzErrMsg point to that
** error message.
................................................................................
#ifndef NDEBUG
  if( pConn->parserTrace ){
    extern void xjd1ParserTrace(FILE*, char*);
    xjd1ParserTrace(stdout, "parser> ");
  }
#endif

  pEngine = xjd1ParserAlloc(xjd1_malloc);
  memset(&sParse, 0, sizeof(sParse));
  if( pEngine==0 ){
    sParse.errCode = XJD1_NOMEM;
    goto abort_parse;
  }

  sParse.pConn = pConn;







>
>
>
>







 







|







374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
...
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
      *tokenType = keywordCode((char*)z, i);
      return i;
    }
  }
  *tokenType = TK_ILLEGAL;
  return 1;
}

static void *parserAlloc(size_t N){ 
  return xjd1_malloc((int)N); 
}

/*
** Run the parser on the given SQL string.  The parser structure is
** passed in.  An SQLITE_ status code is returned.  If an error occurs
** then an and attempt is made to write an error message into 
** memory obtained from xjd1_malloc() and to make *pzErrMsg point to that
** error message.
................................................................................
#ifndef NDEBUG
  if( pConn->parserTrace ){
    extern void xjd1ParserTrace(FILE*, char*);
    xjd1ParserTrace(stdout, "parser> ");
  }
#endif

  pEngine = xjd1ParserAlloc(parserAlloc);
  memset(&sParse, 0, sizeof(sParse));
  if( pEngine==0 ){
    sParse.errCode = XJD1_NOMEM;
    goto abort_parse;
  }

  sParse.pConn = pConn;

Changes to src/trace.c.

292
293
294
295
296
297
298
299


300
301
302
303
304
305
306
      xjd1TraceQuery(pOut, indent+3, p->u.subq.q);
      break;
    }
    case TK_FLATTENOP: {
      xjd1TraceDataSrc(pOut, indent, p->u.flatten.pNext);
      xjd1StringAppendF(pOut, "%*s%s:\n", indent, "",
            p->u.flatten.cOpName=='E' ? "EACH" : "FLATTEN");
      xjd1TraceExprList(pOut, indent+3, p->u.flatten.pList);


      break;
    }
  }
}

/*
** Output the content of an expression.







|
>
>







292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
      xjd1TraceQuery(pOut, indent+3, p->u.subq.q);
      break;
    }
    case TK_FLATTENOP: {
      xjd1TraceDataSrc(pOut, indent, p->u.flatten.pNext);
      xjd1StringAppendF(pOut, "%*s%s:\n", indent, "",
            p->u.flatten.cOpName=='E' ? "EACH" : "FLATTEN");
      xjd1TraceExpr(pOut, p->u.flatten.pExpr);
      xjd1StringAppendF(pOut, " AS ");
      xjd1TraceExpr(pOut, p->u.flatten.pAs);
      break;
    }
  }
}

/*
** Output the content of an expression.

Changes to src/xjd1Int.h.

339
340
341
342
343
344
345
346

347
348
349
350
351
352
353
...
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
      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" */
      ExprList *pList;         /* List of arguments */

      FlattenIter *pIter;      /* Iterator */
    } flatten;
    struct {                /* A subquery.  eDSType==TK_SELECT */
      Query *q;                /* The subquery */
    } subq;
    struct {                /* An empty FROM clause.  eDSType==TK_NULL */
      int isDone;              /* True if single row already returned */
................................................................................
/******************************** expr.c *************************************/
int xjd1ExprInit(Expr*, xjd1_stmt*, Query*, int, void *);
int xjd1ExprListInit(ExprList*, xjd1_stmt*, Query*, int, void *);
JsonNode *xjd1ExprEval(Expr*);
int xjd1ExprTrue(Expr*);
int xjd1ExprClose(Expr*);
int xjd1ExprListClose(ExprList*);
int xjd1FlattenExprInit(Expr*, DataSrc *, char **, const char *);
/* Candidates for the 4th parameter to xjd1ExprInit() */
#define XJD1_EXPR_RESULT  1
#define XJD1_EXPR_WHERE   2
#define XJD1_EXPR_GROUPBY 3
#define XJD1_EXPR_HAVING  4
#define XJD1_EXPR_ORDERBY 5
#define XJD1_EXPR_LIMIT   6







|
>







 







|







339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
...
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
      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;
    struct {                /* A subquery.  eDSType==TK_SELECT */
      Query *q;                /* The subquery */
    } subq;
    struct {                /* An empty FROM clause.  eDSType==TK_NULL */
      int isDone;              /* True if single row already returned */
................................................................................
/******************************** expr.c *************************************/
int xjd1ExprInit(Expr*, xjd1_stmt*, Query*, int, void *);
int xjd1ExprListInit(ExprList*, xjd1_stmt*, Query*, int, void *);
JsonNode *xjd1ExprEval(Expr*);
int xjd1ExprTrue(Expr*);
int xjd1ExprClose(Expr*);
int xjd1ExprListClose(ExprList*);

/* Candidates for the 4th parameter to xjd1ExprInit() */
#define XJD1_EXPR_RESULT  1
#define XJD1_EXPR_WHERE   2
#define XJD1_EXPR_GROUPBY 3
#define XJD1_EXPR_HAVING  4
#define XJD1_EXPR_ORDERBY 5
#define XJD1_EXPR_LIMIT   6

Changes to test/base08.test.

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
58
59
60
61
62
63
64
65
66





67









.result {"y":{"k":"a","v":"A"},"x":{"a":"A","b":"B"}} {"y":{"k":"b","v":"B"},"x":{"a":"A","b":"B"}}

.testcase 7
SELECT FROM c3 EACH(2 AS y);
.error SYNTAX syntax error near "2"

.testcase 8
SELECT FROM c3 EACH(a.b AS y);
.error ERROR error in EACH expression

.testcase 9
SELECT FROM c3 FLATTEN(a.b AS y);
.error ERROR error in FLATTEN expression


.testcase 10
CREATE COLLECTION c4;
INSERT INTO c4 VALUE {a:1, b: [ [1, 2, 3], [4, 5, 6], [7, 8, 9] ]};
INSERT INTO c4 VALUE {a:2, b: { x:[1, 2, 3], y:{t:4, u:5, v:6}, z:[7, 8, 9] }};
SELECT c4.b.v FROM c4 FLATTEN(b) WHERE c4.a==1;
.result 1 2 3 4 5 6 7 8 9

.testcase 11
SELECT c4.b.k FROM c4 FLATTEN(b) WHERE c4.a==1;
.result [0,0] [0,1] [0,2] [1,0] [1,1] [1,2] [2,0] [2,1] [2,2]

.testcase 12
SELECT c4.c.v FROM c4 FLATTEN(b AS c) WHERE c4.a==2;
.result 1 2 3 4 5 6 7 8 9

.testcase 13
SELECT c4.c.k FROM c4 FLATTEN(b AS c) WHERE c4.a==2;
.result ["x",0] ["x",1] ["x",2] ["y","t"] ["y","u"] ["y","v"] ["z",0] ["z",1] ["z",2]
  





  
















<
<
<
<
<
<
<
<
<






|



|



|


|
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
.result {"y":{"k":"a","v":"A"},"x":{"a":"A","b":"B"}} {"y":{"k":"b","v":"B"},"x":{"a":"A","b":"B"}}

.testcase 7
SELECT FROM c3 EACH(2 AS y);
.error SYNTAX syntax error near "2"

.testcase 8









CREATE COLLECTION c4;
INSERT INTO c4 VALUE {a:1, b: [ [1, 2, 3], [4, 5, 6], [7, 8, 9] ]};
INSERT INTO c4 VALUE {a:2, b: { x:[1, 2, 3], y:{t:4, u:5, v:6}, z:[7, 8, 9] }};
SELECT c4.b.v FROM c4 FLATTEN(b) WHERE c4.a==1;
.result 1 2 3 4 5 6 7 8 9

.testcase 9
SELECT c4.b.k FROM c4 FLATTEN(b) WHERE c4.a==1;
.result [0,0] [0,1] [0,2] [1,0] [1,1] [1,2] [2,0] [2,1] [2,2]

.testcase 10
SELECT c4.c.v FROM c4 FLATTEN(b AS c) WHERE c4.a==2;
.result 1 2 3 4 5 6 7 8 9

.testcase 11
SELECT c4.c.k FROM c4 FLATTEN(b AS c) WHERE c4.a==2;
.result ["x",0] ["x",1] ["x",2] ["y","t"] ["y","u"] ["y","v"] ["z",0] ["z",1] ["z",2]

.testcase 12
CREATE COLLECTION c5;
INSERT INTO c5 VALUE { a:1, b:2, c:{d:3, e:[4,5,6,7,8]} };
SELECT c5.c.e.v FROM c5 EACH(c.e);
.result 4 5 6 7 8

.testcase 13
SELECT c5.c.e.v FROM c5 EACH(c["e"]);
.result 4 5 6 7 8

.testcase 14
SELECT c5.x.k FROM c5 EACH(c["e"] AS x);
.result 0 1 2 3 4