diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:15:05 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:15:05 +0000 |
commit | 46651ce6fe013220ed397add242004d764fc0153 (patch) | |
tree | 6e5299f990f88e60174a1d3ae6e48eedd2688b2b /src/backend/parser/parse_collate.c | |
parent | Initial commit. (diff) | |
download | postgresql-14-upstream.tar.xz postgresql-14-upstream.zip |
Adding upstream version 14.5.upstream/14.5upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/backend/parser/parse_collate.c')
-rw-r--r-- | src/backend/parser/parse_collate.c | 1057 |
1 files changed, 1057 insertions, 0 deletions
diff --git a/src/backend/parser/parse_collate.c b/src/backend/parser/parse_collate.c new file mode 100644 index 0000000..4133526 --- /dev/null +++ b/src/backend/parser/parse_collate.c @@ -0,0 +1,1057 @@ +/*------------------------------------------------------------------------- + * + * parse_collate.c + * Routines for assigning collation information. + * + * We choose to handle collation analysis in a post-pass over the output + * of expression parse analysis. This is because we need more state to + * perform this processing than is needed in the finished tree. If we + * did it on-the-fly while building the tree, all that state would have + * to be kept in expression node trees permanently. This way, the extra + * storage is just local variables in this recursive routine. + * + * The info that is actually saved in the finished tree is: + * 1. The output collation of each expression node, or InvalidOid if it + * returns a noncollatable data type. This can also be InvalidOid if the + * result type is collatable but the collation is indeterminate. + * 2. The collation to be used in executing each function. InvalidOid means + * that there are no collatable inputs or their collation is indeterminate. + * This value is only stored in node types that might call collation-using + * functions. + * + * You might think we could get away with storing only one collation per + * node, but the two concepts really need to be kept distinct. Otherwise + * it's too confusing when a function produces a collatable output type but + * has no collatable inputs or produces noncollatable output from collatable + * inputs. + * + * Cases with indeterminate collation might result in an error being thrown + * at runtime. If we knew exactly which functions require collation + * information, we could throw those errors at parse time instead. + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/parser/parse_collate.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "catalog/pg_aggregate.h" +#include "catalog/pg_collation.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "parser/parse_collate.h" +#include "utils/lsyscache.h" + + +/* + * Collation strength (the SQL standard calls this "derivation"). Order is + * chosen to allow comparisons to work usefully. Note: the standard doesn't + * seem to distinguish between NONE and CONFLICT. + */ +typedef enum +{ + COLLATE_NONE, /* expression is of a noncollatable datatype */ + COLLATE_IMPLICIT, /* collation was derived implicitly */ + COLLATE_CONFLICT, /* we had a conflict of implicit collations */ + COLLATE_EXPLICIT /* collation was derived explicitly */ +} CollateStrength; + +typedef struct +{ + ParseState *pstate; /* parse state (for error reporting) */ + Oid collation; /* OID of current collation, if any */ + CollateStrength strength; /* strength of current collation choice */ + int location; /* location of expr that set collation */ + /* Remaining fields are only valid when strength == COLLATE_CONFLICT */ + Oid collation2; /* OID of conflicting collation */ + int location2; /* location of expr that set collation2 */ +} assign_collations_context; + +static bool assign_query_collations_walker(Node *node, ParseState *pstate); +static bool assign_collations_walker(Node *node, + assign_collations_context *context); +static void merge_collation_state(Oid collation, + CollateStrength strength, + int location, + Oid collation2, + int location2, + assign_collations_context *context); +static void assign_aggregate_collations(Aggref *aggref, + assign_collations_context *loccontext); +static void assign_ordered_set_collations(Aggref *aggref, + assign_collations_context *loccontext); +static void assign_hypothetical_collations(Aggref *aggref, + assign_collations_context *loccontext); + + +/* + * assign_query_collations() + * Mark all expressions in the given Query with collation information. + * + * This should be applied to each Query after completion of parse analysis + * for expressions. Note that we do not recurse into sub-Queries, since + * those should have been processed when built. + */ +void +assign_query_collations(ParseState *pstate, Query *query) +{ + /* + * We just use query_tree_walker() to visit all the contained expressions. + * We can skip the rangetable and CTE subqueries, though, since RTEs and + * subqueries had better have been processed already (else Vars referring + * to them would not get created with the right collation). + */ + (void) query_tree_walker(query, + assign_query_collations_walker, + (void *) pstate, + QTW_IGNORE_RANGE_TABLE | + QTW_IGNORE_CTE_SUBQUERIES); +} + +/* + * Walker for assign_query_collations + * + * Each expression found by query_tree_walker is processed independently. + * Note that query_tree_walker may pass us a whole List, such as the + * targetlist, in which case each subexpression must be processed + * independently --- we don't want to bleat if two different targetentries + * have different collations. + */ +static bool +assign_query_collations_walker(Node *node, ParseState *pstate) +{ + /* Need do nothing for empty subexpressions */ + if (node == NULL) + return false; + + /* + * We don't want to recurse into a set-operations tree; it's already been + * fully processed in transformSetOperationStmt. + */ + if (IsA(node, SetOperationStmt)) + return false; + + if (IsA(node, List)) + assign_list_collations(pstate, (List *) node); + else + assign_expr_collations(pstate, node); + + return false; +} + +/* + * assign_list_collations() + * Mark all nodes in the list of expressions with collation information. + * + * The list member expressions are processed independently; they do not have + * to share a common collation. + */ +void +assign_list_collations(ParseState *pstate, List *exprs) +{ + ListCell *lc; + + foreach(lc, exprs) + { + Node *node = (Node *) lfirst(lc); + + assign_expr_collations(pstate, node); + } +} + +/* + * assign_expr_collations() + * Mark all nodes in the given expression tree with collation information. + * + * This is exported for the benefit of various utility commands that process + * expressions without building a complete Query. It should be applied after + * calling transformExpr() plus any expression-modifying operations such as + * coerce_to_boolean(). + */ +void +assign_expr_collations(ParseState *pstate, Node *expr) +{ + assign_collations_context context; + + /* initialize context for tree walk */ + context.pstate = pstate; + context.collation = InvalidOid; + context.strength = COLLATE_NONE; + context.location = -1; + + /* and away we go */ + (void) assign_collations_walker(expr, &context); +} + +/* + * select_common_collation() + * Identify a common collation for a list of expressions. + * + * The expressions should all return the same datatype, else this is not + * terribly meaningful. + * + * none_ok means that it is permitted to return InvalidOid, indicating that + * no common collation could be identified, even for collatable datatypes. + * Otherwise, an error is thrown for conflict of implicit collations. + * + * In theory, none_ok = true reflects the rules of SQL standard clause "Result + * of data type combinations", none_ok = false reflects the rules of clause + * "Collation determination" (in some cases invoked via "Grouping + * operations"). + */ +Oid +select_common_collation(ParseState *pstate, List *exprs, bool none_ok) +{ + assign_collations_context context; + + /* initialize context for tree walk */ + context.pstate = pstate; + context.collation = InvalidOid; + context.strength = COLLATE_NONE; + context.location = -1; + + /* and away we go */ + (void) assign_collations_walker((Node *) exprs, &context); + + /* deal with collation conflict */ + if (context.strength == COLLATE_CONFLICT) + { + if (none_ok) + return InvalidOid; + ereport(ERROR, + (errcode(ERRCODE_COLLATION_MISMATCH), + errmsg("collation mismatch between implicit collations \"%s\" and \"%s\"", + get_collation_name(context.collation), + get_collation_name(context.collation2)), + errhint("You can choose the collation by applying the COLLATE clause to one or both expressions."), + parser_errposition(context.pstate, context.location2))); + } + + /* + * Note: if strength is still COLLATE_NONE, we'll return InvalidOid, but + * that's okay because it must mean none of the expressions returned + * collatable datatypes. + */ + return context.collation; +} + +/* + * assign_collations_walker() + * Recursive guts of collation processing. + * + * Nodes with no children (eg, Vars, Consts, Params) must have been marked + * when built. All upper-level nodes are marked here. + * + * Note: if this is invoked directly on a List, it will attempt to infer a + * common collation for all the list members. In particular, it will throw + * error if there are conflicting explicit collations for different members. + */ +static bool +assign_collations_walker(Node *node, assign_collations_context *context) +{ + assign_collations_context loccontext; + Oid collation; + CollateStrength strength; + int location; + + /* Need do nothing for empty subexpressions */ + if (node == NULL) + return false; + + /* + * Prepare for recursion. For most node types, though not all, the first + * thing we do is recurse to process all nodes below this one. Each level + * of the tree has its own local context. + */ + loccontext.pstate = context->pstate; + loccontext.collation = InvalidOid; + loccontext.strength = COLLATE_NONE; + loccontext.location = -1; + /* Set these fields just to suppress uninitialized-value warnings: */ + loccontext.collation2 = InvalidOid; + loccontext.location2 = -1; + + /* + * Recurse if appropriate, then determine the collation for this node. + * + * Note: the general cases are at the bottom of the switch, after various + * special cases. + */ + switch (nodeTag(node)) + { + case T_CollateExpr: + { + /* + * COLLATE sets an explicitly derived collation, regardless of + * what the child state is. But we must recurse to set up + * collation info below here. + */ + CollateExpr *expr = (CollateExpr *) node; + + (void) expression_tree_walker(node, + assign_collations_walker, + (void *) &loccontext); + + collation = expr->collOid; + Assert(OidIsValid(collation)); + strength = COLLATE_EXPLICIT; + location = expr->location; + } + break; + case T_FieldSelect: + { + /* + * For FieldSelect, the result has the field's declared + * collation, independently of what happened in the arguments. + * (The immediate argument must be composite and thus not + * collatable, anyhow.) The field's collation was already + * looked up and saved in the node. + */ + FieldSelect *expr = (FieldSelect *) node; + + /* ... but first, recurse */ + (void) expression_tree_walker(node, + assign_collations_walker, + (void *) &loccontext); + + if (OidIsValid(expr->resultcollid)) + { + /* Node's result type is collatable. */ + /* Pass up field's collation as an implicit choice. */ + collation = expr->resultcollid; + strength = COLLATE_IMPLICIT; + location = exprLocation(node); + } + else + { + /* Node's result type isn't collatable. */ + collation = InvalidOid; + strength = COLLATE_NONE; + location = -1; /* won't be used */ + } + } + break; + case T_RowExpr: + { + /* + * RowExpr is a special case because the subexpressions are + * independent: we don't want to complain if some of them have + * incompatible explicit collations. + */ + RowExpr *expr = (RowExpr *) node; + + assign_list_collations(context->pstate, expr->args); + + /* + * Since the result is always composite and therefore never + * has a collation, we can just stop here: this node has no + * impact on the collation of its parent. + */ + return false; /* done */ + } + case T_RowCompareExpr: + { + /* + * For RowCompare, we have to find the common collation of + * each pair of input columns and build a list. If we can't + * find a common collation, we just put InvalidOid into the + * list, which may or may not cause an error at runtime. + */ + RowCompareExpr *expr = (RowCompareExpr *) node; + List *colls = NIL; + ListCell *l; + ListCell *r; + + forboth(l, expr->largs, r, expr->rargs) + { + Node *le = (Node *) lfirst(l); + Node *re = (Node *) lfirst(r); + Oid coll; + + coll = select_common_collation(context->pstate, + list_make2(le, re), + true); + colls = lappend_oid(colls, coll); + } + expr->inputcollids = colls; + + /* + * Since the result is always boolean and therefore never has + * a collation, we can just stop here: this node has no impact + * on the collation of its parent. + */ + return false; /* done */ + } + case T_CoerceToDomain: + { + /* + * If the domain declaration included a non-default COLLATE + * spec, then use that collation as the output collation of + * the coercion. Otherwise allow the input collation to + * bubble up. (The input should be of the domain's base type, + * therefore we don't need to worry about it not being + * collatable when the domain is.) + */ + CoerceToDomain *expr = (CoerceToDomain *) node; + Oid typcollation = get_typcollation(expr->resulttype); + + /* ... but first, recurse */ + (void) expression_tree_walker(node, + assign_collations_walker, + (void *) &loccontext); + + if (OidIsValid(typcollation)) + { + /* Node's result type is collatable. */ + if (typcollation == DEFAULT_COLLATION_OID) + { + /* Collation state bubbles up from child. */ + collation = loccontext.collation; + strength = loccontext.strength; + location = loccontext.location; + } + else + { + /* Use domain's collation as an implicit choice. */ + collation = typcollation; + strength = COLLATE_IMPLICIT; + location = exprLocation(node); + } + } + else + { + /* Node's result type isn't collatable. */ + collation = InvalidOid; + strength = COLLATE_NONE; + location = -1; /* won't be used */ + } + + /* + * Save the state into the expression node. We know it + * doesn't care about input collation. + */ + if (strength == COLLATE_CONFLICT) + exprSetCollation(node, InvalidOid); + else + exprSetCollation(node, collation); + } + break; + case T_TargetEntry: + (void) expression_tree_walker(node, + assign_collations_walker, + (void *) &loccontext); + + /* + * TargetEntry can have only one child, and should bubble that + * state up to its parent. We can't use the general-case code + * below because exprType and friends don't work on TargetEntry. + */ + collation = loccontext.collation; + strength = loccontext.strength; + location = loccontext.location; + + /* + * Throw error if the collation is indeterminate for a TargetEntry + * that is a sort/group target. We prefer to do this now, instead + * of leaving the comparison functions to fail at runtime, because + * we can give a syntax error pointer to help locate the problem. + * There are some cases where there might not be a failure, for + * example if the planner chooses to use hash aggregation instead + * of sorting for grouping; but it seems better to predictably + * throw an error. (Compare transformSetOperationTree, which will + * throw error for indeterminate collation of set-op columns, even + * though the planner might be able to implement the set-op + * without sorting.) + */ + if (strength == COLLATE_CONFLICT && + ((TargetEntry *) node)->ressortgroupref != 0) + ereport(ERROR, + (errcode(ERRCODE_COLLATION_MISMATCH), + errmsg("collation mismatch between implicit collations \"%s\" and \"%s\"", + get_collation_name(loccontext.collation), + get_collation_name(loccontext.collation2)), + errhint("You can choose the collation by applying the COLLATE clause to one or both expressions."), + parser_errposition(context->pstate, + loccontext.location2))); + break; + case T_InferenceElem: + case T_RangeTblRef: + case T_JoinExpr: + case T_FromExpr: + case T_OnConflictExpr: + case T_SortGroupClause: + (void) expression_tree_walker(node, + assign_collations_walker, + (void *) &loccontext); + + /* + * When we're invoked on a query's jointree, we don't need to do + * anything with join nodes except recurse through them to process + * WHERE/ON expressions. So just stop here. Likewise, we don't + * need to do anything when invoked on sort/group lists. + */ + return false; + case T_Query: + { + /* + * We get here when we're invoked on the Query belonging to a + * SubLink. Act as though the Query returns its first output + * column, which indeed is what it does for EXPR_SUBLINK and + * ARRAY_SUBLINK cases. In the cases where the SubLink + * returns boolean, this info will be ignored. Special case: + * in EXISTS, the Query might return no columns, in which case + * we need do nothing. + * + * We needn't recurse, since the Query is already processed. + */ + Query *qtree = (Query *) node; + TargetEntry *tent; + + if (qtree->targetList == NIL) + return false; + tent = linitial_node(TargetEntry, qtree->targetList); + if (tent->resjunk) + return false; + + collation = exprCollation((Node *) tent->expr); + /* collation doesn't change if it's converted to array */ + strength = COLLATE_IMPLICIT; + location = exprLocation((Node *) tent->expr); + } + break; + case T_List: + (void) expression_tree_walker(node, + assign_collations_walker, + (void *) &loccontext); + + /* + * When processing a list, collation state just bubbles up from + * the list elements. + */ + collation = loccontext.collation; + strength = loccontext.strength; + location = loccontext.location; + break; + + case T_Var: + case T_Const: + case T_Param: + case T_CoerceToDomainValue: + case T_CaseTestExpr: + case T_SetToDefault: + case T_CurrentOfExpr: + + /* + * General case for childless expression nodes. These should + * already have a collation assigned; it is not this function's + * responsibility to look into the catalogs for base-case + * information. + */ + collation = exprCollation(node); + + /* + * Note: in most cases, there will be an assigned collation + * whenever type_is_collatable(exprType(node)); but an exception + * occurs for a Var referencing a subquery output column for which + * a unique collation was not determinable. That may lead to a + * runtime failure if a collation-sensitive function is applied to + * the Var. + */ + + if (OidIsValid(collation)) + strength = COLLATE_IMPLICIT; + else + strength = COLLATE_NONE; + location = exprLocation(node); + break; + + default: + { + /* + * General case for most expression nodes with children. First + * recurse, then figure out what to assign to this node. + */ + Oid typcollation; + + /* + * For most node types, we want to treat all the child + * expressions alike; but there are a few exceptions, hence + * this inner switch. + */ + switch (nodeTag(node)) + { + case T_Aggref: + { + /* + * Aggref is messy enough that we give it its own + * function, in fact three of them. The FILTER + * clause is independent of the rest of the + * aggregate, however, so it can be processed + * separately. + */ + Aggref *aggref = (Aggref *) node; + + switch (aggref->aggkind) + { + case AGGKIND_NORMAL: + assign_aggregate_collations(aggref, + &loccontext); + break; + case AGGKIND_ORDERED_SET: + assign_ordered_set_collations(aggref, + &loccontext); + break; + case AGGKIND_HYPOTHETICAL: + assign_hypothetical_collations(aggref, + &loccontext); + break; + default: + elog(ERROR, "unrecognized aggkind: %d", + (int) aggref->aggkind); + } + + assign_expr_collations(context->pstate, + (Node *) aggref->aggfilter); + } + break; + case T_WindowFunc: + { + /* + * WindowFunc requires special processing only for + * its aggfilter clause, as for aggregates. + */ + WindowFunc *wfunc = (WindowFunc *) node; + + (void) assign_collations_walker((Node *) wfunc->args, + &loccontext); + + assign_expr_collations(context->pstate, + (Node *) wfunc->aggfilter); + } + break; + case T_CaseExpr: + { + /* + * CaseExpr is a special case because we do not + * want to recurse into the test expression (if + * any). It was already marked with collations + * during transformCaseExpr, and furthermore its + * collation is not relevant to the result of the + * CASE --- only the output expressions are. + */ + CaseExpr *expr = (CaseExpr *) node; + ListCell *lc; + + foreach(lc, expr->args) + { + CaseWhen *when = lfirst_node(CaseWhen, lc); + + /* + * The condition expressions mustn't affect + * the CASE's result collation either; but + * since they are known to yield boolean, it's + * safe to recurse directly on them --- they + * won't change loccontext. + */ + (void) assign_collations_walker((Node *) when->expr, + &loccontext); + (void) assign_collations_walker((Node *) when->result, + &loccontext); + } + (void) assign_collations_walker((Node *) expr->defresult, + &loccontext); + } + break; + case T_SubscriptingRef: + { + /* + * The subscripts are treated as independent + * expressions not contributing to the node's + * collation. Only the container, and the source + * expression if any, contribute. (This models + * the old behavior, in which the subscripts could + * be counted on to be integers and thus not + * contribute anything.) + */ + SubscriptingRef *sbsref = (SubscriptingRef *) node; + + assign_expr_collations(context->pstate, + (Node *) sbsref->refupperindexpr); + assign_expr_collations(context->pstate, + (Node *) sbsref->reflowerindexpr); + (void) assign_collations_walker((Node *) sbsref->refexpr, + &loccontext); + (void) assign_collations_walker((Node *) sbsref->refassgnexpr, + &loccontext); + } + break; + default: + + /* + * Normal case: all child expressions contribute + * equally to loccontext. + */ + (void) expression_tree_walker(node, + assign_collations_walker, + (void *) &loccontext); + break; + } + + /* + * Now figure out what collation to assign to this node. + */ + typcollation = get_typcollation(exprType(node)); + if (OidIsValid(typcollation)) + { + /* Node's result is collatable; what about its input? */ + if (loccontext.strength > COLLATE_NONE) + { + /* Collation state bubbles up from children. */ + collation = loccontext.collation; + strength = loccontext.strength; + location = loccontext.location; + } + else + { + /* + * Collatable output produced without any collatable + * input. Use the type's collation (which is usually + * DEFAULT_COLLATION_OID, but might be different for a + * domain). + */ + collation = typcollation; + strength = COLLATE_IMPLICIT; + location = exprLocation(node); + } + } + else + { + /* Node's result type isn't collatable. */ + collation = InvalidOid; + strength = COLLATE_NONE; + location = -1; /* won't be used */ + } + + /* + * Save the result collation into the expression node. If the + * state is COLLATE_CONFLICT, we'll set the collation to + * InvalidOid, which might result in an error at runtime. + */ + if (strength == COLLATE_CONFLICT) + exprSetCollation(node, InvalidOid); + else + exprSetCollation(node, collation); + + /* + * Likewise save the input collation, which is the one that + * any function called by this node should use. + */ + if (loccontext.strength == COLLATE_CONFLICT) + exprSetInputCollation(node, InvalidOid); + else + exprSetInputCollation(node, loccontext.collation); + } + break; + } + + /* + * Now, merge my information into my parent's state. + */ + merge_collation_state(collation, + strength, + location, + loccontext.collation2, + loccontext.location2, + context); + + return false; +} + +/* + * Merge collation state of a subexpression into the context for its parent. + */ +static void +merge_collation_state(Oid collation, + CollateStrength strength, + int location, + Oid collation2, + int location2, + assign_collations_context *context) +{ + /* + * If the collation strength for this node is different from what's + * already in *context, then this node either dominates or is dominated by + * earlier siblings. + */ + if (strength > context->strength) + { + /* Override previous parent state */ + context->collation = collation; + context->strength = strength; + context->location = location; + /* Bubble up error info if applicable */ + if (strength == COLLATE_CONFLICT) + { + context->collation2 = collation2; + context->location2 = location2; + } + } + else if (strength == context->strength) + { + /* Merge, or detect error if there's a collation conflict */ + switch (strength) + { + case COLLATE_NONE: + /* Nothing + nothing is still nothing */ + break; + case COLLATE_IMPLICIT: + if (collation != context->collation) + { + /* + * Non-default implicit collation always beats default. + */ + if (context->collation == DEFAULT_COLLATION_OID) + { + /* Override previous parent state */ + context->collation = collation; + context->strength = strength; + context->location = location; + } + else if (collation != DEFAULT_COLLATION_OID) + { + /* + * Oops, we have a conflict. We cannot throw error + * here, since the conflict could be resolved by a + * later sibling CollateExpr, or the parent might not + * care about collation anyway. Return enough info to + * throw the error later, if needed. + */ + context->strength = COLLATE_CONFLICT; + context->collation2 = collation; + context->location2 = location; + } + } + break; + case COLLATE_CONFLICT: + /* We're still conflicted ... */ + break; + case COLLATE_EXPLICIT: + if (collation != context->collation) + { + /* + * Oops, we have a conflict of explicit COLLATE clauses. + * Here we choose to throw error immediately; that is what + * the SQL standard says to do, and there's no good reason + * to be less strict. + */ + ereport(ERROR, + (errcode(ERRCODE_COLLATION_MISMATCH), + errmsg("collation mismatch between explicit collations \"%s\" and \"%s\"", + get_collation_name(context->collation), + get_collation_name(collation)), + parser_errposition(context->pstate, location))); + } + break; + } + } +} + +/* + * Aggref is a special case because expressions used only for ordering + * shouldn't be taken to conflict with each other or with regular args, + * indeed shouldn't affect the aggregate's result collation at all. + * We handle this by applying assign_expr_collations() to them rather than + * passing down our loccontext. + * + * Note that we recurse to each TargetEntry, not directly to its contained + * expression, so that the case above for T_TargetEntry will complain if we + * can't resolve a collation for an ORDER BY item (whether or not it is also + * a normal aggregate arg). + * + * We need not recurse into the aggorder or aggdistinct lists, because those + * contain only SortGroupClause nodes which we need not process. + */ +static void +assign_aggregate_collations(Aggref *aggref, + assign_collations_context *loccontext) +{ + ListCell *lc; + + /* Plain aggregates have no direct args */ + Assert(aggref->aggdirectargs == NIL); + + /* Process aggregated args, holding resjunk ones at arm's length */ + foreach(lc, aggref->args) + { + TargetEntry *tle = lfirst_node(TargetEntry, lc); + + if (tle->resjunk) + assign_expr_collations(loccontext->pstate, (Node *) tle); + else + (void) assign_collations_walker((Node *) tle, loccontext); + } +} + +/* + * For ordered-set aggregates, it's somewhat unclear how best to proceed. + * The spec-defined inverse distribution functions have only one sort column + * and don't return collatable types, but this is clearly too restrictive in + * the general case. Our solution is to consider that the aggregate's direct + * arguments contribute normally to determination of the aggregate's own + * collation, while aggregated arguments contribute only when the aggregate + * is designed to have exactly one aggregated argument (i.e., it has a single + * aggregated argument and is non-variadic). If it can have more than one + * aggregated argument, we process the aggregated arguments as independent + * sort columns. This avoids throwing error for something like + * agg(...) within group (order by x collate "foo", y collate "bar") + * while also guaranteeing that variadic aggregates don't change in behavior + * depending on how many sort columns a particular call happens to have. + * + * Otherwise this is much like the plain-aggregate case. + */ +static void +assign_ordered_set_collations(Aggref *aggref, + assign_collations_context *loccontext) +{ + bool merge_sort_collations; + ListCell *lc; + + /* Merge sort collations to parent only if there can be only one */ + merge_sort_collations = (list_length(aggref->args) == 1 && + get_func_variadictype(aggref->aggfnoid) == InvalidOid); + + /* Direct args, if any, are normal children of the Aggref node */ + (void) assign_collations_walker((Node *) aggref->aggdirectargs, + loccontext); + + /* Process aggregated args appropriately */ + foreach(lc, aggref->args) + { + TargetEntry *tle = lfirst_node(TargetEntry, lc); + + if (merge_sort_collations) + (void) assign_collations_walker((Node *) tle, loccontext); + else + assign_expr_collations(loccontext->pstate, (Node *) tle); + } +} + +/* + * Hypothetical-set aggregates are even more special: per spec, we need to + * unify the collations of each pair of hypothetical and aggregated args. + * And we need to force the choice of collation down into the sort column + * to ensure that the sort happens with the chosen collation. Other than + * that, the behavior is like regular ordered-set aggregates. Note that + * hypothetical direct arguments contribute to the aggregate collation + * only when their partner aggregated arguments do. + */ +static void +assign_hypothetical_collations(Aggref *aggref, + assign_collations_context *loccontext) +{ + ListCell *h_cell = list_head(aggref->aggdirectargs); + ListCell *s_cell = list_head(aggref->args); + bool merge_sort_collations; + int extra_args; + + /* Merge sort collations to parent only if there can be only one */ + merge_sort_collations = (list_length(aggref->args) == 1 && + get_func_variadictype(aggref->aggfnoid) == InvalidOid); + + /* Process any non-hypothetical direct args */ + extra_args = list_length(aggref->aggdirectargs) - list_length(aggref->args); + Assert(extra_args >= 0); + while (extra_args-- > 0) + { + (void) assign_collations_walker((Node *) lfirst(h_cell), loccontext); + h_cell = lnext(aggref->aggdirectargs, h_cell); + } + + /* Scan hypothetical args and aggregated args in parallel */ + while (h_cell && s_cell) + { + Node *h_arg = (Node *) lfirst(h_cell); + TargetEntry *s_tle = (TargetEntry *) lfirst(s_cell); + assign_collations_context paircontext; + + /* + * Assign collations internally in this pair of expressions, then + * choose a common collation for them. This should match + * select_common_collation(), but we can't use that function as-is + * because we need access to the whole collation state so we can + * bubble it up to the aggregate function's level. + */ + paircontext.pstate = loccontext->pstate; + paircontext.collation = InvalidOid; + paircontext.strength = COLLATE_NONE; + paircontext.location = -1; + /* Set these fields just to suppress uninitialized-value warnings: */ + paircontext.collation2 = InvalidOid; + paircontext.location2 = -1; + + (void) assign_collations_walker(h_arg, &paircontext); + (void) assign_collations_walker((Node *) s_tle->expr, &paircontext); + + /* deal with collation conflict */ + if (paircontext.strength == COLLATE_CONFLICT) + ereport(ERROR, + (errcode(ERRCODE_COLLATION_MISMATCH), + errmsg("collation mismatch between implicit collations \"%s\" and \"%s\"", + get_collation_name(paircontext.collation), + get_collation_name(paircontext.collation2)), + errhint("You can choose the collation by applying the COLLATE clause to one or both expressions."), + parser_errposition(paircontext.pstate, + paircontext.location2))); + + /* + * At this point paircontext.collation can be InvalidOid only if the + * type is not collatable; no need to do anything in that case. If we + * do have to change the sort column's collation, do it by inserting a + * RelabelType node into the sort column TLE. + * + * XXX This is pretty grotty for a couple of reasons: + * assign_collations_walker isn't supposed to be changing the + * expression structure like this, and a parse-time change of + * collation ought to be signaled by a CollateExpr not a RelabelType + * (the use of RelabelType for collation marking is supposed to be a + * planner/executor thing only). But we have no better alternative. + * In particular, injecting a CollateExpr could result in the + * expression being interpreted differently after dump/reload, since + * we might be effectively promoting an implicit collation to + * explicit. This kluge is relying on ruleutils.c not printing a + * COLLATE clause for a RelabelType, and probably on some other + * fragile behaviors. + */ + if (OidIsValid(paircontext.collation) && + paircontext.collation != exprCollation((Node *) s_tle->expr)) + { + s_tle->expr = (Expr *) + makeRelabelType(s_tle->expr, + exprType((Node *) s_tle->expr), + exprTypmod((Node *) s_tle->expr), + paircontext.collation, + COERCE_IMPLICIT_CAST); + } + + /* + * If appropriate, merge this column's collation state up to the + * aggregate function. + */ + if (merge_sort_collations) + merge_collation_state(paircontext.collation, + paircontext.strength, + paircontext.location, + paircontext.collation2, + paircontext.location2, + loccontext); + + h_cell = lnext(aggref->aggdirectargs, h_cell); + s_cell = lnext(aggref->args, s_cell); + } + Assert(h_cell == NULL && s_cell == NULL); +} |