aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordrh <>2021-02-21 23:44:14 +0000
committerdrh <>2021-02-21 23:44:14 +0000
commita79e2a2d28eab71b7434a77ada3af5c2855d9bef (patch)
tree47b464c9d63c9a1a665779ba0022dacdbf503ce9 /src
parent7601294ad3fe9f7e0db8eb2478dec0de293b8bb6 (diff)
downloadsqlite-a79e2a2d28eab71b7434a77ada3af5c2855d9bef.tar.gz
sqlite-a79e2a2d28eab71b7434a77ada3af5c2855d9bef.zip
Materialize any CTE that is used more than once.
FossilOrigin-Name: ba59159fbe6b83fb6d79fbfee22d983768b0ebbaac7e99d2ac66c810e5e04100
Diffstat (limited to 'src')
-rw-r--r--src/delete.c6
-rw-r--r--src/expr.c5
-rw-r--r--src/prepare.c4
-rw-r--r--src/select.c75
-rw-r--r--src/sqliteInt.h30
-rw-r--r--src/treeview.c8
-rw-r--r--src/where.c2
7 files changed, 101 insertions, 29 deletions
diff --git a/src/delete.c b/src/delete.c
index 860a2739a..117347ef9 100644
--- a/src/delete.c
+++ b/src/delete.c
@@ -209,7 +209,11 @@ Expr *sqlite3LimitWhere(
pSrc->a[0].pTab = 0;
pSelectSrc = sqlite3SrcListDup(pParse->db, pSrc, 0);
pSrc->a[0].pTab = pTab;
- pSrc->a[0].pIBIndex = 0;
+ if( pSrc->a[0].fg.isIndexedBy ){
+ pSrc->a[0].u2.pIBIndex = 0;
+ }else if( pSrc->a[0].fg.isCte ){
+ pSrc->a[0].u2.pCteUse->nUse++;
+ }
/* generate the SELECT expression tree. */
pSelect = sqlite3SelectNew(pParse, pEList, pSelectSrc, pWhere, 0 ,0,
diff --git a/src/expr.c b/src/expr.c
index cf9a213f4..b66556b03 100644
--- a/src/expr.c
+++ b/src/expr.c
@@ -1544,7 +1544,10 @@ SrcList *sqlite3SrcListDup(sqlite3 *db, SrcList *p, int flags){
if( pNewItem->fg.isIndexedBy ){
pNewItem->u1.zIndexedBy = sqlite3DbStrDup(db, pOldItem->u1.zIndexedBy);
}
- pNewItem->pIBIndex = pOldItem->pIBIndex;
+ pNewItem->u2 = pOldItem->u2;
+ if( pNewItem->fg.isCte ){
+ pNewItem->u2.pCteUse->nUse++;
+ }
if( pNewItem->fg.isTabFunc ){
pNewItem->u1.pFuncArg =
sqlite3ExprListDup(db, pOldItem->u1.pFuncArg, flags);
diff --git a/src/prepare.c b/src/prepare.c
index dfa20e2b5..d79495d2c 100644
--- a/src/prepare.c
+++ b/src/prepare.c
@@ -597,7 +597,7 @@ void sqlite3ParserReset(Parse *pParse){
**
** testcase( pParse->earlyCleanup );
*/
-void sqlite3ParserAddCleanup(
+void *sqlite3ParserAddCleanup(
Parse *pParse, /* Destroy when this Parser finishes */
void (*xCleanup)(sqlite3*,void*), /* The cleanup routine */
void *pPtr /* Pointer to object to be cleaned up */
@@ -610,10 +610,12 @@ void sqlite3ParserAddCleanup(
pCleanup->xCleanup = xCleanup;
}else{
xCleanup(pParse->db, pPtr);
+ pPtr = 0;
#if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST)
pParse->earlyCleanup = 1;
#endif
}
+ return pPtr;
}
/*
diff --git a/src/select.c b/src/select.c
index a9d0eb153..3da69943f 100644
--- a/src/select.c
+++ b/src/select.c
@@ -4726,7 +4726,7 @@ int sqlite3IndexedByLookup(Parse *pParse, SrcItem *pFrom){
pParse->checkSchema = 1;
return SQLITE_ERROR;
}
- pFrom->pIBIndex = pIdx;
+ pFrom->u2.pIBIndex = pIdx;
return SQLITE_OK;
}
@@ -4935,8 +4935,18 @@ static int resolveFromTermToCte(
if( cannotBeFunction(pParse, pFrom) ) return 2;
assert( pFrom->pTab==0 );
- pFrom->pTab = pTab = sqlite3DbMallocZero(db, sizeof(Table));
+ pTab = sqlite3DbMallocZero(db, sizeof(Table));
if( pTab==0 ) return 2;
+ if( pCte->pUse==0 ){
+ pCte->pUse = sqlite3DbMallocZero(db, sizeof(pCte->pUse[0]));
+ if( pCte->pUse==0
+ || sqlite3ParserAddCleanup(pParse,sqlite3DbFree,pCte->pUse)==0
+ ){
+ sqlite3DbFree(db, pTab);
+ return 2;
+ }
+ }
+ pFrom->pTab = pTab;
pTab->nTabRef = 1;
pTab->zName = sqlite3DbStrDup(db, pCte->zName);
pTab->iPKey = -1;
@@ -4945,6 +4955,9 @@ static int resolveFromTermToCte(
pFrom->pSelect = sqlite3SelectDup(db, pCte->pSelect, 0);
if( db->mallocFailed ) return 2;
assert( pFrom->pSelect );
+ pFrom->fg.isCte = 1;
+ pFrom->u2.pCteUse = pCte->pUse;
+ pCte->pUse->nUse++;
/* Check if this is a recursive CTE. */
pRecTerm = pSel = pFrom->pSelect;
@@ -6153,6 +6166,7 @@ int sqlite3Select(
*/
for(i=0; i<pTabList->nSrc; i++){
SrcItem *pItem = &pTabList->a[i];
+ SrcItem *pPrior;
SelectDest dest;
Select *pSub;
#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)
@@ -6212,6 +6226,7 @@ int sqlite3Select(
** inside the subquery. This can help the subquery to run more efficiently.
*/
if( OptimizationEnabled(db, SQLITE_PushDown)
+ && (pItem->fg.isCte==0 || pItem->u2.pCteUse->nUse<=1)
&& pushDownWhereTerms(pParse, pSub, p->pWhere, pItem->iCursor,
(pItem->fg.jointype & JT_OUTER)!=0)
){
@@ -6232,16 +6247,18 @@ int sqlite3Select(
/* Generate code to implement the subquery
**
- ** The subquery is implemented as a co-routine if the subquery is
- ** guaranteed to be the outer loop (so that it does not need to be
- ** computed more than once)
+ ** The subquery is implemented as a co-routine if:
+ ** (1) the subquery is guaranteed to be the outer loop (so that
+ ** it does not need to be computed more than once), and
+ ** (2) the subquery is not a CTE that is used more then once.
**
- ** TODO: Are there other reasons beside (1) to use a co-routine
+ ** TODO: Are there other reasons beside (1) and (2) to use a co-routine
** implementation?
*/
if( i==0
&& (pTabList->nSrc==1
|| (pTabList->a[1].fg.jointype&(JT_LEFT|JT_CROSS))!=0) /* (1) */
+ && (pItem->fg.isCte==0 || pItem->u2.pCteUse->nUse<2) /* (2) */
){
/* Implement a co-routine that will return a single row of the result
** set on each invocation.
@@ -6261,16 +6278,30 @@ int sqlite3Select(
sqlite3VdbeEndCoroutine(v, pItem->regReturn);
sqlite3VdbeJumpHere(v, addrTop-1);
sqlite3ClearTempRegCache(pParse);
+ }else if( pItem->fg.isCte && pItem->u2.pCteUse->addrM9e>0 ){
+ /* This is a CTE for which materialization code has already been
+ ** generated. Invoke the subroutine to compute the materialization,
+ ** the make the pItem->iCursor be a copy of the ephemerial table that
+ ** holds the result of the materialization. */
+ CteUse *pCteUse = pItem->u2.pCteUse;
+ sqlite3VdbeAddOp2(v, OP_Gosub, pCteUse->regRtn, pCteUse->addrM9e);
+ if( pItem->iCursor!=pCteUse->iCur ){
+ sqlite3VdbeAddOp2(v, OP_OpenDup, pItem->iCursor, pCteUse->iCur);
+ }
+ pSub->nSelectRow = pCteUse->nRowEst;
+ }else if( (pPrior = isSelfJoinView(pTabList, pItem))!=0 ){
+ /* This view has already been materialized by a prior entry in
+ ** this same FROM clause. Reuse it. */
+ if( pPrior->addrFillSub ){
+ sqlite3VdbeAddOp2(v, OP_Gosub, pPrior->regReturn, pPrior->addrFillSub);
+ }
+ sqlite3VdbeAddOp2(v, OP_OpenDup, pItem->iCursor, pPrior->iCursor);
+ pSub->nSelectRow = pPrior->pSelect->nSelectRow;
}else{
- /* Generate a subroutine that will fill an ephemeral table with
- ** the content of this subquery. pItem->addrFillSub will point
- ** to the address of the generated subroutine. pItem->regReturn
- ** is a register allocated to hold the subroutine return address
- */
+ /* Generate a subroutine that will materialize the view. */
int topAddr;
int onceAddr = 0;
int retAddr;
- SrcItem *pPrior;
testcase( pItem->addrFillSub==0 ); /* Ticket c52b09c7f38903b1311 */
pItem->regReturn = ++pParse->nMem;
@@ -6285,22 +6316,22 @@ int sqlite3Select(
}else{
VdbeNoopComment((v, "materialize \"%s\"", pItem->pTab->zName));
}
- pPrior = isSelfJoinView(pTabList, pItem);
- if( pPrior ){
- sqlite3VdbeAddOp2(v, OP_OpenDup, pItem->iCursor, pPrior->iCursor);
- assert( pPrior->pSelect!=0 );
- pSub->nSelectRow = pPrior->pSelect->nSelectRow;
- }else{
- sqlite3SelectDestInit(&dest, SRT_EphemTab, pItem->iCursor);
- ExplainQueryPlan((pParse, 1, "MATERIALIZE %u", pSub->selId));
- sqlite3Select(pParse, pSub, &dest);
- }
+ sqlite3SelectDestInit(&dest, SRT_EphemTab, pItem->iCursor);
+ ExplainQueryPlan((pParse, 1, "MATERIALIZE %u", pSub->selId));
+ sqlite3Select(pParse, pSub, &dest);
pItem->pTab->nRowLogEst = pSub->nSelectRow;
if( onceAddr ) sqlite3VdbeJumpHere(v, onceAddr);
retAddr = sqlite3VdbeAddOp1(v, OP_Return, pItem->regReturn);
VdbeComment((v, "end %s", pItem->pTab->zName));
sqlite3VdbeChangeP1(v, topAddr, retAddr);
sqlite3ClearTempRegCache(pParse);
+ if( pItem->fg.isCte ){
+ CteUse *pCteUse = pItem->u2.pCteUse;
+ pCteUse->addrM9e = pItem->addrFillSub;
+ pCteUse->regRtn = pItem->regReturn;
+ pCteUse->iCur = pItem->iCursor;
+ pCteUse->nRowEst = pSub->nSelectRow;
+ }
}
if( db->mallocFailed ) goto select_end;
pParse->nHeight -= sqlite3SelectExprHeight(p);
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index 7fd71a86b..ea6ff1381 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -1137,6 +1137,7 @@ typedef struct Bitvec Bitvec;
typedef struct CollSeq CollSeq;
typedef struct Column Column;
typedef struct Cte Cte;
+typedef struct CteUse CteUse;
typedef struct Db Db;
typedef struct DbFixer DbFixer;
typedef struct Schema Schema;
@@ -2941,6 +2942,7 @@ struct SrcItem {
unsigned viaCoroutine :1; /* Implemented as a co-routine */
unsigned isRecursive :1; /* True for recursive reference in WITH */
unsigned fromDDL :1; /* Comes from sqlite_schema */
+ unsigned isCte :1; /* This is a CTE */
} fg;
int iCursor; /* The VDBE cursor number used to access this table */
Expr *pOn; /* The ON clause of a join */
@@ -2950,7 +2952,10 @@ struct SrcItem {
char *zIndexedBy; /* Identifier from "INDEXED BY <zIndex>" clause */
ExprList *pFuncArg; /* Arguments to table-valued-function */
} u1;
- Index *pIBIndex; /* Index structure corresponding to u1.zIndexedBy */
+ union {
+ Index *pIBIndex; /* Index structure corresponding to u1.zIndexedBy */
+ CteUse *pCteUse; /* CTE Usage info info fg.isCte is true */
+ } u2;
};
/*
@@ -3889,6 +3894,7 @@ struct Cte {
ExprList *pCols; /* List of explicit column names, or NULL */
Select *pSelect; /* The definition of this CTE */
const char *zCteErr; /* Error message for circular references */
+ CteUse *pUse; /* Usage information for this CTE */
};
/*
@@ -3901,6 +3907,26 @@ struct With {
Cte a[1]; /* For each CTE in the WITH clause.... */
};
+/*
+** The Cte object is not guaranteed to persist for the entire duration
+** of code generation. (The query flattener or other parser tree
+** edits might delete it.) The following object records information
+** about each Common Table Expression that must be preserved for the
+** duration of the parse.
+**
+** The CteUse objects are freed using sqlite3ParserAddCleanup() rather
+** than sqlite3SelectDelete(), which is what enables them to persist
+** until the end of code generation.
+*/
+struct CteUse {
+ int nUse; /* Number of users of this CTE */
+ int addrM9e; /* Start of subroutine to compute materialization */
+ int regRtn; /* Return address register for addrM9e subroutine */
+ int iCur; /* Ephemeral table holding the materialization */
+ LogEst nRowEst; /* Estimated number of rows in the table */
+};
+
+
#ifdef SQLITE_DEBUG
/*
** An instance of the TreeView object is used for printing the content of
@@ -4889,7 +4915,7 @@ sqlite3_int64 sqlite3StmtCurrentTime(sqlite3_context*);
int sqlite3VdbeParameterIndex(Vdbe*, const char*, int);
int sqlite3TransferBindings(sqlite3_stmt *, sqlite3_stmt *);
void sqlite3ParserReset(Parse*);
-void sqlite3ParserAddCleanup(Parse*,void(*)(sqlite3*,void*),void*);
+void *sqlite3ParserAddCleanup(Parse*,void(*)(sqlite3*,void*),void*);
#ifdef SQLITE_ENABLE_NORMALIZE
char *sqlite3Normalize(Vdbe*, const char*);
#endif
diff --git a/src/treeview.c b/src/treeview.c
index e0fa4971e..b696d764e 100644
--- a/src/treeview.c
+++ b/src/treeview.c
@@ -111,7 +111,10 @@ void sqlite3TreeViewWith(TreeView *pView, const With *pWith, u8 moreToFollow){
}
sqlite3_str_appendf(&x, ")");
}
- sqlite3_str_appendf(&x, " AS");
+ if( pCte->pUse ){
+ sqlite3_str_appendf(&x, " (pUse=0x%p, nUse=%d)", pCte->pUse,
+ pCte->pUse->nUse);
+ }
sqlite3StrAccumFinish(&x);
sqlite3TreeViewItem(pView, zLine, i<pWith->nCte-1);
sqlite3TreeViewSelect(pView, pCte->pSelect, 0);
@@ -150,6 +153,9 @@ void sqlite3TreeViewSrcList(TreeView *pView, const SrcList *pSrc){
if( pItem->fg.fromDDL ){
sqlite3_str_appendf(&x, " DDL");
}
+ if( pItem->fg.isCte ){
+ sqlite3_str_appendf(&x, " CteUse=0x%p", pItem->u2.pCteUse);
+ }
sqlite3StrAccumFinish(&x);
sqlite3TreeViewItem(pView, zLine, i<pSrc->nSrc-1);
if( pItem->pSelect ){
diff --git a/src/where.c b/src/where.c
index 63aba17c7..d7aa81284 100644
--- a/src/where.c
+++ b/src/where.c
@@ -2956,7 +2956,7 @@ static int whereLoopAddBtree(
if( pSrc->fg.isIndexedBy ){
/* An INDEXED BY clause specifies a particular index to use */
- pProbe = pSrc->pIBIndex;
+ pProbe = pSrc->u2.pIBIndex;
}else if( !HasRowid(pTab) ){
pProbe = pTab->pIndex;
}else{