Skip to content

Commit 4b74cc6

Browse files
author
D. Richard Hipp
committed
Materialize any CTE that is used more than once.
1 parent d89d5d3 commit 4b74cc6

File tree

7 files changed

+101
-29
lines changed

7 files changed

+101
-29
lines changed

src/delete.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,11 @@ Expr *sqlite3LimitWhere(
209209
pSrc->a[0].pTab = 0;
210210
pSelectSrc = sqlite3SrcListDup(pParse->db, pSrc, 0);
211211
pSrc->a[0].pTab = pTab;
212-
pSrc->a[0].pIBIndex = 0;
212+
if( pSrc->a[0].fg.isIndexedBy ){
213+
pSrc->a[0].u2.pIBIndex = 0;
214+
}else if( pSrc->a[0].fg.isCte ){
215+
pSrc->a[0].u2.pCteUse->nUse++;
216+
}
213217

214218
/* generate the SELECT expression tree. */
215219
pSelect = sqlite3SelectNew(pParse, pEList, pSelectSrc, pWhere, 0 ,0,

src/expr.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1544,7 +1544,10 @@ SrcList *sqlite3SrcListDup(sqlite3 *db, SrcList *p, int flags){
15441544
if( pNewItem->fg.isIndexedBy ){
15451545
pNewItem->u1.zIndexedBy = sqlite3DbStrDup(db, pOldItem->u1.zIndexedBy);
15461546
}
1547-
pNewItem->pIBIndex = pOldItem->pIBIndex;
1547+
pNewItem->u2 = pOldItem->u2;
1548+
if( pNewItem->fg.isCte ){
1549+
pNewItem->u2.pCteUse->nUse++;
1550+
}
15481551
if( pNewItem->fg.isTabFunc ){
15491552
pNewItem->u1.pFuncArg =
15501553
sqlite3ExprListDup(db, pOldItem->u1.pFuncArg, flags);

src/prepare.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,7 @@ void sqlite3ParserReset(Parse *pParse){
597597
**
598598
** testcase( pParse->earlyCleanup );
599599
*/
600-
void sqlite3ParserAddCleanup(
600+
void *sqlite3ParserAddCleanup(
601601
Parse *pParse, /* Destroy when this Parser finishes */
602602
void (*xCleanup)(sqlite3*,void*), /* The cleanup routine */
603603
void *pPtr /* Pointer to object to be cleaned up */
@@ -610,10 +610,12 @@ void sqlite3ParserAddCleanup(
610610
pCleanup->xCleanup = xCleanup;
611611
}else{
612612
xCleanup(pParse->db, pPtr);
613+
pPtr = 0;
613614
#if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST)
614615
pParse->earlyCleanup = 1;
615616
#endif
616617
}
618+
return pPtr;
617619
}
618620

619621
/*

src/select.c

Lines changed: 53 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4726,7 +4726,7 @@ int sqlite3IndexedByLookup(Parse *pParse, SrcItem *pFrom){
47264726
pParse->checkSchema = 1;
47274727
return SQLITE_ERROR;
47284728
}
4729-
pFrom->pIBIndex = pIdx;
4729+
pFrom->u2.pIBIndex = pIdx;
47304730
return SQLITE_OK;
47314731
}
47324732

@@ -4935,8 +4935,18 @@ static int resolveFromTermToCte(
49354935
if( cannotBeFunction(pParse, pFrom) ) return 2;
49364936

49374937
assert( pFrom->pTab==0 );
4938-
pFrom->pTab = pTab = sqlite3DbMallocZero(db, sizeof(Table));
4938+
pTab = sqlite3DbMallocZero(db, sizeof(Table));
49394939
if( pTab==0 ) return 2;
4940+
if( pCte->pUse==0 ){
4941+
pCte->pUse = sqlite3DbMallocZero(db, sizeof(pCte->pUse[0]));
4942+
if( pCte->pUse==0
4943+
|| sqlite3ParserAddCleanup(pParse,sqlite3DbFree,pCte->pUse)==0
4944+
){
4945+
sqlite3DbFree(db, pTab);
4946+
return 2;
4947+
}
4948+
}
4949+
pFrom->pTab = pTab;
49404950
pTab->nTabRef = 1;
49414951
pTab->zName = sqlite3DbStrDup(db, pCte->zName);
49424952
pTab->iPKey = -1;
@@ -4945,6 +4955,9 @@ static int resolveFromTermToCte(
49454955
pFrom->pSelect = sqlite3SelectDup(db, pCte->pSelect, 0);
49464956
if( db->mallocFailed ) return 2;
49474957
assert( pFrom->pSelect );
4958+
pFrom->fg.isCte = 1;
4959+
pFrom->u2.pCteUse = pCte->pUse;
4960+
pCte->pUse->nUse++;
49484961

49494962
/* Check if this is a recursive CTE. */
49504963
pRecTerm = pSel = pFrom->pSelect;
@@ -6153,6 +6166,7 @@ int sqlite3Select(
61536166
*/
61546167
for(i=0; i<pTabList->nSrc; i++){
61556168
SrcItem *pItem = &pTabList->a[i];
6169+
SrcItem *pPrior;
61566170
SelectDest dest;
61576171
Select *pSub;
61586172
#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)
@@ -6212,6 +6226,7 @@ int sqlite3Select(
62126226
** inside the subquery. This can help the subquery to run more efficiently.
62136227
*/
62146228
if( OptimizationEnabled(db, SQLITE_PushDown)
6229+
&& (pItem->fg.isCte==0 || pItem->u2.pCteUse->nUse<=1)
62156230
&& pushDownWhereTerms(pParse, pSub, p->pWhere, pItem->iCursor,
62166231
(pItem->fg.jointype & JT_OUTER)!=0)
62176232
){
@@ -6232,16 +6247,18 @@ int sqlite3Select(
62326247

62336248
/* Generate code to implement the subquery
62346249
**
6235-
** The subquery is implemented as a co-routine if the subquery is
6236-
** guaranteed to be the outer loop (so that it does not need to be
6237-
** computed more than once)
6250+
** The subquery is implemented as a co-routine if:
6251+
** (1) the subquery is guaranteed to be the outer loop (so that
6252+
** it does not need to be computed more than once), and
6253+
** (2) the subquery is not a CTE that is used more then once.
62386254
**
6239-
** TODO: Are there other reasons beside (1) to use a co-routine
6255+
** TODO: Are there other reasons beside (1) and (2) to use a co-routine
62406256
** implementation?
62416257
*/
62426258
if( i==0
62436259
&& (pTabList->nSrc==1
62446260
|| (pTabList->a[1].fg.jointype&(JT_LEFT|JT_CROSS))!=0) /* (1) */
6261+
&& (pItem->fg.isCte==0 || pItem->u2.pCteUse->nUse<2) /* (2) */
62456262
){
62466263
/* Implement a co-routine that will return a single row of the result
62476264
** set on each invocation.
@@ -6261,16 +6278,30 @@ int sqlite3Select(
62616278
sqlite3VdbeEndCoroutine(v, pItem->regReturn);
62626279
sqlite3VdbeJumpHere(v, addrTop-1);
62636280
sqlite3ClearTempRegCache(pParse);
6281+
}else if( pItem->fg.isCte && pItem->u2.pCteUse->addrM9e>0 ){
6282+
/* This is a CTE for which materialization code has already been
6283+
** generated. Invoke the subroutine to compute the materialization,
6284+
** the make the pItem->iCursor be a copy of the ephemerial table that
6285+
** holds the result of the materialization. */
6286+
CteUse *pCteUse = pItem->u2.pCteUse;
6287+
sqlite3VdbeAddOp2(v, OP_Gosub, pCteUse->regRtn, pCteUse->addrM9e);
6288+
if( pItem->iCursor!=pCteUse->iCur ){
6289+
sqlite3VdbeAddOp2(v, OP_OpenDup, pItem->iCursor, pCteUse->iCur);
6290+
}
6291+
pSub->nSelectRow = pCteUse->nRowEst;
6292+
}else if( (pPrior = isSelfJoinView(pTabList, pItem))!=0 ){
6293+
/* This view has already been materialized by a prior entry in
6294+
** this same FROM clause. Reuse it. */
6295+
if( pPrior->addrFillSub ){
6296+
sqlite3VdbeAddOp2(v, OP_Gosub, pPrior->regReturn, pPrior->addrFillSub);
6297+
}
6298+
sqlite3VdbeAddOp2(v, OP_OpenDup, pItem->iCursor, pPrior->iCursor);
6299+
pSub->nSelectRow = pPrior->pSelect->nSelectRow;
62646300
}else{
6265-
/* Generate a subroutine that will fill an ephemeral table with
6266-
** the content of this subquery. pItem->addrFillSub will point
6267-
** to the address of the generated subroutine. pItem->regReturn
6268-
** is a register allocated to hold the subroutine return address
6269-
*/
6301+
/* Generate a subroutine that will materialize the view. */
62706302
int topAddr;
62716303
int onceAddr = 0;
62726304
int retAddr;
6273-
SrcItem *pPrior;
62746305

62756306
testcase( pItem->addrFillSub==0 ); /* Ticket c52b09c7f38903b1311 */
62766307
pItem->regReturn = ++pParse->nMem;
@@ -6285,22 +6316,22 @@ int sqlite3Select(
62856316
}else{
62866317
VdbeNoopComment((v, "materialize \"%s\"", pItem->pTab->zName));
62876318
}
6288-
pPrior = isSelfJoinView(pTabList, pItem);
6289-
if( pPrior ){
6290-
sqlite3VdbeAddOp2(v, OP_OpenDup, pItem->iCursor, pPrior->iCursor);
6291-
assert( pPrior->pSelect!=0 );
6292-
pSub->nSelectRow = pPrior->pSelect->nSelectRow;
6293-
}else{
6294-
sqlite3SelectDestInit(&dest, SRT_EphemTab, pItem->iCursor);
6295-
ExplainQueryPlan((pParse, 1, "MATERIALIZE %u", pSub->selId));
6296-
sqlite3Select(pParse, pSub, &dest);
6297-
}
6319+
sqlite3SelectDestInit(&dest, SRT_EphemTab, pItem->iCursor);
6320+
ExplainQueryPlan((pParse, 1, "MATERIALIZE %u", pSub->selId));
6321+
sqlite3Select(pParse, pSub, &dest);
62986322
pItem->pTab->nRowLogEst = pSub->nSelectRow;
62996323
if( onceAddr ) sqlite3VdbeJumpHere(v, onceAddr);
63006324
retAddr = sqlite3VdbeAddOp1(v, OP_Return, pItem->regReturn);
63016325
VdbeComment((v, "end %s", pItem->pTab->zName));
63026326
sqlite3VdbeChangeP1(v, topAddr, retAddr);
63036327
sqlite3ClearTempRegCache(pParse);
6328+
if( pItem->fg.isCte ){
6329+
CteUse *pCteUse = pItem->u2.pCteUse;
6330+
pCteUse->addrM9e = pItem->addrFillSub;
6331+
pCteUse->regRtn = pItem->regReturn;
6332+
pCteUse->iCur = pItem->iCursor;
6333+
pCteUse->nRowEst = pSub->nSelectRow;
6334+
}
63046335
}
63056336
if( db->mallocFailed ) goto select_end;
63066337
pParse->nHeight -= sqlite3SelectExprHeight(p);

src/sqliteInt.h

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1137,6 +1137,7 @@ typedef struct Bitvec Bitvec;
11371137
typedef struct CollSeq CollSeq;
11381138
typedef struct Column Column;
11391139
typedef struct Cte Cte;
1140+
typedef struct CteUse CteUse;
11401141
typedef struct Db Db;
11411142
typedef struct DbFixer DbFixer;
11421143
typedef struct Schema Schema;
@@ -2941,6 +2942,7 @@ struct SrcItem {
29412942
unsigned viaCoroutine :1; /* Implemented as a co-routine */
29422943
unsigned isRecursive :1; /* True for recursive reference in WITH */
29432944
unsigned fromDDL :1; /* Comes from sqlite_schema */
2945+
unsigned isCte :1; /* This is a CTE */
29442946
} fg;
29452947
int iCursor; /* The VDBE cursor number used to access this table */
29462948
Expr *pOn; /* The ON clause of a join */
@@ -2950,7 +2952,10 @@ struct SrcItem {
29502952
char *zIndexedBy; /* Identifier from "INDEXED BY <zIndex>" clause */
29512953
ExprList *pFuncArg; /* Arguments to table-valued-function */
29522954
} u1;
2953-
Index *pIBIndex; /* Index structure corresponding to u1.zIndexedBy */
2955+
union {
2956+
Index *pIBIndex; /* Index structure corresponding to u1.zIndexedBy */
2957+
CteUse *pCteUse; /* CTE Usage info info fg.isCte is true */
2958+
} u2;
29542959
};
29552960

29562961
/*
@@ -3889,6 +3894,7 @@ struct Cte {
38893894
ExprList *pCols; /* List of explicit column names, or NULL */
38903895
Select *pSelect; /* The definition of this CTE */
38913896
const char *zCteErr; /* Error message for circular references */
3897+
CteUse *pUse; /* Usage information for this CTE */
38923898
};
38933899

38943900
/*
@@ -3901,6 +3907,26 @@ struct With {
39013907
Cte a[1]; /* For each CTE in the WITH clause.... */
39023908
};
39033909

3910+
/*
3911+
** The Cte object is not guaranteed to persist for the entire duration
3912+
** of code generation. (The query flattener or other parser tree
3913+
** edits might delete it.) The following object records information
3914+
** about each Common Table Expression that must be preserved for the
3915+
** duration of the parse.
3916+
**
3917+
** The CteUse objects are freed using sqlite3ParserAddCleanup() rather
3918+
** than sqlite3SelectDelete(), which is what enables them to persist
3919+
** until the end of code generation.
3920+
*/
3921+
struct CteUse {
3922+
int nUse; /* Number of users of this CTE */
3923+
int addrM9e; /* Start of subroutine to compute materialization */
3924+
int regRtn; /* Return address register for addrM9e subroutine */
3925+
int iCur; /* Ephemeral table holding the materialization */
3926+
LogEst nRowEst; /* Estimated number of rows in the table */
3927+
};
3928+
3929+
39043930
#ifdef SQLITE_DEBUG
39053931
/*
39063932
** An instance of the TreeView object is used for printing the content of
@@ -4889,7 +4915,7 @@ sqlite3_int64 sqlite3StmtCurrentTime(sqlite3_context*);
48894915
int sqlite3VdbeParameterIndex(Vdbe*, const char*, int);
48904916
int sqlite3TransferBindings(sqlite3_stmt *, sqlite3_stmt *);
48914917
void sqlite3ParserReset(Parse*);
4892-
void sqlite3ParserAddCleanup(Parse*,void(*)(sqlite3*,void*),void*);
4918+
void *sqlite3ParserAddCleanup(Parse*,void(*)(sqlite3*,void*),void*);
48934919
#ifdef SQLITE_ENABLE_NORMALIZE
48944920
char *sqlite3Normalize(Vdbe*, const char*);
48954921
#endif

src/treeview.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,10 @@ void sqlite3TreeViewWith(TreeView *pView, const With *pWith, u8 moreToFollow){
111111
}
112112
sqlite3_str_appendf(&x, ")");
113113
}
114-
sqlite3_str_appendf(&x, " AS");
114+
if( pCte->pUse ){
115+
sqlite3_str_appendf(&x, " (pUse=0x%p, nUse=%d)", pCte->pUse,
116+
pCte->pUse->nUse);
117+
}
115118
sqlite3StrAccumFinish(&x);
116119
sqlite3TreeViewItem(pView, zLine, i<pWith->nCte-1);
117120
sqlite3TreeViewSelect(pView, pCte->pSelect, 0);
@@ -150,6 +153,9 @@ void sqlite3TreeViewSrcList(TreeView *pView, const SrcList *pSrc){
150153
if( pItem->fg.fromDDL ){
151154
sqlite3_str_appendf(&x, " DDL");
152155
}
156+
if( pItem->fg.isCte ){
157+
sqlite3_str_appendf(&x, " CteUse=0x%p", pItem->u2.pCteUse);
158+
}
153159
sqlite3StrAccumFinish(&x);
154160
sqlite3TreeViewItem(pView, zLine, i<pSrc->nSrc-1);
155161
if( pItem->pSelect ){

src/where.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2956,7 +2956,7 @@ static int whereLoopAddBtree(
29562956

29572957
if( pSrc->fg.isIndexedBy ){
29582958
/* An INDEXED BY clause specifies a particular index to use */
2959-
pProbe = pSrc->pIBIndex;
2959+
pProbe = pSrc->u2.pIBIndex;
29602960
}else if( !HasRowid(pTab) ){
29612961
pProbe = pTab->pIndex;
29622962
}else{

0 commit comments

Comments
 (0)