Skip to content

Commit

Permalink
Adds JSON_TABLE() support
Browse files Browse the repository at this point in the history
PG17 has added basic JSON_TABLE() functionality
JSON_TABLE() allows JSON data to be converted into a relational view
and thus used, for example, in a FROM clause, like other tabular
data.

We treat JSON_TABLE the same as correlated functions (e.g., recurring tuples).
In the end, for multi-shard JSON_TABLE commands, we apply the same
restrictions as reference tables (e.g., cannot perform a lateral outer join
when a distributed subquery references a (reference table)/JSON_TABLE etc.)

Relevant PG commit:
postgres/postgres@de3600452

Onder had previously added json table support for PG15BETA1,
but we reverted that commit because json table was reverted in PG15.
ce7f1a5
Therefore, I referred to that commit for this commit as well,
with a few changes due to some differences between PG15/PG17:

1) In PG15Beta1, we had also PLAN clauses for JSON_TABLE, and Onder's commit
includes tests for those as well. However, PLAN nodes are not added in PG17.
Therefore I didn't include the json_table_select_only test, which had mostly
queries involving PLAN. I only included the last query from json_table_select_only
test.

2) In PG15 timeline (Citus 11.1), we didn't support outer joins where the
outer rel is a recurring one and the inner one is a non-recurring one.
However, Onur added support for that one in Citus 11.2, therefore I updated
the tests from Onder's commit accordingly.
  • Loading branch information
naisila committed Dec 27, 2024
1 parent b4cc721 commit 8cc75cb
Show file tree
Hide file tree
Showing 7 changed files with 977 additions and 6 deletions.
3 changes: 2 additions & 1 deletion src/backend/distributed/planner/multi_logical_planner.c
Original file line number Diff line number Diff line change
Expand Up @@ -1170,7 +1170,8 @@ HasComplexRangeTableType(Query *queryTree)
if (rangeTableEntry->rtekind != RTE_RELATION &&
rangeTableEntry->rtekind != RTE_SUBQUERY &&
rangeTableEntry->rtekind != RTE_FUNCTION &&
rangeTableEntry->rtekind != RTE_VALUES)
rangeTableEntry->rtekind != RTE_VALUES &&
!IsJsonTableRTE(rangeTableEntry))
{
hasComplexRangeTableType = true;
}
Expand Down
56 changes: 52 additions & 4 deletions src/backend/distributed/planner/query_pushdown_planning.c
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ typedef enum RecurringTuplesType
RECURRING_TUPLES_FUNCTION,
RECURRING_TUPLES_EMPTY_JOIN_TREE,
RECURRING_TUPLES_RESULT_FUNCTION,
RECURRING_TUPLES_VALUES
RECURRING_TUPLES_VALUES,
RECURRING_TUPLES_JSON_TABLE
} RecurringTuplesType;

/*
Expand Down Expand Up @@ -347,7 +348,8 @@ IsFunctionOrValuesRTE(Node *node)
RangeTblEntry *rangeTblEntry = (RangeTblEntry *) node;

if (rangeTblEntry->rtekind == RTE_FUNCTION ||
rangeTblEntry->rtekind == RTE_VALUES)
rangeTblEntry->rtekind == RTE_VALUES ||
IsJsonTableRTE(rangeTblEntry))
{
return true;
}
Expand Down Expand Up @@ -700,6 +702,13 @@ DeferErrorIfFromClauseRecurs(Query *queryTree)
"the FROM clause contains VALUES", NULL,
NULL);
}
else if (recurType == RECURRING_TUPLES_JSON_TABLE)
{
return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED,
"correlated subqueries are not supported when "
"the FROM clause contains JSON_TABLE", NULL,
NULL);
}


/*
Expand Down Expand Up @@ -1204,7 +1213,8 @@ DeferErrorIfUnsupportedTableCombination(Query *queryTree)
*/
if (rangeTableEntry->rtekind == RTE_RELATION ||
rangeTableEntry->rtekind == RTE_SUBQUERY ||
rangeTableEntry->rtekind == RTE_RESULT)
rangeTableEntry->rtekind == RTE_RESULT ||
IsJsonTableRTE(rangeTableEntry))
{
/* accepted */
}
Expand Down Expand Up @@ -1372,6 +1382,13 @@ DeferErrorIfUnsupportedUnionQuery(Query *subqueryTree)
"VALUES is not supported within a "
"UNION", NULL);
}
else if (recurType == RECURRING_TUPLES_JSON_TABLE)
{
return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED,
"cannot push down this subquery",
"JSON_TABLE is not supported within a "
"UNION", NULL);
}

return NULL;
}
Expand Down Expand Up @@ -1477,6 +1494,11 @@ RecurringTypeDescription(RecurringTuplesType recurType)
return "a VALUES clause";
}

case RECURRING_TUPLES_JSON_TABLE:
{
return "a JSON_TABLE";
}

case RECURRING_TUPLES_INVALID:
{
/*
Expand Down Expand Up @@ -1673,7 +1695,8 @@ DeferredErrorIfUnsupportedLateralSubquery(PlannerInfo *plannerInfo,
* strings anyway.
*/
if (recurType != RECURRING_TUPLES_VALUES &&
recurType != RECURRING_TUPLES_RESULT_FUNCTION)
recurType != RECURRING_TUPLES_RESULT_FUNCTION &&
recurType != RECURRING_TUPLES_JSON_TABLE)
{
recurTypeDescription = psprintf("%s (%s)", recurTypeDescription,
recurringRangeTableEntry->eref->
Expand Down Expand Up @@ -1750,6 +1773,26 @@ ContainsRecurringRangeTable(List *rangeTable, RecurringTuplesType *recurType)
}


/*
* IsJsonTableRTE checks whether the RTE refers to a JSON_TABLE
* table function, which was introduced in PostgreSQL 15.
*/
bool
IsJsonTableRTE(RangeTblEntry *rte)
{
#if PG_VERSION_NUM >= PG_VERSION_17
if (rte == NULL)
{
return false;
}
return (rte->rtekind == RTE_TABLEFUNC &&
rte->tablefunc->functype == TFT_JSON_TABLE);
#endif

return false;
}


/*
* HasRecurringTuples returns whether any part of the expression will generate
* the same set of tuples in every query on shards when executing a distributed
Expand Down Expand Up @@ -1811,6 +1854,11 @@ HasRecurringTuples(Node *node, RecurringTuplesType *recurType)
*recurType = RECURRING_TUPLES_VALUES;
return true;
}
else if (IsJsonTableRTE(rangeTableEntry))
{
*recurType = RECURRING_TUPLES_JSON_TABLE;
return true;
}

return false;
}
Expand Down
1 change: 1 addition & 0 deletions src/include/distributed/query_pushdown_planning.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ extern DeferredErrorMessage * DeferErrorIfCannotPushdownSubquery(Query *subquery
bool
outerMostQueryHasLimit);
extern DeferredErrorMessage * DeferErrorIfUnsupportedUnionQuery(Query *queryTree);
extern bool IsJsonTableRTE(RangeTblEntry *rte);


#endif /* QUERY_PUSHDOWN_PLANNING_H */
Loading

0 comments on commit 8cc75cb

Please sign in to comment.