| /* |
| * parse_cte.c |
| * Handle WITH clause in parser. |
| * |
| * 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. |
| * |
| */ |
| #include "postgres.h" |
| |
| #include "parser/parse_node.h" |
| #include "parser/parse_cte.h" |
| #include "parser/parse_expr.h" |
| #include "parser/parse_relation.h" |
| #include "parser/analyze.h" |
| #include "nodes/parsenodes.h" |
| #include "nodes/nodeFuncs.h" |
| |
| static void analyzeCTE(ParseState *pstate, CommonTableExpr *cte); |
| static void analyzeCTETargetList(ParseState *pstate, CommonTableExpr *cte, List *tlist); |
| |
| /* |
| * transformWithClause - |
| * Transform the list of WITH clause "common table expressions" into |
| * Query nodes. |
| * |
| * The result is the list of transformed CTEs to be put into the output |
| * Query. (This is in fact the same as the ending value of p_ctenamespace, |
| * but it seems cleaner to not expose that in the function's API.) |
| */ |
| List * |
| transformWithClause(ParseState *pstate, WithClause *withClause) |
| { |
| if (withClause == NULL) |
| return NULL; |
| |
| if (withClause->recursive) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_GP_FEATURE_NOT_SUPPORTED), |
| errmsg("RECURSIVE option in WITH clause is not supported"))); |
| } |
| |
| /* Only one WITH clause per query level */ |
| Assert(pstate->p_ctenamespace == NIL); |
| Assert(pstate->p_future_ctes == NIL); |
| |
| /* |
| * Check if CTE list in the WITH clause contains duplicate query names. |
| * If so, error out. |
| * |
| * Also, initialize other variables in CommonTableExpr. |
| */ |
| ListCell *lc; |
| foreach (lc, withClause->ctes) |
| { |
| CommonTableExpr *cte = (CommonTableExpr *)lfirst(lc); |
| |
| ListCell *lc2; |
| for_each_cell (lc2, lnext(lc)) |
| { |
| CommonTableExpr *cte2 = (CommonTableExpr *)lfirst(lc2); |
| Assert(cte != NULL && cte2 != NULL && |
| cte->ctename != NULL && cte2->ctename != NULL); |
| |
| if (strcmp(cte->ctename, cte2->ctename) == 0) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_ALIAS), |
| errmsg("query name \"%s\" in WITH clause must not be specified more than once", |
| cte2->ctename), |
| parser_errposition(pstate, cte2->location))); |
| } |
| |
| } |
| |
| cte->cterecursive = false; |
| cte->cterefcount = 0; |
| } |
| |
| /* |
| * 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; |
| } |
| |
| /* |
| * GetCTEForRTE |
| * Find CommonTableExpr stored in pstate that is referenced by rte. |
| * |
| * rtelevelsup is the number of query levels above the given pstate that the |
| * RTE came from. Callers that don't have this information readily available |
| * may pass -1 instead. |
| * |
| * Report error if not such CTE found. |
| */ |
| CommonTableExpr * |
| GetCTEForRTE(ParseState *pstate, RangeTblEntry *rte, int rtelevelsup) |
| { |
| Assert(pstate != NULL && rte != NULL); |
| Assert(rte->rtekind == RTE_CTE); |
| |
| /* Determine RTE's levelsup if caller didn't know it */ |
| if (rtelevelsup < 0) |
| (void) RTERangeTablePosn(pstate, rte, &rtelevelsup); |
| |
| Index levelsup = rte->ctelevelsup + rtelevelsup; |
| while (levelsup > 0) |
| { |
| pstate = pstate->parentParseState; |
| Assert(pstate != NULL); |
| levelsup--; |
| } |
| |
| if (pstate->p_ctenamespace == NULL) |
| return NULL; |
| |
| ListCell *lc; |
| foreach (lc, pstate->p_ctenamespace) |
| { |
| CommonTableExpr *cte = (CommonTableExpr *)lfirst(lc); |
| if (strcmp(cte->ctename, rte->ctename) == 0) |
| { |
| return cte; |
| } |
| } |
| |
| /* shouldn't happen */ |
| elog(ERROR, "unexpected error while parsing WITH query \"%s\"", rte->ctename); |
| return NULL; |
| } |
| |
| |
| /* |
| * analyzeCTE |
| * Analyze the given CommonTableExpr. |
| * |
| * Report errors if the given CTE has one of the following: |
| * (1) not a Query statement. |
| * (2) containing INTO clause. |
| */ |
| static void |
| analyzeCTE(ParseState *pstate, CommonTableExpr *cte) |
| { |
| Assert(cte != NULL); |
| Assert(cte->ctequery != NULL); |
| Assert(!IsA(cte->ctequery, Query)); |
| |
| List *queryList; |
| |
| queryList = parse_sub_analyze(cte->ctequery, pstate); |
| Assert(list_length(queryList) == 1); |
| |
| Query *query = (Query *)linitial(queryList); |
| cte->ctequery = (Node *)query; |
| |
| /* Check if the query is what we expected. */ |
| if (!IsA(query, Query)) |
| elog(ERROR, "unexpected non-Query statement in WITH clause"); |
| if (query->utilityStmt != NULL) |
| elog(ERROR, "unexpected utility statement in WITH clause"); |
| |
| if (query->intoClause) |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("query defined in WITH clause cannot have SELECT INTO"), |
| parser_errposition(pstate, |
| exprLocation((Node *) query->intoClause)))); |
| |
| /* CTE queries are always marked as not canSetTag */ |
| query->canSetTag = false; |
| |
| /* Compute the column types, typmods. */ |
| analyzeCTETargetList(pstate, cte, GetCTETargetList(cte)); |
| } |
| |
| /* |
| * reportDuplicateNames |
| * Report error when a given list of names (in String) contain duplicate values for a given |
| * query name. |
| */ |
| static void |
| reportDuplicateNames(const char *queryName, List *names) |
| { |
| if (names == NULL) |
| return; |
| |
| ListCell *lc; |
| foreach (lc, names) |
| { |
| Value *string = (Value *)lfirst(lc); |
| Assert(IsA(string, String)); |
| |
| ListCell *rest; |
| for_each_cell(rest, lnext(lc)) |
| { |
| Value *string2 = (Value *)lfirst(rest); |
| Assert(IsA(string, String)); |
| |
| if (strcmp(strVal(string), strVal(string2)) == 0) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("WITH query \"%s\" must not have duplicate column name: %s", |
| queryName, strVal(string)), |
| errhint("Specify a column list without duplicate names"))); |
| } |
| } |
| } |
| } |
| |
| /* |
| * analayzeCTETargetList |
| * Compute colnames, coltypes, and coltypmods from the targetlist generated |
| * after analyze. |
| */ |
| static void |
| analyzeCTETargetList(ParseState *pstate, CommonTableExpr *cte, List *tlist) |
| { |
| Assert(cte->ctecolnames == NIL); |
| |
| /* |
| * We need to determine column names, types, and typmods. 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. Also, the alias can not have the same name. |
| * We report errors if this is not the case.) |
| * |
| */ |
| cte->ctecolnames = copyObject(cte->aliascolnames); |
| cte->ctecoltypes = cte->ctecoltypmods = NIL; |
| int numaliases = list_length(cte->aliascolnames); |
| |
| int varattno = 0; |
| ListCell *tlistitem; |
| foreach(tlistitem, tlist) |
| { |
| TargetEntry *te = (TargetEntry *) lfirst(tlistitem); |
| Oid coltype; |
| int32 coltypmod; |
| |
| if (te->resjunk) |
| continue; |
| varattno++; |
| Assert(varattno == te->resno); |
| if (varattno > numaliases) |
| { |
| if (numaliases > 0) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg(ERRMSG_GP_WITH_COLUMNS_MISMATCH, cte->ctename), |
| parser_errposition(pstate, cte->location))); |
| } |
| |
| char *attrname; |
| |
| attrname = pstrdup(te->resname); |
| cte->ctecolnames = lappend(cte->ctecolnames, makeString(attrname)); |
| } |
| coltype = exprType((Node *) te->expr); |
| coltypmod = exprTypmod((Node *) te->expr); |
| |
| cte->ctecoltypes = lappend_oid(cte->ctecoltypes, coltype); |
| cte->ctecoltypmods = lappend_int(cte->ctecoltypmods, coltypmod); |
| } |
| |
| if (varattno < numaliases) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg(ERRMSG_GP_WITH_COLUMNS_MISMATCH, cte->ctename), |
| parser_errposition(pstate, cte->location))); |
| } |
| |
| reportDuplicateNames(cte->ctename, cte->ctecolnames); |
| } |