mirror of
https://github.com/postgres/postgres.git
synced 2025-07-18 17:42:25 +03:00
SEARCH and CYCLE clauses
This adds the SQL standard feature that adds the SEARCH and CYCLE clauses to recursive queries to be able to do produce breadth- or depth-first search orders and detect cycles. These clauses can be rewritten into queries using existing syntax, and that is what this patch does in the rewriter. Reviewed-by: Vik Fearing <vik@postgresfriends.org> Reviewed-by: Pavel Stehule <pavel.stehule@gmail.com> Discussion: https://www.postgresql.org/message-id/flat/db80ceee-6f97-9b4a-8ee8-3ba0c58e5be2@2ndquadrant.com
This commit is contained in:
@ -18,9 +18,13 @@
|
||||
#include "catalog/pg_type.h"
|
||||
#include "nodes/nodeFuncs.h"
|
||||
#include "parser/analyze.h"
|
||||
#include "parser/parse_coerce.h"
|
||||
#include "parser/parse_collate.h"
|
||||
#include "parser/parse_cte.h"
|
||||
#include "parser/parse_expr.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/lsyscache.h"
|
||||
#include "utils/typcache.h"
|
||||
|
||||
|
||||
/* Enumeration of contexts in which a self-reference is disallowed */
|
||||
@ -334,6 +338,195 @@ analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
|
||||
if (lctyp != NULL || lctypmod != NULL || lccoll != NULL) /* shouldn't happen */
|
||||
elog(ERROR, "wrong number of output columns in WITH");
|
||||
}
|
||||
|
||||
if (cte->search_clause || cte->cycle_clause)
|
||||
{
|
||||
Query *ctequery;
|
||||
SetOperationStmt *sos;
|
||||
|
||||
if (!cte->cterecursive)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("WITH query is not recursive"),
|
||||
parser_errposition(pstate, cte->location)));
|
||||
|
||||
/*
|
||||
* SQL requires a WITH list element (CTE) to be "expandable" in order
|
||||
* to allow a search or cycle clause. That is a stronger requirement
|
||||
* than just being recursive. It basically means the query expression
|
||||
* looks like
|
||||
*
|
||||
* non-recursive query UNION [ALL] recursive query
|
||||
*
|
||||
* and that the recursive query is not itself a set operation.
|
||||
*
|
||||
* As of this writing, most of these criteria are already satisfied by
|
||||
* all recursive CTEs allowed by PostgreSQL. In the future, if
|
||||
* further variants recursive CTEs are accepted, there might be
|
||||
* further checks required here to determine what is "expandable".
|
||||
*/
|
||||
|
||||
ctequery = castNode(Query, cte->ctequery);
|
||||
Assert(ctequery->setOperations);
|
||||
sos = castNode(SetOperationStmt, ctequery->setOperations);
|
||||
|
||||
/*
|
||||
* This left side check is not required for expandability, but
|
||||
* rewriteSearchAndCycle() doesn't currently have support for it, so
|
||||
* we catch it here.
|
||||
*/
|
||||
if (!IsA(sos->larg, RangeTblRef))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("with a SEARCH or CYCLE clause, the left side of the UNION must be a SELECT")));
|
||||
|
||||
if (!IsA(sos->rarg, RangeTblRef))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("with a SEARCH or CYCLE clause, the right side of the UNION must be a SELECT")));
|
||||
}
|
||||
|
||||
if (cte->search_clause)
|
||||
{
|
||||
ListCell *lc;
|
||||
List *seen = NIL;
|
||||
|
||||
foreach(lc, cte->search_clause->search_col_list)
|
||||
{
|
||||
Value *colname = lfirst(lc);
|
||||
|
||||
if (!list_member(cte->ctecolnames, colname))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("search column \"%s\" not in WITH query column list",
|
||||
strVal(colname)),
|
||||
parser_errposition(pstate, cte->search_clause->location)));
|
||||
|
||||
if (list_member(seen, colname))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DUPLICATE_COLUMN),
|
||||
errmsg("search column \"%s\" specified more than once",
|
||||
strVal(colname)),
|
||||
parser_errposition(pstate, cte->search_clause->location)));
|
||||
seen = lappend(seen, colname);
|
||||
}
|
||||
|
||||
if (list_member(cte->ctecolnames, makeString(cte->search_clause->search_seq_column)))
|
||||
ereport(ERROR,
|
||||
errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("search sequence column name \"%s\" already used in WITH query column list",
|
||||
cte->search_clause->search_seq_column),
|
||||
parser_errposition(pstate, cte->search_clause->location));
|
||||
}
|
||||
|
||||
if (cte->cycle_clause)
|
||||
{
|
||||
ListCell *lc;
|
||||
List *seen = NIL;
|
||||
TypeCacheEntry *typentry;
|
||||
Oid op;
|
||||
|
||||
foreach(lc, cte->cycle_clause->cycle_col_list)
|
||||
{
|
||||
Value *colname = lfirst(lc);
|
||||
|
||||
if (!list_member(cte->ctecolnames, colname))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("cycle column \"%s\" not in WITH query column list",
|
||||
strVal(colname)),
|
||||
parser_errposition(pstate, cte->cycle_clause->location)));
|
||||
|
||||
if (list_member(seen, colname))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DUPLICATE_COLUMN),
|
||||
errmsg("cycle column \"%s\" specified more than once",
|
||||
strVal(colname)),
|
||||
parser_errposition(pstate, cte->cycle_clause->location)));
|
||||
seen = lappend(seen, colname);
|
||||
}
|
||||
|
||||
if (list_member(cte->ctecolnames, makeString(cte->cycle_clause->cycle_mark_column)))
|
||||
ereport(ERROR,
|
||||
errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("cycle mark column name \"%s\" already used in WITH query column list",
|
||||
cte->cycle_clause->cycle_mark_column),
|
||||
parser_errposition(pstate, cte->cycle_clause->location));
|
||||
|
||||
cte->cycle_clause->cycle_mark_value = transformExpr(pstate, cte->cycle_clause->cycle_mark_value,
|
||||
EXPR_KIND_CYCLE_MARK);
|
||||
cte->cycle_clause->cycle_mark_default = transformExpr(pstate, cte->cycle_clause->cycle_mark_default,
|
||||
EXPR_KIND_CYCLE_MARK);
|
||||
|
||||
if (list_member(cte->ctecolnames, makeString(cte->cycle_clause->cycle_path_column)))
|
||||
ereport(ERROR,
|
||||
errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("cycle path column name \"%s\" already used in WITH query column list",
|
||||
cte->cycle_clause->cycle_path_column),
|
||||
parser_errposition(pstate, cte->cycle_clause->location));
|
||||
|
||||
if (strcmp(cte->cycle_clause->cycle_mark_column,
|
||||
cte->cycle_clause->cycle_path_column) == 0)
|
||||
ereport(ERROR,
|
||||
errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("cycle mark column name and cycle path column name are the same"),
|
||||
parser_errposition(pstate, cte->cycle_clause->location));
|
||||
|
||||
cte->cycle_clause->cycle_mark_type = select_common_type(pstate,
|
||||
list_make2(cte->cycle_clause->cycle_mark_value,
|
||||
cte->cycle_clause->cycle_mark_default),
|
||||
"CYCLE", NULL);
|
||||
cte->cycle_clause->cycle_mark_value = coerce_to_common_type(pstate,
|
||||
cte->cycle_clause->cycle_mark_value,
|
||||
cte->cycle_clause->cycle_mark_type,
|
||||
"CYCLE/SET/TO");
|
||||
cte->cycle_clause->cycle_mark_default = coerce_to_common_type(pstate,
|
||||
cte->cycle_clause->cycle_mark_default,
|
||||
cte->cycle_clause->cycle_mark_type,
|
||||
"CYCLE/SET/DEFAULT");
|
||||
|
||||
cte->cycle_clause->cycle_mark_typmod = select_common_typmod(pstate,
|
||||
list_make2(cte->cycle_clause->cycle_mark_value,
|
||||
cte->cycle_clause->cycle_mark_default),
|
||||
cte->cycle_clause->cycle_mark_type);
|
||||
|
||||
cte->cycle_clause->cycle_mark_collation = select_common_collation(pstate,
|
||||
list_make2(cte->cycle_clause->cycle_mark_value,
|
||||
cte->cycle_clause->cycle_mark_default),
|
||||
true);
|
||||
|
||||
typentry = lookup_type_cache(cte->cycle_clause->cycle_mark_type, TYPECACHE_EQ_OPR);
|
||||
if (!typentry->eq_opr)
|
||||
ereport(ERROR,
|
||||
errcode(ERRCODE_UNDEFINED_FUNCTION),
|
||||
errmsg("could not identify an equality operator for type %s",
|
||||
format_type_be(cte->cycle_clause->cycle_mark_type)));
|
||||
op = get_negator(typentry->eq_opr);
|
||||
if (!op)
|
||||
ereport(ERROR,
|
||||
errcode(ERRCODE_UNDEFINED_FUNCTION),
|
||||
errmsg("could not identify an inequality operator for type %s",
|
||||
format_type_be(cte->cycle_clause->cycle_mark_type)));
|
||||
|
||||
cte->cycle_clause->cycle_mark_neop = op;
|
||||
}
|
||||
|
||||
if (cte->search_clause && cte->cycle_clause)
|
||||
{
|
||||
if (strcmp(cte->search_clause->search_seq_column,
|
||||
cte->cycle_clause->cycle_mark_column) == 0)
|
||||
ereport(ERROR,
|
||||
errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("search sequence column name and cycle mark column name are the same"),
|
||||
parser_errposition(pstate, cte->search_clause->location));
|
||||
|
||||
if (strcmp(cte->search_clause->search_seq_column,
|
||||
cte->cycle_clause->cycle_path_column) == 0)
|
||||
ereport(ERROR,
|
||||
errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("search_sequence column name and cycle path column name are the same"),
|
||||
parser_errposition(pstate, cte->search_clause->location));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
Reference in New Issue
Block a user