diff --git a/src/backend/distributed/executor/multi_utility.c b/src/backend/distributed/executor/multi_utility.c index 636073c1b..2438e2dc5 100644 --- a/src/backend/distributed/executor/multi_utility.c +++ b/src/backend/distributed/executor/multi_utility.c @@ -3111,7 +3111,9 @@ InterShardDDLTaskList(Oid leftRelationId, Oid rightRelationId, * * This code is heavily borrowed from RangeVarCallbackForDropRelation() in * commands/tablecmds.c in Postgres source. We need this to ensure the right - * order of locking while dealing with DROP INDEX statments. + * order of locking while dealing with DROP INDEX statments. Because we are + * exclusively using this callback for INDEX processing, the PARTITION-related + * logic from PostgreSQL's similar callback has been omitted as unneeded. */ static void RangeVarCallbackForDropIndex(const RangeVar *rel, Oid relOid, Oid oldRelOid, void *arg) diff --git a/src/backend/distributed/utils/ruleutils_10.c b/src/backend/distributed/utils/ruleutils_10.c index 6dfdad67c..1fd4a6e3d 100644 --- a/src/backend/distributed/utils/ruleutils_10.c +++ b/src/backend/distributed/utils/ruleutils_10.c @@ -93,7 +93,7 @@ #define PRETTYINDENT_JOIN 4 #define PRETTYINDENT_VAR 4 -#define PRETTYINDENT_LIMIT 40 /* wrap limit */ +#define PRETTYINDENT_LIMIT 40 /* wrap limit */ /* Pretty flags */ #define PRETTYFLAG_PAREN 1 @@ -125,8 +125,8 @@ typedef struct bool varprefix; /* TRUE to print prefixes on Vars */ Oid distrelid; /* the distributed table being modified, if valid */ int64 shardid; /* a distributed table's shardid, if positive */ - ParseExprKind special_exprkind; /* set only for exprkinds needing - * special handling */ + ParseExprKind special_exprkind; /* set only for exprkinds needing special + * handling */ } deparse_context; /* @@ -292,7 +292,7 @@ typedef struct */ typedef struct { - char name[NAMEDATALEN]; /* Hash key --- must be first */ + char name[NAMEDATALEN]; /* Hash key --- must be first */ int counter; /* Largest addition used so far for name */ } NameHashEntry; @@ -390,6 +390,9 @@ static void get_rule_expr(Node *node, deparse_context *context, bool showimplicit); static void get_rule_expr_toplevel(Node *node, deparse_context *context, bool showimplicit); +static void get_rule_expr_funccall(Node *node, deparse_context *context, + bool showimplicit); +static bool looks_like_function(Node *node); static void get_oper_expr(OpExpr *expr, deparse_context *context); static void get_func_expr(FuncExpr *expr, deparse_context *context, bool showimplicit); @@ -2148,19 +2151,19 @@ get_select_query_def(Query *query, deparse_context *context, break; case LCS_FORKEYSHARE: appendContextKeyword(context, " FOR KEY SHARE", - -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); + -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); break; case LCS_FORSHARE: appendContextKeyword(context, " FOR SHARE", - -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); + -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); break; case LCS_FORNOKEYUPDATE: appendContextKeyword(context, " FOR NO KEY UPDATE", - -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); + -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); break; case LCS_FORUPDATE: appendContextKeyword(context, " FOR UPDATE", - -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); + -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); break; } @@ -3018,8 +3021,8 @@ get_insert_query_def(Query *query, deparse_context *context) * tle->resname, since resname will fail to track RENAME. */ appendStringInfoString(buf, - quote_identifier(get_relid_attribute_name(rte->relid, - tle->resno))); + quote_identifier(get_relid_attribute_name(rte->relid, + tle->resno))); /* * Print any indirection needed (subfields or subscripts), and strip @@ -3299,8 +3302,11 @@ get_update_query_targetlist_def(Query *query, List *targetList, /* * We must dig down into the expr to see if it's a PARAM_MULTIEXPR * Param. That could be buried under FieldStores and ArrayRefs - * (cf processIndirection()), and underneath those there could be - * an implicit type coercion. + * and CoerceToDomains (cf processIndirection()), and underneath + * those there could be an implicit type coercion. Because we + * would ignore implicit type coercions anyway, we don't need to + * be as careful as processIndirection() is about descending past + * implicit CoerceToDomains. */ expr = (Node *) tle->expr; while (expr) @@ -3319,6 +3325,14 @@ get_update_query_targetlist_def(Query *query, List *targetList, break; expr = (Node *) aref->refassgnexpr; } + else if (IsA(expr, CoerceToDomain)) + { + CoerceToDomain *cdomain = (CoerceToDomain *) expr; + + if (cdomain->coercionformat != COERCE_IMPLICIT_CAST) + break; + expr = (Node *) cdomain->arg; + } else break; } @@ -3330,7 +3344,7 @@ get_update_query_targetlist_def(Query *query, List *targetList, cur_ma_sublink = (SubLink *) lfirst(next_ma_cell); next_ma_cell = lnext(next_ma_cell); remaining_ma_columns = count_nonjunk_tlist_entries( - ((Query *) cur_ma_sublink->subselect)->targetList); + ((Query *) cur_ma_sublink->subselect)->targetList); Assert(((Param *) expr)->paramid == ((cur_ma_sublink->subLinkId << 16) | 1)); appendStringInfoChar(buf, '('); @@ -3342,8 +3356,8 @@ get_update_query_targetlist_def(Query *query, List *targetList, * tle->resname, since resname will fail to track RENAME. */ appendStringInfoString(buf, - quote_identifier(get_relid_attribute_name(rte->relid, - tle->resno))); + quote_identifier(get_relid_attribute_name(rte->relid, + tle->resno))); /* * Print any indirection needed (subfields or subscripts), and strip @@ -4423,6 +4437,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags) case T_MinMaxExpr: case T_SQLValueFunction: case T_XmlExpr: + case T_NextValueExpr: case T_NullIfExpr: case T_Aggref: case T_WindowFunc: @@ -4532,17 +4547,17 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags) return false; return true; /* own parentheses */ } - case T_BoolExpr: /* lower precedence */ - case T_ArrayRef: /* other separators */ - case T_ArrayExpr: /* other separators */ - case T_RowExpr: /* other separators */ + case T_BoolExpr: /* lower precedence */ + case T_ArrayRef: /* other separators */ + case T_ArrayExpr: /* other separators */ + case T_RowExpr: /* other separators */ case T_CoalesceExpr: /* own parentheses */ - case T_MinMaxExpr: /* own parentheses */ - case T_XmlExpr: /* own parentheses */ - case T_NullIfExpr: /* other separators */ + case T_MinMaxExpr: /* own parentheses */ + case T_XmlExpr: /* own parentheses */ + case T_NullIfExpr: /* other separators */ case T_Aggref: /* own parentheses */ - case T_WindowFunc: /* own parentheses */ - case T_CaseExpr: /* other separators */ + case T_WindowFunc: /* own parentheses */ + case T_CaseExpr: /* other separators */ return true; default: return false; @@ -4583,16 +4598,16 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags) return false; return true; /* own parentheses */ } - case T_ArrayRef: /* other separators */ - case T_ArrayExpr: /* other separators */ - case T_RowExpr: /* other separators */ + case T_ArrayRef: /* other separators */ + case T_ArrayExpr: /* other separators */ + case T_RowExpr: /* other separators */ case T_CoalesceExpr: /* own parentheses */ - case T_MinMaxExpr: /* own parentheses */ - case T_XmlExpr: /* own parentheses */ - case T_NullIfExpr: /* other separators */ + case T_MinMaxExpr: /* own parentheses */ + case T_XmlExpr: /* own parentheses */ + case T_NullIfExpr: /* other separators */ case T_Aggref: /* own parentheses */ - case T_WindowFunc: /* own parentheses */ - case T_CaseExpr: /* other separators */ + case T_WindowFunc: /* own parentheses */ + case T_CaseExpr: /* other separators */ return true; default: return false; @@ -4891,7 +4906,7 @@ get_rule_expr(Node *node, deparse_context *context, appendStringInfo(buf, " %s %s (", generate_operator_name(expr->opno, exprType(arg1), - get_base_element_type(exprType(arg2))), + get_base_element_type(exprType(arg2))), expr->useOr ? "ANY" : "ALL"); get_rule_expr_paren(arg2, context, true, node); @@ -4911,7 +4926,7 @@ get_rule_expr(Node *node, deparse_context *context, ((SubLink *) arg2)->subLinkType == EXPR_SUBLINK) appendStringInfo(buf, "::%s", format_type_with_typemod(exprType(arg2), - exprTypmod(arg2))); + exprTypmod(arg2))); appendStringInfoChar(buf, ')'); if (!PRETTY_PAREN(context)) appendStringInfoChar(buf, ')'); @@ -5269,7 +5284,7 @@ get_rule_expr(Node *node, deparse_context *context, */ if (arrayexpr->elements == NIL) appendStringInfo(buf, "::%s", - format_type_with_typemod(arrayexpr->array_typeid, -1)); + format_type_with_typemod(arrayexpr->array_typeid, -1)); } break; @@ -5331,7 +5346,7 @@ get_rule_expr(Node *node, deparse_context *context, appendStringInfoChar(buf, ')'); if (rowexpr->row_format == COERCE_EXPLICIT_CAST) appendStringInfo(buf, "::%s", - format_type_with_typemod(rowexpr->row_typeid, -1)); + format_type_with_typemod(rowexpr->row_typeid, -1)); } break; @@ -5364,9 +5379,9 @@ get_rule_expr(Node *node, deparse_context *context, * be perfect. */ appendStringInfo(buf, ") %s ROW(", - generate_operator_name(linitial_oid(rcexpr->opnos), - exprType(linitial(rcexpr->largs)), - exprType(linitial(rcexpr->rargs)))); + generate_operator_name(linitial_oid(rcexpr->opnos), + exprType(linitial(rcexpr->largs)), + exprType(linitial(rcexpr->rargs)))); sep = ""; foreach(arg, rcexpr->rargs) { @@ -5564,7 +5579,7 @@ get_rule_expr(Node *node, deparse_context *context, Assert(!con->constisnull); if (DatumGetBool(con->constvalue)) appendStringInfoString(buf, - " PRESERVE WHITESPACE"); + " PRESERVE WHITESPACE"); else appendStringInfoString(buf, " STRIP WHITESPACE"); @@ -5592,15 +5607,15 @@ get_rule_expr(Node *node, deparse_context *context, { case XML_STANDALONE_YES: appendStringInfoString(buf, - ", STANDALONE YES"); + ", STANDALONE YES"); break; case XML_STANDALONE_NO: appendStringInfoString(buf, - ", STANDALONE NO"); + ", STANDALONE NO"); break; case XML_STANDALONE_NO_VALUE: appendStringInfoString(buf, - ", STANDALONE NO VALUE"); + ", STANDALONE NO VALUE"); break; default: break; @@ -5752,6 +5767,22 @@ get_rule_expr(Node *node, deparse_context *context, } break; + case T_NextValueExpr: + { + NextValueExpr *nvexpr = (NextValueExpr *) node; + + /* + * This isn't exactly nextval(), but that seems close enough + * for EXPLAIN's purposes. + */ + appendStringInfoString(buf, "nextval("); + simple_quote_literal(buf, + generate_relation_name(nvexpr->seqid, + NIL)); + appendStringInfoChar(buf, ')'); + } + break; + case T_InferenceElem: { InferenceElem *iexpr = (InferenceElem *) node; @@ -5786,7 +5817,7 @@ get_rule_expr(Node *node, deparse_context *context, if (iexpr->infercollid) appendStringInfo(buf, " COLLATE %s", - generate_collation_name(iexpr->infercollid)); + generate_collation_name(iexpr->infercollid)); /* Add the operator class name, if not default */ if (iexpr->inferopclass) @@ -5810,12 +5841,11 @@ get_rule_expr(Node *node, deparse_context *context, case PARTITION_STRATEGY_LIST: Assert(spec->listdatums != NIL); - appendStringInfoString(buf, "FOR VALUES"); - appendStringInfoString(buf, " IN ("); + appendStringInfoString(buf, "FOR VALUES IN ("); sep = ""; foreach(cell, spec->listdatums) { - Const *val = lfirst(cell); + Const *val = castNode(Const, lfirst(cell)); appendStringInfoString(buf, sep); get_const_expr(val, context, -1); @@ -5831,50 +5861,9 @@ get_rule_expr(Node *node, deparse_context *context, list_length(spec->lowerdatums) == list_length(spec->upperdatums)); - appendStringInfoString(buf, "FOR VALUES"); - appendStringInfoString(buf, " FROM"); - appendStringInfoString(buf, " ("); - sep = ""; - foreach(cell, spec->lowerdatums) - { - PartitionRangeDatum *datum = lfirst(cell); - Const *val; - - appendStringInfoString(buf, sep); - if (datum->kind == PARTITION_RANGE_DATUM_MINVALUE) - appendStringInfoString(buf, "MINVALUE"); - else if (datum->kind == PARTITION_RANGE_DATUM_MAXVALUE) - appendStringInfoString(buf, "MAXVALUE"); - else - { - val = (Const *) datum->value; - get_const_expr(val, context, -1); - } - sep = ", "; - } - appendStringInfoString(buf, ")"); - - appendStringInfoString(buf, " TO"); - appendStringInfoString(buf, " ("); - sep = ""; - foreach(cell, spec->upperdatums) - { - PartitionRangeDatum *datum = lfirst(cell); - Const *val; - - appendStringInfoString(buf, sep); - if (datum->kind == PARTITION_RANGE_DATUM_MINVALUE) - appendStringInfoString(buf, "MINVALUE"); - else if (datum->kind == PARTITION_RANGE_DATUM_MAXVALUE) - appendStringInfoString(buf, "MAXVALUE"); - else - { - val = (Const *) datum->value; - get_const_expr(val, context, -1); - } - sep = ", "; - } - appendStringInfoString(buf, ")"); + appendStringInfo(buf, "FOR VALUES FROM %s TO %s", + get_range_partbound_string(spec->lowerdatums), + get_range_partbound_string(spec->upperdatums)); break; default: @@ -5931,6 +5920,64 @@ get_rule_expr_toplevel(Node *node, deparse_context *context, get_rule_expr(node, context, showimplicit); } +/* + * get_rule_expr_funccall - Parse back a function-call expression + * + * Same as get_rule_expr(), except that we guarantee that the output will + * look like a function call, or like one of the things the grammar treats as + * equivalent to a function call (see the func_expr_windowless production). + * This is needed in places where the grammar uses func_expr_windowless and + * you can't substitute a parenthesized a_expr. If what we have isn't going + * to look like a function call, wrap it in a dummy CAST() expression, which + * will satisfy the grammar --- and, indeed, is likely what the user wrote to + * produce such a thing. + */ +static void +get_rule_expr_funccall(Node *node, deparse_context *context, + bool showimplicit) +{ + if (looks_like_function(node)) + get_rule_expr(node, context, showimplicit); + else + { + StringInfo buf = context->buf; + + appendStringInfoString(buf, "CAST("); + /* no point in showing any top-level implicit cast */ + get_rule_expr(node, context, false); + appendStringInfo(buf, " AS %s)", + format_type_with_typemod(exprType(node), + exprTypmod(node))); + } +} + +/* + * Helper function to identify node types that satisfy func_expr_windowless. + * If in doubt, "false" is always a safe answer. + */ +static bool +looks_like_function(Node *node) +{ + if (node == NULL) + return false; /* probably shouldn't happen */ + switch (nodeTag(node)) + { + case T_FuncExpr: + /* OK, unless it's going to deparse as a cast */ + return (((FuncExpr *) node)->funcformat == COERCE_EXPLICIT_CALL); + case T_NullIfExpr: + case T_CoalesceExpr: + case T_MinMaxExpr: + case T_SQLValueFunction: + case T_XmlExpr: + /* these are all accepted by func_expr_common_subexpr */ + return true; + default: + break; + } + return false; +} + /* * get_oper_expr - Parse back an OpExpr node @@ -6385,7 +6432,7 @@ get_const_expr(Const *constval, deparse_context *context, int showtype) else { appendStringInfo(buf, "'%s'", extval); - needlabel = true; /* we must attach a cast */ + needlabel = true; /* we must attach a cast */ } break; @@ -6404,7 +6451,7 @@ get_const_expr(Const *constval, deparse_context *context, int showtype) else { appendStringInfo(buf, "'%s'", extval); - needlabel = true; /* we must attach a cast */ + needlabel = true; /* we must attach a cast */ } break; @@ -6566,8 +6613,8 @@ get_sublink_expr(SubLink *sublink, deparse_context *context) get_rule_expr(linitial(opexpr->args), context, true); if (!opname) opname = generate_operator_name(opexpr->opno, - exprType(linitial(opexpr->args)), - exprType(lsecond(opexpr->args))); + exprType(linitial(opexpr->args)), + exprType(lsecond(opexpr->args))); sep = ", "; } appendStringInfoChar(buf, ')'); @@ -6581,7 +6628,7 @@ get_sublink_expr(SubLink *sublink, deparse_context *context) get_rule_expr((Node *) rcexpr->largs, context, true); opname = generate_operator_name(linitial_oid(rcexpr->opnos), exprType(linitial(rcexpr->largs)), - exprType(linitial(rcexpr->rargs))); + exprType(linitial(rcexpr->rargs))); appendStringInfoChar(buf, ')'); } else @@ -6598,7 +6645,7 @@ get_sublink_expr(SubLink *sublink, deparse_context *context) break; case ANY_SUBLINK: - if (strcmp(opname, "=") == 0) /* Represent = ANY as IN */ + if (strcmp(opname, "=") == 0) /* Represent = ANY as IN */ appendStringInfoString(buf, " IN "); else appendStringInfo(buf, " %s ANY ", opname); @@ -6921,7 +6968,7 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context) if (list_length(rte->functions) == 1 && (rtfunc1->funccolnames == NIL || !rte->funcordinality)) { - get_rule_expr(rtfunc1->funcexpr, context, true); + get_rule_expr_funccall(rtfunc1->funcexpr, context, true); /* we'll print the coldeflist below, if it has one */ } else @@ -6984,7 +7031,7 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context) if (funcno > 0) appendStringInfoString(buf, ", "); - get_rule_expr(rtfunc->funcexpr, context, true); + get_rule_expr_funccall(rtfunc->funcexpr, context, true); if (rtfunc->funccolnames != NIL) { /* Reconstruct the column definition list */ @@ -7177,6 +7224,11 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context) if (!PRETTY_PAREN(context)) appendStringInfoChar(buf, ')'); } + else if (j->jointype != JOIN_INNER) + { + /* If we didn't say CROSS JOIN above, we must provide an ON */ + appendStringInfoString(buf, " ON TRUE"); + } if (!PRETTY_PAREN(context) || j->alias != NULL) appendStringInfoChar(buf, ')'); @@ -7373,13 +7425,17 @@ get_opclass_name(Oid opclass, Oid actual_datatype, * * We strip any top-level FieldStore or assignment ArrayRef nodes that * appear in the input, printing them as decoration for the base column - * name (which we assume the caller just printed). Return the subexpression - * that's to be assigned. + * name (which we assume the caller just printed). We might also need to + * strip CoerceToDomain nodes, but only ones that appear above assignment + * nodes. + * + * Returns the subexpression that's to be assigned. */ static Node * processIndirection(Node *node, deparse_context *context) { StringInfo buf = context->buf; + CoerceToDomain *cdomain = NULL; for (;;) { @@ -7404,7 +7460,7 @@ processIndirection(Node *node, deparse_context *context) */ Assert(list_length(fstore->fieldnums) == 1); fieldname = get_relid_attribute_name(typrelid, - linitial_int(fstore->fieldnums)); + linitial_int(fstore->fieldnums)); appendStringInfo(buf, ".%s", quote_identifier(fieldname)); /* @@ -7427,10 +7483,28 @@ processIndirection(Node *node, deparse_context *context) */ node = (Node *) aref->refassgnexpr; } + else if (IsA(node, CoerceToDomain)) + { + cdomain = (CoerceToDomain *) node; + /* If it's an explicit domain coercion, we're done */ + if (cdomain->coercionformat != COERCE_IMPLICIT_CAST) + break; + /* Tentatively descend past the CoerceToDomain */ + node = (Node *) cdomain->arg; + } else break; } + /* + * If we descended past a CoerceToDomain whose argument turned out not to + * be a FieldStore or array assignment, back up to the CoerceToDomain. + * (This is not enough to be fully correct if there are nested implicit + * CoerceToDomains, but such cases shouldn't ever occur.) + */ + if (cdomain && node == (Node *) cdomain->arg) + node = (Node *) cdomain; + return node; } @@ -7791,4 +7865,44 @@ generate_operator_name(Oid operid, Oid arg1, Oid arg2) return buf.data; } +/* + * get_one_range_partition_bound_string + * A C string representation of one range partition bound + */ +char * +get_range_partbound_string(List *bound_datums) +{ + deparse_context context; + StringInfo buf = makeStringInfo(); + ListCell *cell; + char *sep; + + memset(&context, 0, sizeof(deparse_context)); + context.buf = buf; + + appendStringInfoString(buf, "("); + sep = ""; + foreach(cell, bound_datums) + { + PartitionRangeDatum *datum = + castNode(PartitionRangeDatum, lfirst(cell)); + + appendStringInfoString(buf, sep); + if (datum->kind == PARTITION_RANGE_DATUM_MINVALUE) + appendStringInfoString(buf, "MINVALUE"); + else if (datum->kind == PARTITION_RANGE_DATUM_MAXVALUE) + appendStringInfoString(buf, "MAXVALUE"); + else + { + Const *val = castNode(Const, datum->value); + + get_const_expr(val, &context, -1); + } + sep = ", "; + } + appendStringInfoString(buf, ")"); + + return buf->data; +} + #endif /* (PG_VERSION_NUM >= 100000) */ diff --git a/src/backend/distributed/utils/ruleutils_96.c b/src/backend/distributed/utils/ruleutils_96.c index 0f19fe73e..99ff8bd83 100644 --- a/src/backend/distributed/utils/ruleutils_96.c +++ b/src/backend/distributed/utils/ruleutils_96.c @@ -386,6 +386,9 @@ static void get_rule_expr(Node *node, deparse_context *context, bool showimplicit); static void get_rule_expr_toplevel(Node *node, deparse_context *context, bool showimplicit); +static void get_rule_expr_funccall(Node *node, deparse_context *context, + bool showimplicit); +static bool looks_like_function(Node *node); static void get_oper_expr(OpExpr *expr, deparse_context *context); static void get_func_expr(FuncExpr *expr, deparse_context *context, bool showimplicit); @@ -3282,8 +3285,11 @@ get_update_query_targetlist_def(Query *query, List *targetList, /* * We must dig down into the expr to see if it's a PARAM_MULTIEXPR * Param. That could be buried under FieldStores and ArrayRefs - * (cf processIndirection()), and underneath those there could be - * an implicit type coercion. + * and CoerceToDomains (cf processIndirection()), and underneath + * those there could be an implicit type coercion. Because we + * would ignore implicit type coercions anyway, we don't need to + * be as careful as processIndirection() is about descending past + * implicit CoerceToDomains. */ expr = (Node *) tle->expr; while (expr) @@ -3302,6 +3308,14 @@ get_update_query_targetlist_def(Query *query, List *targetList, break; expr = (Node *) aref->refassgnexpr; } + else if (IsA(expr, CoerceToDomain)) + { + CoerceToDomain *cdomain = (CoerceToDomain *) expr; + + if (cdomain->coercionformat != COERCE_IMPLICIT_CAST) + break; + expr = (Node *) cdomain->arg; + } else break; } @@ -5763,6 +5777,63 @@ get_rule_expr_toplevel(Node *node, deparse_context *context, get_rule_expr(node, context, showimplicit); } +/* + * get_rule_expr_funccall - Parse back a function-call expression + * + * Same as get_rule_expr(), except that we guarantee that the output will + * look like a function call, or like one of the things the grammar treats as + * equivalent to a function call (see the func_expr_windowless production). + * This is needed in places where the grammar uses func_expr_windowless and + * you can't substitute a parenthesized a_expr. If what we have isn't going + * to look like a function call, wrap it in a dummy CAST() expression, which + * will satisfy the grammar --- and, indeed, is likely what the user wrote to + * produce such a thing. + */ +static void +get_rule_expr_funccall(Node *node, deparse_context *context, + bool showimplicit) +{ + if (looks_like_function(node)) + get_rule_expr(node, context, showimplicit); + else + { + StringInfo buf = context->buf; + + appendStringInfoString(buf, "CAST("); + /* no point in showing any top-level implicit cast */ + get_rule_expr(node, context, false); + appendStringInfo(buf, " AS %s)", + format_type_with_typemod(exprType(node), + exprTypmod(node))); + } +} + +/* + * Helper function to identify node types that satisfy func_expr_windowless. + * If in doubt, "false" is always a safe answer. + */ +static bool +looks_like_function(Node *node) +{ + if (node == NULL) + return false; /* probably shouldn't happen */ + switch (nodeTag(node)) + { + case T_FuncExpr: + /* OK, unless it's going to deparse as a cast */ + return (((FuncExpr *) node)->funcformat == COERCE_EXPLICIT_CALL); + case T_NullIfExpr: + case T_CoalesceExpr: + case T_MinMaxExpr: + case T_XmlExpr: + /* these are all accepted by func_expr_common_subexpr */ + return true; + default: + break; + } + return false; +} + /* * get_oper_expr - Parse back an OpExpr node @@ -6641,7 +6712,7 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context) if (list_length(rte->functions) == 1 && (rtfunc1->funccolnames == NIL || !rte->funcordinality)) { - get_rule_expr(rtfunc1->funcexpr, context, true); + get_rule_expr_funccall(rtfunc1->funcexpr, context, true); /* we'll print the coldeflist below, if it has one */ } else @@ -6704,7 +6775,7 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context) if (funcno > 0) appendStringInfoString(buf, ", "); - get_rule_expr(rtfunc->funcexpr, context, true); + get_rule_expr_funccall(rtfunc->funcexpr, context, true); if (rtfunc->funccolnames != NIL) { /* Reconstruct the column definition list */ @@ -6894,6 +6965,11 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context) if (!PRETTY_PAREN(context)) appendStringInfoChar(buf, ')'); } + else if (j->jointype != JOIN_INNER) + { + /* If we didn't say CROSS JOIN above, we must provide an ON */ + appendStringInfoString(buf, " ON TRUE"); + } if (!PRETTY_PAREN(context) || j->alias != NULL) appendStringInfoChar(buf, ')'); @@ -7090,13 +7166,17 @@ get_opclass_name(Oid opclass, Oid actual_datatype, * * We strip any top-level FieldStore or assignment ArrayRef nodes that * appear in the input, printing them as decoration for the base column - * name (which we assume the caller just printed). Return the subexpression - * that's to be assigned. + * name (which we assume the caller just printed). We might also need to + * strip CoerceToDomain nodes, but only ones that appear above assignment + * nodes. + * + * Returns the subexpression that's to be assigned. */ static Node * processIndirection(Node *node, deparse_context *context) { StringInfo buf = context->buf; + CoerceToDomain *cdomain = NULL; for (;;) { @@ -7144,10 +7224,28 @@ processIndirection(Node *node, deparse_context *context) */ node = (Node *) aref->refassgnexpr; } + else if (IsA(node, CoerceToDomain)) + { + cdomain = (CoerceToDomain *) node; + /* If it's an explicit domain coercion, we're done */ + if (cdomain->coercionformat != COERCE_IMPLICIT_CAST) + break; + /* Tentatively descend past the CoerceToDomain */ + node = (Node *) cdomain->arg; + } else break; } + /* + * If we descended past a CoerceToDomain whose argument turned out not to + * be a FieldStore or array assignment, back up to the CoerceToDomain. + * (This is not enough to be fully correct if there are nested implicit + * CoerceToDomains, but such cases shouldn't ever occur.) + */ + if (cdomain && node == (Node *) cdomain->arg) + node = (Node *) cdomain; + return node; }