diff --git a/manifest b/manifest index 1f03409943..896f5cf947 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\snew\sSelectDest\scodes,\sSRT_Queue\sand\sSRT_DistQueue\sin\santicipation\sof\sadding\nORDER\sBY\ssupport\son\srecursive\squeries.\s\sFactor\sout\sthe\srecursive\squery\ncode\sgenerator\sinto\sa\sseparate\sprocedure. -D 2014-01-22T13:35:53.476 +C Get\sORDER\sBY\sworking\sfor\srecursive\squeries. +D 2014-01-22T17:28:35.279 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in 2ef13430cd359f7b361bb863504e227b25cc7f81 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -219,12 +219,12 @@ F src/printf.c 85d07756e45d7496d19439dcae3e6e9e0090f269 F src/random.c d10c1f85b6709ca97278428fd5db5bbb9c74eece F src/resolve.c 7eda9097b29fcf3d2b42fdc17d1de672134e09b6 F src/rowset.c 64655f1a627c9c212d9ab497899e7424a34222e0 -F src/select.c 11c02c82a6f3cb8a491452fc7474a568a48c64ef +F src/select.c b5430b99c0339dcfe9d06a4c251548e650e386e0 F src/shell.c 24722d24d4ea8ca93db35e44db7308de786767ca F src/sqlite.h.in eed7f7d66a60daaa7b4a597dcd9bad87aad9611b F src/sqlite3.rc 11094cc6a157a028b301a9f06b3d03089ea37c3e F src/sqlite3ext.h 886f5a34de171002ad46fae8c36a7d8051c190fc -F src/sqliteInt.h 16c73326604e5603c3c10c97ac1e63473590d4ff +F src/sqliteInt.h 87a90ad4818ac5d68d3463eb7fe3ed96e5209b25 F src/sqliteLimit.h 164b0e6749d31e0daa1a4589a169d31c0dec7b3d F src/status.c 7ac05a5c7017d0b9f0b4bcd701228b784f987158 F src/table.c 2cd62736f845d82200acfa1287e33feb3c15d62e @@ -1092,7 +1092,7 @@ F test/wild001.test bca33f499866f04c24510d74baf1e578d4e44b1c F test/win32heap.test ea19770974795cff26e11575e12d422dbd16893c F test/win32lock.test 7a6bd73a5dcdee39b5bb93e92395e1773a194361 F test/win32longpath.test 169c75a3b2e43481f4a62122510210c67b08f26d -F test/with1.test cec63b56797a70842afa8929c241dfdb3d864283 +F test/with1.test f4aa699a3712207e02f13945cd2b67d01a9d9f57 F test/with2.test 2fe78fcd8deef2a0f9cfc49bfc755911d0b3fd64 F test/withM.test e97f2a8c506ab3ea9eab94e6f6072f6cc924c991 F test/without_rowid1.test aaa26da19d543cd8d3d2d0e686dfa255556c15c8 @@ -1152,7 +1152,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 cceacc0e79c4e54682daddf2056c6bb8e88d9484 -R 3e53ba84b5d9e416a6e5f351b0fd4313 +P 3eb5f9f8d6ac1ee145cb4119087c516f66fe1456 +R a615c5dcec438a7db4f9b5a3e0983f82 U drh -Z aa06559c4dc503939fd80ca5aa152df9 +Z 4ad026d26a172a85d439a21b0ddb47e4 diff --git a/manifest.uuid b/manifest.uuid index 2b7addbf9a..87e23ab961 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -3eb5f9f8d6ac1ee145cb4119087c516f66fe1456 \ No newline at end of file +37b343b01841b338954ddfa9b76d92aa50037aec \ No newline at end of file diff --git a/src/select.c b/src/select.c index 0152db8b25..13e9562ba2 100644 --- a/src/select.c +++ b/src/select.c @@ -581,10 +581,8 @@ static void selectInnerLoop( pDest->iSdst = pParse->nMem+1; pDest->nSdst = nResultCol; pParse->nMem += nResultCol; - if( eDest==SRT_Queue ) pParse->nMem++; }else{ assert( pDest->nSdst==nResultCol ); - assert( eDest!=SRT_Queue ); } regResult = pDest->iSdst; if( srcTab>=0 ){ @@ -664,30 +662,10 @@ static void selectInnerLoop( ** table iParm. */ #ifndef SQLITE_OMIT_COMPOUND_SELECT -#ifndef SQLITE_OMIT_CTE - case SRT_Queue: { - sqlite3VdbeAddOp2(v, OP_Sequence, iParm, regResult+nResultCol); - nResultCol++; - /* Fall through into SRT_Union */ - } - case SRT_DistQueue: -#endif /* SQLITE_OMIT_CTE */ case SRT_Union: { int r1; r1 = sqlite3GetTempReg(pParse); sqlite3VdbeAddOp3(v, OP_MakeRecord, regResult, nResultCol, r1); -#ifndef SQLITE_OMIT_CTE - if( eDest==SRT_DistQueue ){ - /* If the destination is DistQueue, then cursor (iParm+1) is open - ** on a second ephemeral index that holds all values every previously - ** added to the queue. Only add this new value if it has never before - ** been added */ - int addr = sqlite3VdbeCurrentAddr(v) + 3; - sqlite3VdbeAddOp4Int(v, OP_Found, iParm+1, addr, r1, 0); - sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm+1, r1); - assert( pOrderBy==0 ); - } -#endif /* SQLITE_OMIT_CTE */ sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm, r1); sqlite3ReleaseTempReg(pParse, r1); break; @@ -809,6 +787,51 @@ static void selectInnerLoop( break; } +#ifndef SQLITE_OMIT_CTE + /* Write the results into a priority queue that is order according to + ** pDest->pOrderBy (in pSO). pDest->iSDParm (in iParm) is the cursor for an + ** index with pSO->nExpr+2 columns. Build a key using pSO for the first + ** pSO->nExpr columns, then make sure all keys are unique by adding a + ** final OP_Sequence column. The last column is the record as a blob. + */ + case SRT_DistQueue: + case SRT_Queue: { + int nKey; + int r1, r2, r3; + int addrTest = 0; + ExprList *pSO; + pSO = pDest->pOrderBy; + assert( pSO ); + nKey = pSO->nExpr; + r1 = sqlite3GetTempReg(pParse); + r2 = sqlite3GetTempRange(pParse, nKey+2); + r3 = r2+nKey+1; + sqlite3VdbeAddOp3(v, OP_MakeRecord, regResult, nResultCol, r3); + if( eDest==SRT_DistQueue ){ + /* If the destination is DistQueue, then cursor (iParm+1) is open + ** on a second ephemeral index that holds all values every previously + ** added to the queue. Only add this new value if it has never before + ** been added */ + addrTest = sqlite3VdbeAddOp4Int(v, OP_Found, iParm+1, 0, r3, 0); + sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm+1, r3); + } + for(i=0; ia[i].u.x.iOrderByCol - 1, + r2+i); + } + sqlite3VdbeAddOp2(v, OP_Sequence, iParm, r2+nKey); + sqlite3VdbeAddOp3(v, OP_MakeRecord, r2, nKey+2, r1); + sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm, r1); + if( addrTest ) sqlite3VdbeJumpHere(v, addrTest); + sqlite3ReleaseTempReg(pParse, r1); + sqlite3ReleaseTempRange(pParse, r2, nKey+2); + break; + } +#endif /* SQLITE_OMIT_CTE */ + + + #if !defined(SQLITE_OMIT_TRIGGER) /* Discard the results. This is used for SELECT statements inside ** the body of a TRIGGER. The purpose of such selects is to call @@ -897,7 +920,7 @@ int sqlite3KeyInfoIsWriteable(KeyInfo *p){ return p->nRef==1; } ** function is responsible for seeing that this structure is eventually ** freed. */ -static KeyInfo *keyInfoFromExprList(Parse *pParse, ExprList *pList){ +static KeyInfo *keyInfoFromExprList(Parse *pParse, ExprList *pList, int nExtra){ int nExpr; KeyInfo *pInfo; struct ExprList_item *pItem; @@ -905,7 +928,7 @@ static KeyInfo *keyInfoFromExprList(Parse *pParse, ExprList *pList){ int i; nExpr = pList->nExpr; - pInfo = sqlite3KeyInfoAlloc(db, nExpr, 1); + pInfo = sqlite3KeyInfoAlloc(db, nExpr+nExtra, 1); if( pInfo ){ assert( sqlite3KeyInfoIsWriteable(pInfo) ); for(i=0, pItem=pList->a; ipOffset==0 || p->pLimit ); - if( p->pOrderBy || p->pLimit ){ + if( /*p->pOrderBy ||*/ p->pLimit ){ sqlite3ErrorMsg(pParse, "%s in a recursive query", p->pOrderBy ? "ORDER BY" : "LIMIT" ); @@ -1780,22 +1802,34 @@ static void generateWithRecursiveQuery( } } - /* Allocate cursors for Queue and Distinct. The cursor number for + /* Detach the ORDER BY clause from the compound SELECT */ + pOrderBy = p->pOrderBy; + p->pOrderBy = 0; + + /* Allocate cursors numbers for Queue and Distinct. The cursor number for ** the Distinct table must be exactly one greater than Queue in order - ** for the SRT_DistTable destination to work. */ + ** for the SRT_DistTable and SRT_DistQueue destinations to work. */ iQueue = pParse->nTab++; if( p->op==TK_UNION ){ - assert( SRT_Table+1==SRT_DistTable ); - assert( SRT_Queue+1==SRT_DistQueue ); - eDest++; + eDest = pOrderBy ? SRT_DistQueue : SRT_DistTable; iDistinct = pParse->nTab++; + }else{ + eDest = pOrderBy ? SRT_Queue : SRT_Table; } sqlite3SelectDestInit(&destQueue, eDest, iQueue); /* Allocate cursors for Current, Queue, and Distinct. */ regCurrent = ++pParse->nMem; sqlite3VdbeAddOp3(v, OP_OpenPseudo, iCurrent, regCurrent, nCol); - sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iQueue, nCol); + if( pOrderBy ){ + KeyInfo *pKeyInfo = keyInfoFromExprList(pParse, pOrderBy, 1); + sqlite3VdbeAddOp4(v, OP_OpenEphemeral, iQueue, pOrderBy->nExpr+2, 0, + (char*)pKeyInfo, P4_KEYINFO); + destQueue.pOrderBy = pOrderBy; + }else{ + sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iQueue, nCol); + } + VdbeComment((v, "Queue table")); if( iDistinct ){ p->addrOpenEphm[0] = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iDistinct, 0); p->selFlags |= SF_UsesEphemeral; @@ -1803,19 +1837,26 @@ static void generateWithRecursiveQuery( /* Store the results of the setup-query in Queue. */ rc = sqlite3Select(pParse, pSetup, &destQueue); - if( rc ) return; + if( rc ) goto end_of_recursive_query; /* Find the next row in the Queue and output that row */ addrTop = sqlite3VdbeAddOp2(v, OP_Rewind, iQueue, addrBreak); - selectInnerLoop(pParse, p, p->pEList, iQueue, - 0, 0, pDest, addrCont, addrBreak); - sqlite3VdbeResolveLabel(v, addrCont); /* Transfer the next row in Queue over to Current */ sqlite3VdbeAddOp1(v, OP_NullRow, iCurrent); /* To reset column cache */ - sqlite3VdbeAddOp2(v, OP_RowData, iQueue, regCurrent); + if( pOrderBy ){ + sqlite3VdbeAddOp3(v, OP_Column, iQueue, pOrderBy->nExpr+1, regCurrent); + }else{ + sqlite3VdbeAddOp2(v, OP_RowData, iQueue, regCurrent); + } sqlite3VdbeAddOp1(v, OP_Delete, iQueue); + /* Output the single row in Current */ + addrCont = sqlite3VdbeMakeLabel(v); + selectInnerLoop(pParse, p, p->pEList, iCurrent, + 0, 0, pDest, addrCont, addrBreak); + sqlite3VdbeResolveLabel(v, addrCont); + /* Execute the recursive SELECT taking the single row in Current as ** the value for the recursive-table. Store the results in the Queue. */ @@ -1827,6 +1868,10 @@ static void generateWithRecursiveQuery( /* Keep running the loop until the Queue is empty */ sqlite3VdbeAddOp2(v, OP_Goto, 0, addrTop); sqlite3VdbeResolveLabel(v, addrBreak); + +end_of_recursive_query: + p->pOrderBy = pOrderBy; + return; } #endif @@ -4227,7 +4272,7 @@ static void resetAccumulator(Parse *pParse, AggInfo *pAggInfo){ "argument"); pFunc->iDistinct = -1; }else{ - KeyInfo *pKeyInfo = keyInfoFromExprList(pParse, pE->x.pList); + KeyInfo *pKeyInfo = keyInfoFromExprList(pParse, pE->x.pList, 0); sqlite3VdbeAddOp4(v, OP_OpenEphemeral, pFunc->iDistinct, 0, 0, (char*)pKeyInfo, P4_KEYINFO); } @@ -4636,7 +4681,7 @@ int sqlite3Select( */ if( pOrderBy ){ KeyInfo *pKeyInfo; - pKeyInfo = keyInfoFromExprList(pParse, pOrderBy); + pKeyInfo = keyInfoFromExprList(pParse, pOrderBy, 0); pOrderBy->iECursor = pParse->nTab++; p->addrOpenEphm[2] = addrSortIndex = sqlite3VdbeAddOp4(v, OP_OpenEphemeral, @@ -4668,7 +4713,7 @@ int sqlite3Select( sDistinct.tabTnct = pParse->nTab++; sDistinct.addrTnct = sqlite3VdbeAddOp4(v, OP_OpenEphemeral, sDistinct.tabTnct, 0, 0, - (char*)keyInfoFromExprList(pParse, p->pEList), + (char*)keyInfoFromExprList(pParse, p->pEList, 0), P4_KEYINFO); sqlite3VdbeChangeP5(v, BTREE_UNORDERED); sDistinct.eTnctType = WHERE_DISTINCT_UNORDERED; @@ -4792,7 +4837,7 @@ int sqlite3Select( ** will be converted into a Noop. */ sAggInfo.sortingIdx = pParse->nTab++; - pKeyInfo = keyInfoFromExprList(pParse, pGroupBy); + pKeyInfo = keyInfoFromExprList(pParse, pGroupBy, 0); addrSortingIdx = sqlite3VdbeAddOp4(v, OP_SorterOpen, sAggInfo.sortingIdx, sAggInfo.nSortingColumn, 0, (char*)pKeyInfo, P4_KEYINFO); diff --git a/src/sqliteInt.h b/src/sqliteInt.h index ac48291d80..c1fa7b69ad 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -2251,11 +2251,12 @@ struct Select { ** a SELECT statement. */ struct SelectDest { - u8 eDest; /* How to dispose of the results. On of SRT_* above. */ - char affSdst; /* Affinity used when eDest==SRT_Set */ - int iSDParm; /* A parameter used by the eDest disposal method */ - int iSdst; /* Base register where results are written */ - int nSdst; /* Number of registers allocated */ + u8 eDest; /* How to dispose of the results. On of SRT_* above. */ + char affSdst; /* Affinity used when eDest==SRT_Set */ + int iSDParm; /* A parameter used by the eDest disposal method */ + int iSdst; /* Base register where results are written */ + int nSdst; /* Number of registers allocated */ + ExprList *pOrderBy; /* Key columns for SRT_Queue and SRT_DistQueue */ }; /* diff --git a/test/with1.test b/test/with1.test index fcf59267f2..0895312b0c 100644 --- a/test/with1.test +++ b/test/with1.test @@ -152,7 +152,44 @@ do_execsql_test 5.1 { do_catchsql_test 5.2 { WITH i(x) AS ( VALUES(1) UNION ALL SELECT x+1 FROM i ORDER BY 1) SELECT x FROM i LIMIT 10; -} {1 {ORDER BY in a recursive query}} +} {0 {1 2 3 4 5 6 7 8 9 10}} + +do_execsql_test 5.2.1 { + CREATE TABLE edge(xfrom, xto, seq, PRIMARY KEY(xfrom, xto)) WITHOUT ROWID; + INSERT INTO edge VALUES(0, 1, 10); + INSERT INTO edge VALUES(1, 2, 20); + INSERT INTO edge VALUES(0, 3, 30); + INSERT INTO edge VALUES(2, 4, 40); + INSERT INTO edge VALUES(3, 4, 40); + INSERT INTO edge VALUES(2, 5, 50); + INSERT INTO edge VALUES(3, 6, 60); + INSERT INTO edge VALUES(5, 7, 70); + INSERT INTO edge VALUES(3, 7, 70); + INSERT INTO edge VALUES(4, 8, 80); + INSERT INTO edge VALUES(7, 8, 80); + INSERT INTO edge VALUES(8, 9, 90); + + WITH RECURSIVE + ancest(id, mtime) AS + (VALUES(0, 0) + UNION + SELECT edge.xto, edge.seq FROM edge, ancest + WHERE edge.xfrom=ancest.id + ORDER BY 2 + ) + SELECT * FROM ancest; +} {0 0 1 10 2 20 3 30 4 40 5 50 6 60 7 70 8 80 9 90} +do_execsql_test 5.2.2 { + WITH RECURSIVE + ancest(id, mtime) AS + (VALUES(0, 0) + UNION ALL + SELECT edge.xto, edge.seq FROM edge, ancest + WHERE edge.xfrom=ancest.id + ORDER BY 2 + ) + SELECT * FROM ancest; +} {0 0 1 10 2 20 3 30 4 40 4 40 5 50 6 60 7 70 7 70 8 80 8 80 8 80 8 80 9 90 9 90 9 90 9 90} do_catchsql_test 5.3 { WITH i(x) AS ( VALUES(1) UNION ALL SELECT x+1 FROM i LIMIT 10 )