Skip to content

Commit

Permalink
Mark null-returning RTEs in outer joins as nullable
Browse files Browse the repository at this point in the history
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/[email protected]

  - The 'Vars and PlaceHolderVars' section in the Postgresql v16's
    optimizer/README.md
  • Loading branch information
rafsun42 committed Dec 19, 2023
1 parent fc7db23 commit 2a1f358
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 10 deletions.
7 changes: 4 additions & 3 deletions src/backend/catalog/ag_label.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
72 changes: 71 additions & 1 deletion src/backend/parser/cypher_clause.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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.
Expand Down
6 changes: 0 additions & 6 deletions src/backend/utils/adt/agtype.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 2a1f358

Please sign in to comment.