Skip to content

Commit b209246

Browse files
author
D. Richard Hipp
committed
Implement the MATERIALIZED and NOT MATERIALIZED hints on common
table expressions.
2 parents d89d5d3 + ac0c306 commit b209246

File tree

11 files changed

+134
-35
lines changed

11 files changed

+134
-35
lines changed

src/build.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5211,7 +5211,8 @@ Cte *sqlite3CteNew(
52115211
Parse *pParse, /* Parsing context */
52125212
Token *pName, /* Name of the common-table */
52135213
ExprList *pArglist, /* Optional column name list for the table */
5214-
Select *pQuery /* Query used to initialize the table */
5214+
Select *pQuery, /* Query used to initialize the table */
5215+
u8 eM10d /* The MATERIALIZED flag */
52155216
){
52165217
Cte *pNew;
52175218
sqlite3 *db = pParse->db;
@@ -5226,6 +5227,7 @@ Cte *sqlite3CteNew(
52265227
pNew->pSelect = pQuery;
52275228
pNew->pCols = pArglist;
52285229
pNew->zName = sqlite3NameFromToken(pParse->db, pName);
5230+
pNew->eM10d = eM10d;
52295231
}
52305232
return pNew;
52315233
}

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/parse.y

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ columnname(A) ::= nm(A) typetoken(Y). {sqlite3AddColumn(pParse,&A,&Y);}
250250
%ifndef SQLITE_OMIT_GENERATED_COLUMNS
251251
GENERATED ALWAYS
252252
%endif
253+
MATERIALIZED
253254
REINDEX RENAME CTIME_KW IF
254255
.
255256
%wildcard ANY.
@@ -1666,8 +1667,12 @@ with ::= .
16661667
with ::= WITH wqlist(W). { sqlite3WithPush(pParse, W, 1); }
16671668
with ::= WITH RECURSIVE wqlist(W). { sqlite3WithPush(pParse, W, 1); }
16681669

1669-
wqitem(A) ::= nm(X) eidlist_opt(Y) AS LP select(Z) RP. {
1670-
A = sqlite3CteNew(pParse, &X, Y, Z); /*A-overwrites-X*/
1670+
%type wqas {u8}
1671+
wqas(A) ::= AS. {A = M10d_Any;}
1672+
wqas(A) ::= AS MATERIALIZED. {A = M10d_Yes;}
1673+
wqas(A) ::= AS NOT MATERIALIZED. {A = M10d_No;}
1674+
wqitem(A) ::= nm(X) eidlist_opt(Y) wqas(M) LP select(Z) RP. {
1675+
A = sqlite3CteNew(pParse, &X, Y, Z, M); /*A-overwrites-X*/
16711676
}
16721677
wqlist(A) ::= wqitem(X). {
16731678
A = sqlite3WithAdd(pParse, 0, X); /*A-overwrites-X*/

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: 63 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4540,6 +4540,10 @@ static int propagateConstants(
45404540
** changes to the WHERE clause of the inner query could change the
45414541
** window over which window functions are calculated).
45424542
**
4543+
** (7) The inner query is a Common Table Expression (CTE) that should
4544+
** be materialized. (This restriction is implemented in the calling
4545+
** routine.)
4546+
**
45434547
** Return 0 if no changes are made and non-zero if one or more WHERE clause
45444548
** terms are duplicated into the subquery.
45454549
*/
@@ -4726,7 +4730,7 @@ int sqlite3IndexedByLookup(Parse *pParse, SrcItem *pFrom){
47264730
pParse->checkSchema = 1;
47274731
return SQLITE_ERROR;
47284732
}
4729-
pFrom->pIBIndex = pIdx;
4733+
pFrom->u2.pIBIndex = pIdx;
47304734
return SQLITE_OK;
47314735
}
47324736

@@ -4923,6 +4927,7 @@ static int resolveFromTermToCte(
49234927
int bMayRecursive; /* True if compound joined by UNION [ALL] */
49244928
With *pSavedWith; /* Initial value of pParse->pWith */
49254929
int iRecTab = -1; /* Cursor for recursive table */
4930+
CteUse *pCteUse;
49264931

49274932
/* If pCte->zCteErr is non-NULL at this point, then this is an illegal
49284933
** recursive reference to CTE pCte. Leave an error in pParse and return
@@ -4935,8 +4940,20 @@ static int resolveFromTermToCte(
49354940
if( cannotBeFunction(pParse, pFrom) ) return 2;
49364941

49374942
assert( pFrom->pTab==0 );
4938-
pFrom->pTab = pTab = sqlite3DbMallocZero(db, sizeof(Table));
4943+
pTab = sqlite3DbMallocZero(db, sizeof(Table));
49394944
if( pTab==0 ) return 2;
4945+
pCteUse = pCte->pUse;
4946+
if( pCteUse==0 ){
4947+
pCte->pUse = pCteUse = sqlite3DbMallocZero(db, sizeof(pCteUse[0]));
4948+
if( pCteUse==0
4949+
|| sqlite3ParserAddCleanup(pParse,sqlite3DbFree,pCteUse)==0
4950+
){
4951+
sqlite3DbFree(db, pTab);
4952+
return 2;
4953+
}
4954+
pCteUse->eM10d = pCte->eM10d;
4955+
}
4956+
pFrom->pTab = pTab;
49404957
pTab->nTabRef = 1;
49414958
pTab->zName = sqlite3DbStrDup(db, pCte->zName);
49424959
pTab->iPKey = -1;
@@ -4945,6 +4962,12 @@ static int resolveFromTermToCte(
49454962
pFrom->pSelect = sqlite3SelectDup(db, pCte->pSelect, 0);
49464963
if( db->mallocFailed ) return 2;
49474964
assert( pFrom->pSelect );
4965+
pFrom->fg.isCte = 1;
4966+
pFrom->u2.pCteUse = pCteUse;
4967+
pCteUse->nUse++;
4968+
if( pCteUse->nUse>=2 && pCteUse->eM10d==M10d_Any ){
4969+
pCteUse->eM10d = M10d_Yes;
4970+
}
49484971

49494972
/* Check if this is a recursive CTE. */
49504973
pRecTerm = pSel = pFrom->pSelect;
@@ -6153,6 +6176,7 @@ int sqlite3Select(
61536176
*/
61546177
for(i=0; i<pTabList->nSrc; i++){
61556178
SrcItem *pItem = &pTabList->a[i];
6179+
SrcItem *pPrior;
61566180
SelectDest dest;
61576181
Select *pSub;
61586182
#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)
@@ -6212,6 +6236,7 @@ int sqlite3Select(
62126236
** inside the subquery. This can help the subquery to run more efficiently.
62136237
*/
62146238
if( OptimizationEnabled(db, SQLITE_PushDown)
6239+
&& (pItem->fg.isCte==0 || pItem->u2.pCteUse->eM10d!=M10d_Yes)
62156240
&& pushDownWhereTerms(pParse, pSub, p->pWhere, pItem->iCursor,
62166241
(pItem->fg.jointype & JT_OUTER)!=0)
62176242
){
@@ -6232,16 +6257,18 @@ int sqlite3Select(
62326257

62336258
/* Generate code to implement the subquery
62346259
**
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)
6260+
** The subquery is implemented as a co-routine if:
6261+
** (1) the subquery is guaranteed to be the outer loop (so that
6262+
** it does not need to be computed more than once), and
6263+
** (2) the subquery is not a CTE that should be materialized
62386264
**
6239-
** TODO: Are there other reasons beside (1) to use a co-routine
6265+
** TODO: Are there other reasons beside (1) and (2) to use a co-routine
62406266
** implementation?
62416267
*/
62426268
if( i==0
62436269
&& (pTabList->nSrc==1
62446270
|| (pTabList->a[1].fg.jointype&(JT_LEFT|JT_CROSS))!=0) /* (1) */
6271+
&& (pItem->fg.isCte==0 || pItem->u2.pCteUse->eM10d!=M10d_Yes) /* (2) */
62456272
){
62466273
/* Implement a co-routine that will return a single row of the result
62476274
** set on each invocation.
@@ -6261,16 +6288,30 @@ int sqlite3Select(
62616288
sqlite3VdbeEndCoroutine(v, pItem->regReturn);
62626289
sqlite3VdbeJumpHere(v, addrTop-1);
62636290
sqlite3ClearTempRegCache(pParse);
6291+
}else if( pItem->fg.isCte && pItem->u2.pCteUse->addrM9e>0 ){
6292+
/* This is a CTE for which materialization code has already been
6293+
** generated. Invoke the subroutine to compute the materialization,
6294+
** the make the pItem->iCursor be a copy of the ephemerial table that
6295+
** holds the result of the materialization. */
6296+
CteUse *pCteUse = pItem->u2.pCteUse;
6297+
sqlite3VdbeAddOp2(v, OP_Gosub, pCteUse->regRtn, pCteUse->addrM9e);
6298+
if( pItem->iCursor!=pCteUse->iCur ){
6299+
sqlite3VdbeAddOp2(v, OP_OpenDup, pItem->iCursor, pCteUse->iCur);
6300+
}
6301+
pSub->nSelectRow = pCteUse->nRowEst;
6302+
}else if( (pPrior = isSelfJoinView(pTabList, pItem))!=0 ){
6303+
/* This view has already been materialized by a prior entry in
6304+
** this same FROM clause. Reuse it. */
6305+
if( pPrior->addrFillSub ){
6306+
sqlite3VdbeAddOp2(v, OP_Gosub, pPrior->regReturn, pPrior->addrFillSub);
6307+
}
6308+
sqlite3VdbeAddOp2(v, OP_OpenDup, pItem->iCursor, pPrior->iCursor);
6309+
pSub->nSelectRow = pPrior->pSelect->nSelectRow;
62646310
}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-
*/
6311+
/* Generate a subroutine that will materialize the view. */
62706312
int topAddr;
62716313
int onceAddr = 0;
62726314
int retAddr;
6273-
SrcItem *pPrior;
62746315

62756316
testcase( pItem->addrFillSub==0 ); /* Ticket c52b09c7f38903b1311 */
62766317
pItem->regReturn = ++pParse->nMem;
@@ -6285,22 +6326,22 @@ int sqlite3Select(
62856326
}else{
62866327
VdbeNoopComment((v, "materialize \"%s\"", pItem->pTab->zName));
62876328
}
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-
}
6329+
sqlite3SelectDestInit(&dest, SRT_EphemTab, pItem->iCursor);
6330+
ExplainQueryPlan((pParse, 1, "MATERIALIZE %u", pSub->selId));
6331+
sqlite3Select(pParse, pSub, &dest);
62986332
pItem->pTab->nRowLogEst = pSub->nSelectRow;
62996333
if( onceAddr ) sqlite3VdbeJumpHere(v, onceAddr);
63006334
retAddr = sqlite3VdbeAddOp1(v, OP_Return, pItem->regReturn);
63016335
VdbeComment((v, "end %s", pItem->pTab->zName));
63026336
sqlite3VdbeChangeP1(v, topAddr, retAddr);
63036337
sqlite3ClearTempRegCache(pParse);
6338+
if( pItem->fg.isCte ){
6339+
CteUse *pCteUse = pItem->u2.pCteUse;
6340+
pCteUse->addrM9e = pItem->addrFillSub;
6341+
pCteUse->regRtn = pItem->regReturn;
6342+
pCteUse->iCur = pItem->iCursor;
6343+
pCteUse->nRowEst = pSub->nSelectRow;
6344+
}
63046345
}
63056346
if( db->mallocFailed ) goto select_end;
63066347
pParse->nHeight -= sqlite3SelectExprHeight(p);

src/sqliteInt.h

Lines changed: 38 additions & 3 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,8 +3894,17 @@ 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 */
3898+
u8 eM10d; /* The MATERIALIZED flag */
38923899
};
38933900

3901+
/*
3902+
** Allowed values for the materialized flag (eM10d):
3903+
*/
3904+
#define M10d_Yes 0 /* AS MATERIALIZED */
3905+
#define M10d_Any 1 /* Not specified. Query planner's choice */
3906+
#define M10d_No 2 /* AS NOT MATERIALIZED */
3907+
38943908
/*
38953909
** An instance of the With object represents a WITH clause containing
38963910
** one or more CTEs (common table expressions).
@@ -3901,6 +3915,27 @@ struct With {
39013915
Cte a[1]; /* For each CTE in the WITH clause.... */
39023916
};
39033917

3918+
/*
3919+
** The Cte object is not guaranteed to persist for the entire duration
3920+
** of code generation. (The query flattener or other parser tree
3921+
** edits might delete it.) The following object records information
3922+
** about each Common Table Expression that must be preserved for the
3923+
** duration of the parse.
3924+
**
3925+
** The CteUse objects are freed using sqlite3ParserAddCleanup() rather
3926+
** than sqlite3SelectDelete(), which is what enables them to persist
3927+
** until the end of code generation.
3928+
*/
3929+
struct CteUse {
3930+
int nUse; /* Number of users of this CTE */
3931+
int addrM9e; /* Start of subroutine to compute materialization */
3932+
int regRtn; /* Return address register for addrM9e subroutine */
3933+
int iCur; /* Ephemeral table holding the materialization */
3934+
LogEst nRowEst; /* Estimated number of rows in the table */
3935+
u8 eM10d; /* The MATERIALIZED flag */
3936+
};
3937+
3938+
39043939
#ifdef SQLITE_DEBUG
39053940
/*
39063941
** An instance of the TreeView object is used for printing the content of
@@ -4889,7 +4924,7 @@ sqlite3_int64 sqlite3StmtCurrentTime(sqlite3_context*);
48894924
int sqlite3VdbeParameterIndex(Vdbe*, const char*, int);
48904925
int sqlite3TransferBindings(sqlite3_stmt *, sqlite3_stmt *);
48914926
void sqlite3ParserReset(Parse*);
4892-
void sqlite3ParserAddCleanup(Parse*,void(*)(sqlite3*,void*),void*);
4927+
void *sqlite3ParserAddCleanup(Parse*,void(*)(sqlite3*,void*),void*);
48934928
#ifdef SQLITE_ENABLE_NORMALIZE
48944929
char *sqlite3Normalize(Vdbe*, const char*);
48954930
#endif
@@ -4904,7 +4939,7 @@ const char *sqlite3JournalModename(int);
49044939
int sqlite3WalDefaultHook(void*,sqlite3*,const char*,int);
49054940
#endif
49064941
#ifndef SQLITE_OMIT_CTE
4907-
Cte *sqlite3CteNew(Parse*,Token*,ExprList*,Select*);
4942+
Cte *sqlite3CteNew(Parse*,Token*,ExprList*,Select*,u8);
49084943
void sqlite3CteDelete(sqlite3*,Cte*);
49094944
With *sqlite3WithAdd(Parse*,With*,Cte*);
49104945
void sqlite3WithDelete(sqlite3*,With*);

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/vdbe.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3949,7 +3949,7 @@ case OP_OpenEphemeral: {
39493949
aMem[pOp->p3].z = "";
39503950
}
39513951
pCx = p->apCsr[pOp->p1];
3952-
if( pCx && pCx->pBtx ){
3952+
if( pCx && ALWAYS(pCx->pBtx) ){
39533953
/* If the ephermeral table is already open, erase all existing content
39543954
** so that the table is empty again, rather than creating a new table. */
39553955
assert( pCx->isEphemeral );

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)