Header And Logo

PostgreSQL
| The world's most advanced open source database.

Data Structures | Typedefs | Enumerations | Functions | Variables

parse_cte.c File Reference

#include "postgres.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_type.h"
#include "nodes/nodeFuncs.h"
#include "parser/analyze.h"
#include "parser/parse_cte.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
Include dependency graph for parse_cte.c:

Go to the source code of this file.

Data Structures

struct  CteItem
struct  CteState

Typedefs

typedef struct CteItem CteItem
typedef struct CteState CteState

Enumerations

enum  RecursionContext {
  RECURSION_OK, RECURSION_NONRECURSIVETERM, RECURSION_SUBLINK, RECURSION_OUTERJOIN,
  RECURSION_INTERSECT, RECURSION_EXCEPT
}

Functions

static void analyzeCTE (ParseState *pstate, CommonTableExpr *cte)
static void makeDependencyGraph (CteState *cstate)
static bool makeDependencyGraphWalker (Node *node, CteState *cstate)
static void TopologicalSort (ParseState *pstate, CteItem *items, int numitems)
static void checkWellFormedRecursion (CteState *cstate)
static bool checkWellFormedRecursionWalker (Node *node, CteState *cstate)
static void checkWellFormedSelectStmt (SelectStmt *stmt, CteState *cstate)
ListtransformWithClause (ParseState *pstate, WithClause *withClause)
void analyzeCTETargetList (ParseState *pstate, CommonTableExpr *cte, List *tlist)

Variables

static const char *const recursion_errormsgs []

Typedef Documentation

typedef struct CteItem CteItem
typedef struct CteState CteState

Enumeration Type Documentation

Enumerator:
RECURSION_OK 
RECURSION_NONRECURSIVETERM 
RECURSION_SUBLINK 
RECURSION_OUTERJOIN 
RECURSION_INTERSECT 
RECURSION_EXCEPT 

Definition at line 27 of file parse_cte.c.

{
    RECURSION_OK,
    RECURSION_NONRECURSIVETERM, /* inside the left-hand term */
    RECURSION_SUBLINK,          /* inside a sublink */
    RECURSION_OUTERJOIN,        /* inside nullable side of an outer join */
    RECURSION_INTERSECT,        /* underneath INTERSECT (ALL) */
    RECURSION_EXCEPT            /* underneath EXCEPT (ALL) */
} RecursionContext;


Function Documentation

static void analyzeCTE ( ParseState pstate,
CommonTableExpr cte 
) [static]

Definition at line 237 of file parse_cte.c.

References analyzeCTETargetList(), Assert, Query::canSetTag, CMD_SELECT, Query::commandType, CommonTableExpr::ctecolcollations, CommonTableExpr::ctecoltypes, CommonTableExpr::ctecoltypmods, CommonTableExpr::ctename, CommonTableExpr::ctequery, CommonTableExpr::cterecursive, elog, ereport, errcode(), errhint(), errmsg(), ERROR, TargetEntry::expr, exprCollation(), exprLocation(), exprType(), exprTypmod(), format_type_with_typemod(), get_collation_name(), GetCTETargetList, IsA, lfirst, lfirst_int, lfirst_oid, list_head(), lnext, CommonTableExpr::location, NULL, ParseState::parentParseState, parse_sub_analyze(), parser_errposition(), TargetEntry::resjunk, TargetEntry::resno, and Query::utilityStmt.

Referenced by transformWithClause().

{
    Query      *query;

    /* Analysis not done already */
    Assert(!IsA(cte->ctequery, Query));

    query = parse_sub_analyze(cte->ctequery, pstate, cte, false);
    cte->ctequery = (Node *) query;

    /*
     * Check that we got something reasonable.  These first two cases should
     * be prevented by the grammar.
     */
    if (!IsA(query, Query))
        elog(ERROR, "unexpected non-Query statement in WITH");
    if (query->utilityStmt != NULL)
        elog(ERROR, "unexpected utility statement in WITH");

    /*
     * We disallow data-modifying WITH except at the top level of a query,
     * because it's not clear when such a modification should be executed.
     */
    if (query->commandType != CMD_SELECT &&
        pstate->parentParseState != NULL)
        ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                 errmsg("WITH clause containing a data-modifying statement must be at the top level"),
                 parser_errposition(pstate, cte->location)));

    /*
     * CTE queries are always marked not canSetTag.  (Currently this only
     * matters for data-modifying statements, for which the flag will be
     * propagated to the ModifyTable plan node.)
     */
    query->canSetTag = false;

    if (!cte->cterecursive)
    {
        /* Compute the output column names/types if not done yet */
        analyzeCTETargetList(pstate, cte, GetCTETargetList(cte));
    }
    else
    {
        /*
         * Verify that the previously determined output column types and
         * collations match what the query really produced.  We have to check
         * this because the recursive term could have overridden the
         * non-recursive term, and we don't have any easy way to fix that.
         */
        ListCell   *lctlist,
                   *lctyp,
                   *lctypmod,
                   *lccoll;
        int         varattno;

        lctyp = list_head(cte->ctecoltypes);
        lctypmod = list_head(cte->ctecoltypmods);
        lccoll = list_head(cte->ctecolcollations);
        varattno = 0;
        foreach(lctlist, GetCTETargetList(cte))
        {
            TargetEntry *te = (TargetEntry *) lfirst(lctlist);
            Node       *texpr;

            if (te->resjunk)
                continue;
            varattno++;
            Assert(varattno == te->resno);
            if (lctyp == NULL || lctypmod == NULL || lccoll == NULL)    /* shouldn't happen */
                elog(ERROR, "wrong number of output columns in WITH");
            texpr = (Node *) te->expr;
            if (exprType(texpr) != lfirst_oid(lctyp) ||
                exprTypmod(texpr) != lfirst_int(lctypmod))
                ereport(ERROR,
                        (errcode(ERRCODE_DATATYPE_MISMATCH),
                         errmsg("recursive query \"%s\" column %d has type %s in non-recursive term but type %s overall",
                                cte->ctename, varattno,
                                format_type_with_typemod(lfirst_oid(lctyp),
                                                       lfirst_int(lctypmod)),
                                format_type_with_typemod(exprType(texpr),
                                                         exprTypmod(texpr))),
                         errhint("Cast the output of the non-recursive term to the correct type."),
                         parser_errposition(pstate, exprLocation(texpr))));
            if (exprCollation(texpr) != lfirst_oid(lccoll))
                ereport(ERROR,
                        (errcode(ERRCODE_COLLATION_MISMATCH),
                         errmsg("recursive query \"%s\" column %d has collation \"%s\" in non-recursive term but collation \"%s\" overall",
                                cte->ctename, varattno,
                                get_collation_name(lfirst_oid(lccoll)),
                                get_collation_name(exprCollation(texpr))),
                         errhint("Use the COLLATE clause to set the collation of the non-recursive term."),
                         parser_errposition(pstate, exprLocation(texpr))));
            lctyp = lnext(lctyp);
            lctypmod = lnext(lctypmod);
            lccoll = lnext(lccoll);
        }
        if (lctyp != NULL || lctypmod != NULL || lccoll != NULL)        /* shouldn't happen */
            elog(ERROR, "wrong number of output columns in WITH");
    }
}

void analyzeCTETargetList ( ParseState pstate,
CommonTableExpr cte,
List tlist 
)

Definition at line 352 of file parse_cte.c.

References CommonTableExpr::aliascolnames, Assert, copyObject(), CommonTableExpr::ctecolcollations, CommonTableExpr::ctecolnames, CommonTableExpr::ctecoltypes, CommonTableExpr::ctecoltypmods, CommonTableExpr::ctename, CommonTableExpr::cterecursive, ereport, errcode(), errmsg(), ERROR, TargetEntry::expr, exprCollation(), exprType(), exprTypmod(), lappend(), lappend_int(), lappend_oid(), lfirst, list_length(), CommonTableExpr::location, makeString(), NIL, OidIsValid, parser_errposition(), pstrdup(), TargetEntry::resjunk, TargetEntry::resname, TargetEntry::resno, and UNKNOWNOID.

Referenced by analyzeCTE(), and determineRecursiveColTypes().

{
    int         numaliases;
    int         varattno;
    ListCell   *tlistitem;

    /* Not done already ... */
    Assert(cte->ctecolnames == NIL);

    /*
     * We need to determine column names, types, and collations.  The alias
     * column names override anything coming from the query itself.  (Note:
     * the SQL spec says that the alias list must be empty or exactly as long
     * as the output column set; but we allow it to be shorter for consistency
     * with Alias handling.)
     */
    cte->ctecolnames = copyObject(cte->aliascolnames);
    cte->ctecoltypes = cte->ctecoltypmods = cte->ctecolcollations = NIL;
    numaliases = list_length(cte->aliascolnames);
    varattno = 0;
    foreach(tlistitem, tlist)
    {
        TargetEntry *te = (TargetEntry *) lfirst(tlistitem);
        Oid         coltype;
        int32       coltypmod;
        Oid         colcoll;

        if (te->resjunk)
            continue;
        varattno++;
        Assert(varattno == te->resno);
        if (varattno > numaliases)
        {
            char       *attrname;

            attrname = pstrdup(te->resname);
            cte->ctecolnames = lappend(cte->ctecolnames, makeString(attrname));
        }
        coltype = exprType((Node *) te->expr);
        coltypmod = exprTypmod((Node *) te->expr);
        colcoll = exprCollation((Node *) te->expr);

        /*
         * If the CTE is recursive, force the exposed column type of any
         * "unknown" column to "text".  This corresponds to the fact that
         * SELECT 'foo' UNION SELECT 'bar' will ultimately produce text. We
         * might see "unknown" as a result of an untyped literal in the
         * non-recursive term's select list, and if we don't convert to text
         * then we'll have a mismatch against the UNION result.
         *
         * The column might contain 'foo' COLLATE "bar", so don't override
         * collation if it's already set.
         */
        if (cte->cterecursive && coltype == UNKNOWNOID)
        {
            coltype = TEXTOID;
            coltypmod = -1;     /* should be -1 already, but be sure */
            if (!OidIsValid(colcoll))
                colcoll = DEFAULT_COLLATION_OID;
        }
        cte->ctecoltypes = lappend_oid(cte->ctecoltypes, coltype);
        cte->ctecoltypmods = lappend_int(cte->ctecoltypmods, coltypmod);
        cte->ctecolcollations = lappend_oid(cte->ctecolcollations, colcoll);
    }
    if (varattno < numaliases)
        ereport(ERROR,
                (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
                 errmsg("WITH query \"%s\" has %d columns available but %d columns specified",
                        cte->ctename, varattno, numaliases),
                 parser_errposition(pstate, cte->location)));
}

static void checkWellFormedRecursion ( CteState cstate  )  [static]

Definition at line 632 of file parse_cte.c.

References Assert, checkWellFormedRecursionWalker(), CteState::context, CteItem::cte, CommonTableExpr::ctename, CommonTableExpr::ctequery, CommonTableExpr::cterecursive, WithClause::ctes, CteState::curitem, elog, ereport, errcode(), errmsg(), ERROR, exprLocation(), i, CteState::innerwiths, IsA, CteState::items, SelectStmt::larg, SelectStmt::limitCount, SelectStmt::limitOffset, CommonTableExpr::location, SelectStmt::lockingClause, NIL, CteState::numitems, SelectStmt::op, parser_errposition(), CteState::pstate, SelectStmt::rarg, CteState::selfrefcount, SETOP_UNION, SelectStmt::sortClause, and SelectStmt::withClause.

Referenced by transformWithClause().

{
    int         i;

    for (i = 0; i < cstate->numitems; i++)
    {
        CommonTableExpr *cte = cstate->items[i].cte;
        SelectStmt *stmt = (SelectStmt *) cte->ctequery;

        Assert(!IsA(stmt, Query));      /* not analyzed yet */

        /* Ignore items that weren't found to be recursive */
        if (!cte->cterecursive)
            continue;

        /* Must be a SELECT statement */
        if (!IsA(stmt, SelectStmt))
            ereport(ERROR,
                    (errcode(ERRCODE_INVALID_RECURSION),
                     errmsg("recursive query \"%s\" must not contain data-modifying statements",
                            cte->ctename),
                     parser_errposition(cstate->pstate, cte->location)));

        /* Must have top-level UNION */
        if (stmt->op != SETOP_UNION)
            ereport(ERROR,
                    (errcode(ERRCODE_INVALID_RECURSION),
                     errmsg("recursive query \"%s\" does not have the form non-recursive-term UNION [ALL] recursive-term",
                            cte->ctename),
                     parser_errposition(cstate->pstate, cte->location)));

        /* The left-hand operand mustn't contain self-reference at all */
        cstate->curitem = i;
        cstate->innerwiths = NIL;
        cstate->selfrefcount = 0;
        cstate->context = RECURSION_NONRECURSIVETERM;
        checkWellFormedRecursionWalker((Node *) stmt->larg, cstate);
        Assert(cstate->innerwiths == NIL);

        /* Right-hand operand should contain one reference in a valid place */
        cstate->curitem = i;
        cstate->innerwiths = NIL;
        cstate->selfrefcount = 0;
        cstate->context = RECURSION_OK;
        checkWellFormedRecursionWalker((Node *) stmt->rarg, cstate);
        Assert(cstate->innerwiths == NIL);
        if (cstate->selfrefcount != 1)  /* shouldn't happen */
            elog(ERROR, "missing recursive reference");

        /* WITH mustn't contain self-reference, either */
        if (stmt->withClause)
        {
            cstate->curitem = i;
            cstate->innerwiths = NIL;
            cstate->selfrefcount = 0;
            cstate->context = RECURSION_SUBLINK;
            checkWellFormedRecursionWalker((Node *) stmt->withClause->ctes,
                                           cstate);
            Assert(cstate->innerwiths == NIL);
        }

        /*
         * Disallow ORDER BY and similar decoration atop the UNION. These
         * don't make sense because it's impossible to figure out what they
         * mean when we have only part of the recursive query's results. (If
         * we did allow them, we'd have to check for recursive references
         * inside these subtrees.)
         */
        if (stmt->sortClause)
            ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                  errmsg("ORDER BY in a recursive query is not implemented"),
                     parser_errposition(cstate->pstate,
                                  exprLocation((Node *) stmt->sortClause))));
        if (stmt->limitOffset)
            ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                     errmsg("OFFSET in a recursive query is not implemented"),
                     parser_errposition(cstate->pstate,
                                        exprLocation(stmt->limitOffset))));
        if (stmt->limitCount)
            ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                     errmsg("LIMIT in a recursive query is not implemented"),
                     parser_errposition(cstate->pstate,
                                        exprLocation(stmt->limitCount))));
        if (stmt->lockingClause)
            ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                     errmsg("FOR UPDATE/SHARE in a recursive query is not implemented"),
                     parser_errposition(cstate->pstate,
                               exprLocation((Node *) stmt->lockingClause))));
    }
}

static bool checkWellFormedRecursionWalker ( Node node,
CteState cstate 
) [static]

Definition at line 731 of file parse_cte.c.

References checkWellFormedSelectStmt(), CteState::context, CteItem::cte, CommonTableExpr::ctename, CommonTableExpr::ctequery, WithClause::ctes, CteState::curitem, elog, ereport, errcode(), errmsg(), ERROR, CteState::innerwiths, IsA, CteState::items, JOIN_FULL, JOIN_INNER, JOIN_LEFT, JOIN_RIGHT, JoinExpr::jointype, lappend(), JoinExpr::larg, lcons(), lfirst, list_delete_first(), list_head(), RangeVar::location, NIL, NULL, parser_errposition(), CteState::pstate, JoinExpr::quals, JoinExpr::rarg, raw_expression_tree_walker(), recursion_errormsgs, RECURSION_OK, WithClause::recursive, RangeVar::relname, RangeVar::schemaname, CteState::selfrefcount, SubLink::subselect, SubLink::testexpr, and SelectStmt::withClause.

Referenced by checkWellFormedRecursion(), and checkWellFormedSelectStmt().

{
    RecursionContext save_context = cstate->context;

    if (node == NULL)
        return false;
    if (IsA(node, RangeVar))
    {
        RangeVar   *rv = (RangeVar *) node;

        /* If unqualified name, might be a CTE reference */
        if (!rv->schemaname)
        {
            ListCell   *lc;
            CommonTableExpr *mycte;

            /* ... but first see if it's captured by an inner WITH */
            foreach(lc, cstate->innerwiths)
            {
                List       *withlist = (List *) lfirst(lc);
                ListCell   *lc2;

                foreach(lc2, withlist)
                {
                    CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc2);

                    if (strcmp(rv->relname, cte->ctename) == 0)
                        return false;   /* yes, so bail out */
                }
            }

            /* No, could be a reference to the query level we are working on */
            mycte = cstate->items[cstate->curitem].cte;
            if (strcmp(rv->relname, mycte->ctename) == 0)
            {
                /* Found a recursive reference to the active query */
                if (cstate->context != RECURSION_OK)
                    ereport(ERROR,
                            (errcode(ERRCODE_INVALID_RECURSION),
                             errmsg(recursion_errormsgs[cstate->context],
                                    mycte->ctename),
                             parser_errposition(cstate->pstate,
                                                rv->location)));
                /* Count references */
                if (++(cstate->selfrefcount) > 1)
                    ereport(ERROR,
                            (errcode(ERRCODE_INVALID_RECURSION),
                             errmsg("recursive reference to query \"%s\" must not appear more than once",
                                    mycte->ctename),
                             parser_errposition(cstate->pstate,
                                                rv->location)));
            }
        }
        return false;
    }
    if (IsA(node, SelectStmt))
    {
        SelectStmt *stmt = (SelectStmt *) node;
        ListCell   *lc;

        if (stmt->withClause)
        {
            if (stmt->withClause->recursive)
            {
                /*
                 * In the RECURSIVE case, all query names of the WITH are
                 * visible to all WITH items as well as the main query. So
                 * push them all on, process, pop them all off.
                 */
                cstate->innerwiths = lcons(stmt->withClause->ctes,
                                           cstate->innerwiths);
                foreach(lc, stmt->withClause->ctes)
                {
                    CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);

                    (void) checkWellFormedRecursionWalker(cte->ctequery, cstate);
                }
                checkWellFormedSelectStmt(stmt, cstate);
                cstate->innerwiths = list_delete_first(cstate->innerwiths);
            }
            else
            {
                /*
                 * In the non-RECURSIVE case, query names are visible to the
                 * WITH items after them and to the main query.
                 */
                ListCell   *cell1;

                cstate->innerwiths = lcons(NIL, cstate->innerwiths);
                cell1 = list_head(cstate->innerwiths);
                foreach(lc, stmt->withClause->ctes)
                {
                    CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);

                    (void) checkWellFormedRecursionWalker(cte->ctequery, cstate);
                    lfirst(cell1) = lappend((List *) lfirst(cell1), cte);
                }
                checkWellFormedSelectStmt(stmt, cstate);
                cstate->innerwiths = list_delete_first(cstate->innerwiths);
            }
        }
        else
            checkWellFormedSelectStmt(stmt, cstate);
        /* We're done examining the SelectStmt */
        return false;
    }
    if (IsA(node, WithClause))
    {
        /*
         * Prevent raw_expression_tree_walker from recursing directly into a
         * WITH clause.  We need that to happen only under the control of the
         * code above.
         */
        return false;
    }
    if (IsA(node, JoinExpr))
    {
        JoinExpr   *j = (JoinExpr *) node;

        switch (j->jointype)
        {
            case JOIN_INNER:
                checkWellFormedRecursionWalker(j->larg, cstate);
                checkWellFormedRecursionWalker(j->rarg, cstate);
                checkWellFormedRecursionWalker(j->quals, cstate);
                break;
            case JOIN_LEFT:
                checkWellFormedRecursionWalker(j->larg, cstate);
                if (save_context == RECURSION_OK)
                    cstate->context = RECURSION_OUTERJOIN;
                checkWellFormedRecursionWalker(j->rarg, cstate);
                cstate->context = save_context;
                checkWellFormedRecursionWalker(j->quals, cstate);
                break;
            case JOIN_FULL:
                if (save_context == RECURSION_OK)
                    cstate->context = RECURSION_OUTERJOIN;
                checkWellFormedRecursionWalker(j->larg, cstate);
                checkWellFormedRecursionWalker(j->rarg, cstate);
                cstate->context = save_context;
                checkWellFormedRecursionWalker(j->quals, cstate);
                break;
            case JOIN_RIGHT:
                if (save_context == RECURSION_OK)
                    cstate->context = RECURSION_OUTERJOIN;
                checkWellFormedRecursionWalker(j->larg, cstate);
                cstate->context = save_context;
                checkWellFormedRecursionWalker(j->rarg, cstate);
                checkWellFormedRecursionWalker(j->quals, cstate);
                break;
            default:
                elog(ERROR, "unrecognized join type: %d",
                     (int) j->jointype);
        }
        return false;
    }
    if (IsA(node, SubLink))
    {
        SubLink    *sl = (SubLink *) node;

        /*
         * we intentionally override outer context, since subquery is
         * independent
         */
        cstate->context = RECURSION_SUBLINK;
        checkWellFormedRecursionWalker(sl->subselect, cstate);
        cstate->context = save_context;
        checkWellFormedRecursionWalker(sl->testexpr, cstate);
        return false;
    }
    return raw_expression_tree_walker(node,
                                      checkWellFormedRecursionWalker,
                                      (void *) cstate);
}

static void checkWellFormedSelectStmt ( SelectStmt stmt,
CteState cstate 
) [static]

Definition at line 911 of file parse_cte.c.

References checkWellFormedRecursionWalker(), CteState::context, elog, ERROR, SelectStmt::op, raw_expression_tree_walker(), RECURSION_OK, SETOP_EXCEPT, SETOP_INTERSECT, SETOP_NONE, and SETOP_UNION.

Referenced by checkWellFormedRecursionWalker().

{
    RecursionContext save_context = cstate->context;

    if (save_context != RECURSION_OK)
    {
        /* just recurse without changing state */
        raw_expression_tree_walker((Node *) stmt,
                                   checkWellFormedRecursionWalker,
                                   (void *) cstate);
    }
    else
    {
        switch (stmt->op)
        {
            case SETOP_NONE:
            case SETOP_UNION:
                raw_expression_tree_walker((Node *) stmt,
                                           checkWellFormedRecursionWalker,
                                           (void *) cstate);
                break;
            case SETOP_INTERSECT:
                if (stmt->all)
                    cstate->context = RECURSION_INTERSECT;
                checkWellFormedRecursionWalker((Node *) stmt->larg,
                                               cstate);
                checkWellFormedRecursionWalker((Node *) stmt->rarg,
                                               cstate);
                cstate->context = save_context;
                checkWellFormedRecursionWalker((Node *) stmt->sortClause,
                                               cstate);
                checkWellFormedRecursionWalker((Node *) stmt->limitOffset,
                                               cstate);
                checkWellFormedRecursionWalker((Node *) stmt->limitCount,
                                               cstate);
                checkWellFormedRecursionWalker((Node *) stmt->lockingClause,
                                               cstate);
                /* stmt->withClause is intentionally ignored here */
                break;
            case SETOP_EXCEPT:
                if (stmt->all)
                    cstate->context = RECURSION_EXCEPT;
                checkWellFormedRecursionWalker((Node *) stmt->larg,
                                               cstate);
                cstate->context = RECURSION_EXCEPT;
                checkWellFormedRecursionWalker((Node *) stmt->rarg,
                                               cstate);
                cstate->context = save_context;
                checkWellFormedRecursionWalker((Node *) stmt->sortClause,
                                               cstate);
                checkWellFormedRecursionWalker((Node *) stmt->limitOffset,
                                               cstate);
                checkWellFormedRecursionWalker((Node *) stmt->limitCount,
                                               cstate);
                checkWellFormedRecursionWalker((Node *) stmt->lockingClause,
                                               cstate);
                /* stmt->withClause is intentionally ignored here */
                break;
            default:
                elog(ERROR, "unrecognized set op: %d",
                     (int) stmt->op);
        }
    }
}

static void makeDependencyGraph ( CteState cstate  )  [static]

Definition at line 430 of file parse_cte.c.

References Assert, CteItem::cte, CommonTableExpr::ctequery, CteState::curitem, i, CteState::innerwiths, CteState::items, makeDependencyGraphWalker(), NIL, CteState::numitems, CteState::pstate, and TopologicalSort().

Referenced by transformWithClause().

{
    int         i;

    for (i = 0; i < cstate->numitems; i++)
    {
        CommonTableExpr *cte = cstate->items[i].cte;

        cstate->curitem = i;
        cstate->innerwiths = NIL;
        makeDependencyGraphWalker((Node *) cte->ctequery, cstate);
        Assert(cstate->innerwiths == NIL);
    }

    TopologicalSort(cstate->pstate, cstate->items, cstate->numitems);
}

static bool makeDependencyGraphWalker ( Node node,
CteState cstate 
) [static]

Definition at line 452 of file parse_cte.c.

References bms_add_member(), CteItem::cte, CommonTableExpr::ctename, CommonTableExpr::ctequery, CommonTableExpr::cterecursive, WithClause::ctes, CteState::curitem, CteItem::depends_on, i, CteItem::id, CteState::innerwiths, IsA, CteState::items, lappend(), lcons(), lfirst, list_delete_first(), list_head(), NIL, NULL, CteState::numitems, raw_expression_tree_walker(), WithClause::recursive, RangeVar::relname, RangeVar::schemaname, and SelectStmt::withClause.

Referenced by makeDependencyGraph().

{
    if (node == NULL)
        return false;
    if (IsA(node, RangeVar))
    {
        RangeVar   *rv = (RangeVar *) node;

        /* If unqualified name, might be a CTE reference */
        if (!rv->schemaname)
        {
            ListCell   *lc;
            int         i;

            /* ... but first see if it's captured by an inner WITH */
            foreach(lc, cstate->innerwiths)
            {
                List       *withlist = (List *) lfirst(lc);
                ListCell   *lc2;

                foreach(lc2, withlist)
                {
                    CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc2);

                    if (strcmp(rv->relname, cte->ctename) == 0)
                        return false;   /* yes, so bail out */
                }
            }

            /* No, could be a reference to the query level we are working on */
            for (i = 0; i < cstate->numitems; i++)
            {
                CommonTableExpr *cte = cstate->items[i].cte;

                if (strcmp(rv->relname, cte->ctename) == 0)
                {
                    int         myindex = cstate->curitem;

                    if (i != myindex)
                    {
                        /* Add cross-item dependency */
                        cstate->items[myindex].depends_on =
                            bms_add_member(cstate->items[myindex].depends_on,
                                           cstate->items[i].id);
                    }
                    else
                    {
                        /* Found out this one is self-referential */
                        cte->cterecursive = true;
                    }
                    break;
                }
            }
        }
        return false;
    }
    if (IsA(node, SelectStmt))
    {
        SelectStmt *stmt = (SelectStmt *) node;
        ListCell   *lc;

        if (stmt->withClause)
        {
            if (stmt->withClause->recursive)
            {
                /*
                 * In the RECURSIVE case, all query names of the WITH are
                 * visible to all WITH items as well as the main query. So
                 * push them all on, process, pop them all off.
                 */
                cstate->innerwiths = lcons(stmt->withClause->ctes,
                                           cstate->innerwiths);
                foreach(lc, stmt->withClause->ctes)
                {
                    CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);

                    (void) makeDependencyGraphWalker(cte->ctequery, cstate);
                }
                (void) raw_expression_tree_walker(node,
                                                  makeDependencyGraphWalker,
                                                  (void *) cstate);
                cstate->innerwiths = list_delete_first(cstate->innerwiths);
            }
            else
            {
                /*
                 * In the non-RECURSIVE case, query names are visible to the
                 * WITH items after them and to the main query.
                 */
                ListCell   *cell1;

                cstate->innerwiths = lcons(NIL, cstate->innerwiths);
                cell1 = list_head(cstate->innerwiths);
                foreach(lc, stmt->withClause->ctes)
                {
                    CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);

                    (void) makeDependencyGraphWalker(cte->ctequery, cstate);
                    lfirst(cell1) = lappend((List *) lfirst(cell1), cte);
                }
                (void) raw_expression_tree_walker(node,
                                                  makeDependencyGraphWalker,
                                                  (void *) cstate);
                cstate->innerwiths = list_delete_first(cstate->innerwiths);
            }
            /* We're done examining the SelectStmt */
            return false;
        }
        /* if no WITH clause, just fall through for normal processing */
    }
    if (IsA(node, WithClause))
    {
        /*
         * Prevent raw_expression_tree_walker from recursing directly into a
         * WITH clause.  We need that to happen only under the control of the
         * code above.
         */
        return false;
    }
    return raw_expression_tree_walker(node,
                                      makeDependencyGraphWalker,
                                      (void *) cstate);
}

static void TopologicalSort ( ParseState pstate,
CteItem items,
int  numitems 
) [static]

Definition at line 580 of file parse_cte.c.

References bms_del_member(), bms_is_empty(), CteItem::depends_on, ereport, errcode(), errmsg(), ERROR, i, and parser_errposition().

Referenced by makeDependencyGraph().

{
    int         i,
                j;

    /* for each position in sequence ... */
    for (i = 0; i < numitems; i++)
    {
        /* ... scan the remaining items to find one that has no dependencies */
        for (j = i; j < numitems; j++)
        {
            if (bms_is_empty(items[j].depends_on))
                break;
        }

        /* if we didn't find one, the dependency graph has a cycle */
        if (j >= numitems)
            ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
            errmsg("mutual recursion between WITH items is not implemented"),
                     parser_errposition(pstate, items[i].cte->location)));

        /*
         * Found one.  Move it to front and remove it from every other item's
         * dependencies.
         */
        if (i != j)
        {
            CteItem     tmp;

            tmp = items[i];
            items[i] = items[j];
            items[j] = tmp;
        }

        /*
         * Items up through i are known to have no dependencies left, so we
         * can skip them in this loop.
         */
        for (j = i + 1; j < numitems; j++)
        {
            items[j].depends_on = bms_del_member(items[j].depends_on,
                                                 items[i].id);
        }
    }
}

List* transformWithClause ( ParseState pstate,
WithClause withClause 
)

Definition at line 105 of file parse_cte.c.

References analyzeCTE(), Assert, checkWellFormedRecursion(), CteItem::cte, CommonTableExpr::ctename, CommonTableExpr::ctequery, CommonTableExpr::cterecursive, CommonTableExpr::cterefcount, WithClause::ctes, ereport, errcode(), errmsg(), ERROR, for_each_cell, i, CteItem::id, IsA, CteState::items, lappend(), lfirst, list_copy(), list_delete_first(), list_length(), lnext, CommonTableExpr::location, makeDependencyGraph(), NIL, CteState::numitems, ParseState::p_ctenamespace, ParseState::p_future_ctes, ParseState::p_hasModifyingCTE, palloc0(), parser_errposition(), CteState::pstate, and WithClause::recursive.

Referenced by transformDeleteStmt(), transformInsertStmt(), transformSelectStmt(), transformSetOperationStmt(), transformUpdateStmt(), and transformValuesClause().

{
    ListCell   *lc;

    /* Only one WITH clause per query level */
    Assert(pstate->p_ctenamespace == NIL);
    Assert(pstate->p_future_ctes == NIL);

    /*
     * For either type of WITH, there must not be duplicate CTE names in the
     * list.  Check this right away so we needn't worry later.
     *
     * Also, tentatively mark each CTE as non-recursive, and initialize its
     * reference count to zero, and set pstate->p_hasModifyingCTE if needed.
     */
    foreach(lc, withClause->ctes)
    {
        CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
        ListCell   *rest;

        for_each_cell(rest, lnext(lc))
        {
            CommonTableExpr *cte2 = (CommonTableExpr *) lfirst(rest);

            if (strcmp(cte->ctename, cte2->ctename) == 0)
                ereport(ERROR,
                        (errcode(ERRCODE_DUPLICATE_ALIAS),
                    errmsg("WITH query name \"%s\" specified more than once",
                           cte2->ctename),
                         parser_errposition(pstate, cte2->location)));
        }

        cte->cterecursive = false;
        cte->cterefcount = 0;

        if (!IsA(cte->ctequery, SelectStmt))
        {
            /* must be a data-modifying statement */
            Assert(IsA(cte->ctequery, InsertStmt) ||
                   IsA(cte->ctequery, UpdateStmt) ||
                   IsA(cte->ctequery, DeleteStmt));

            pstate->p_hasModifyingCTE = true;
        }
    }

    if (withClause->recursive)
    {
        /*
         * For WITH RECURSIVE, we rearrange the list elements if needed to
         * eliminate forward references.  First, build a work array and set up
         * the data structure needed by the tree walkers.
         */
        CteState    cstate;
        int         i;

        cstate.pstate = pstate;
        cstate.numitems = list_length(withClause->ctes);
        cstate.items = (CteItem *) palloc0(cstate.numitems * sizeof(CteItem));
        i = 0;
        foreach(lc, withClause->ctes)
        {
            cstate.items[i].cte = (CommonTableExpr *) lfirst(lc);
            cstate.items[i].id = i;
            i++;
        }

        /*
         * Find all the dependencies and sort the CteItems into a safe
         * processing order.  Also, mark CTEs that contain self-references.
         */
        makeDependencyGraph(&cstate);

        /*
         * Check that recursive queries are well-formed.
         */
        checkWellFormedRecursion(&cstate);

        /*
         * Set up the ctenamespace for parse analysis.  Per spec, all the WITH
         * items are visible to all others, so stuff them all in before parse
         * analysis.  We build the list in safe processing order so that the
         * planner can process the queries in sequence.
         */
        for (i = 0; i < cstate.numitems; i++)
        {
            CommonTableExpr *cte = cstate.items[i].cte;

            pstate->p_ctenamespace = lappend(pstate->p_ctenamespace, cte);
        }

        /*
         * Do parse analysis in the order determined by the topological sort.
         */
        for (i = 0; i < cstate.numitems; i++)
        {
            CommonTableExpr *cte = cstate.items[i].cte;

            analyzeCTE(pstate, cte);
        }
    }
    else
    {
        /*
         * For non-recursive WITH, just analyze each CTE in sequence and then
         * add it to the ctenamespace.  This corresponds to the spec's
         * definition of the scope of each WITH name.  However, to allow error
         * reports to be aware of the possibility of an erroneous reference,
         * we maintain a list in p_future_ctes of the not-yet-visible CTEs.
         */
        pstate->p_future_ctes = list_copy(withClause->ctes);

        foreach(lc, withClause->ctes)
        {
            CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);

            analyzeCTE(pstate, cte);
            pstate->p_ctenamespace = lappend(pstate->p_ctenamespace, cte);
            pstate->p_future_ctes = list_delete_first(pstate->p_future_ctes);
        }
    }

    return pstate->p_ctenamespace;
}


Variable Documentation

const char* const recursion_errormsgs[] [static]
Initial value:
 {
    
    NULL,
    
    gettext_noop("recursive reference to query \"%s\" must not appear within its non-recursive term"),
    
    gettext_noop("recursive reference to query \"%s\" must not appear within a subquery"),
    
    gettext_noop("recursive reference to query \"%s\" must not appear within an outer join"),
    
    gettext_noop("recursive reference to query \"%s\" must not appear within INTERSECT"),
    
    gettext_noop("recursive reference to query \"%s\" must not appear within EXCEPT")
}

Definition at line 38 of file parse_cte.c.

Referenced by checkWellFormedRecursionWalker().