From 2a1f3584c96a53e1530d497bd96414918258e3e5 Mon Sep 17 00:00:00 2001 From: Rafsun Masud Date: Tue, 19 Dec 2023 13:30:51 -0800 Subject: [PATCH] Mark null-returning RTEs in outer joins as nullable RTEs that appear in the right side of a left join are marked as 'nullable'. The column references to a nullable RTE within the join's RTE are also marked as 'nullable'. This concept is introduced in Postgresql v16. The change in Postgresql v16's pullup_replace_vars_callback() function causes different plans to be generated depending on whether appropriate RTEs are marked nullable. Without marking nullable, in a left join, any function call expression containing right RTE's columns as arguments are not evaluated at the scan level, rather it is evaluated after the join is performed. At that point, the function call may receive null input, which was unexpected in previous Postgresql versions. See: ---- - Postgresql v16's commit: Make Vars be outer-join-aware https://www.postgresql.org/message-id/830269.1656693747@sss.pgh.pa.us - The 'Vars and PlaceHolderVars' section in the Postgresql v16's optimizer/README.md --- src/backend/catalog/ag_label.c | 7 +-- src/backend/parser/cypher_clause.c | 72 +++++++++++++++++++++++++++++- src/backend/utils/adt/agtype.c | 6 --- 3 files changed, 75 insertions(+), 10 deletions(-) diff --git a/src/backend/catalog/ag_label.c b/src/backend/catalog/ag_label.c index dea7e4be4..bbaad5292 100644 --- a/src/backend/catalog/ag_label.c +++ b/src/backend/catalog/ag_label.c @@ -188,9 +188,10 @@ Datum _label_name(PG_FUNCTION_ARGS) uint32 label_id; if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) - PG_RETURN_NULL(); - //ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), - // errmsg("graph_oid and label_id must not be null"))); + { + ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("graph_oid and label_id must not be null"))); + } graph = PG_GETARG_OID(0); diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index c4201be23..facbe64ce 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -45,6 +45,7 @@ #include "parser/parse_relation.h" #include "parser/parse_target.h" #include "parser/parsetree.h" +#include "parser/parse_relation.h" #include "rewrite/rewriteHandler.h" #include "utils/typcache.h" #include "utils/lsyscache.h" @@ -331,6 +332,8 @@ static List *make_target_list_from_join(ParseState *pstate, RangeTblEntry *rte); static FuncExpr *make_clause_func_expr(char *function_name, Node *clause_information); +static void markRelsAsNulledBy(ParseState *pstate, Node *n, int jindex); + /* for VLE support */ static ParseNamespaceItem *transform_RangeFunction(cypher_parsestate *cpstate, RangeFunction *r); @@ -2472,8 +2475,17 @@ static void get_res_cols(ParseState *pstate, ParseNamespaceItem *l_pnsi, if (var == NULL) { + Var *v; + + /* + * Each join (left) RTE's Var, that references a column of the + * right RTE, needs to be marked 'nullable'. + */ + v = lfirst(r_lvar); + markNullableIfNeeded(pstate, v); + colnames = lappend(colnames, lfirst(r_lname)); - colvars = lappend(colvars, lfirst(r_lvar)); + colvars = lappend(colvars, v); } } @@ -2522,6 +2534,13 @@ static RangeTblEntry *transform_cypher_optional_match_clause(cypher_parsestate * j->rarg = transform_clause_for_join(cpstate, clause, &r_rte, &r_nsitem, r_alias); + /* + * Since this is a left join, we need to mark j->rarg as it may potentially + * emit NULL. The jindex argument holds rtindex of the join's RTE, which is + * created right after j->arg's RTE in this case. + */ + markRelsAsNulledBy(pstate, j->rarg, r_nsitem->p_rtindex + 1); + // we are done transform the lateral left join pstate->p_lateral_active = false; @@ -6377,6 +6396,13 @@ transform_merge_make_lateral_join(cypher_parsestate *cpstate, Query *query, j->rarg = transform_clause_for_join(cpstate, isolated_merge_clause, &r_rte, &r_nsitem, r_alias); + /* + * Since this is a left join, we need to mark j->rarg as it may potentially + * emit NULL. The jindex argument holds rtindex of the join's RTE, which is + * created right after j->arg's RTE in this case. + */ + markRelsAsNulledBy(pstate, j->rarg, r_nsitem->p_rtindex + 1); + // deactivate the lateral flag pstate->p_lateral_active = false; @@ -7095,6 +7121,50 @@ static FuncExpr *make_clause_func_expr(char *function_name, return func_expr; } +/* + * This function is borrowed from PG version 16.1. + * + * It is used in transformations involving left join in Optional Match and + * Merge in a similar way PG16's transformFromClauseItem() uses it. + */ +static void markRelsAsNulledBy(ParseState *pstate, Node *n, int jindex) +{ + int varno; + ListCell *lc; + + /* Note: we can't see FromExpr here */ + if (IsA(n, RangeTblRef)) + { + varno = ((RangeTblRef *) n)->rtindex; + } + else if (IsA(n, JoinExpr)) + { + JoinExpr *j = (JoinExpr *) n; + + /* recurse to children */ + markRelsAsNulledBy(pstate, j->larg, jindex); + markRelsAsNulledBy(pstate, j->rarg, jindex); + varno = j->rtindex; + } + else + { + elog(ERROR, "unrecognized node type: %d", (int) nodeTag(n)); + varno = 0; /* keep compiler quiet */ + } + + /* + * Now add jindex to the p_nullingrels set for relation varno. Since we + * maintain the p_nullingrels list lazily, we might need to extend it to + * make the varno'th entry exist. + */ + while (list_length(pstate->p_nullingrels) < varno) + { + pstate->p_nullingrels = lappend(pstate->p_nullingrels, NULL); + } + lc = list_nth_cell(pstate->p_nullingrels, varno - 1); + lfirst(lc) = bms_add_member((Bitmapset *) lfirst(lc), jindex); +} + /* * Utility function that helps a clause add the information needed to * the query from the previous clause. diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index 6dd02680d..ce0d1bb0e 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -2193,12 +2193,9 @@ Datum _agtype_build_vertex(PG_FUNCTION_ARGS) /* handles null */ if (fcinfo->args[0].isnull) { - /* ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("_agtype_build_vertex() graphid cannot be NULL"))); - */ - PG_RETURN_NULL(); } if (fcinfo->args[1].isnull) @@ -2267,12 +2264,9 @@ Datum _agtype_build_edge(PG_FUNCTION_ARGS) /* process graph id */ if (fcinfo->args[0].isnull) { - PG_RETURN_NULL(); - /* ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("_agtype_build_edge() graphid cannot be NULL"))); - */ } id = AG_GETARG_GRAPHID(0);