From: drh Date: Tue, 21 Jan 2014 00:19:43 +0000 (+0000) Subject: Add support for the LEVEL pseudo-column in the recursive part of X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=ce365d6b7e17860bd369216bbcf77e616faaec52;p=thirdparty%2Fsqlite.git Add support for the LEVEL pseudo-column in the recursive part of a common table expression. LEVEL has the value of 1 on the first iteration and successively larger integer values of subsequent iterations. It cannot have a table qualifier. Actual columns named "level" can still be accessed by including the table name qualifier. FossilOrigin-Name: cc1cb3217800ff666a00bf4f544a5a477589bc1b --- diff --git a/addopcodes.awk b/addopcodes.awk index dcd31eff84..98962ec2e6 100644 --- a/addopcodes.awk +++ b/addopcodes.awk @@ -31,4 +31,5 @@ END { printf "#define TK_%-29s %4d\n", "UMINUS", ++max printf "#define TK_%-29s %4d\n", "UPLUS", ++max printf "#define TK_%-29s %4d\n", "REGISTER", ++max + printf "#define TK_%-29s %4d\n", "LEVEL", ++max } diff --git a/manifest b/manifest index 7c7eeb389c..afe5fdea0f 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C In\swhere.c,\sdo\snot\sallocate\sspace\sin\ssqlite3_index_info\sstructures\sfor\sthe\sinternal\sWHERE\sclause\s"terms"\sgenerated\sto\srecord\scolumn\sequivalencies. -D 2014-01-20T19:55:33.120 +C Add\ssupport\sfor\sthe\sLEVEL\spseudo-column\sin\sthe\srecursive\spart\sof\na\scommon\stable\sexpression.\s\sLEVEL\shas\sthe\svalue\sof\s1\son\sthe\sfirst\siteration\nand\ssuccessively\slarger\sinteger\svalues\sof\ssubsequent\siterations.\s\sIt\scannot\nhave\sa\stable\squalifier.\s\sActual\scolumns\snamed\s"level"\scan\sstill\sbe\saccessed\nby\sincluding\sthe\stable\sname\squalifier. +D 2014-01-21T00:19:43.076 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in 2ef13430cd359f7b361bb863504e227b25cc7f81 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -8,7 +8,7 @@ F Makefile.vxworks db21ed42a01d5740e656b16f92cb5d8d5e5dd315 F README cd04a36fbc7ea56932a4052d7d0b7f09f27c33d6 F VERSION 8ed548d87d0a27fd7d7620476f9e25f9fa742d73 F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50 -F addopcodes.awk 9eb448a552d5c0185cf62c463f9c173cedae3811 +F addopcodes.awk 9d87688069e97afd397035613fe5822408a0914e F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2 F art/sqlite370.ico af56c1d00fee7cd4753e8631ed60703ed0fc6e90 F art/sqlite370.jpg d512473dae7e378a67e28ff96a34da7cb331def2 @@ -175,7 +175,7 @@ F src/complete.c dc1d136c0feee03c2f7550bafc0d29075e36deac F src/ctime.c 77779efbe78dd678d84bfb4fc2e87b6b6ad8dccd F src/date.c 593c744b2623971e45affd0bde347631bdfa4625 F src/delete.c 91e1321021db5dc266360531b8b6550009d771ff -F src/expr.c 8c7e482bc8f7982333f046851a610ccdb8a1ba94 +F src/expr.c 1c954bc76bfefa7391bcd2ebe77b9c0fff0eccb8 F src/fault.c 160a0c015b6c2629d3899ed2daf63d75754a32bb F src/fkey.c 2ab0f5384b70594468ef3ac5c7ed8ca24bfd17d5 F src/func.c 6325ac2ec10833ccf4d5c36d323709221d37ea19 @@ -217,14 +217,14 @@ F src/pragma.c ed409ce4104cf4d9de6ead40ace70974f124853b F src/prepare.c 677521ab7132615a8a26107a1d1c3132f44ae337 F src/printf.c 85d07756e45d7496d19439dcae3e6e9e0090f269 F src/random.c d10c1f85b6709ca97278428fd5db5bbb9c74eece -F src/resolve.c 7eda9097b29fcf3d2b42fdc17d1de672134e09b6 +F src/resolve.c f35795a7d6fdb2f500ccbe9229e14a4b69e1e140 F src/rowset.c 64655f1a627c9c212d9ab497899e7424a34222e0 -F src/select.c a27ac21844df3123b7c1e89d79cd7034d4eb0e8e +F src/select.c a2709e1b91731d65bc0839974ff95cdc58cd81bd F src/shell.c 9f3bc02a658b8f61d2cbe60cfc482f660c1c6c48 F src/sqlite.h.in eed7f7d66a60daaa7b4a597dcd9bad87aad9611b F src/sqlite3.rc 11094cc6a157a028b301a9f06b3d03089ea37c3e F src/sqlite3ext.h 886f5a34de171002ad46fae8c36a7d8051c190fc -F src/sqliteInt.h 99fd628541e420b98fc52072635e8ba431706250 +F src/sqliteInt.h d5a534b30be3f0002a1cde919113f399d8b81a79 F src/sqliteLimit.h 164b0e6749d31e0daa1a4589a169d31c0dec7b3d F src/status.c 7ac05a5c7017d0b9f0b4bcd701228b784f987158 F src/table.c 2cd62736f845d82200acfa1287e33feb3c15d62e @@ -1094,6 +1094,7 @@ F test/win32lock.test 7a6bd73a5dcdee39b5bb93e92395e1773a194361 F test/win32longpath.test 169c75a3b2e43481f4a62122510210c67b08f26d F test/with1.test cec63b56797a70842afa8929c241dfdb3d864283 F test/with2.test 2fe78fcd8deef2a0f9cfc49bfc755911d0b3fd64 +F test/with3.test e302761b7bf3a9b5e66afa62f763f2719f8fe8ba F test/withM.test e97f2a8c506ab3ea9eab94e6f6072f6cc924c991 F test/without_rowid1.test aaa26da19d543cd8d3d2d0e686dfa255556c15c8 F test/without_rowid2.test af260339f79d13cb220288b67cd287fbcf81ad99 @@ -1152,7 +1153,7 @@ F tool/vdbe-compress.tcl 0cf56e9263a152b84da86e75a5c0cdcdb7a47891 F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh d1a6de74685f360ab718efda6265994b99bbea01 F tool/win/sqlite.vsix 030f3eeaf2cb811a3692ab9c14d021a75ce41fff -P eba8a564e62f84a9620008beead80081fe90a1b7 -R c774305f58a7e7fff71d714265811a28 -U dan -Z 8e05aa582afc8018a3754b6d40d97300 +P 7d9e22187daaa3160b875a1df17b924969bf718e +R 868eac95940900577c41ddbd3ee20aa3 +U drh +Z 41ceab9a1912e9f5aae2b29b90c57305 diff --git a/manifest.uuid b/manifest.uuid index 1199189ccc..49ef00d45e 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -7d9e22187daaa3160b875a1df17b924969bf718e \ No newline at end of file +cc1cb3217800ff666a00bf4f544a5a477589bc1b \ No newline at end of file diff --git a/src/expr.c b/src/expr.c index 5f11dec420..498d4564e6 100644 --- a/src/expr.c +++ b/src/expr.c @@ -2458,6 +2458,12 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ pExpr->op2); break; } +#ifndef SQLITE_OMIT_CTE + case TK_LEVEL: { + inReg = pParse->regLevel; + break; + } +#endif case TK_INTEGER: { codeInteger(pParse, pExpr, 0, target); break; @@ -3081,6 +3087,7 @@ int sqlite3ExprCodeTemp(Parse *pParse, Expr *pExpr, int *pReg){ pExpr = sqlite3ExprSkipCollate(pExpr); if( ConstFactorOk(pParse) && pExpr->op!=TK_REGISTER + && pExpr->op!=TK_LEVEL && sqlite3ExprIsConstantNotJoin(pExpr) ){ ExprList *p = pParse->pConstExpr; diff --git a/src/resolve.c b/src/resolve.c index b0adb86295..9418386629 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -226,7 +226,7 @@ static int lookupName( struct SrcList_item *pMatch = 0; /* The matching pSrcList item */ NameContext *pTopNC = pNC; /* First namecontext in the list */ Schema *pSchema = 0; /* Schema of the expression */ - int isTrigger = 0; /* True if resolved to a trigger column */ + u8 newOp = TK_COLUMN; /* pExpr->op after resolving name */ Table *pTab = 0; /* Table hold the row */ Column *pCol; /* A column of pTab */ @@ -267,6 +267,22 @@ static int lookupName( ExprList *pEList; SrcList *pSrcList = pNC->pSrcList; +#ifndef SQLITE_OMIT_CTE + /* The identifier "LEVEL", with a table or database qualifier and within a + ** recursive common table expression, resolves to the special LEVEL pseudo-column. + ** To access table names called "level", add a table qualifier. + */ + if( (pNC->ncFlags&NC_Recursive)!=0 && zTab==0 && sqlite3_stricmp(zCol,"level")==0 ){ + assert( cnt==0 ); + cnt = 1; + newOp = TK_LEVEL; + pExpr->iColumn = -1; + pExpr->affinity = SQLITE_AFF_INTEGER; + pNC->ncFlags |= NC_UsesLevel; + break; + } +#endif + if( pSrcList ){ for(i=0, pItem=pSrcList->a; inSrc; i++, pItem++){ pTab = pItem->pTab; @@ -371,7 +387,7 @@ static int lookupName( } pExpr->iColumn = (i16)iCol; pExpr->pTab = pTab; - isTrigger = 1; + newOp = TK_TRIGGER; } } } @@ -495,11 +511,11 @@ static int lookupName( pExpr->pLeft = 0; sqlite3ExprDelete(db, pExpr->pRight); pExpr->pRight = 0; - pExpr->op = (isTrigger ? TK_TRIGGER : TK_COLUMN); + pExpr->op = newOp; lookupname_end: if( cnt==1 ){ assert( pNC!=0 ); - if( pExpr->op!=TK_AS ){ + if( pExpr->op!=TK_AS && pExpr->op!=TK_LEVEL ){ sqlite3AuthRead(pParse, pExpr, pSchema, pNC->pSrcList); } /* Increment the nRef value on all name contexts from TopNC up to @@ -1196,6 +1212,7 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ ** resolve the result-set expression list. */ sNC.ncFlags = NC_AllowAgg; + if( p->selFlags & SF_Recursive ) sNC.ncFlags |= NC_Recursive; sNC.pSrcList = p->pSrc; sNC.pNext = pOuterNC; @@ -1274,6 +1291,10 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ } } } + if( sNC.ncFlags & NC_UsesLevel ){ + p->selFlags |= SF_UsesLevel; + } + sNC.ncFlags &= ~(NC_Recursive|NC_UsesLevel); /* Advance to the next term of the compound */ diff --git a/src/select.c b/src/select.c index dd58ed22e6..70e140620c 100644 --- a/src/select.c +++ b/src/select.c @@ -1792,8 +1792,8 @@ static int multiSelect( #ifndef SQLITE_OMIT_CTE if( p->selFlags & SF_Recursive ){ - SrcList *pSrc = p->pSrc; - int nCol = p->pEList->nExpr; + SrcList *pSrc = p->pSrc; /* The FROM clause of the right-most SELECT */ + int nCol = p->pEList->nExpr; /* Number of columns in the result set */ int addrNext; int addrSwap; int iCont, iBreak; @@ -1803,6 +1803,8 @@ static int multiSelect( int eDest = SRT_Table; SelectDest tmp2dest; int i; + int regLevel = 0; /* Register for LEVEL value */ + int savedRegLevel; /* Saved value of pParse->regLevel */ /* Check that there is no ORDER BY or LIMIT clause. Neither of these ** are supported on recursive queries. */ @@ -1845,6 +1847,14 @@ static int multiSelect( rc = sqlite3Select(pParse, pPrior, &tmp2dest); if( rc ) goto multi_select_end; + /* Allocate and initialize a register to hold the LEVEL pseudo-column */ + savedRegLevel = pParse->regLevel; + if( p->selFlags & SF_UsesLevel ){ + regLevel = pParse->regLevel = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Integer, 0, regLevel); + VdbeComment((v, "level=0")); + } + /* Clear tmp1. Then switch the contents of tmp1 and tmp2. Then return ** the contents of tmp1 to the caller. Or, if tmp1 is empty at this ** point, the recursive query has finished - jump to address iBreak. */ @@ -1855,6 +1865,10 @@ static int multiSelect( 0, 0, &dest, iCont, iBreak); sqlite3VdbeResolveLabel(v, iCont); sqlite3VdbeAddOp2(v, OP_Next, tmp1, addrNext); + if( regLevel ){ + sqlite3VdbeAddOp2(v, OP_AddImm, regLevel, 1); + VdbeComment((v, "level++")); + } /* Execute the recursive SELECT. Store the results in tmp2. While this ** SELECT is running, the contents of tmp1 are read by recursive @@ -1867,6 +1881,7 @@ static int multiSelect( sqlite3VdbeAddOp2(v, OP_Goto, 0, addrSwap); sqlite3VdbeResolveLabel(v, iBreak); + pParse->regLevel = savedRegLevel; }else #endif diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 317e9eb227..cbb12294a8 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -2107,6 +2107,8 @@ struct NameContext { #define NC_IsCheck 0x04 /* True if resolving names in a CHECK constraint */ #define NC_InAggFunc 0x08 /* True if analyzing arguments to an agg func */ #define NC_PartIdx 0x10 /* True if resolving a partial index WHERE */ +#define NC_Recursive 0x20 /* Resolvingn a recursive CTE definition */ +#define NC_UsesLevel 0x40 /* The LEVEL pseudo-column has been seen */ /* ** An instance of the following structure contains all information @@ -2164,6 +2166,7 @@ struct Select { #define SF_NestedFrom 0x0200 /* Part of a parenthesized FROM clause */ #define SF_MaybeConvert 0x0400 /* Need convertCompoundSelectToSubquery() */ #define SF_Recursive 0x0800 /* The recursive part of a recursive CTE */ +#define SF_UsesLevel 0x1000 /* Uses the LEVEL pseudo-column */ /* @@ -2370,6 +2373,7 @@ struct Parse { 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 */ + int regLevel; /* Register holding the LEVEL variable */ u8 bFreeWith; /* True if pWith should be freed with parser */ }; diff --git a/test/with3.test b/test/with3.test new file mode 100644 index 0000000000..4646cd6550 --- /dev/null +++ b/test/with3.test @@ -0,0 +1,106 @@ +# 2014 January 11 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. The +# focus of this file is testing the WITH clause and in particular the +# LEVEL pseudo-column +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set ::testprefix with3 + +ifcapable {!cte} { + finish_test + return +} + +do_execsql_test 1.0 { + WITH RECURSIVE + cnt(x) AS (VALUES(10) UNION SELECT x+1 FROM cnt WHERE level<10) + SELECT * FROM cnt; +} {10 11 12 13 14 15 16 17 18 19} +do_execsql_test 1.1 { + WITH RECURSIVE + cnt(x,y) AS (VALUES(10,0) UNION SELECT x+1,level FROM cnt WHERE level<6) + SELECT x, y, '|' FROM cnt; +} {10 0 | 11 1 | 12 2 | 13 3 | 14 4 | 15 5 |} +do_execsql_test 1.2 { + WITH RECURSIVE + cnt(x,level) AS ( + VALUES(10,99) + UNION + SELECT x+1, level FROM cnt WHERE level<6 + ) + SELECT x, level, '|' FROM cnt; +} {10 99 | 11 1 | 12 2 | 13 3 | 14 4 | 15 5 |} +do_execsql_test 1.3 { + WITH RECURSIVE + cnt(x,level) AS ( + VALUES(10,99) + UNION + SELECT x+1, cnt.level FROM cnt WHERE level<6 + ) + SELECT x, level, '|' FROM cnt; +} {10 99 | 11 99 | 12 99 | 13 99 | 14 99 | 15 99 |} +do_execsql_test 1.4 { + WITH RECURSIVE + cnt(x,level) AS ( + VALUES(10,0) + UNION + SELECT x+1, cnt.level+level FROM cnt WHERE level<6 + ) + SELECT x, level, '|' FROM cnt; +} {10 0 | 11 1 | 12 3 | 13 6 | 14 10 | 15 15 |} +do_execsql_test 1.5 { + CREATE TABLE t1(level); + WITH RECURSIVE + cnt(x) AS (VALUES(10) UNION SELECT x*10 FROM cnt WHERE level<4) + INSERT INTO t1 SELECT x FROM cnt; + SELECT * FROM t1; +} {10 100 1000 10000} +do_execsql_test 1.6 { + WITH RECURSIVE + cnt(x, level) AS ( + VALUES(1,1) + UNION + SELECT x+1, level*t1.level FROM cnt, t1 WHERE level<3 + ) + SELECT x, level FROM cnt ORDER BY x, level; +} {1 1 2 10 2 100 2 1000 2 10000 3 20 3 200 3 2000 3 20000} + +do_execsql_test 1.11 { + CREATE TEMP TABLE powersoftwo(a,b); + WITH RECURSIVE + tmp(a,b) AS (VALUES(0,1) UNION SELECT a+1, b*2 FROM tmp WHERE level<32) + INSERT INTO powersoftwo SELECT a, b FROM tmp; + WITH RECURSIVE + cnt(x,y) AS ( + VALUES(0,0) UNION + SELECT x+1, (x+1)*(SELECT b FROM powersoftwo WHERE a=level) FROM cnt + WHERE level<5 + ) + SELECT * FROM cnt; +} {0 0 1 2 2 8 3 24 4 64} + +do_catchsql_test 2.1 { + WITH RECURSIVE + cnt(x) AS (VALUES(1) UNION SELECT x+1 FROM cnt WHERE level<10) + SELECT x, level FROM cnt; +} {1 {no such column: level}} +do_catchsql_test 2.2 { + WITH RECURSIVE + cnt(x) AS (VALUES(level) UNION SELECT x+1 FROM cnt WHERE level<10) + SELECT x FROM cnt; +} {1 {no such column: level}} + + + +finish_test