Skip to content

Commit

Permalink
Fix behavioural differences due to return type and handle constant st…
Browse files Browse the repository at this point in the history
…ring literal inputs differently for COALESCE function (babelfish-for-postgresql#2533)

This change adds a new function select_common_type_for_coalesce_function to choose the return datatype which will be called from tsql_select_common_type_hook whenever the caller function is T-SQL COALESCE.
Issues Resolved

[BABEL-726]

Engine PR : babelfish-for-postgresql/postgresql_modified_for_babelfish#345

Signed-off-by: Sai Rohan Basa [email protected]
  • Loading branch information
basasairohan authored Apr 25, 2024
1 parent 81c59da commit b80c264
Show file tree
Hide file tree
Showing 24 changed files with 1,199 additions and 45 deletions.
44 changes: 0 additions & 44 deletions contrib/babelfishpg_tsql/src/hooks.c
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,6 @@ static void insert_pltsql_function_defaults(HeapTuple func_tuple, List *defaults
static int print_pltsql_function_arguments(StringInfo buf, HeapTuple proctup, bool print_table_args, bool print_defaults);
static void pltsql_GetNewObjectId(VariableCache variableCache);
static void pltsql_validate_var_datatype_scale(const TypeName *typeName, Type typ);
static Oid select_common_type_for_isnull(ParseState *pstate, List *exprs);

/*****************************************
* Executor Hooks
Expand Down Expand Up @@ -326,8 +325,6 @@ InstallExtendedHooks(void)
prev_drop_relation_refcnt_hook = drop_relation_refcnt_hook;
drop_relation_refcnt_hook = pltsql_drop_relation_refcnt_hook;

select_common_type_hook = select_common_type_for_isnull;

bbf_InitializeParallelDSM_hook = babelfixedparallelstate_insert;
bbf_ParallelWorkerMain_hook = babelfixedparallelstate_restore;
}
Expand Down Expand Up @@ -3709,44 +3706,3 @@ sort_nulls_first(SortGroupClause * sortcl, bool reverse)
sortcl->nulls_first = !reverse;
}
}

/*
* select_common_type_for_isnull - Deduce common data type for ISNULL(check_expression , replacement_value)
* function.
* This function should return same as check_expression. If that expression is NULL then reyurn the data type of
* replacement_value. If replacement_value is also NULL then return INT.
*/
static Oid
select_common_type_for_isnull(ParseState *pstate, List *exprs)
{
Node *pexpr;
Oid ptype;

Assert(exprs != NIL);
pexpr = (Node *) linitial(exprs);
ptype = exprType(pexpr);

/* Check if first arg (check_expression) is NULL literal */
if (IsA(pexpr, Const) && ((Const *) pexpr)->constisnull && ptype == UNKNOWNOID)
{
Node *nexpr = (Node *) lfirst(list_second_cell(exprs));
Oid ntype = exprType(nexpr);
/* Check if second arg (replace_expression) is NULL literal */
if (IsA(nexpr, Const) && ((Const *) nexpr)->constisnull && ntype == UNKNOWNOID)
{
return INT4OID;
}
/* If second argument is non-null string literal */
if (ntype == UNKNOWNOID)
{
return get_sys_varcharoid();
}
return ntype;
}
/* If first argument is non-null string literal */
if (ptype == UNKNOWNOID)
{
return get_sys_varcharoid();
}
return ptype;
}
159 changes: 159 additions & 0 deletions contrib/babelfishpg_tsql/src/pltsql_coerce.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ extern bool babelfish_dump_restore;
PG_FUNCTION_INFO_V1(init_tsql_coerce_hash_tab);
PG_FUNCTION_INFO_V1(init_tsql_datatype_precedence_hash_tab);

static Oid select_common_type_for_isnull(ParseState *pstate, List *exprs);
static Oid tsql_select_common_type_hook(ParseState *pstate, List *exprs, const char *context);
static Oid select_common_type_for_coalesce_function(ParseState *pstate, List *exprs);
static Node* tsql_handle_constant_literals_hook(ParseState *pstate, Node *e);

/* Memory Context */
static MemoryContext pltsql_coercion_context = NULL;

Expand Down Expand Up @@ -1152,6 +1157,8 @@ init_tsql_datatype_precedence_hash_tab(PG_FUNCTION_ARGS)
determine_datatype_precedence_hook = tsql_has_higher_precedence;
func_select_candidate_hook = tsql_func_select_candidate;
coerce_string_literal_hook = tsql_coerce_string_literal_hook;
select_common_type_hook = tsql_select_common_type_hook;
handle_constant_literals_hook = tsql_handle_constant_literals_hook;

if (!OidIsValid(sys_nspoid))
PG_RETURN_INT32(0);
Expand Down Expand Up @@ -1526,3 +1533,155 @@ pltsql_bpchar_name(PG_FUNCTION_ARGS)

PG_RETURN_NAME(result);
}

static Oid
tsql_select_common_type_hook(ParseState *pstate, List *exprs, const char *context)
{
if (sql_dialect != SQL_DIALECT_TSQL)
return InvalidOid;
if (!context)
return InvalidOid;
else if (strncmp(context, "ISNULL", strlen("ISNULL")) == 0)
return select_common_type_for_isnull(pstate, exprs);
else if(strncmp(context, "TSQL_COALESCE", strlen("TSQL_COALESCE")) == 0)
return select_common_type_for_coalesce_function(pstate, exprs);

return InvalidOid;
}

/*
* select_common_type_for_isnull - Deduce common data type for ISNULL(check_expression , replacement_value)
* function.
* This function should return same as check_expression. If that expression is NULL then reyurn the data type of
* replacement_value. If replacement_value is also NULL then return INT.
*/
static Oid
select_common_type_for_isnull(ParseState *pstate, List *exprs)
{
Node *pexpr;
Oid ptype;

Assert(exprs != NIL);
pexpr = (Node *) linitial(exprs);
ptype = exprType(pexpr);

/* Check if first arg (check_expression) is NULL literal */
if (IsA(pexpr, Const) && ((Const *) pexpr)->constisnull && ptype == UNKNOWNOID)
{
Node *nexpr = (Node *) lfirst(list_second_cell(exprs));
Oid ntype = exprType(nexpr);
/* Check if second arg (replace_expression) is NULL literal */
if (IsA(nexpr, Const) && ((Const *) nexpr)->constisnull && ntype == UNKNOWNOID)
{
return INT4OID;
}
/* If second argument is non-null string literal */
if (ntype == UNKNOWNOID)
{
return get_sys_varcharoid();
}
return ntype;
}
/* If first argument is non-null string literal */
if (ptype == UNKNOWNOID)
{
return get_sys_varcharoid();
}
return ptype;
}

static Oid
select_common_type_for_coalesce_function(ParseState *pstate, List *exprs)
{
Node *pexpr;
Oid ptype;
ListCell *lc;
Oid commontype = InvalidOid;
int curr_precedence = INT_MAX, temp_precedence = 0;

Assert(exprs != NIL);

if (exprs->length < 2)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("COALESCE function should have at least 2 arguments")));

foreach(lc, exprs)
{
pexpr = (Node *) lfirst(lc);
ptype = exprType(pexpr);

/* Check if arg is NULL literal */
if (IsA(pexpr, Const) && ((Const *) pexpr)->constisnull)
continue;

/* If the arg is non-null string literal */
if (ptype == UNKNOWNOID)
{
Oid curr_oid = get_sys_varcharoid();
temp_precedence = tsql_get_type_precedence(curr_oid);
if (commontype == InvalidOid
|| temp_precedence < curr_precedence)
{
commontype = curr_oid;
curr_precedence = temp_precedence;
}

continue;
}

temp_precedence = tsql_get_type_precedence(ptype);

if (commontype == InvalidOid || temp_precedence < curr_precedence)
{
commontype = ptype;
curr_precedence = temp_precedence;
}
}

if (commontype == InvalidOid)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("At least one of the arguments to COALESCE must be a non-NULL constant")));

return commontype;
}

/*
* Hook to handle constant string literal inputs for
* COALESCE function. This function also handles empty and
* white space string literals.
*/
static Node*
tsql_handle_constant_literals_hook(ParseState *pstate, Node *e)
{
Const *con;
char *val;
int i = -1;

if (exprType(e) != UNKNOWNOID || !IsA(e, Const))
return e;

con = (Const *) e;
val = DatumGetCString(con->constvalue);

if (val != NULL)
i = strlen(val) - 1;

/*
* Additional handling for empty or white space string literals as
* T-SQL treats an empty string literal as 0 in certain datatypes
*/
for (; i >= 0; i--)
{
if (!isspace(val[i]))
break;
}

if (i != -1)
e = coerce_to_common_type(pstate, e,
VARCHAROID,
"COALESCE");

return e;
}
2 changes: 1 addition & 1 deletion test/JDBC/expected/BABEL-2089.out
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ go
select * from babel_2089_OpsDbView
go
~~START~~
int#!#nvarchar#!#nvarchar#!#nvarchar#!#nvarchar#!#datetime#!#datetime#!#nvarchar#!#nvarchar#!#nvarchar#!#int#!#int#!#nvarchar#!#int#!#int#!#nvarchar#!#int#!#int#!#int#!#int#!#varchar#!#datetime2#!#int#!#int#!#int#!#int#!#int#!#datetime2#!#int#!#varchar#!#varchar#!#int#!#nvarchar#!#int#!#varchar
int#!#nvarchar#!#nvarchar#!#nvarchar#!#nvarchar#!#datetime#!#datetime#!#nvarchar#!#nvarchar#!#nvarchar#!#int#!#int#!#nvarchar#!#int#!#int#!#nvarchar#!#int#!#int#!#int#!#int#!#varchar#!#datetime2#!#int#!#int#!#int#!#int#!#int#!#datetime2#!#int#!#varchar#!#varchar#!#int#!#nvarchar#!#int#!#nvarchar
~~END~~


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
DROP FUNCTION babel_726_f1
GO

DROP VIEW babel_726_v1
GO

DROP PROCEDURE babel_726_p1
GO
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
CREATE FUNCTION babel_726_f1(@a smallint, @b bit)
RETURNS BIT AS
BEGIN
DECLARE @ans BIT
SELECT @ans = COALESCE(@b, @a)
RETURN @ans
END
GO

CREATE VIEW babel_726_v1 AS SELECT COALESCE(CAST(1 as BIT), CAST(5 as SMALLINT))
GO

CREATE PROCEDURE babel_726_p1 AS SELECT COALESCE(CAST(1 as BIT), CAST(5 as SMALLINT))
GO

SELECT babel_726_f1(6,0)
GO
~~START~~
bit
0
~~END~~


SELECT * FROM babel_726_v1
GO
~~START~~
bit
1
~~END~~


EXEC babel_726_p1
GO
~~START~~
bit
1
~~END~~

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
SELECT babel_726_f1(6,0)
GO
~~START~~
bit
0
~~END~~


SELECT * FROM babel_726_v1
GO
~~START~~
bit
1
~~END~~


EXEC babel_726_p1
GO
~~START~~
smallint
1
~~END~~

Loading

0 comments on commit b80c264

Please sign in to comment.