/ Check-in [54eee9fe]
Login

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

Overview
Comment:Improve the error messages used to report illegal recursive cte references.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | common-table-expr
Files: files | file ages | folders
SHA1: 54eee9fe99290e59469bd3e1a66bb749887d37ee
User & Date: dan 2014-01-16 21:02:02
Context
2014-01-16
21:59
Tweaks to error message text. check-in: 090a77d9 user: drh tags: common-table-expr
21:02
Improve the error messages used to report illegal recursive cte references. check-in: 54eee9fe user: dan tags: common-table-expr
18:34
Allow only a single recursive reference in a recursive CTE. Also require that this reference is not part of a sub-query. check-in: a296b733 user: dan tags: common-table-expr
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/build.c.

4241
4242
4243
4244
4245
4246
4247

4248
4249
4250
4251
4252
4253
4254
    sqlite3SelectDelete(db, pQuery);
    sqlite3DbFree(db, zName);
    pNew = pWith;
  }else{
    pNew->a[pNew->nCte].pSelect = pQuery;
    pNew->a[pNew->nCte].pCols = pArglist;
    pNew->a[pNew->nCte].zName = zName;

    pNew->nCte++;
  }

  return pNew;
}

/*







>







4241
4242
4243
4244
4245
4246
4247
4248
4249
4250
4251
4252
4253
4254
4255
    sqlite3SelectDelete(db, pQuery);
    sqlite3DbFree(db, zName);
    pNew = pWith;
  }else{
    pNew->a[pNew->nCte].pSelect = pQuery;
    pNew->a[pNew->nCte].pCols = pArglist;
    pNew->a[pNew->nCte].zName = zName;
    pNew->a[pNew->nCte].zErr = 0;
    pNew->nCte++;
  }

  return pNew;
}

/*

Changes to src/select.c.

3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
3578
3579
3580
....
3598
3599
3600
3601
3602
3603
3604










3605
3606
3607
3608
3609
3610
3611
....
3612
3613
3614
3615
3616
3617
3618
3619

3620
3621
3622
3623
3624
3625
3626
....
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
....
3654
3655
3656
3657
3658
3659
3660

3661
3662







3663
3664
3665
3666
3667
3668
3669
void sqlite3WithPush(Parse *pParse, With *pWith){
  if( pWith ){
    pWith->pOuter = pParse->pWith;
    pParse->pWith = pWith;
  }
}

/*
** If argument pCte is not NULL, check if it is already a part of the
** stack of CTEs stored by the parser. If so, this indicates an illegal
** recursive reference in a CTE, set of mutually recursive CTEs. Store
** an error in the parser and return SQLITE_ERROR if this is the case.
**
** Otherwise, if pCte is not already part of the stack of CTEs stored
** in the parser, push it onto the stop of that stack.
*/ 
static int ctePush(Parse *pParse, struct Cte *pCte){
  if( pCte ){
    struct Cte *p;
    for(p=pParse->pCte; p; p=p->pOuterCte){
      if( p==pCte ){
        sqlite3ErrorMsg(
            pParse, "illegal recursive defininition in cte: %s", pCte->zName
        );
        return SQLITE_ERROR;
      }
    }
    
    pCte->pOuterCte = pParse->pCte;
    pParse->pCte = pCte;
  }
  return SQLITE_OK;
}
/*
** If argument pCte is not NULL, it must be a pointer to the CTE currently
** on top of the stack of CTEs stored in the parser. Remove it from that
** stack.
*/
static void ctePop(Parse *pParse, struct Cte *pCte){
  if( pCte ){
    assert( pParse->pCte==pCte );
    pParse->pCte = pCte->pOuterCte;
  }
}

/*
** This function checks if argument pFrom refers to a CTE declared by 
** a WITH clause on the stack currently maintained by the parser. And,
** if currently processing a CTE expression, if it is a recursive
** reference to the current CTE.
**
** If pFrom falls into either of the two categories above, pFrom->pTab
................................................................................
  assert( pFrom->pTab==0 );

  pCte = searchWith(pParse->pWith, pFrom);
  if( pCte ){
    ExprList *pEList;
    Select *pSel;
    Select *pLeft;                /* Left-most SELECT statement */











    pFrom->pTab = pTab = sqlite3DbMallocZero(db, sizeof(Table));
    if( pTab==0 ) return WRC_Abort;
    pTab->nRef = 1;
    pTab->zName = sqlite3MPrintf(db, "%s", pCte->zName);
    pTab->iPKey = -1;
    pTab->nRowEst = 1048576;
................................................................................
    pTab->tabFlags |= TF_Ephemeral;
    pFrom->pSelect = sqlite3SelectDup(db, pCte->pSelect, 0);
    if( db->mallocFailed ) return SQLITE_NOMEM;
    assert( pFrom->pSelect );

    /* Check if this is a recursive CTE. */
    pSel = pFrom->pSelect;
    if( pSel->op==TK_ALL || pSel->op==TK_UNION ){

      int i;
      SrcList *pSrc = pFrom->pSelect->pSrc;
      for(i=0; i<pSrc->nSrc; i++){
        struct SrcList_item *pItem = &pSrc->a[i];
        if( pItem->zDatabase==0 
         && pItem->zName!=0 
         && 0==sqlite3StrICmp(pItem->zName, pCte->zName)
................................................................................
      sqlite3ErrorMsg(
          pParse, "multiple recursive references in cte: %s", pCte->zName
      );
      return WRC_Abort;
    }
    assert( pTab->nRef==1 || ((pSel->selFlags&SF_Recursive) && pTab->nRef==2 ));

    if( ctePush(pParse, pCte) ) return WRC_Abort;
    sqlite3WalkSelect(pWalker, pTab->nRef==2 ? pSel->pPrior : pSel);

    for(pLeft=pSel; pLeft->pPrior; pLeft=pLeft->pPrior);
    pEList = pLeft->pEList;
    if( pCte->pCols ){
      if( pEList->nExpr!=pCte->pCols->nExpr ){
        sqlite3ErrorMsg(pParse, "cte \"%s\" returns %d values for %d columns",
            pCte->zName, pEList->nExpr, pCte->pCols->nExpr
................................................................................
        );
        return WRC_Abort;
      }
      pEList = pCte->pCols;
    }
    selectColumnsFromExprList(pParse, pEList, &pTab->nCol, &pTab->aCol);


    if( pSel->selFlags & SF_Recursive ) sqlite3WalkSelect(pWalker, pSel);
    ctePop(pParse, pCte);







  }

  return SQLITE_OK;
}
#endif

/*







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







 







>
>
>
>
>
>
>
>
>
>







 







|
>







 







|
|







 







>
|
<
>
>
>
>
>
>
>







3529
3530
3531
3532
3533
3534
3535






































3536
3537
3538
3539
3540
3541
3542
....
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
3578
3579
3580
3581
3582
3583
....
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598
3599
....
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
....
3627
3628
3629
3630
3631
3632
3633
3634
3635

3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
void sqlite3WithPush(Parse *pParse, With *pWith){
  if( pWith ){
    pWith->pOuter = pParse->pWith;
    pParse->pWith = pWith;
  }
}







































/*
** This function checks if argument pFrom refers to a CTE declared by 
** a WITH clause on the stack currently maintained by the parser. And,
** if currently processing a CTE expression, if it is a recursive
** reference to the current CTE.
**
** If pFrom falls into either of the two categories above, pFrom->pTab
................................................................................
  assert( pFrom->pTab==0 );

  pCte = searchWith(pParse->pWith, pFrom);
  if( pCte ){
    ExprList *pEList;
    Select *pSel;
    Select *pLeft;                /* Left-most SELECT statement */
    int bMayRecursive;            /* True if compound joined by UNION [ALL] */

    /* If pCte->zErr is non-NULL at this point, then this is an illegal
    ** recursive reference to CTE pCte. Leave an error in pParse and return
    ** early. If pCte->zErr is NULL, then this is not a recursive reference.
    ** In this case, proceed.  */
    if( pCte->zErr ){
      sqlite3ErrorMsg(pParse, pCte->zErr, pCte->zName);
      return WRC_Abort;
    }

    pFrom->pTab = pTab = sqlite3DbMallocZero(db, sizeof(Table));
    if( pTab==0 ) return WRC_Abort;
    pTab->nRef = 1;
    pTab->zName = sqlite3MPrintf(db, "%s", pCte->zName);
    pTab->iPKey = -1;
    pTab->nRowEst = 1048576;
................................................................................
    pTab->tabFlags |= TF_Ephemeral;
    pFrom->pSelect = sqlite3SelectDup(db, pCte->pSelect, 0);
    if( db->mallocFailed ) return SQLITE_NOMEM;
    assert( pFrom->pSelect );

    /* Check if this is a recursive CTE. */
    pSel = pFrom->pSelect;
    bMayRecursive = ( pSel->op==TK_ALL || pSel->op==TK_UNION );
    if( bMayRecursive ){
      int i;
      SrcList *pSrc = pFrom->pSelect->pSrc;
      for(i=0; i<pSrc->nSrc; i++){
        struct SrcList_item *pItem = &pSrc->a[i];
        if( pItem->zDatabase==0 
         && pItem->zName!=0 
         && 0==sqlite3StrICmp(pItem->zName, pCte->zName)
................................................................................
      sqlite3ErrorMsg(
          pParse, "multiple recursive references in cte: %s", pCte->zName
      );
      return WRC_Abort;
    }
    assert( pTab->nRef==1 || ((pSel->selFlags&SF_Recursive) && pTab->nRef==2 ));

    pCte->zErr = "circular reference to cte: %s";
    sqlite3WalkSelect(pWalker, bMayRecursive ? pSel->pPrior : pSel);

    for(pLeft=pSel; pLeft->pPrior; pLeft=pLeft->pPrior);
    pEList = pLeft->pEList;
    if( pCte->pCols ){
      if( pEList->nExpr!=pCte->pCols->nExpr ){
        sqlite3ErrorMsg(pParse, "cte \"%s\" returns %d values for %d columns",
            pCte->zName, pEList->nExpr, pCte->pCols->nExpr
................................................................................
        );
        return WRC_Abort;
      }
      pEList = pCte->pCols;
    }
    selectColumnsFromExprList(pParse, pEList, &pTab->nCol, &pTab->aCol);

    if( bMayRecursive ){
      if( pSel->selFlags & SF_Recursive ){

        pCte->zErr = "multiple recursive references in cte: %s";
      }else{
        pCte->zErr = "recursive reference may not appear in sub-query: %s";
      }
      sqlite3WalkSelect(pWalker, pSel);
    }
    pCte->zErr = 0;
  }

  return SQLITE_OK;
}
#endif

/*

Changes to src/sqliteInt.h.

2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
....
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
#ifndef SQLITE_OMIT_VIRTUALTABLE
  Token sArg;               /* Complete text of a module argument */
  Table **apVtabLock;       /* Pointer to virtual tables needing locking */
#endif
  Table *pZombieTab;        /* List of Table objects to delete after code gen */
  TriggerPrg *pTriggerPrg;  /* Linked list of coded triggers */
  With *pWith;              /* Current WITH clause, or NULL */
  struct Cte *pCte;         /* Current CTE, or NULL */
};

/*
** Return true if currently inside an sqlite3_declare_vtab() call.
*/
#ifdef SQLITE_OMIT_VIRTUALTABLE
  #define IN_DECLARE_VTAB 0
................................................................................
struct With {
  int nCte;                       /* Number of CTEs in the WITH clause */
  With *pOuter;                   /* Containing WITH clause, or NULL */
  struct Cte {                    /* For each CTE in the WITH clause.... */
    char *zName;                    /* Name of this CTE */
    ExprList *pCols;                /* List of explicit column names, or NULL */
    Select *pSelect;                /* The definition of this CTE */
    struct Cte *pOuterCte;          /* Next WITH clause in outer context */
  } a[1];
};

/*
** Assuming zIn points to the first byte of a UTF-8 character,
** advance zIn to point to the first byte of the next UTF-8 character.
*/







<







 







|







2367
2368
2369
2370
2371
2372
2373

2374
2375
2376
2377
2378
2379
2380
....
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
#ifndef SQLITE_OMIT_VIRTUALTABLE
  Token sArg;               /* Complete text of a module argument */
  Table **apVtabLock;       /* Pointer to virtual tables needing locking */
#endif
  Table *pZombieTab;        /* List of Table objects to delete after code gen */
  TriggerPrg *pTriggerPrg;  /* Linked list of coded triggers */
  With *pWith;              /* Current WITH clause, or NULL */

};

/*
** Return true if currently inside an sqlite3_declare_vtab() call.
*/
#ifdef SQLITE_OMIT_VIRTUALTABLE
  #define IN_DECLARE_VTAB 0
................................................................................
struct With {
  int nCte;                       /* Number of CTEs in the WITH clause */
  With *pOuter;                   /* Containing WITH clause, or NULL */
  struct Cte {                    /* For each CTE in the WITH clause.... */
    char *zName;                    /* Name of this CTE */
    ExprList *pCols;                /* List of explicit column names, or NULL */
    Select *pSelect;                /* The definition of this CTE */
    const char *zErr;               /* Error message for circular references */
  } a[1];
};

/*
** Assuming zIn points to the first byte of a UTF-8 character,
** advance zIn to point to the first byte of the next UTF-8 character.
*/

Changes to test/with1.test.

72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
...
262
263
264
265
266
267
268
269
270


























































271
272
273
274
  WITH tmp2(x) AS ( SELECT * FROM tmp1),
       tmp1(a) AS ( SELECT * FROM t1 )
  SELECT * FROM tmp2;
} {1 2}

#-------------------------------------------------------------------------
do_catchsql_test 3.1 {
  WITH tmp2(x) AS ( SELECT * FROM tmp1),
       tmp1(a) AS ( SELECT * FROM tmp2 )
  SELECT * FROM tmp1;
} {1 {illegal recursive defininition in cte: tmp1}}

do_catchsql_test 3.2 {
  CREATE TABLE t2(x INTEGER);
  WITH tmp(a) AS (SELECT * FROM t1),
       tmp(a) AS (SELECT * FROM t1)
  SELECT * FROM tmp;
} {1 {duplicate cte name: tmp}}
................................................................................
  WITH x(i) AS (
    SELECT 1
    UNION ALL
    SELECT i+1 FROM x WHERE i<10
  )
  SELECT count(*) FROM x
} {10}




























































finish_test










|


|







 









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




72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
...
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
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
  WITH tmp2(x) AS ( SELECT * FROM tmp1),
       tmp1(a) AS ( SELECT * FROM t1 )
  SELECT * FROM tmp2;
} {1 2}

#-------------------------------------------------------------------------
do_catchsql_test 3.1 {
  WITH tmp2(x) AS ( SELECT * FROM tmp1 ),
       tmp1(a) AS ( SELECT * FROM tmp2 )
  SELECT * FROM tmp1;
} {1 {circular reference to cte: tmp1}}

do_catchsql_test 3.2 {
  CREATE TABLE t2(x INTEGER);
  WITH tmp(a) AS (SELECT * FROM t1),
       tmp(a) AS (SELECT * FROM t1)
  SELECT * FROM tmp;
} {1 {duplicate cte name: tmp}}
................................................................................
  WITH x(i) AS (
    SELECT 1
    UNION ALL
    SELECT i+1 FROM x WHERE i<10
  )
  SELECT count(*) FROM x
} {10}


#-------------------------------------------------------------------------

do_execsql_test 7.1 {
  CREATE TABLE tree(i, p);
  INSERT INTO tree VALUES(1, NULL);
  INSERT INTO tree VALUES(2, 1);
  INSERT INTO tree VALUES(3, 1);
  INSERT INTO tree VALUES(4, 2);
  INSERT INTO tree VALUES(5, 4);
}

do_execsql_test 7.2 {
  WITH t(id, path) AS (
    SELECT i, '' FROM tree WHERE p IS NULL
    UNION ALL
    SELECT i, path || '/' || i FROM tree, t WHERE p = id
  ) 
  SELECT path FROM t;
} {{} /2 /3 /2/4 /2/4/5}

do_execsql_test 7.3 {
  WITH t(id) AS (
    VALUES(2)
    UNION ALL
    SELECT i FROM tree, t WHERE p = id
  ) 
  SELECT id FROM t;
} {2 4 5}

do_catchsql_test 7.4 {
  WITH t(id) AS (
    VALUES(2)
    UNION ALL
    SELECT i FROM tree WHERE p IN (SELECT id FROM t)
  ) 
  SELECT id FROM t;
} {1 {recursive reference may not appear in sub-query: t}}

do_catchsql_test 7.5 {
  WITH t(id) AS (
    VALUES(2)
    UNION ALL
    SELECT i FROM tree, t WHERE p = id AND p IN (SELECT id FROM t)
  ) 
  SELECT id FROM t;
} {1 {multiple recursive references in cte: t}}

do_catchsql_test 7.6 {
  WITH t(id) AS (
    SELECT i FROM tree WHERE 2 IN (SELECT id FROM t)
    UNION ALL
    SELECT i FROM tree, t WHERE p = id
  ) 
  SELECT id FROM t;
} {1 {circular reference to cte: t}}



finish_test