UnQL

Check-in [269c12e5ee]
Login

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

Overview
Comment:Add some support for FLATTEN.
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 269c12e5ee019ac84415cd9ffea13b1933731080
User & Date: dan 2011-07-26 19:04:55
Context
2011-07-27
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
2011-07-25
19:26
Add partial support for the EACH keyword. check-in: 5d36d9f104 user: dan tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/datasrc.c.

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
...
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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
...
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
static JsonNode *newStringValue(const char *z){
  JsonNode *pRet = xjd1JsonNew(0);
  pRet->eJType = XJD1_STRING;
  pRet->u.z = xjd1PoolDup(0, z, -1);
  return pRet;
}
















































































































































































static JsonNode *flattenedObject(
  JsonNode *pBase,
  JsonNode *pKey,
  JsonNode *pValue,
  const char *zAs
){
  JsonNode *pRet;                 /* Value to return */
  JsonNode *pKV;                  /* Value to return */

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

  pRet = xjd1JsonEdit(xjd1JsonRef(pBase));
  assert( pRet->nRef==1 && pRet!=pBase );
  xjd1JsonInsert(pRet, zAs, pKV);

  return pRet;
}

static int valueIsEmpty(JsonNode *p){
  if( p && ( 
      (p->eJType==XJD1_ARRAY && p->u.ar.nElem>0)
   || (p->eJType==XJD1_STRUCT && p->u.st.pFirst!=0)
  )){
    return 0;
  }
  return 1;
}

/*
** Advance a data source to the next row.
** Return XJD1_DONE if the data source is empty and XJD1_ROW if
** the step results in a row of content being available.

*/
int xjd1DataSrcStep(DataSrc *p){
  int rc= XJD1_DONE;
  if( p==0 ) return XJD1_DONE;
  switch( p->eDSType ){
    case TK_COMMA: {

................................................................................
        rc = XJD1_DONE;
      }
      break;
    }

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

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



      while( rc==XJD1_ROW && valueIsEmpty(p->u.flatten.pValue) ){

        rc = xjd1DataSrcStep(p->u.flatten.pNext);
        if( rc==XJD1_ROW ){


          p->u.flatten.pValue = xjd1ExprEval(pItem->pExpr);
          p->u.flatten.iIdx = 0;

        }
      }

      if( rc==XJD1_ROW ){
        JsonNode *pOut = 0;
        JsonNode *pVal = p->u.flatten.pValue;
        JsonNode *pBase = p->u.flatten.pNext->pValue;
        int iIdx = p->u.flatten.iIdx++;
        int bEof = 0;

        switch( pVal->eJType ){
          case XJD1_STRUCT: {
            int i;
            JsonStructElem *pElem = pVal->u.st.pFirst;
            for(i=0; i<iIdx; i++) pElem = pElem->pNext;
            bEof = (pElem->pNext==0);
            pOut = flattenedObject(pBase, newStringValue(pElem->zLabel), 
                xjd1JsonRef(pElem->pValue), pItem->zAs
            );
            break;
          }
          case XJD1_ARRAY: {
            assert( iIdx<pVal->u.ar.nElem );
            bEof = ( (iIdx+1)>=pVal->u.ar.nElem );
            pOut = flattenedObject(pBase, newIntValue(iIdx),
                xjd1JsonRef(pVal->u.ar.apElem[iIdx]), pItem->zAs
            );
            break;
          }
        }

        if( bEof ){
          p->u.flatten.pValue = 0;
          xjd1JsonFree(p->u.flatten.pValue);
        }
        p->pValue = pOut;
      }
      break;
    }

    case TK_NULL: {
      rc = (p->u.null.isDone ? XJD1_DONE : XJD1_ROW);
      p->u.null.isDone = 1;
      break;
................................................................................
    }
    case TK_ID: {
      sqlite3_reset(p->u.tab.pStmt);
      break;
    }
    case TK_FLATTENOP: {
      xjd1DataSrcRewind(p->u.flatten.pNext);
      xjd1JsonFree(p->u.flatten.pValue);
      p->u.flatten.pValue = 0;
      p->u.flatten.iIdx = 0;
      break;
    }
    case TK_NULL: {
      p->u.null.isDone = 0;
      break;
    }
  }







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

|





|







<

>



<
<
<
<
<
<
<
<
<
<

|
<
|
>







 







<


<

>
>
|
>

|
>
>
|
<
>




|
|

|
<

<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
<
<
<
|
|
<
<
<
<
<
<
<







 







|
|
<







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
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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
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
261
262

263
264
265
266
267










268
269

270
271
272
273
274
275
276
277
278
...
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
349
350
...
415
416
417
418
419
420
421
422
423

424
425
426
427
428
429
430
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:
**
**     flattenIterNew()
**     flattenIterNext()
**     flattenIterEntry()
**     flattenIterFree()
*/
struct FlattenIter {
  int nAlloc;                     /* Allocated size of aIter[] array */
  int nIter;                      /* Number of aIter[] array elements in use */
  int isRecursive;                /* True for FLATTEN, false for EACH */
  struct FlattenIterElem {
    JsonNode *pVal;               /* Struct or List to iterate through. */
    union {
      JsonStructElem *pElem;      /* If pVal->eJType==XJD1_STRUCT */
      int iElem;                  /* If pVal->eJType==XJD1_ARRAY */
    } current;
  } aIter[1];
};

/*
** 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 */
){
  struct FlattenIterElem *p = &pIter->aIter[pIter->nIter-1];

  assert( p->pVal->eJType==XJD1_STRUCT || p->pVal->eJType==XJD1_ARRAY );
  if( p->pVal->eJType==XJD1_STRUCT ){
    *ppVal = xjd1JsonRef(p->current.pElem->pValue);
  }else{
    *ppVal = xjd1JsonRef(p->pVal->u.ar.apElem[p->current.iElem-1]);
  }

  if( ppKey ){
    JsonNode *pKey = 0;
    JsonNode *pList = 0;
    int i;

    if( pIter->isRecursive ){
      pList = xjd1JsonNew(0);
      pList->eJType = XJD1_ARRAY;
      pList->u.ar.nElem = pIter->nIter;
      pList->u.ar.apElem = xjd1MallocZero(pIter->nIter*sizeof(JsonNode *));
    }

    for(i=pIter->nIter-1; i>=0; i--){
      p = &pIter->aIter[i];
      if( p->pVal->eJType==XJD1_STRUCT ){
        pKey = newStringValue(p->current.pElem->zLabel);
      }else{
        pKey = newIntValue(p->current.iElem-1);
      }
      if( pIter->isRecursive==0 ) break;
      pList->u.ar.apElem[i] = pKey;
    }

    *ppKey = (pIter->isRecursive ? pList : pKey);
  }
  return XJD1_OK;
}

static int flattenIterNext(FlattenIter **ppIter){
  FlattenIter *pIter = *ppIter;
  int rc = XJD1_DONE;
  if( pIter ){
    while( rc==XJD1_DONE && pIter->nIter ){
      struct FlattenIterElem *p = &(*ppIter)->aIter[pIter->nIter-1];
      if( p->pVal->eJType==XJD1_STRUCT ){
        if( p->current.pElem==0 ){
          p->current.pElem = p->pVal->u.st.pFirst;
        }else{
          p->current.pElem = p->current.pElem->pNext;
        }
        if( p->current.pElem ){
          rc = XJD1_ROW;
        }else{
          pIter->nIter--;
        }
      }else{
        assert( p->pVal->eJType==XJD1_ARRAY );
        p->current.iElem++;
        if( p->current.iElem<=p->pVal->u.ar.nElem ){
          rc = XJD1_ROW;
        }else{
          pIter->nIter--;
        }
      }

      if( pIter->isRecursive && rc==XJD1_ROW ){
        JsonNode *pVal;
        flattenIterEntry(pIter, 0, &pVal);
        if( pVal->eJType==XJD1_STRUCT || pVal->eJType==XJD1_ARRAY ){
          assert( pIter->nIter<=pIter->nAlloc );
          if( pIter->nIter==pIter->nAlloc ){
            int nNew = sizeof(FlattenIter) + (pIter->nAlloc*2-1)*sizeof(*p);
            FlattenIter *pNew;

            pNew = (FlattenIter *)xjd1_realloc(pIter, nNew);
            pNew->nAlloc = pNew->nAlloc * 2;
            *ppIter = pNew;
            pIter = pNew;
          }

          pIter->aIter[pIter->nIter].pVal = pVal;
          pIter->aIter[pIter->nIter].current.pElem = 0;
          pIter->aIter[pIter->nIter].current.iElem = 0;
          pIter->nIter++;
          rc = XJD1_DONE;
        }
      }
    }
  }
  return rc;
}

/*
** Free an iterator allocated by flattenIterNew().
*/
static void flattenIterFree(FlattenIter *pIter){
  if( pIter ){
    int i;
    for(i=0; i<pIter->nIter; i++){
      xjd1JsonFree(pIter->aIter[i].pVal);
    }
    xjd1_free(pIter);
  }
}



/*
** This function makes a copy of the JSON value (type XJD1_STRUCT) passed as
** the first object, adds a property to it, and returns a pointer to the copy.
** The ref-count of the returned object is 1.
**
** The name of the property added is passed as the zAs argument. The property
** is set to a structure containing two fields, "k" (value pKey) and "v" 
** (value pValue). i.e. if the arguments are as follows:
**
**     pBase   = {a:a, b:b}
**     pKey    = "abc"
**     pValue  = "xyz"
**     zAs     = "c"
**
** then the returned object is:
**
**     {a:a, b:b, c:{k:"abc", v:"xyz"}}
**
** If object pBase already has a field named "zAs", then it is replaced in
** the returned copy.
**
** 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 
** being available.
*/
int xjd1DataSrcStep(DataSrc *p){
  int rc= XJD1_DONE;
  if( p==0 ) return XJD1_DONE;
  switch( p->eDSType ){
    case TK_COMMA: {

................................................................................
        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);
      p->u.null.isDone = 1;
      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_NULL: {
      p->u.null.isDone = 0;
      break;
    }
  }

Changes to src/memory.c.

47
48
49
50
51
52
53







54
55
56
57
58
59
60
}
void xjd1_free(void *p){
  global.xFree(p);
}
void *xjd1_realloc(void *p, int N){
  return global.xRealloc(p, N);
}









/*
** Create a new memory allocation pool.  Return a pointer to the
** memory pool or NULL on OOM error.
*/
Pool *xjd1PoolNew(void){







>
>
>
>
>
>
>







47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
}
void xjd1_free(void *p){
  global.xFree(p);
}
void *xjd1_realloc(void *p, int N){
  return global.xRealloc(p, N);
}

void *xjd1MallocZero(int N){
  void *pRet;
  pRet = global.xMalloc(N);
  if( pRet ) memset(pRet, 0, N);
  return pRet;
}


/*
** Create a new memory allocation pool.  Return a pointer to the
** memory pool or NULL on OOM error.
*/
Pool *xjd1PoolNew(void){

Changes to src/xjd1Int.h.

57
58
59
60
61
62
63

64
65
66
67
68
69
70
...
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
...
441
442
443
444
445
446
447
448
449
450
451
452
453
454

455
456
457
458
459
460
461
typedef struct AggExpr AggExpr;
typedef struct Aggregate Aggregate;
typedef struct Command Command;
typedef struct DataSrc DataSrc;
typedef struct Expr Expr;
typedef struct ExprItem ExprItem;
typedef struct ExprList ExprList;

typedef struct Function Function;
typedef struct JsonNode JsonNode;
typedef struct JsonStructElem JsonStructElem;
typedef struct Parse Parse;
typedef struct PoolChunk PoolChunk;
typedef struct Pool Pool;
typedef struct Query Query;
................................................................................
      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 */
      JsonNode *pValue;        /* Value to flatten on (or NULL) */
      int iIdx;                /* Index of value field just returned */
    } 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 */
    } null;
................................................................................
JsonNode *xjd1JsonNew(Pool*);
JsonNode *xjd1JsonEdit(JsonNode*);
void xjd1JsonFree(JsonNode*);
void xjd1JsonToNull(JsonNode*);
void xjd1DequoteString(char*,int);
int xjd1JsonInsert(JsonNode *, const char *, JsonNode *);

/******************************** malloc.c ***********************************/
Pool *xjd1PoolNew(void);
void xjd1PoolClear(Pool*);
void xjd1PoolDelete(Pool*);
void *xjd1PoolMalloc(Pool*, int);
void *xjd1PoolMallocZero(Pool*, int);
char *xjd1PoolDup(Pool*, const char *, int);


/******************************** pragma.c ***********************************/
int xjd1PragmaStep(xjd1_stmt*);

/******************************** query.c ************************************/
int xjd1QueryInit(Query*,xjd1_stmt*,void*);
int xjd1QueryRewind(Query*);







>







 







|
<







 







|






>







57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
...
340
341
342
343
344
345
346
347

348
349
350
351
352
353
354
...
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
typedef struct AggExpr AggExpr;
typedef struct Aggregate Aggregate;
typedef struct Command Command;
typedef struct DataSrc DataSrc;
typedef struct Expr Expr;
typedef struct ExprItem ExprItem;
typedef struct ExprList ExprList;
typedef struct FlattenIter FlattenIter;
typedef struct Function Function;
typedef struct JsonNode JsonNode;
typedef struct JsonStructElem JsonStructElem;
typedef struct Parse Parse;
typedef struct PoolChunk PoolChunk;
typedef struct Pool Pool;
typedef struct Query Query;
................................................................................
      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 */
    } null;
................................................................................
JsonNode *xjd1JsonNew(Pool*);
JsonNode *xjd1JsonEdit(JsonNode*);
void xjd1JsonFree(JsonNode*);
void xjd1JsonToNull(JsonNode*);
void xjd1DequoteString(char*,int);
int xjd1JsonInsert(JsonNode *, const char *, JsonNode *);

/******************************** memory.c ***********************************/
Pool *xjd1PoolNew(void);
void xjd1PoolClear(Pool*);
void xjd1PoolDelete(Pool*);
void *xjd1PoolMalloc(Pool*, int);
void *xjd1PoolMallocZero(Pool*, int);
char *xjd1PoolDup(Pool*, const char *, int);
void *xjd1MallocZero(int);

/******************************** pragma.c ***********************************/
int xjd1PragmaStep(xjd1_stmt*);

/******************************** query.c ************************************/
int xjd1QueryInit(Query*,xjd1_stmt*,void*);
int xjd1QueryRewind(Query*);

Changes to test/all.test.

3
4
5
6
7
8
9

.read base01.test
.read base02.test
.read base03.test
.read base04.test
.read base05.test
.read base06.test
.read base07.test








>
3
4
5
6
7
8
9
10
.read base01.test
.read base02.test
.read base03.test
.read base04.test
.read base05.test
.read base06.test
.read base07.test
.read base08.test

Changes to test/base08.test.

40
41
42
43
44
45
46





















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





























>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
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]