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 |
Timelines: | family | ancestors | descendants | both | common-table-expr |
Files: | files | file ages | folders |
SHA1: |
54eee9fe99290e59469bd3e1a66bb749 |
User & Date: | dan 2014-01-16 21:02:02.206 |
Context
2014-01-16
| ||
21:59 | Tweaks to error message text. (check-in: 090a77d978 user: drh tags: common-table-expr) | |
21:02 | Improve the error messages used to report illegal recursive cte references. (check-in: 54eee9fe99 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: a296b73360 user: dan tags: common-table-expr) | |
Changes
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 | void sqlite3WithPush(Parse *pParse, With *pWith){ if( pWith ){ pWith->pOuter = pParse->pWith; pParse->pWith = pWith; } } | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 | 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 |
︙ | ︙ | |||
3598 3599 3600 3601 3602 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612 3613 3614 3615 3616 3617 3618 | 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; | > > > > > > > > > > | > | 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 | 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) |
︙ | ︙ | |||
3638 3639 3640 3641 3642 3643 3644 | sqlite3ErrorMsg( pParse, "multiple recursive references in cte: %s", pCte->zName ); return WRC_Abort; } assert( pTab->nRef==1 || ((pSel->selFlags&SF_Recursive) && pTab->nRef==2 )); | | | > | > > > > > > | | 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 | 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 | #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 */ | < | 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 | #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 |
︙ | ︙ | |||
2646 2647 2648 2649 2650 2651 2652 | 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 */ | | | 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 | 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 | WITH tmp2(x) AS ( SELECT * FROM tmp1), tmp1(a) AS ( SELECT * FROM t1 ) SELECT * FROM tmp2; } {1 2} #------------------------------------------------------------------------- do_catchsql_test 3.1 { | | | | 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | 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}} |
︙ | ︙ | |||
262 263 264 265 266 267 268 269 270 271 272 273 274 | WITH x(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM x WHERE i<10 ) SELECT count(*) FROM x } {10} finish_test | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 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 |