blob: 3392adc0ca72a3bc620c12bcb9d395aa64c7ebd1 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
//---------------------------------------------------------------------------
// @filename:
// CQueryMutators.cpp
//
// @doc:
// Implementation of methods used during translating a GPDB Query object into a
// DXL Tree
//
// @owner:
// raghav
//
// @test:
//
//---------------------------------------------------------------------------
#include "postgres.h"
#include "nodes/plannodes.h"
#include "nodes/parsenodes.h"
#include "nodes/makefuncs.h"
#include "optimizer/walkers.h"
#include "gpopt/base/CUtils.h"
#include "gpopt/mdcache/CMDAccessor.h"
#include "gpopt/mdcache/CMDAccessorUtils.h"
#include "gpopt/translate/CQueryMutators.h"
#include "gpopt/translate/CTranslatorDXLToPlStmt.h"
#include "naucrates/md/IMDScalarOp.h"
#include "naucrates/md/IMDAggregate.h"
#include "naucrates/md/IMDTypeBool.h"
#include "gpopt/gpdbwrappers.h"
using namespace gpdxl;
using namespace gpmd;
//---------------------------------------------------------------------------
// @function:
// CQueryMutators::FNeedsPrLNormalization
//
// @doc:
// Is the group by project list flat (contains only aggregates
// and grouping columns)
//---------------------------------------------------------------------------
BOOL
CQueryMutators::FNeedsPrLNormalization
(
const Query *pquery
)
{
if (!pquery->hasAggs && NULL == pquery->groupClause)
{
return false;
}
SContextTLWalker ctxTLWalker(pquery->targetList, pquery->groupClause);
const ULONG ulArity = gpdb::UlListLength(pquery->targetList);
for (ULONG ul = 0; ul < ulArity; ul++)
{
TargetEntry *pte = (TargetEntry*) gpdb::PvListNth(pquery->targetList, ul);
if (FNeedsToFallback((Node *) pte->expr, &ctxTLWalker))
{
// TODO: raghav, Oct 14 2013, remove temporary fix (revert exception to assert) to avoid crash during algebrization
GPOS_RAISE(gpdxl::ExmaDXL, gpdxl::ExmiQuery2DXLError, GPOS_WSZ_LIT("No attribute"));
}
// Normalize when there is an expression that is neither used for grouping
// nor is an aggregate function
if (!IsA(pte->expr, PercentileExpr) && !IsA(pte->expr, Aggref) && !IsA(pte->expr, GroupingFunc) && !CTranslatorUtils::FGroupingColumn( (Node*) pte->expr, pquery->groupClause, pquery->targetList))
{
return true;
}
}
return false;
}
//---------------------------------------------------------------------------
// @function:
// CQueryMutators::FNeedsToFallback
//
// @doc:
// Fall back when the target list refers to a attribute which algebrizer
// at this point cannot resolve
//---------------------------------------------------------------------------
BOOL
CQueryMutators::FNeedsToFallback
(
Node *pnode,
void *pvCtx
)
{
if (NULL == pnode)
{
return false;
}
if (IsA(pnode, Const) || IsA(pnode, Aggref) || IsA(pnode, PercentileExpr) || IsA(pnode, GroupingFunc) || IsA(pnode, SubLink))
{
return false;
}
SContextTLWalker *pctx = (SContextTLWalker *) pvCtx;
TargetEntry *pteFound = gpdb::PteMember(pnode, pctx->m_plTE);
if (NULL != pteFound && CTranslatorUtils::FGroupingColumn( (Node *) pteFound->expr, pctx->m_groupClause, pctx->m_plTE))
{
return false;
}
if (IsA(pnode, SubLink))
{
return false;
}
if (IsA(pnode, Var))
{
Var *pvar = (Var *) pnode;
if (0 == pvar->varlevelsup)
{
// if we reach a Var that was not a grouping column then there is an equivalent column
// which the algebrizer at this point cannot resolve
// example: consider two table r(a,b) and s(c,d) and the following query
// SELECT a from r LEFT JOIN s on (r.a = s.c) group by r.a
// In the query object, generated by the parse, the output columns refer to the output of
// the left outer join while the grouping column refers to the base table column.
// While r.a and a are equivalent, the algebrizer at this point cannot detect this.
// Therefore, we fall back.
return true;
}
return false;
}
return gpdb::FWalkExpressionTree(pnode, (PfFallback) CQueryMutators::FNeedsToFallback, pvCtx);
}
//---------------------------------------------------------------------------
// @function:
// CQueryMutators::PqueryNormalizeGrpByPrL
//
// @doc:
// Flatten expressions in project list to contain only aggregates and
// grouping columns
// ORGINAL QUERY:
// SELECT * from r where r.a > (SELECT max(c) + min(d) FROM t where r.b = t.e)
// NEW QUERY:
// SELECT * from r where r.a > (SELECT x1+x2 as x3
// FROM (SELECT max(c) as x2, min(d) as x2
// FROM t where r.b = t.e) t2)
//---------------------------------------------------------------------------
Query *
CQueryMutators::PqueryNormalizeGrpByPrL
(
IMemoryPool *pmp,
CMDAccessor *pmda,
const Query *pquery
)
{
Query *pqueryCopy = (Query *) gpdb::PvCopyObject(const_cast<Query*>(pquery));
if (!FNeedsPrLNormalization(pqueryCopy))
{
return pqueryCopy;
}
Query *pqueryNew = PqueryConvertToDerivedTable(pqueryCopy, false /*fFixTargetList*/, true /*fFixHavingQual*/);
gpdb::GPDBFree(pqueryCopy);
GPOS_ASSERT(1 == gpdb::UlListLength(pqueryNew->rtable));
Query *pqueryDrdTbl = (Query *) ((RangeTblEntry *) gpdb::PvListNth(pqueryNew->rtable, 0))->subquery;
SContextGrpbyPlMutator ctxGbPrLMutator(pmp, pmda, pqueryDrdTbl, NULL);
List *plTEcopy = (List*) gpdb::PvCopyObject(pqueryDrdTbl->targetList);
ListCell *plc = NULL;
// first normalize grouping columns
ForEach (plc, plTEcopy)
{
TargetEntry *pte = (TargetEntry*) lfirst(plc);
GPOS_ASSERT(NULL != pte);
if (CTranslatorUtils::FGroupingColumn(pte, pqueryDrdTbl->groupClause))
{
pte->expr = (Expr*) PnodeFixGrpCol( (Node*) pte->expr, pte, &ctxGbPrLMutator);
}
}
plc = NULL;
// normalize remaining project elements
ForEach (plc, plTEcopy)
{
TargetEntry *pte = (TargetEntry*) lfirst(plc);
GPOS_ASSERT(NULL != pte);
BOOL fGroupingCol = CTranslatorUtils::FGroupingColumn(pte, pqueryDrdTbl->groupClause);
if (!fGroupingCol)
{
pte->expr = (Expr*) PnodeGrpbyPrLMutator( (Node*) pte->expr, &ctxGbPrLMutator);
GPOS_ASSERT
(
(!IsA(pte->expr, Aggref) && !IsA(pte->expr, PercentileExpr)) && !IsA(pte->expr, GroupingFunc) &&
"New target list entry should not contain any Aggrefs or PercentileExpr"
);
}
}
pqueryDrdTbl->targetList = ctxGbPrLMutator.m_plTENewGroupByQuery;
pqueryNew->targetList = plTEcopy;
ReassignSortClause(pqueryNew, pqueryDrdTbl);
return pqueryNew;
}
//---------------------------------------------------------------------------
// @function:
// CQueryMutators::PnodeIncrementLevelsupMutator
//
// @doc:
// Increment any the query levels up of any outer reference by one
//---------------------------------------------------------------------------
Node *
CQueryMutators::PnodeIncrementLevelsupMutator
(
Node *pnode,
void *pvCtx
)
{
if (NULL == pnode)
{
return NULL;
}
SContextIncLevelsupMutator *pctxIncLvlMutator = (SContextIncLevelsupMutator *) pvCtx;
if (IsA(pnode, Var))
{
Var *pvar = (Var *) gpdb::PvCopyObject(pnode);
// Consider the following use case:
// ORGINAL QUERY:
// SELECT * from r where r.a > (SELECT max(c) + min(d)
// FROM t where r.b = t.e)
// NEW QUERY:
// SELECT * from r where r.a > (SELECT x1+x2 as x3
// FROM (SELECT max(c) as x2, min(d) as x2
// FROM t where r.b = t.e) t2)
//
// In such a scenario, we need increment the levels up for the
// correlation variable r.b in the subquery by 1.
if (pvar->varlevelsup > pctxIncLvlMutator->m_ulCurrLevelsUp)
{
pvar->varlevelsup++;
return (Node *) pvar;
}
return (Node *) pvar;
}
if (IsA(pnode, CommonTableExpr))
{
CommonTableExpr *pcte = (CommonTableExpr *) gpdb::PvCopyObject(pnode);
GPOS_ASSERT(IsA(pcte->ctequery, Query));
Query *pqueryCte = (Query *) pcte->ctequery;
pctxIncLvlMutator->m_ulCurrLevelsUp++;
pcte->ctequery = PnodeIncrementLevelsupMutator((Node *) pqueryCte, pctxIncLvlMutator);
pctxIncLvlMutator->m_ulCurrLevelsUp--;
gpdb::GPDBFree(pqueryCte);
return (Node *) pcte;
}
if (IsA(pnode, SubLink))
{
SubLink *psublink = (SubLink *) gpdb::PvCopyObject(pnode);
GPOS_ASSERT(IsA(psublink->subselect, Query));
Query *pquerySublink = (Query *) psublink->subselect;
pctxIncLvlMutator->m_ulCurrLevelsUp++;
psublink->subselect = PnodeIncrementLevelsupMutator( (Node *) pquerySublink, pctxIncLvlMutator);
pctxIncLvlMutator->m_ulCurrLevelsUp--;
gpdb::GPDBFree(pquerySublink);
return (Node *) psublink;
}
if (IsA(pnode, TargetEntry) && 0 == pctxIncLvlMutator->m_ulCurrLevelsUp && !pctxIncLvlMutator->m_fFixTargetListTopLevel)
{
return (Node *) gpdb::PvCopyObject(pnode);
}
// recurse into query structure
if (IsA(pnode, Query))
{
Query *pquery = gpdb::PqueryMutateQueryTree
(
(Query *) pnode,
(Pfnode) CQueryMutators::PnodeIncrementLevelsupMutator,
pctxIncLvlMutator,
1 // flag -- do not mutate range table entries
);
// fix the outer reference in derived table entries
List *plRtable = pquery->rtable;
ListCell *plc = NULL;
ForEach (plc, plRtable)
{
RangeTblEntry *prte = (RangeTblEntry *) lfirst(plc);
if (RTE_SUBQUERY == prte->rtekind)
{
Query *pquerySubquery = prte->subquery;
// since we did not walk inside derived tables
pctxIncLvlMutator->m_ulCurrLevelsUp++;
prte->subquery = (Query *) PnodeIncrementLevelsupMutator( (Node *) pquerySubquery, pctxIncLvlMutator);
pctxIncLvlMutator->m_ulCurrLevelsUp--;
gpdb::GPDBFree(pquerySubquery);
}
}
return (Node *) pquery;
}
return gpdb::PnodeMutateExpressionTree(pnode, (Pfnode) CQueryMutators::PnodeIncrementLevelsupMutator, pvCtx);
}
//---------------------------------------------------------------------------
// @function:
// CQueryMutators::PnodeFixCTELevelsupMutator
//
// @doc:
// Increment any the query levels up of any CTE range table reference by one
//---------------------------------------------------------------------------
Node *
CQueryMutators::PnodeFixCTELevelsupMutator
(
Node *pnode,
void *pvCtx
)
{
if (NULL == pnode)
{
return NULL;
}
SContextIncLevelsupMutator *pctxinclvlmutator = (SContextIncLevelsupMutator *) pvCtx;
// recurse into query structure
if (IsA(pnode, Query))
{
Query *pquery = gpdb::PqueryMutateQueryTree
(
(Query *) pnode,
(Pfnode) CQueryMutators::PnodeFixCTELevelsupMutator,
pvCtx,
1 // flag -- do not mutate range table entries
);
List *plRtable = pquery->rtable;
ListCell *plc = NULL;
ForEach (plc, plRtable)
{
RangeTblEntry *prte = (RangeTblEntry *) lfirst(plc);
if (RTE_CTE == prte->rtekind && FNeedsLevelsUpCorrection(pctxinclvlmutator, prte->ctelevelsup))
{
// fix the levels up for CTE range table entry when needed
// the walker in GPDB does not walk range table entries of type CTE
prte->ctelevelsup++;
}
if (RTE_SUBQUERY == prte->rtekind)
{
Query *pquerySubquery = prte->subquery;
// since we did not walk inside derived tables
pctxinclvlmutator->m_ulCurrLevelsUp++;
prte->subquery = (Query *) PnodeFixCTELevelsupMutator( (Node *) pquerySubquery, pctxinclvlmutator);
pctxinclvlmutator->m_ulCurrLevelsUp--;
gpdb::GPDBFree(pquerySubquery);
}
}
return (Node *) pquery;
}
if (IsA(pnode, CommonTableExpr))
{
CommonTableExpr *pcte = (CommonTableExpr *) gpdb::PvCopyObject(pnode);
GPOS_ASSERT(IsA(pcte->ctequery, Query));
Query *pqueryCte = (Query *) pcte->ctequery;
pcte->ctequery = NULL;
pctxinclvlmutator->m_ulCurrLevelsUp++;
pcte->ctequery = PnodeFixCTELevelsupMutator((Node *) pqueryCte, pctxinclvlmutator);
pctxinclvlmutator->m_ulCurrLevelsUp--;
gpdb::GPDBFree(pqueryCte);
return (Node *) pcte;
}
// recurse into a query attached to sublink
if (IsA(pnode, SubLink))
{
SubLink *psublink = (SubLink *) gpdb::PvCopyObject(pnode);
GPOS_ASSERT(IsA(psublink->subselect, Query));
Query *pquerySublink = (Query *) psublink->subselect;
psublink->subselect = NULL;
pctxinclvlmutator->m_ulCurrLevelsUp++;
psublink->subselect = PnodeFixCTELevelsupMutator((Node *) pquerySublink, pctxinclvlmutator);
pctxinclvlmutator->m_ulCurrLevelsUp--;
gpdb::GPDBFree(pquerySublink);
return (Node *) psublink;
}
return gpdb::PnodeMutateExpressionTree(pnode, (Pfnode) CQueryMutators::PnodeFixCTELevelsupMutator, pctxinclvlmutator);
}
//---------------------------------------------------------------------------
// @function:
// CQueryMutators::FCorrectCTELevelsup
//
// @doc:
// Check if the cte levels up is the expected query level
//---------------------------------------------------------------------------
BOOL
CQueryMutators::FNeedsLevelsUpCorrection
(
SContextIncLevelsupMutator *pctxinclvlmutator,
Index idxCtelevelsup
)
{
// when converting the query to derived table, all references to cte defined at the current level
// or above needs to be incremented
return idxCtelevelsup >= pctxinclvlmutator->m_ulCurrLevelsUp;
}
//---------------------------------------------------------------------------
// @function:
// CQueryMutators::PnodeGrpbyPrLMutator
//
// @doc:
// Traverse the project list of a groupby operator and extract all aggregate
// functions in an arbitrarily complex project element
//---------------------------------------------------------------------------
Node *
CQueryMutators::PnodeGrpbyPrLMutator
(
Node *pnode,
void *pctx
)
{
if (NULL == pnode)
{
return NULL;
}
if (IsA(pnode, Const))
{
return (Node *) gpdb::PvCopyObject(pnode);
}
SContextGrpbyPlMutator *pctxGrpByMutator = (SContextGrpbyPlMutator *) pctx;
if (IsA(pnode, Var) && pctxGrpByMutator->m_fAggregateArg)
{
// if we are mutating an aggregate argument do nothing since the aggregate will be place in the derived table's target list
// fix any outer references in the grouping column expression or arguments of an aggregate
return (Node *) PvarOuterReferenceIncrLevelsUp((Var*) pnode);
}
// if we find an aggregate or precentile expression then insert into the new derived table
// and refer to it in the top-level query
if (IsA(pnode, Aggref))
{
Aggref *paggrefOld = (Aggref*) pnode;
Aggref *paggref = PaggrefFlatCopy(paggrefOld);
paggref->agglevelsup = paggrefOld->agglevelsup;
List *plArgsNew = NIL;
ListCell *plc = NULL;
BOOL fAggregate = pctxGrpByMutator->m_fAggregateArg;
pctxGrpByMutator->m_fAggregateArg = true;
ForEach (plc, paggrefOld->args)
{
Node *pnodeArg = (Node *) gpdb::PvCopyObject((Node*) lfirst(plc));
GPOS_ASSERT(NULL != pnodeArg);
// traverse each argument and fix levels up when needed
plArgsNew = gpdb::PlAppendElement
(
plArgsNew,
gpdb::PnodeMutateQueryOrExpressionTree
(
pnodeArg,
(Pfnode) CQueryMutators::PnodeGrpbyPrLMutator,
(void *) pctx,
0 // flags -- mutate into cte-lists
)
);
}
pctxGrpByMutator->m_fAggregateArg = fAggregate;
paggref->args = plArgsNew;
const ULONG ulAttno = gpdb::UlListLength(pctxGrpByMutator->m_plTENewGroupByQuery) + 1;
TargetEntry *pte = PteAggregateOrPercentileExpr(pctxGrpByMutator->m_pmp, pctxGrpByMutator->m_pmda, (Node *) paggref, ulAttno);
// Add a new target entry to the query
pctxGrpByMutator->m_plTENewGroupByQuery = gpdb::PlAppendElement(pctxGrpByMutator->m_plTENewGroupByQuery, pte);
Var *pvarNew = gpdb::PvarMakeVar
(
1, // varno
(AttrNumber) ulAttno,
gpdb::OidExprType(pnode),
gpdb::IExprTypeMod(pnode),
0 // query levelsup
);
return (Node*) pvarNew;
}
if (IsA(pnode, PercentileExpr) || IsA(pnode, GroupingFunc))
{
Node *pnodeCopy = (Node *) gpdb::PvCopyObject(pnode);
const ULONG ulAttno = gpdb::UlListLength(pctxGrpByMutator->m_plTENewGroupByQuery) + 1;
TargetEntry *pte = PteAggregateOrPercentileExpr(pctxGrpByMutator->m_pmp, pctxGrpByMutator->m_pmda, pnodeCopy, ulAttno);
// Add a new target entry to the query
pctxGrpByMutator->m_plTENewGroupByQuery = gpdb::PlAppendElement(pctxGrpByMutator->m_plTENewGroupByQuery, pte);
Var *pvarNew = gpdb::PvarMakeVar
(
1, // varno
(AttrNumber) ulAttno,
gpdb::OidExprType(pnode),
gpdb::IExprTypeMod(pnode),
0 // query levelsup
);
return (Node*) pvarNew;
}
if (!pctxGrpByMutator->m_fAggregateArg)
{
// if we find a target entry in the new derived table then return the appropriate var
// else investigate it to see if it needs to be added to the new derived table
TargetEntry *pteFound = gpdb::PteMember(pnode, pctxGrpByMutator->m_plTENewGroupByQuery);
if (NULL != pteFound)
{
pteFound->resjunk = false;
Var *pvarNew = gpdb::PvarMakeVar
(
1, // varno
pteFound->resno,
gpdb::OidExprType((Node*) pteFound->expr),
gpdb::IExprTypeMod( (Node*) pteFound->expr),
0 // query levelsup
);
return (Node*) pvarNew;
}
// if it is grouping column then we have already added it to the derived table
// so merely refer to it in the top-level query
TargetEntry *pteFoundNewDrvdTable = gpdb::PteMember(pnode, pctxGrpByMutator->m_plTENewGroupByQuery);
if (NULL != pteFoundNewDrvdTable)
{
return (Node *) gpdb::PvarMakeVar
(
1, // varno
(AttrNumber) pteFoundNewDrvdTable->resno,
gpdb::OidExprType( (Node*) pteFoundNewDrvdTable->expr),
gpdb::IExprTypeMod( (Node*) pteFoundNewDrvdTable->expr),
0 // query levelsup
);
}
}
// do not traverse into sub queries as they will be inserted into top-level query as is
if (IsA(pnode, SubLink))
{
return (Node *) gpdb::PvCopyObject(pnode);
}
return gpdb::PnodeMutateExpressionTree(pnode, (Pfnode) CQueryMutators::PnodeGrpbyPrLMutator, pctx);
}
//---------------------------------------------------------------------------
// @function:
// CQueryMutators::PnodeGrpColMutator
//
// @doc:
// Mutate the grouping columns, fix levels up when necessary
//
//---------------------------------------------------------------------------
Node *
CQueryMutators::PnodeGrpColMutator
(
Node *pnode,
void *pctx
)
{
if (NULL == pnode)
{
return NULL;
}
if (IsA(pnode, Const))
{
return (Node *) gpdb::PvCopyObject(pnode);
}
SContextGrpbyPlMutator *pctxGrpByMutator = (SContextGrpbyPlMutator *) pctx;
if (IsA(pnode, Var))
{
Var *pvarCopy = (Var *) gpdb::PvCopyObject(pnode);
if (pvarCopy->varlevelsup > pctxGrpByMutator->m_ulCurrLevelsUp)
{
pvarCopy->varlevelsup++;
}
return (Node *) pvarCopy;
}
if (IsA(pnode, Aggref))
{
// merely fix the arguments of an aggregate
Aggref *paggrefOld = (Aggref*) pnode;
Aggref *paggref = PaggrefFlatCopy(paggrefOld);
paggref->agglevelsup = paggrefOld->agglevelsup;
List *plArgsNew = NIL;
ListCell *plc = NULL;
BOOL fAggregate = pctxGrpByMutator->m_fAggregateArg;
pctxGrpByMutator->m_fAggregateArg = true;
ForEach (plc, paggrefOld->args)
{
Node *pnodeArg = (Node *) gpdb::PvCopyObject((Node*) lfirst(plc));
GPOS_ASSERT(NULL != pnodeArg);
// traverse each argument and fix levels up when needed
plArgsNew = gpdb::PlAppendElement
(
plArgsNew,
gpdb::PnodeMutateQueryOrExpressionTree
(
pnodeArg,
(Pfnode) CQueryMutators::PnodeGrpColMutator,
(void *) pctxGrpByMutator,
0 // flags -- mutate into cte-lists
)
);
}
pctxGrpByMutator->m_fAggregateArg = fAggregate;
paggref->args = plArgsNew;
return (Node*) paggref;
}
if (IsA(pnode, PercentileExpr) || IsA(pnode, GroupingFunc))
{
return (Node *) gpdb::PvCopyObject(pnode);
}
if (IsA(pnode, SubLink))
{
SubLink *psublinkOld = (SubLink *) pnode;
SubLink *psublinkNew = MakeNode(SubLink);
psublinkNew->subLinkType = psublinkOld->subLinkType;
psublinkNew->location = psublinkOld->location;
psublinkNew->operName = (List *) gpdb::PvCopyObject(psublinkOld->operName);
psublinkNew->testexpr = gpdb::PnodeMutateQueryOrExpressionTree
(
psublinkOld->testexpr,
(Pfnode) CQueryMutators::PnodeGrpColMutator,
(void *) pctxGrpByMutator,
0 // flags -- mutate into cte-lists
);
pctxGrpByMutator->m_ulCurrLevelsUp++;
GPOS_ASSERT(IsA(psublinkOld->subselect, Query));
psublinkNew->subselect = gpdb::PnodeMutateQueryOrExpressionTree
(
psublinkOld->subselect,
(Pfnode) CQueryMutators::PnodeGrpColMutator,
(void *) pctxGrpByMutator,
0 // flags -- mutate into cte-lists
);
pctxGrpByMutator->m_ulCurrLevelsUp--;
return (Node *) psublinkNew;
}
if (IsA(pnode, CommonTableExpr))
{
CommonTableExpr *pcte = (CommonTableExpr *) gpdb::PvCopyObject(pnode);
pctxGrpByMutator->m_ulCurrLevelsUp++;
GPOS_ASSERT(IsA(pcte->ctequery, Query));
pcte->ctequery = gpdb::PnodeMutateQueryOrExpressionTree
(
pcte->ctequery,
(Pfnode) CQueryMutators::PnodeGrpColMutator,
(void *) pctxGrpByMutator,
0 // flags --- mutate into cte-lists
);
pctxGrpByMutator->m_ulCurrLevelsUp--;
return (Node *) pcte;
}
// recurse into query structure
if (IsA(pnode, Query))
{
Query *pquery = gpdb::PqueryMutateQueryTree
(
(Query *) pnode,
(Pfnode) CQueryMutators::PnodeGrpColMutator,
pctxGrpByMutator,
1 // flag -- do not mutate range table entries
);
// fix the outer reference in derived table entries
List *plRtable = pquery->rtable;
ListCell *plc = NULL;
ForEach (plc, plRtable)
{
RangeTblEntry *prte = (RangeTblEntry *) lfirst(plc);
if (RTE_SUBQUERY == prte->rtekind)
{
Query *pquerySubquery = prte->subquery;
// since we did not walk inside derived tables
pctxGrpByMutator->m_ulCurrLevelsUp++;
prte->subquery = (Query *) PnodeGrpColMutator( (Node *) pquerySubquery, pctxGrpByMutator);
pctxGrpByMutator->m_ulCurrLevelsUp--;
gpdb::GPDBFree(pquerySubquery);
}
}
return (Node *) pquery;
}
return gpdb::PnodeMutateExpressionTree(pnode, (Pfnode) CQueryMutators::PnodeGrpColMutator, pctx);
}
//---------------------------------------------------------------------------
// @function:
// CQueryMutators::PnodeFixGrpCol
//
// @doc:
// Mutate the grouping columns, fix levels up when necessary
//---------------------------------------------------------------------------
Node *
CQueryMutators::PnodeFixGrpCol
(
Node *pnode,
TargetEntry *pteOriginal,
SContextGrpbyPlMutator *pctx
)
{
GPOS_ASSERT(NULL != pnode);
ULONG ulArity = gpdb::UlListLength(pctx->m_plTENewGroupByQuery) + 1;
// fix any outer references in the grouping column expression
Node *pnodeExpr = (Node *) PnodeGrpColMutator( pnode, pctx);
CHAR* szName = CQueryMutators::SzTEName(pteOriginal,pctx->m_pquery);
TargetEntry *pteNew = gpdb::PteMakeTargetEntry((Expr*) pnodeExpr, (AttrNumber) ulArity, szName, false /*resjunk */);
pteNew->ressortgroupref = pteOriginal->ressortgroupref;
pteNew->resjunk = false;
pctx->m_plTENewGroupByQuery = gpdb::PlAppendElement(pctx->m_plTENewGroupByQuery, pteNew);
Var *pvarNew = gpdb::PvarMakeVar
(
1, // varno
(AttrNumber) ulArity,
gpdb::OidExprType( (Node*) pteOriginal->expr),
gpdb::IExprTypeMod( (Node*) pteOriginal->expr),
0 // query levelsup
);
return (Node*) pvarNew;
}
//---------------------------------------------------------------------------
// @function:
// CQueryMutators::PteAggregateOrPercentileExpr
//
// @doc:
// Return a target entry for an aggregate or percentile expression
//---------------------------------------------------------------------------
TargetEntry *
CQueryMutators::PteAggregateOrPercentileExpr
(
IMemoryPool *pmp,
CMDAccessor *pmda,
Node *pnode,
ULONG ulAttno
)
{
GPOS_ASSERT(IsA(pnode, PercentileExpr) || IsA(pnode, Aggref) || IsA(pnode, GroupingFunc));
// get the function/aggregate name
CHAR *szName = NULL;
if (IsA(pnode, PercentileExpr))
{
PercentileExpr *ppercentile = (PercentileExpr*) pnode;
if (PERC_MEDIAN == ppercentile->perckind)
{
szName = CTranslatorUtils::SzFromWsz(GPOS_WSZ_LIT("Median"));
}
else if (PERC_CONT == ppercentile->perckind)
{
szName = CTranslatorUtils::SzFromWsz(GPOS_WSZ_LIT("Cont"));
}
else
{
GPOS_ASSERT(PERC_DISC == ppercentile->perckind);
szName = CTranslatorUtils::SzFromWsz(GPOS_WSZ_LIT("Disc"));
}
}
else if (IsA(pnode, GroupingFunc))
{
szName = CTranslatorUtils::SzFromWsz(GPOS_WSZ_LIT("grouping"));
}
else
{
Aggref *paggref = (Aggref*) pnode;
CMDIdGPDB *pmdidAgg = CTranslatorUtils::PmdidWithVersion(pmp, paggref->aggfnoid);
const IMDAggregate *pmdagg = pmda->Pmdagg(pmdidAgg);
pmdidAgg->Release();
const CWStringConst *pstr = pmdagg->Mdname().Pstr();
szName = CTranslatorUtils::SzFromWsz(pstr->Wsz());
}
GPOS_ASSERT(NULL != szName);
return gpdb::PteMakeTargetEntry((Expr*) pnode, (AttrNumber) ulAttno, szName, false);
}
//---------------------------------------------------------------------------
// @function:
// CQueryMutators::PnodeHavingQualMutator
//
// @doc:
// This mutator function checks to see if the current node is an AggRef
// or a correlated variable from the derived table.
// If it is an Aggref:
// Then replaces it with the appropriate attribute from the top-level query.
// If it is a correlated Var:
// Then replaces it with a attribute from the top-level query.
//---------------------------------------------------------------------------
Node *
CQueryMutators::PnodeHavingQualMutator
(
Node *pnode,
void *pctx
)
{
if (NULL == pnode)
{
return NULL;
}
if (IsA(pnode, Const))
{
return (Node *) gpdb::PvCopyObject(pnode);
}
SContextHavingQualMutator *pctxHavingQualMutator = (SContextHavingQualMutator *) pctx;
// check to see if the node is in the target list of the derived table.
// check if we have the corresponding pte entry in derived tables target list
if (0 == pctxHavingQualMutator->m_ulCurrLevelsUp)
{
if (IsA(pnode, Var) && pctxHavingQualMutator->m_fAggregateArg)
{
// fix outer references used in the aggregates
return (Node *) PvarOuterReferenceIncrLevelsUp((Var*) pnode);
}
// check if an entry already exists, if so no need for duplicate
Node *pnodeFound = PnodeFind(pnode, pctxHavingQualMutator);
if (NULL != pnodeFound)
{
return pnodeFound;
}
if (IsA(pnode, PercentileExpr) || IsA(pnode, GroupingFunc))
{
// create a new entry in the derived table and return its corresponding var
Node *pnodeCopy = (Node*) gpdb::PvCopyObject(pnode);
return (Node *) PvarInsertIntoDerivedTable(pnodeCopy, pctxHavingQualMutator);
}
}
if (IsA(pnode, Aggref))
{
Aggref *paggrefOld = (Aggref *) pnode;
if (paggrefOld->agglevelsup == pctxHavingQualMutator->m_ulCurrLevelsUp)
{
Aggref *paggrefNew = PaggrefFlatCopy(paggrefOld);
BOOL fAggregateOld = pctxHavingQualMutator->m_fAggregateArg;
ULONG ulAggregateLevelUp = pctxHavingQualMutator->m_ulAggregateLevelUp;
pctxHavingQualMutator->m_fAggregateArg = true;
pctxHavingQualMutator->m_ulAggregateLevelUp = paggrefOld->agglevelsup;
List *plargsNew = NIL;
ListCell *plc = NULL;
ForEach (plc, paggrefOld->args)
{
Node *pnodeArg = (Node*) lfirst(plc);
GPOS_ASSERT(NULL != pnodeArg);
// traverse each argument and fix levels up when needed
plargsNew = gpdb::PlAppendElement
(
plargsNew,
gpdb::PnodeMutateQueryOrExpressionTree
(
pnodeArg,
(Pfnode) CQueryMutators::PnodeHavingQualMutator,
(void *) pctxHavingQualMutator,
0 // mutate into cte-lists
)
);
}
paggrefNew->args = plargsNew;
pctxHavingQualMutator->m_fAggregateArg = fAggregateOld;
pctxHavingQualMutator->m_ulAggregateLevelUp = ulAggregateLevelUp;
// check if an entry already exists, if so no need for duplicate
Node *pnodeFound = PnodeFind((Node*) paggrefNew, pctxHavingQualMutator);
if (NULL != pnodeFound)
{
return pnodeFound;
}
// create a new entry in the derived table and return its corresponding var
return (Node *) PvarInsertIntoDerivedTable((Node *) paggrefNew, pctxHavingQualMutator);
}
}
if (IsA(pnode, Var))
{
Var *pvar = (Var *) gpdb::PvCopyObject(pnode);
if (pvar->varlevelsup == pctxHavingQualMutator->m_ulCurrLevelsUp)
{
// process outer references
if (pvar->varlevelsup == pctxHavingQualMutator->m_ulAggregateLevelUp)
{
// an argument of an outer aggregate
pvar->varlevelsup = 0;
return (Node *) pvar;
}
pvar->varlevelsup = 0;
TargetEntry *pteFound = gpdb::PteMember( (Node*) pvar, pctxHavingQualMutator->m_plTENewGroupByQuery);
if (NULL == pteFound)
{
// Consider two table r(a,b) and s(c,d) and the following query
// SELECT 1 from r LEFT JOIN s on (r.a = s.c) group by r.a having count(*) > a
// The having clause refers to the output of the left outer join while the
// grouping column refers to the base table column.
// While r.a and a are equivalent, the algebrizer at this point cannot detect this.
// Therefore, pteFound will be NULL and we fall back.
pctxHavingQualMutator->m_fFallbackToPlanner = true;
return NULL;
}
pvar->varlevelsup = pctxHavingQualMutator->m_ulCurrLevelsUp;
pvar->varno = 1;
pvar->varattno = pteFound->resno;
pteFound->resjunk = false;
return (Node*) pvar;
}
return (Node *) pvar;
}
if (IsA(pnode, CommonTableExpr))
{
CommonTableExpr *pcte = (CommonTableExpr *) gpdb::PvCopyObject(pnode);
pctxHavingQualMutator->m_ulCurrLevelsUp++;
GPOS_ASSERT(IsA(pcte->ctequery, Query));
pcte->ctequery = gpdb::PnodeMutateQueryOrExpressionTree
(
pcte->ctequery,
(Pfnode) CQueryMutators::PnodeHavingQualMutator,
(void *) pctxHavingQualMutator,
0 // flags --- mutate into cte-lists
);
pctxHavingQualMutator->m_ulCurrLevelsUp--;
return (Node *) pcte;
}
if (IsA(pnode, SubLink))
{
SubLink *psublinkOld = (SubLink *) pnode;
SubLink *psublinkNew = MakeNode(SubLink);
psublinkNew->subLinkType = psublinkOld->subLinkType;
psublinkNew->location = psublinkOld->location;
psublinkNew->operName = (List *) gpdb::PvCopyObject(psublinkOld->operName);
psublinkNew->testexpr = gpdb::PnodeMutateQueryOrExpressionTree
(
psublinkOld->testexpr,
(Pfnode) CQueryMutators::PnodeHavingQualMutator,
(void *) pctxHavingQualMutator,
0 // flags -- mutate into cte-lists
);
pctxHavingQualMutator->m_ulCurrLevelsUp++;
GPOS_ASSERT(IsA(psublinkOld->subselect, Query));
psublinkNew->subselect = gpdb::PnodeMutateQueryOrExpressionTree
(
psublinkOld->subselect,
(Pfnode) CQueryMutators::PnodeHavingQualMutator,
(void *) pctxHavingQualMutator,
0 // flags -- mutate into cte-lists
);
pctxHavingQualMutator->m_ulCurrLevelsUp--;
return (Node *) psublinkNew;
}
return gpdb::PnodeMutateExpressionTree(pnode, (Pfnode) CQueryMutators::PnodeHavingQualMutator, pctxHavingQualMutator);
}
//---------------------------------------------------------------------------
// @function:
// CQueryMutators::PvarInsertIntoDerivedTable
//
// @doc:
// Create a new entry in the derived table and return its corresponding var
//---------------------------------------------------------------------------
Var *
CQueryMutators::PvarInsertIntoDerivedTable
(
Node *pnode,
SContextHavingQualMutator *context
)
{
GPOS_ASSERT(NULL != pnode);
GPOS_ASSERT(NULL != context);
GPOS_ASSERT(IsA(pnode, PercentileExpr) || IsA(pnode, Aggref) || IsA(pnode, GroupingFunc));
const ULONG ulAttno = gpdb::UlListLength(context->m_plTENewGroupByQuery) + 1;
TargetEntry *pte = PteAggregateOrPercentileExpr(context->m_pmp, context->m_pmda, (Node *) pnode, ulAttno);
context->m_plTENewGroupByQuery = gpdb::PlAppendElement(context->m_plTENewGroupByQuery, pte);
Var *pvarNew = gpdb::PvarMakeVar
(
1, // varno
ulAttno,
gpdb::OidExprType((Node*) pnode),
gpdb::IExprTypeMod((Node*) pnode),
context->m_ulCurrLevelsUp
);
return pvarNew;
}
//---------------------------------------------------------------------------
// @function:
// CQueryMutators::PnodeFind
//
// @doc:
// Check if a matching entry already exists in the list of target
// entries, if yes return its corresponding var, otherwise return NULL
//---------------------------------------------------------------------------
Node *
CQueryMutators::PnodeFind
(
Node *pnode,
SContextHavingQualMutator *context
)
{
GPOS_ASSERT(NULL != pnode);
GPOS_ASSERT(NULL != context);
TargetEntry *pteFound = gpdb::PteMember(pnode, context->m_plTENewGroupByQuery);
if (NULL != pteFound)
{
gpdb::GPDBFree(pnode);
Var *pvarNew = gpdb::PvarMakeVar
(
1, // varno
pteFound->resno,
gpdb::OidExprType( (Node*) pteFound->expr),
gpdb::IExprTypeMod( (Node*) pteFound->expr),
context->m_ulCurrLevelsUp
);
pteFound->resjunk = false;
return (Node*) pvarNew;
}
return NULL;
}
//---------------------------------------------------------------------------
// @function:
// CQueryMutators::PaggrefFlatCopy
//
// @doc:
// Make a copy of the aggref (minus the arguments)
//---------------------------------------------------------------------------
Aggref *
CQueryMutators::PaggrefFlatCopy
(
Aggref *paggrefOld
)
{
Aggref *paggrefNew = MakeNode(Aggref);
paggrefNew->aggfnoid = paggrefOld->aggfnoid;
paggrefNew->aggdistinct = paggrefOld->aggdistinct;
paggrefNew->agglevelsup = 0;
paggrefNew->location = paggrefOld->location;
paggrefNew->aggtype = paggrefOld->aggtype;
paggrefNew->aggstage = paggrefOld->aggstage;
paggrefNew->aggstar = paggrefOld->aggstar;
return paggrefNew;
}
//---------------------------------------------------------------------------
// @function:
// CQueryMutators::PvarOuterReferenceIncrLevelsUp
//
// @doc:
// Increment the levels up of outer references
//---------------------------------------------------------------------------
Var *
CQueryMutators::PvarOuterReferenceIncrLevelsUp
(
Var *pvar
)
{
GPOS_ASSERT(NULL != pvar);
Var *pvarCopy = (Var *) gpdb::PvCopyObject(pvar);
if (0 != pvarCopy->varlevelsup)
{
pvarCopy->varlevelsup++;
}
return pvarCopy;
}
//---------------------------------------------------------------------------
// @function:
// CQueryMutators::PqueryNormalizeHaving
//
// @doc:
// Pull up having qual into a select and fix correlated references
// to the top-level query
//---------------------------------------------------------------------------
Query *
CQueryMutators::PqueryNormalizeHaving
(
IMemoryPool *pmp,
CMDAccessor *pmda,
const Query *pquery
)
{
Query *pqueryCopy = (Query *) gpdb::PvCopyObject(const_cast<Query*>(pquery));
if (NULL == pquery->havingQual)
{
return pqueryCopy;
}
Query *pqueryNew = PqueryConvertToDerivedTable(pqueryCopy, true /*fFixTargetList*/, false /*fFixHavingQual*/);
gpdb::GPDBFree(pqueryCopy);
RangeTblEntry *prte = ((RangeTblEntry *) gpdb::PvListNth(pqueryNew->rtable, 0));
Query *pqueryDrdTbl = (Query *) prte->subquery;
// Add all necessary target list entries of subquery
// into the target list of the RTE as well as the new top most query
ListCell *plc = NULL;
ULONG ulTECount = 1;
ForEach (plc, pqueryDrdTbl->targetList)
{
TargetEntry *pte = (TargetEntry*) lfirst(plc);
GPOS_ASSERT(NULL != pte);
// Add to the target lists:
// (1) All grouping / sorting columns even if they do not appear in the subquery output (resjunked)
// (2) All non-resjunked target list entries
if (CTranslatorUtils::FGroupingColumn(pte, pqueryDrdTbl->groupClause) ||
CTranslatorUtils::FSortingColumn(pte, pqueryDrdTbl->sortClause) || !pte->resjunk)
{
TargetEntry *pteNew = Pte(pte, ulTECount);
pqueryNew->targetList = gpdb::PlAppendElement(pqueryNew->targetList, pteNew);
// Ensure that such target entries is not suppressed in the target list of the RTE
// and has a name
pte->resname = SzTEName(pte, pqueryDrdTbl);
pte->resjunk = false;
pteNew->ressortgroupref = pte->ressortgroupref;
ulTECount++;
}
}
SContextHavingQualMutator ctxHavingQualMutator(pmp, pmda, ulTECount, pqueryDrdTbl->targetList);
// fix outer references in the qual
pqueryNew->jointree->quals = PnodeHavingQualMutator(pqueryDrdTbl->havingQual, &ctxHavingQualMutator);
if (ctxHavingQualMutator.m_fFallbackToPlanner)
{
// TODO: raghav, Oct 14 2013, remove temporary fix (revert exception to assert) to avoid crash during algebrization
GPOS_RAISE(gpdxl::ExmaDXL, gpdxl::ExmiQuery2DXLError, GPOS_WSZ_LIT("No attribute"));
}
pqueryDrdTbl->havingQual = NULL;
ReassignSortClause(pqueryNew, prte->subquery);
if (!prte->subquery->hasAggs && NIL == prte->subquery->groupClause)
{
// if the derived table has no grouping columns or aggregates then the
// subquery is equivalent to select XXXX FROM CONST-TABLE
// (where XXXX is the original subquery's target list)
Query *pqueryNewSubquery = MakeNode(Query);
pqueryNewSubquery->commandType = CMD_SELECT;
pqueryNewSubquery->targetList = NIL;
pqueryNewSubquery->hasAggs = false;
pqueryNewSubquery->hasWindFuncs = false;
pqueryNewSubquery->hasSubLinks = false;
ListCell *plc = NULL;
ForEach (plc, prte->subquery->targetList)
{
TargetEntry *pte = (TargetEntry*) lfirst(plc);
GPOS_ASSERT(NULL != pte);
GPOS_ASSERT(!pte->resjunk);
pqueryNewSubquery->targetList = gpdb::PlAppendElement
(
pqueryNewSubquery->targetList,
(TargetEntry *) gpdb::PvCopyObject(const_cast<TargetEntry*>(pte))
);
}
gpdb::GPDBFree(prte->subquery);
prte->subquery = pqueryNewSubquery;
prte->subquery->jointree = MakeNode(FromExpr);
prte->subquery->groupClause = NIL;
prte->subquery->sortClause = NIL;
prte->subquery->windowClause = NIL;
}
return pqueryNew;
}
//---------------------------------------------------------------------------
// @function:
// CQueryMutators::PqueryNormalize
//
// @doc:
// Normalize queries with having and group by clauses
//---------------------------------------------------------------------------
Query *
CQueryMutators::PqueryNormalize
(
IMemoryPool *pmp,
CMDAccessor *pmda,
const Query *pquery,
ULONG ulQueryLevel
)
{
// flatten join alias vars defined at the current level of the query
Query *pqueryResolveJoinVarReferences = gpdb::PqueryFlattenJoinAliasVar(const_cast<Query*>(pquery), ulQueryLevel);
// eliminate distinct clause
Query *pqueryEliminateDistinct = CQueryMutators::PqueryEliminateDistinctClause(pqueryResolveJoinVarReferences);
GPOS_ASSERT(NULL == pqueryEliminateDistinct->distinctClause);
gpdb::GPDBFree(pqueryResolveJoinVarReferences);
// fix window frame edge boundary
Query *pqueryFixedWindowFrameEdge = CQueryMutators::PqueryFixWindowFrameEdgeBoundary(pqueryEliminateDistinct);
gpdb::GPDBFree(pqueryEliminateDistinct);
// normalize window operator's project list
Query *pqueryWindowPlNormalized = CQueryMutators::PqueryNormalizeWindowPrL(pmp, pmda, pqueryFixedWindowFrameEdge);
gpdb::GPDBFree(pqueryFixedWindowFrameEdge);
// pull-up having quals into a select
Query *pqueryHavingNormalized = CQueryMutators::PqueryNormalizeHaving(pmp, pmda, pqueryWindowPlNormalized);
GPOS_ASSERT(NULL == pqueryHavingNormalized->havingQual);
gpdb::GPDBFree(pqueryWindowPlNormalized);
// normalize the group by project list
Query *pqueryNew = CQueryMutators::PqueryNormalizeGrpByPrL(pmp, pmda, pqueryHavingNormalized);
gpdb::GPDBFree(pqueryHavingNormalized);
return pqueryNew;
}
//---------------------------------------------------------------------------
// @function:
// CQueryMutators::Pte
//
// @doc:
// Given an Target list entry in the derived table, create a new
// TargetEntry to be added to the top level query. This function allocates
// memory
//---------------------------------------------------------------------------
TargetEntry *
CQueryMutators::Pte
(
TargetEntry *pteOld,
ULONG ulVarAttno
)
{
Var *pvarNew = gpdb::PvarMakeVar
(
1,
(AttrNumber) ulVarAttno,
gpdb::OidExprType( (Node*) pteOld->expr),
gpdb::IExprTypeMod( (Node*) pteOld->expr),
0 // query levelsup
);
TargetEntry *pteNew = gpdb::PteMakeTargetEntry((Expr*) pvarNew, (AttrNumber) ulVarAttno, pteOld->resname, pteOld->resjunk);
return pteNew;
}
//---------------------------------------------------------------------------
// @function:
// CQueryMutators::SzTEName
//
// @doc:
// Return the column name of the target list entry
//---------------------------------------------------------------------------
CHAR *
CQueryMutators::SzTEName
(
TargetEntry *pte,
Query *pquery
)
{
if (NULL != pte->resname)
{
return pte->resname;
}
// Since a resjunked target list entry will not have a column name create a dummy column name
CWStringConst strUnnamedCol(GPOS_WSZ_LIT("?column?"));
return CTranslatorUtils::SzFromWsz(strUnnamedCol.Wsz());
}
//---------------------------------------------------------------------------
// @function:
// CQueryMutators::PqueryConvertToDerivedTable
//
// @doc:
// Converts query into a derived table and return the new top-level query
//---------------------------------------------------------------------------
Query *
CQueryMutators::PqueryConvertToDerivedTable
(
const Query *pquery,
BOOL fFixTargetList,
BOOL fFixHavingQual
)
{
Query *pqueryCopy = (Query *) gpdb::PvCopyObject(const_cast<Query*>(pquery));
// fix any outer references
SContextIncLevelsupMutator ctxIncLvlMutator(0, fFixTargetList);
Node *pnodeHavingQual = NULL;
if (!fFixHavingQual)
{
pnodeHavingQual = pqueryCopy->havingQual;
pqueryCopy->havingQual = NULL;
}
// fix outer references
Query *pqueryDrvd = (Query *) PnodeIncrementLevelsupMutator((Node*) pqueryCopy, &ctxIncLvlMutator);
gpdb::GPDBFree(pqueryCopy);
// fix the CTE levels up -- while the old query is converted into a derived table, its cte list
// is re-assigned to the new top-level query. The references to the ctes listed in the old query
// as well as those listed before the current query level are accordingly adjusted in the new
// derived table.
List *plCteOriginal = pqueryDrvd->cteList;
pqueryDrvd->cteList = NIL;
SContextIncLevelsupMutator ctxinclvlmutator(0 /*starting level */, fFixTargetList);
Query *pqueryDrvdNew = (Query *) PnodeFixCTELevelsupMutator( (Node *) pqueryDrvd, &ctxinclvlmutator);
gpdb::GPDBFree(pqueryDrvd);
pqueryDrvd = pqueryDrvdNew;
// create a range table entry for the query node
RangeTblEntry *prte = MakeNode(RangeTblEntry);
prte->rtekind = RTE_SUBQUERY;
prte->subquery = pqueryDrvd;
prte->inFromCl = true;
prte->subquery->cteList = NIL;
if (NULL != pnodeHavingQual)
{
pqueryDrvd->havingQual = pnodeHavingQual;
}
// create a new range table reference for the new RTE
RangeTblRef *prtref = MakeNode(RangeTblRef);
prtref->rtindex = 1;
// intoClause, if not null, must be set on the top query, not on the derived table
IntoClause *origIntoClause = pqueryDrvd->intoClause;
pqueryDrvd->intoClause = NULL;
struct GpPolicy* origIntoPolicy = pqueryDrvd->intoPolicy;
pqueryDrvd->intoPolicy = NULL;
// create a new top-level query with the new RTE in its from clause
Query *pqueryNew = MakeNode(Query);
pqueryNew->cteList = plCteOriginal;
pqueryNew->hasAggs = false;
pqueryNew->rtable = gpdb::PlAppendElement(pqueryNew->rtable, prte);
pqueryNew->intoClause = origIntoClause;
pqueryNew->intoPolicy = origIntoPolicy;
FromExpr *pfromexpr = MakeNode(FromExpr);
pfromexpr->quals = NULL;
pfromexpr->fromlist = gpdb::PlAppendElement(pfromexpr->fromlist, prtref);
pqueryNew->jointree = pfromexpr;
pqueryNew->commandType = CMD_SELECT;
GPOS_ASSERT(1 == gpdb::UlListLength(pqueryNew->rtable));
return pqueryNew;
}
//---------------------------------------------------------------------------
// @function:
// CQueryMutators::PqueryEliminateDistinctClause
//
// @doc:
// Eliminate distinct columns by translating it into a grouping columns
//---------------------------------------------------------------------------
Query *
CQueryMutators::PqueryEliminateDistinctClause
(
const Query *pquery
)
{
if (0 == gpdb::UlListLength(pquery->distinctClause))
{
return (Query*) gpdb::PvCopyObject(const_cast<Query*>(pquery));
}
// create a derived table out of the previous query
Query *pqueryNew = PqueryConvertToDerivedTable(pquery, true /*fFixTargetList*/, true /*fFixHavingQual*/);
GPOS_ASSERT(1 == gpdb::UlListLength(pqueryNew->rtable));
Query *pqueryDrdTbl = (Query *) ((RangeTblEntry *) gpdb::PvListNth(pqueryNew->rtable, 0))->subquery;
ReassignSortClause(pqueryNew, pqueryDrdTbl);
pqueryNew->targetList = NIL;
List *plTE = pqueryDrdTbl->targetList;
ListCell *plc = NULL;
// build the project list of the new top-level query
ForEach (plc, plTE)
{
ULONG ulResNo = gpdb::UlListLength(pqueryNew->targetList) + 1;
TargetEntry *pte = (TargetEntry*) lfirst(plc);
GPOS_ASSERT(NULL != pte);
if (!pte->resjunk)
{
// create a new target entry that points to the corresponding entry in the derived table
Var *pvarNew = gpdb::PvarMakeVar
(
1,
pte->resno,
gpdb::OidExprType((Node*) pte->expr),
gpdb::IExprTypeMod((Node*) pte->expr),
0 // query levels up
);
TargetEntry *pteNew= gpdb::PteMakeTargetEntry((Expr*) pvarNew, (AttrNumber) ulResNo, pte->resname, false);
pteNew->ressortgroupref = pte->ressortgroupref;
pqueryNew->targetList = gpdb::PlAppendElement(pqueryNew->targetList, pteNew);
}
if (0 < pte->ressortgroupref &&
!CTranslatorUtils::FGroupingColumn(pte, pqueryDrdTbl->groupClause) &&
!CTranslatorUtils::FWindowSpec(pte, pqueryDrdTbl->windowClause))
{
// initialize the ressortgroupref of target entries not used in the grouping clause
pte->ressortgroupref = 0;
}
}
if (gpdb::UlListLength(pqueryNew->targetList) != gpdb::UlListLength(pquery->distinctClause))
{
GPOS_RAISE(gpdxl::ExmaDXL, gpdxl::ExmiQuery2DXLUnsupportedFeature, GPOS_WSZ_LIT("DISTINCT operation on a subset of target list columns"));
}
ListCell *plcDistinctCl = NULL;
ForEach (plcDistinctCl, pquery->distinctClause)
{
SortClause *psortcl = (SortClause*) lfirst(plcDistinctCl);
GPOS_ASSERT(NULL != psortcl);
GroupClause *pgrpcl = MakeNode(GroupClause);
pgrpcl->tleSortGroupRef = psortcl->tleSortGroupRef;
pgrpcl->sortop = psortcl->sortop;
pqueryNew->groupClause = gpdb::PlAppendElement(pqueryNew->groupClause, pgrpcl);
}
pqueryNew->distinctClause = NIL;
pqueryDrdTbl->distinctClause = NIL;
return pqueryNew;
}
//---------------------------------------------------------------------------
// @function:
// CQueryMutators::FNeedsWindowPrLNormalization
//
// @doc:
// Check whether the window operator's project list only contains
// window functions and columns used in the window specification
//---------------------------------------------------------------------------
BOOL
CQueryMutators::FNeedsWindowPrLNormalization
(
const Query *pquery
)
{
if (!pquery->hasWindFuncs)
{
return false;
}
const ULONG ulArity = gpdb::UlListLength(pquery->targetList);
for (ULONG ul = 0; ul < ulArity; ul++)
{
TargetEntry *pte = (TargetEntry*) gpdb::PvListNth(pquery->targetList, ul);
if (!CTranslatorUtils::FWindowSpec( (Node *) pte->expr, pquery->windowClause, pquery->targetList) && !IsA(pte->expr, WindowRef) && !IsA(pte->expr, Var))
{
// computed columns in the target list that is not
// used in the order by or partition by of the window specification(s)
return true;
}
}
return false;
}
//---------------------------------------------------------------------------
// @function:
// CQueryMutators::PqueryFixWindowFrameEdgeBoundary
//
// @doc:
// Fix window frame edge boundary when its value is defined by a subquery
//---------------------------------------------------------------------------
Query *
CQueryMutators::PqueryFixWindowFrameEdgeBoundary
(
const Query *pquery
)
{
Query *pqueryNew = (Query *) gpdb::PvCopyObject(const_cast<Query*>(pquery));
List *plWindowClause = pqueryNew->windowClause;
ListCell *plcWindowCl = NULL;
ForEach (plcWindowCl, plWindowClause)
{
WindowSpec *pwindowspec = (WindowSpec*) lfirst(plcWindowCl);
if (NULL != pwindowspec->frame)
{
WindowFrame *pwindowframe = pwindowspec->frame;
if (NULL != pwindowframe->lead->val && IsA(pwindowframe->lead->val, SubLink))
{
if (WINDOW_BOUND_PRECEDING == pwindowframe->lead->kind)
{
pwindowframe->lead->kind = WINDOW_DELAYED_BOUND_PRECEDING;
}
else
{
GPOS_ASSERT(WINDOW_BOUND_FOLLOWING == pwindowframe->lead->kind);
pwindowframe->lead->kind = WINDOW_DELAYED_BOUND_FOLLOWING;
}
}
if (NULL != pwindowframe->trail->val && IsA(pwindowframe->trail->val, SubLink))
{
if (WINDOW_BOUND_PRECEDING == pwindowframe->trail->kind)
{
pwindowframe->trail->kind = WINDOW_DELAYED_BOUND_PRECEDING;
}
else
{
GPOS_ASSERT(WINDOW_BOUND_FOLLOWING == pwindowframe->trail->kind);
pwindowframe->trail->kind = WINDOW_DELAYED_BOUND_FOLLOWING;
}
}
}
}
return pqueryNew;
}
//---------------------------------------------------------------------------
// @function:
// CQueryMutators::PqueryNormalizeWindowPrL
//
// @doc:
// Flatten expressions in project list to contain only window functions and
// columns used in the window specification
//
// ORGINAL QUERY:
// SELECT row_number() over() + rank() over(partition by a+b order by a-b) from foo
//
// NEW QUERY:
// SELECT rn+rk from (SELECT row_number() over() as rn rank() over(partition by a+b order by a-b) as rk FROM foo) foo_new
//---------------------------------------------------------------------------
Query *
CQueryMutators::PqueryNormalizeWindowPrL
(
IMemoryPool *pmp,
CMDAccessor *pmda,
const Query *pquery
)
{
Query *pqueryCopy = (Query *) gpdb::PvCopyObject(const_cast<Query*>(pquery));
if (!FNeedsWindowPrLNormalization(pquery))
{
return pqueryCopy;
}
// we do not fix target list of the derived table since we will be mutating it below
// to ensure that it does not have operations with window function
Query *pqueryNew = PqueryConvertToDerivedTable(pqueryCopy, false /*fFixTargetList*/, true /*fFixHavingQual*/);
gpdb::GPDBFree(pqueryCopy);
GPOS_ASSERT(1 == gpdb::UlListLength(pqueryNew->rtable));
Query *pqueryDrdTbl = (Query *) ((RangeTblEntry *) gpdb::PvListNth(pqueryNew->rtable, 0))->subquery;
SContextGrpbyPlMutator ctxWindowPrLMutator(pmp, pmda, pqueryDrdTbl, NULL);
ListCell *plc = NULL;
List *plTE = pqueryDrdTbl->targetList;
ForEach (plc, plTE)
{
TargetEntry *pte = (TargetEntry*) lfirst(plc);
const ULONG ulResNoNew = gpdb::UlListLength(pqueryNew->targetList) + 1;
if (CTranslatorUtils::FWindowSpec(pte, pquery->windowClause))
{
// insert the target list entry used in the window specification as is
TargetEntry *pteNew = (TargetEntry *) gpdb::PvCopyObject(pte);
pteNew->resno = gpdb::UlListLength(ctxWindowPrLMutator.m_plTENewGroupByQuery) + 1;
ctxWindowPrLMutator.m_plTENewGroupByQuery = gpdb::PlAppendElement(ctxWindowPrLMutator.m_plTENewGroupByQuery, pteNew);
if (!pte->resjunk || CTranslatorUtils::FSortingColumn(pte, pquery->sortClause))
{
// if the target list entry used in the window specification is present
// in the query output then add it to the target list of the new top level query
Var *pvarNew = gpdb::PvarMakeVar
(
1,
pteNew->resno,
gpdb::OidExprType((Node*) pte->expr),
gpdb::IExprTypeMod((Node*) pte->expr),
0 // query levels up
);
TargetEntry *pteNewCopy = gpdb::PteMakeTargetEntry((Expr*) pvarNew, ulResNoNew, pte->resname, pte->resjunk);
// Copy the resortgroupref and resjunk information for the top-level target list entry
// Set target list entry of the derived table to be non-resjunked
pteNewCopy->resjunk = pteNew->resjunk;
pteNewCopy->ressortgroupref = pteNew->ressortgroupref;
pteNew->resjunk = false;
pqueryNew->targetList = gpdb::PlAppendElement(pqueryNew->targetList, pteNewCopy);
}
}
else
{
// normalize target list entry
ctxWindowPrLMutator.m_ulRessortgroupref = pte->ressortgroupref;
Expr *pexprNew = (Expr*) PnodeWindowPrLMutator( (Node*) pte->expr, &ctxWindowPrLMutator);
TargetEntry *pteNew = gpdb::PteMakeTargetEntry(pexprNew, ulResNoNew, pte->resname, pte->resjunk);
pteNew->ressortgroupref = pte->ressortgroupref;
pqueryNew->targetList = gpdb::PlAppendElement(pqueryNew->targetList, pteNew);
}
}
pqueryDrdTbl->targetList = ctxWindowPrLMutator.m_plTENewGroupByQuery;
GPOS_ASSERT(gpdb::UlListLength(pqueryNew->targetList) <= gpdb::UlListLength(pquery->targetList));
pqueryNew->hasWindFuncs = false;
ReassignSortClause(pqueryNew, pqueryDrdTbl);
return pqueryNew;
}
//---------------------------------------------------------------------------
// @function:
// CQueryMutators::PnodeWindowPrLMutator
//
// @doc:
// Traverse the project list of extract all window functions in an
// arbitrarily complex project element
//---------------------------------------------------------------------------
Node *
CQueryMutators::PnodeWindowPrLMutator
(
Node *pnode,
void *pctx
)
{
if (NULL == pnode)
{
return NULL;
}
// do not traverse into sub queries as they will be inserted are inserted into
// top-level query as is
if (IsA(pnode, SubLink))
{
return (Node *) gpdb::PvCopyObject(pnode);
}
SContextGrpbyPlMutator *pctxWindowPrLMutator = (SContextGrpbyPlMutator *) pctx;
const ULONG ulResNo = gpdb::UlListLength(pctxWindowPrLMutator->m_plTENewGroupByQuery) + 1;
if (IsA(pnode, WindowRef))
{
// insert window operator into the derived table
// and refer to it in the top-level query's target list
WindowRef *pwindowref = (WindowRef*) gpdb::PvCopyObject(pnode);
// get the function name and add it to the target list
CMDIdGPDB *pmdidFunc = CTranslatorUtils::PmdidWithVersion(pctxWindowPrLMutator->m_pmp, pwindowref->winfnoid);
const CWStringConst *pstr = CMDAccessorUtils::PstrWindowFuncName(pctxWindowPrLMutator->m_pmda, pmdidFunc);
pmdidFunc->Release();
TargetEntry *pte = gpdb::PteMakeTargetEntry
(
(Expr*) gpdb::PvCopyObject(pnode),
(AttrNumber) ulResNo,
CTranslatorUtils::SzFromWsz(pstr->Wsz()),
false /* resjunk */
);
pctxWindowPrLMutator->m_plTENewGroupByQuery = gpdb::PlAppendElement(pctxWindowPrLMutator->m_plTENewGroupByQuery, pte);
// return a variable referring to the new derived table's corresponding target list entry
Var *pvarNew = gpdb::PvarMakeVar
(
1,
(AttrNumber) ulResNo,
gpdb::OidExprType(pnode),
gpdb::IExprTypeMod(pnode),
0 // query levelsup
);
return (Node*) pvarNew;
}
if (IsA(pnode, Var))
{
Var *pvarNew = NULL;
TargetEntry *pteFound = gpdb::PteMember(pnode, pctxWindowPrLMutator->m_plTENewGroupByQuery);
if (NULL == pteFound)
{
// insert target entry into the target list of the derived table
CWStringConst strUnnamedCol(GPOS_WSZ_LIT("?column?"));
TargetEntry *pte = gpdb::PteMakeTargetEntry
(
(Expr*) gpdb::PvCopyObject(pnode),
(AttrNumber) ulResNo,
CTranslatorUtils::SzFromWsz(strUnnamedCol.Wsz()),
false /* resjunk */
);
pctxWindowPrLMutator->m_plTENewGroupByQuery = gpdb::PlAppendElement(pctxWindowPrLMutator->m_plTENewGroupByQuery, pte);
pvarNew = gpdb::PvarMakeVar
(
1,
(AttrNumber) ulResNo,
gpdb::OidExprType(pnode),
gpdb::IExprTypeMod(pnode),
0 // query levelsup
);
}
else
{
pteFound->resjunk = false; // ensure that the derived target list is not resjunked
pvarNew = gpdb::PvarMakeVar
(
1,
pteFound->resno,
gpdb::OidExprType(pnode),
gpdb::IExprTypeMod(pnode),
0 // query levelsup
);
}
return (Node*) pvarNew;
}
return gpdb::PnodeMutateExpressionTree(pnode, (Pfnode) CQueryMutators::PnodeWindowPrLMutator, pctx);
}
//---------------------------------------------------------------------------
// @function:
// CQueryMutators::ReassignSortClause
//
// @doc:
// Reassign the sorting clause from the derived table to the new top-level query
//---------------------------------------------------------------------------
void
CQueryMutators::ReassignSortClause
(
Query *pqueryNew,
Query *pqueryDrdTbl
)
{
pqueryNew->sortClause = pqueryDrdTbl->sortClause;
pqueryNew->limitOffset = pqueryDrdTbl->limitOffset;
pqueryNew->limitCount = pqueryDrdTbl->limitCount;
pqueryDrdTbl->sortClause = NULL;
pqueryDrdTbl->limitOffset = NULL;
pqueryDrdTbl->limitCount = NULL;
}
// EOF