Skip to content

Commit

Permalink
Add support for subselect statments in FOR JSON AUTO (#2275)
Browse files Browse the repository at this point in the history
This commit adds support for sub select statements in FOR JSON AUTO.


task: babel-4701
Signed-off-by: Jake Owen <[email protected]>
  • Loading branch information
Jakeowen1 authored Jan 26, 2024
1 parent 0a141e4 commit ec5b520
Show file tree
Hide file tree
Showing 10 changed files with 416 additions and 68 deletions.
2 changes: 1 addition & 1 deletion contrib/babelfishpg_tds/error_mapping.txt
Original file line number Diff line number Diff line change
Expand Up @@ -185,5 +185,5 @@ XX000 ERRCODE_INTERNAL_ERROR "The table-valued parameter \"%s\" must be declared
22023 ERRCODE_INVALID_PARAMETER_VALUE "The datepart %s is not supported by date function %s for data type %s." SQL_ERROR_9810 16
22008 ERRCODE_DATETIME_VALUE_OUT_OF_RANGE "Adding a value to a \'%s\' column caused an overflow." SQL_ERROR_517 16
42P01 ERRCODE_UNDEFINED_TABLE "FOR JSON AUTO requires at least one table for generating JSON objects. Use FOR JSON PATH or add a FROM clause with a table name." SQL_ERROR_13600 16
42P01 ERRCODE_FEATURE_NOT_SUPPORTED "Values for json auto is not currently supported." SQL_ERROR_13600 16
42P01 ERRCODE_FEATURE_NOT_SUPPORTED "sub-select and values for json auto are not currently supported." SQL_ERROR_13600 16

152 changes: 87 additions & 65 deletions contrib/babelfishpg_tsql/src/pl_handler.c
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,11 @@ typedef struct {
int nestLevel;
} forjson_table;

static bool handleForJsonAuto(Query *query);
static bool handleForJsonAuto(Query *query, forjson_table **tableInfoArr, int numTables);
static bool isJsonAuto(List* target);
static bool check_json_auto_walker(Node *node, ParseState *pstate);
static TargetEntry* buildJsonEntry(forjson_table *table, TargetEntry* te);
static TargetEntry* buildJsonEntry(int nestLevel, char* tableAlias, TargetEntry* te);
static void modifyColumnEntries(List* targetList, forjson_table **tableInfoArr, int numTables, Alias *colnameAlias, bool isCve);

extern bool pltsql_ansi_defaults;
extern bool pltsql_quoted_identifier;
Expand Down Expand Up @@ -1344,7 +1345,7 @@ pltsql_post_parse_analyze(ParseState *pstate, Query *query, JumbleState *jstate)
}

static bool
handleForJsonAuto(Query *query)
handleForJsonAuto(Query *query, forjson_table **tableInfoArr, int numTables)
{
Query* subq;
List* target = query->targetList;
Expand All @@ -1356,11 +1357,8 @@ handleForJsonAuto(Query *query)
RangeTblEntry* subqRte;
RangeTblEntry* queryRte;
Alias *colnameAlias;
forjson_table **tableInfoArr;
int numTables = 0;
int currTables = 0;
int currMax = 0;
int i = 0;
int newTables = 0;
int currTables = numTables;

if(!isJsonAuto(target))
return false;
Expand All @@ -1374,24 +1372,30 @@ handleForJsonAuto(Query *query)
if(subq != NULL && (subq->cteList == NULL || list_length(subq->cteList) == 0)) {
subqRtable = (List*) subq->rtable;
if(subqRtable != NULL && list_length(subqRtable) > 0) {
forjson_table **tempArr;
foreach(lc, subqRtable) {
subqRte = castNode(RangeTblEntry, lfirst(lc));
if(subqRte->rtekind == RTE_RELATION) {
numTables++;
newTables++;
} else if(subqRte->rtekind == RTE_SUBQUERY) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Values for json auto is not currently supported ")));
errmsg("sub-select and values for json auto are not currently supported.")));
}
}

if(numTables == 0) {
if(numTables + newTables == 0) {
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("FOR JSON AUTO requires at least one table for generating JSON objects. Use FOR JSON PATH or add a FROM clause with a table name.")));
}

tableInfoArr = malloc(numTables * sizeof(forjson_table));
tempArr = palloc((numTables + newTables) * sizeof(forjson_table));
for(int j = 0; j < numTables; j++) {
tempArr[j] = tableInfoArr[j];
}
tableInfoArr = tempArr;
tempArr = NULL;
queryRte = linitial_node(RangeTblEntry, query->rtable);
colnameAlias = (Alias*) queryRte->eref;

Expand All @@ -1407,52 +1411,43 @@ handleForJsonAuto(Query *query)
currTables++;
}
}

foreach(lc, subq->targetList) {
TargetEntry* te = castNode(TargetEntry, lfirst(lc));
int oid = te->resorigtbl;
for(int j = 0; j < numTables; j++) {
if(tableInfoArr[j]->oid == oid) {
// build entry
String* s = castNode(String, lfirst(list_nth_cell(colnameAlias->colnames, i)));
if(tableInfoArr[j]->nestLevel == -1) {
currMax++;
tableInfoArr[j]->nestLevel = currMax;
}
te = buildJsonEntry(tableInfoArr[j], te);
s->sval = te->resname;
break;
}
}
i++;
}
free(tableInfoArr);
numTables = numTables + newTables;
modifyColumnEntries(subq->targetList, tableInfoArr, numTables, colnameAlias, false);
return true;
}
} else if(subq->cteList != NULL && list_length(subq->cteList) > 0) {
Query* ctequery;
CommonTableExpr* cte;
forjson_table **tempArr;
foreach(lc, subq->cteList) {
cte = castNode(CommonTableExpr, lfirst(lc));
ctequery = (Query*) cte->ctequery;
foreach(lc2, ctequery->rtable) {
subqRte = castNode(RangeTblEntry, lfirst(lc2));
if(subqRte->rtekind == RTE_RELATION)
numTables++;
newTables++;
}
}

if(numTables == 0) {
if(newTables == 0) {
forjson_table *table = palloc(sizeof(forjson_table));
tableInfoArr = malloc(sizeof(forjson_table));
tempArr = palloc((numTables + 1) * sizeof(forjson_table));
table->oid = 0;
table->nestLevel = -1;
table->alias = "cteplaceholder";
tableInfoArr[numTables] = table;
numTables++;
tempArr[numTables] = table;
newTables++;
} else {
tableInfoArr = malloc(numTables * sizeof(forjson_table));
tempArr = palloc((numTables + newTables) * sizeof(forjson_table));
}

for(int j = 0; j < numTables; j++) {
tempArr[j] = tableInfoArr[j];
}

tableInfoArr = tempArr;
tempArr = NULL;
numTables = numTables + newTables;
queryRte = linitial_node(RangeTblEntry, query->rtable);
colnameAlias = (Alias*) queryRte->eref;

Expand All @@ -1472,26 +1467,9 @@ handleForJsonAuto(Query *query)
}
}
}

modifyColumnEntries(subq->targetList, tableInfoArr, numTables, colnameAlias, true);

foreach(lc, subq->targetList) {
TargetEntry* te = castNode(TargetEntry, lfirst(lc));
int oid = te->resorigtbl;
for(int j = 0; j < numTables; j++) {
if(tableInfoArr[j]->oid == oid) {
// build entry
String* s = castNode(String, lfirst(list_nth_cell(colnameAlias->colnames, i)));
if(tableInfoArr[j]->nestLevel == -1) {
currMax++;
tableInfoArr[j]->nestLevel = currMax;
}
te = buildJsonEntry(tableInfoArr[j], te);
s->sval = te->resname;
break;
}
}
i++;
}
free(tableInfoArr);
return true;
}
}
Expand Down Expand Up @@ -1531,35 +1509,79 @@ isJsonAuto(List* target)
}

static TargetEntry*
buildJsonEntry(forjson_table *table, TargetEntry* te)
buildJsonEntry(int nestLevel, char* tableAlias, TargetEntry* te)
{
char nest[NAMEDATALEN]; // check size appropriate
StringInfo new_resname = makeStringInfo();
sprintf(nest, "%d", table->nestLevel);
sprintf(nest, "%d", nestLevel);
// Adding JSONAUTOALIAS prevents us from modifying
// a column more than once
if(!strcmp(te->resname, "\?column\?")) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("column expressions and data sources without names or aliases cannot be formatted as JSON text using FOR JSON clause. Add alias to the unnamed column or table")));
}
if(!strncmp(te->resname, "JSONAUTOALIAS", 13))
return te;
}
appendStringInfoString(new_resname, "JSONAUTOALIAS.");
appendStringInfoString(new_resname, nest);
appendStringInfoChar(new_resname, '.');
appendStringInfoString(new_resname, table->alias);
appendStringInfoString(new_resname, tableAlias);
appendStringInfoChar(new_resname, '.');
appendStringInfoString(new_resname, te->resname);
te->resname = new_resname->data;
return te;
}

static bool check_json_auto_walker(Node *node, ParseState *pstate) {
static void modifyColumnEntries(List* targetList, forjson_table **tableInfoArr, int numTables, Alias *colnameAlias, bool isCve)
{
int i = 0;
int currMax = 0;
ListCell* lc;
foreach(lc, targetList) {
TargetEntry* te = castNode(TargetEntry, lfirst(lc));
int oid = te->resorigtbl;
String* s = castNode(String, lfirst(list_nth_cell(colnameAlias->colnames, i)));
if(te->expr != NULL && nodeTag(te->expr) == T_SubLink) {
SubLink *sl = castNode(SubLink, te->expr);
if(sl->subselect != NULL && nodeTag(sl->subselect) == T_Query) {
if(handleForJsonAuto(castNode(Query, sl->subselect), tableInfoArr, numTables)) {
CoerceViaIO *iocoerce = makeNode(CoerceViaIO);
iocoerce->arg = (Expr*) sl;
iocoerce->resulttype = T_JsonArrayQueryConstructor;
iocoerce->resultcollid = 0;
iocoerce->coerceformat = COERCE_EXPLICIT_CAST;
buildJsonEntry(1, "temp", te);
s->sval = te->resname;
te->expr = (Expr*) iocoerce;
continue;
}
}
}
for(int j = 0; j < numTables; j++) {
if(tableInfoArr[j]->oid == oid) {
// build entry
if(tableInfoArr[j]->nestLevel == -1) {
currMax++;
tableInfoArr[j]->nestLevel = currMax;
}
te = buildJsonEntry(tableInfoArr[j]->nestLevel, tableInfoArr[j]->alias, te);
s->sval = te->resname;
break;
} else if(!isCve && oid == 0 && j == numTables - 1) {
te = buildJsonEntry(1, "temp", te);
s->sval = te->resname;
break;
}
}
i++;
}
}

static bool check_json_auto_walker(Node *node, ParseState *pstate)
{
if (node == NULL)
return false;
if (IsA(node, Query)) {
if(handleForJsonAuto((Query*) node))
if(handleForJsonAuto((Query*) node, NULL, 0))
return true;
else {
return query_tree_walker((Query*) node,
Expand Down
5 changes: 5 additions & 0 deletions contrib/babelfishpg_tsql/src/tsql_for/forjson.c
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,11 @@ tsql_auto_row_to_json(JsonbValue* jsonbArray, Datum record, bool include_null_va

// Determine if the value should be inserted as a nested json object
parts = determine_parts(colname, &num);
if(strcmp(parts[0], "JSONAUTOALIAS") != 0) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("sub-select and values for json auto are not currently supported.")));
}
colname = remove_index_and_alias(colname);
nestedVal = value;

Expand Down
2 changes: 1 addition & 1 deletion test/JDBC/expected/TestErrorHelperFunctions.out
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ XX000#!#The table-valued parameter "%s" must be declared with the READONLY optio
22023#!#The datepart %s is not supported by date function %s for data type %s.#!##!#9810
22008#!#Adding a value to a '%s' column caused an overflow.#!##!#517
42P01#!#FOR JSON AUTO requires at least one table for generating JSON objects. Use FOR JSON PATH or add a FROM clause with a table name.#!##!#13600
42P01#!#Values for json auto is not currently supported.#!##!#13600
42P01#!#sub-select and values for json auto are not currently supported.#!##!#13600
~~END~~


Expand Down
29 changes: 29 additions & 0 deletions test/JDBC/expected/forjsonauto-vu-cleanup.out
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,35 @@ GO
DROP PROCEDURE forjson_vu_p_5
GO

DROP PROCEDURE forjson_vu_p_6
GO

DROP PROCEDURE forjson_vu_p_7
GO

DROP PROCEDURE forjson_vu_p_8
GO

DROP PROCEDURE forjson_vu_p_9
GO

DROP PROCEDURE forjson_vu_p_10
GO

DROP PROCEDURE forjson_vu_p_11
GO

DROP PROCEDURE forjson_vu_p_12
GO

DROP PROCEDURE forjson_vu_p_13
GO

DROP PROCEDURE forjson_vu_p_14
GO

DROP PROCEDURE forjson_vu_p_15
GO

DROP FUNCTION forjson_vu_f_1()
GO
Expand Down
Loading

0 comments on commit ec5b520

Please sign in to comment.