Skip to content

Commit

Permalink
Fix formatting issue in CAST MONEY to CHAR/VARCHAR (babelfish-for-pos…
Browse files Browse the repository at this point in the history
…tgresql#3367)

While CASTing money/smallmoney to (N)VARCHAR/(N)CHAR, the value get rounded
to 4 decimal places instead of 2. Fixed this by implementing CASTs from money to
CHAR/VARCHAR datatype which rounds off input money value to 2 decimal places and
converts it to string.

Additionally, while CASTing money values to (N)CHAR, the resultant string gets left padded
instead of usual right padding. This commit also fixes this issue.

Task: BABEL-5478
Signed-off-by: Rishabh Tanwar <[email protected]>
  • Loading branch information
rishabhtanwar29 authored and ritanwar committed Jan 13, 2025
1 parent fb39d3b commit 34cb510
Show file tree
Hide file tree
Showing 101 changed files with 1,502 additions and 1,233 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,27 @@ FOR TYPE sys.SMALLDATETIME USING btree FAMILY smalldatetime_ops AS
END IF;
END $$;

CREATE OR REPLACE FUNCTION sys.fixeddecimal2varchar(sys.FIXEDDECIMAL, integer, BOOLEAN)
RETURNS sys.VARCHAR
AS 'babelfishpg_common', 'fixeddecimal2varchar'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE OR REPLACE FUNCTION sys.fixeddecimal2bpchar(sys.FIXEDDECIMAL, integer, BOOLEAN)
RETURNS sys.BPCHAR
AS 'babelfishpg_common', 'fixeddecimal2bpchar'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE CAST (sys.FIXEDDECIMAL AS sys.VARCHAR)
WITH FUNCTION sys.fixeddecimal2varchar(sys.FIXEDDECIMAL, integer, BOOLEAN) AS IMPLICIT;

CREATE CAST (sys.FIXEDDECIMAL AS pg_catalog.VARCHAR)
WITH FUNCTION sys.fixeddecimal2varchar(sys.FIXEDDECIMAL, integer, BOOLEAN) AS IMPLICIT;

CREATE CAST (sys.FIXEDDECIMAL AS sys.BPCHAR)
WITH FUNCTION sys.fixeddecimal2bpchar(sys.FIXEDDECIMAL, integer, BOOLEAN) AS IMPLICIT;

CREATE CAST (sys.FIXEDDECIMAL AS pg_catalog.BPCHAR)
WITH FUNCTION sys.fixeddecimal2bpchar(sys.FIXEDDECIMAL, integer, BOOLEAN) AS IMPLICIT;

-- Reset search_path to not affect any subsequent scripts
SELECT set_config('search_path', trim(leading 'sys, ' from current_setting('search_path')), false);
22 changes: 22 additions & 0 deletions contrib/babelfishpg_common/sql/varchar.sql
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,28 @@ LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE CAST (sys.VARCHAR AS pg_catalog.NUMERIC)
WITH FUNCTION sys.varchar2numeric(sys.VARCHAR) AS IMPLICIT;

CREATE OR REPLACE FUNCTION sys.fixeddecimal2varchar(sys.FIXEDDECIMAL, integer, BOOLEAN)
RETURNS sys.VARCHAR
AS 'babelfishpg_common', 'fixeddecimal2varchar'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE OR REPLACE FUNCTION sys.fixeddecimal2bpchar(sys.FIXEDDECIMAL, integer, BOOLEAN)
RETURNS sys.BPCHAR
AS 'babelfishpg_common', 'fixeddecimal2bpchar'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE CAST (sys.FIXEDDECIMAL AS sys.VARCHAR)
WITH FUNCTION sys.fixeddecimal2varchar(sys.FIXEDDECIMAL, integer, BOOLEAN) AS IMPLICIT;

CREATE CAST (sys.FIXEDDECIMAL AS pg_catalog.VARCHAR)
WITH FUNCTION sys.fixeddecimal2varchar(sys.FIXEDDECIMAL, integer, BOOLEAN) AS IMPLICIT;

CREATE CAST (sys.FIXEDDECIMAL AS sys.BPCHAR)
WITH FUNCTION sys.fixeddecimal2bpchar(sys.FIXEDDECIMAL, integer, BOOLEAN) AS IMPLICIT;

CREATE CAST (sys.FIXEDDECIMAL AS pg_catalog.BPCHAR)
WITH FUNCTION sys.fixeddecimal2bpchar(sys.FIXEDDECIMAL, integer, BOOLEAN) AS IMPLICIT;

CREATE OR REPLACE FUNCTION sys.nvarchar_larger(sys.NVARCHAR, sys.NVARCHAR)
RETURNS sys.NVARCHAR
AS 'text_larger'
Expand Down
86 changes: 86 additions & 0 deletions contrib/babelfishpg_common/src/varchar.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ static inline int varcharTruelen(VarChar *arg);

#define DEFAULT_LCID 1033

/* Linkage to function in fixeddecimal module */
typedef char *(*fixeddecimal2str_t) (int64 val, char *buffer,
int64 fixeddecimal_multiplier,
int64 fixeddecimal_scale);
static fixeddecimal2str_t fixeddecimal2str_p = NULL;

/*
* is_basetype_nchar_nvarchar - given datatype is nvarchar or nchar
* or created over nvarchar or nchar.
Expand Down Expand Up @@ -382,6 +388,8 @@ PG_FUNCTION_INFO_V1(varchar2date);
PG_FUNCTION_INFO_V1(varchar2time);
PG_FUNCTION_INFO_V1(varchar2money);
PG_FUNCTION_INFO_V1(varchar2numeric);
PG_FUNCTION_INFO_V1(fixeddecimal2varchar);
PG_FUNCTION_INFO_V1(fixeddecimal2bpchar);

/*****************************************************************************
* varchar - varchar(n)
Expand Down Expand Up @@ -930,6 +938,84 @@ varchar2numeric(PG_FUNCTION_ARGS)
PG_RETURN_NUMERIC(result);
}

#define FIXEDDECIMAL_2_VARCHAR_MULTIPLIER 100LL
#define FIXEDDECIMAL_2_VARCHAR_SCALE 2
Datum
fixeddecimal2varchar(PG_FUNCTION_ARGS)
{
int64 val = PG_GETARG_INT64(0);
int32 maxByteLen = PG_GETARG_INT32(1);
char buf[MAXINT8LEN + 1];
char *end;
int32 len;
Datum res;

/* fetch function pointer for cross-module calls. */
if (fixeddecimal2str_p == NULL)
fixeddecimal2str_p = (fixeddecimal2str_t)
load_external_function("$libdir/babelfishpg_money", "fixeddecimal2str", true, NULL);

end = fixeddecimal2str_p(val, buf, FIXEDDECIMAL_2_VARCHAR_MULTIPLIER, FIXEDDECIMAL_2_VARCHAR_SCALE);
len = (end - buf);

if (maxByteLen < 0)
maxByteLen = len + VARHDRSZ;
maxByteLen -= VARHDRSZ;
if (len > maxByteLen)
ereport(ERROR,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("There is insufficient result space to convert a money/smallmoney value to varchar/nvarchar.")));

res = DirectFunctionCall3(varcharin,
CStringGetDatum(buf),
ObjectIdGetDatum(0),
Int32GetDatum(-1));

PG_RETURN_DATUM(res);
}

Datum
fixeddecimal2bpchar(PG_FUNCTION_ARGS)
{
int64 val = PG_GETARG_INT64(0);
int32 maxByteLen = PG_GETARG_INT32(1);
char buf[MAXINT8LEN + 1];
char *buf_padded;
char *end;
int32 len;
Datum res;

/* fetch function pointer for cross-module calls. */
if (fixeddecimal2str_p == NULL)
fixeddecimal2str_p = (fixeddecimal2str_t)
load_external_function("$libdir/babelfishpg_money", "fixeddecimal2str", true, NULL);

end = fixeddecimal2str_p(val, buf, FIXEDDECIMAL_2_VARCHAR_MULTIPLIER, FIXEDDECIMAL_2_VARCHAR_SCALE);
len = (end - buf);

if (maxByteLen < 0)
maxByteLen = len + VARHDRSZ;
maxByteLen -= VARHDRSZ;
if (len > maxByteLen)
ereport(ERROR,
(errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION),
errmsg("There is insufficient result space to convert a money/smallmoney value to varchar/nvarchar.")));

/* Left pad money value with the spaces */
buf_padded = (char *) palloc(maxByteLen + 1);
memset(buf_padded, ' ', maxByteLen - len);
memcpy(buf_padded + maxByteLen - len, buf, len);
buf_padded[maxByteLen] = '\0';
res = DirectFunctionCall3(bpcharin,
CStringGetDatum(buf_padded),
ObjectIdGetDatum(0),
Int32GetDatum(-1));

PG_RETURN_DATUM(res);
}
#undef FIXEDDECIMAL_2_VARCHAR_MULTIPLIER
#undef FIXEDDECIMAL_2_VARCHAR_SCALE

/*****************************************************************************
* bpchar - char() *
*****************************************************************************/
Expand Down
36 changes: 29 additions & 7 deletions contrib/babelfishpg_money/fixeddecimal.c
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,9 @@ static int64 scanfixeddecimal(const char *str, int *precision, int *scale);
static FixedDecimalAggState *makeFixedDecimalAggState(FunctionCallInfo fcinfo);
static void fixeddecimal_accum(FixedDecimalAggState *state, int64 newval);
static int64 int8fixeddecimal_internal(int64 arg, const char *typename);
extern PGDLLEXPORT char *fixeddecimal2str(int64 val, char *buffer,
int64 fixeddecimal_multiplier,
int64 fixeddecimal_scale);

/***********************************************************************
**
Expand Down Expand Up @@ -392,12 +395,31 @@ pg_int64tostr_zeropad(char *str, int64 value, int64 padding)
* Prints the fixeddecimal 'val' to buffer as a string.
* Returns a pointer to the end of the written string.
*/
static char *
fixeddecimal2str(int64 val, char *buffer)
char *
fixeddecimal2str(int64 val, char *buffer,
int64 fixeddecimal_multiplier,
int64 fixeddecimal_scale)
{
char *ptr = buffer;
int64 integralpart = val / FIXEDDECIMAL_MULTIPLIER;
int64 fractionalpart = val % FIXEDDECIMAL_MULTIPLIER;
int64 integralpart;
int64 fractionalpart;

/*
* By default, fixeddecimal contains 4 digits after decimal but if we only need
* 2 decimal points, remove last two decimal digits, round off remaining value.
*/
if (fixeddecimal_scale < 4)
{
int64 scale_factor = FIXEDDECIMAL_MULTIPLIER / (fixeddecimal_multiplier * 10);
int64 last_digit = abs(val / scale_factor) % 10;

val = val / fixeddecimal_multiplier;
if (last_digit >= 5)
val = val >= 0 ? val + 1LL : val - 1LL;
}

integralpart = val / fixeddecimal_multiplier;
fractionalpart = val % fixeddecimal_multiplier;

if (val < 0)
{
Expand All @@ -413,7 +435,7 @@ fixeddecimal2str(int64 val, char *buffer)
}
ptr = pg_int64tostr(ptr, integralpart);
*ptr++ = '.';
ptr = pg_int64tostr_zeropad(ptr, fractionalpart, FIXEDDECIMAL_SCALE);
ptr = pg_int64tostr_zeropad(ptr, fractionalpart, fixeddecimal_scale);
return ptr;
}

Expand Down Expand Up @@ -810,7 +832,7 @@ fixeddecimalout(PG_FUNCTION_ARGS)
{
int64 val = PG_GETARG_INT64(0);
char buf[MAXINT8LEN + 1];
char *end = fixeddecimal2str(val, buf);
char *end = fixeddecimal2str(val, buf, FIXEDDECIMAL_MULTIPLIER, FIXEDDECIMAL_SCALE);

PG_RETURN_CSTRING(pnstrdup(buf, end - buf));
}
Expand Down Expand Up @@ -2929,7 +2951,7 @@ fixeddecimalaggstateout(PG_FUNCTION_ARGS)
char buf[MAXINT8LEN + 1 + MAXINT8LEN + 1];
char *p;

p = fixeddecimal2str(state->sumX, buf);
p = fixeddecimal2str(state->sumX, buf, FIXEDDECIMAL_MULTIPLIER, FIXEDDECIMAL_SCALE);
*p++ = ':';
p = pg_int64tostr(p, state->N);

Expand Down
11 changes: 6 additions & 5 deletions contrib/babelfishpg_tsql/src/pltsql_coerce.c
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,11 @@ tsql_cast_raw_info_t tsql_cast_raw_infos[] =
{TSQL_CAST_ENTRY, "sys", "bpchar", "pg_catalog", "name", "bpchar_to_name", 'i', 'f'},
{TSQL_CAST_ENTRY, "pg_catalog", "varchar", "pg_catalog", "name", "varchar_to_name", 'i', 'f'},
{TSQL_CAST_ENTRY, "sys", "varchar", "pg_catalog", "name", "varchar_to_name", 'i', 'f'},
/* fixeddecimal */
{PG_CAST_ENTRY, "sys", "fixeddecimal", "pg_catalog", "bpchar", NULL, 'i', 'f'},
{PG_CAST_ENTRY, "sys", "fixeddecimal", "sys", "bpchar", NULL, 'i', 'f'},
{PG_CAST_ENTRY, "sys", "fixeddecimal", "pg_catalog", "varchar", NULL, 'i', 'f'},
{PG_CAST_ENTRY, "sys", "fixeddecimal", "sys", "varchar", NULL, 'i', 'f'},
/* string -> float8 via I/O */
{TSQL_CAST_WITHOUT_FUNC_ENTRY, "pg_catalog", "text", "pg_catalog", "float8", NULL, 'i', 'i'},
{TSQL_CAST_WITHOUT_FUNC_ENTRY, "pg_catalog", "bpchar", "pg_catalog", "float8", NULL, 'i', 'i'},
Expand Down Expand Up @@ -302,11 +307,7 @@ tsql_cast_raw_info_t tsql_cast_raw_infos[] =
{TSQL_CAST_WITHOUT_FUNC_ENTRY, "pg_catalog", "numeric", "sys", "varchar", NULL, 'i', 'i'},
/* // fixeddecimal -> string via I/O */
{TSQL_CAST_WITHOUT_FUNC_ENTRY, "sys", "fixeddecimal", "pg_catalog", "text", NULL, 'i', 'i'},
{TSQL_CAST_WITHOUT_FUNC_ENTRY, "sys", "fixeddecimal", "pg_catalog", "bpchar", NULL, 'i', 'i'},
{TSQL_CAST_WITHOUT_FUNC_ENTRY, "sys", "fixeddecimal", "sys", "bpchar", NULL, 'i', 'i'},
{TSQL_CAST_WITHOUT_FUNC_ENTRY, "sys", "fixeddecimal", "pg_catalog", "varchar", NULL, 'i', 'i'},
{TSQL_CAST_WITHOUT_FUNC_ENTRY, "sys", "fixeddecimal", "sys", "varchar", NULL, 'i', 'i'},
/* fixeddecimal -> string via I/O */
/* uniqueidentifier -> string via I/O */
{TSQL_CAST_WITHOUT_FUNC_ENTRY, "sys", "uniqueidentifier", "pg_catalog", "text", NULL, 'i', 'i'},
{TSQL_CAST_WITHOUT_FUNC_ENTRY, "sys", "uniqueidentifier", "pg_catalog", "bpchar", NULL, 'i', 'i'},
{TSQL_CAST_WITHOUT_FUNC_ENTRY, "sys", "uniqueidentifier", "sys", "bpchar", NULL, 'i', 'i'},
Expand Down
14 changes: 7 additions & 7 deletions test/JDBC/expected/BABEL-1193.out
Original file line number Diff line number Diff line change
Expand Up @@ -221,9 +221,9 @@ SELECT * FROM t14;
GO
~~START~~
varchar#!#varchar#!#varchar#!#varchar#!#varchar#!#varchar#!#varchar#!#varchar
12#!#4553#!#123456#!#12.345#!#2344.456#!#12.34#!#456.3334#!#1123.6777
-12#!#-1234#!#-123456#!#-12.345#!#-2344.456#!#12.34#!#-456.3334#!#-1123.6777
0#!#0#!#0#!#0#!#0#!#0.00#!#0.0000#!#0.0000
12#!#4553#!#123456#!#12.345#!#2344.456#!#12.34#!#456.33#!#1123.68
-12#!#-1234#!#-123456#!#-12.345#!#-2344.456#!#12.34#!#-456.33#!#-1123.68
0#!#0#!#0#!#0#!#0#!#0.00#!#0.00#!#0.00
~~END~~

-- numeric types -> char(10)
Expand All @@ -237,9 +237,9 @@ SELECT * FROM t15;
GO
~~START~~
char#!#char#!#char#!#char#!#char#!#char#!#char#!#char
12 #!#4553 #!#123456 #!#12.345 #!#2344.456 #!#12.34 #!#456.3334 #!#1123.6777
-12 #!#-1234 #!#-123456 #!#-12.345 #!#-2344.456 #!#12.34 #!#-456.3334 #!#-1123.6777
0 #!#0 #!#0 #!#0 #!#0 #!#0.00 #!#0.0000 #!#0.0000
12 #!#4553 #!#123456 #!#12.345 #!#2344.456 #!#12.34 #!# 456.33#!# 1123.68
-12 #!#-1234 #!#-123456 #!#-12.345 #!#-2344.456 #!#12.34 #!# -456.33#!# -1123.68
0 #!#0 #!#0 #!#0 #!#0 #!#0.00 #!# 0.00#!# 0.00
~~END~~


Expand All @@ -248,7 +248,7 @@ select upper(cast (1 as smallint)), upper(cast (1 as int)), upper(cast (1 as big
GO
~~START~~
varchar#!#varchar#!#varchar#!#varchar#!#varchar#!#varchar#!#varchar#!#varchar
1#!#1#!#1#!#1#!#1#!#2.1#!#1.0000#!#1.0000
1#!#1#!#1#!#1#!#1#!#2.1#!#1.00#!#1.00
~~END~~

select round(cast ('123' as text), 1), round(cast ('123' as char(3)), 1), round(cast ('123' as varchar(3)), 1);
Expand Down
4 changes: 2 additions & 2 deletions test/JDBC/expected/BABEL-3486-vu-verify.out
Original file line number Diff line number Diff line change
Expand Up @@ -1586,7 +1586,7 @@ SELECT * FROM BABEL_3486_vu_prepare_v63
GO
~~START~~
varchar
1.0000
1.00
~~END~~

DROP VIEW BABEL_3486_vu_prepare_v63
Expand All @@ -1596,7 +1596,7 @@ EXEC BABEL_3486_vu_prepare_p63
GO
~~START~~
varchar
1.0000
1.00
~~END~~

DROP PROCEDURE BABEL_3486_vu_prepare_p63
Expand Down
4 changes: 2 additions & 2 deletions test/JDBC/expected/BABEL-889-vu-verify.out
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ select cast(cast(cast('$123.123' as money) as sql_variant) as varchar);
go
~~START~~
varchar
123.1230
123.12
~~END~~

-- smallmoney
Expand All @@ -146,7 +146,7 @@ select cast(cast(cast('$123.123' as smallmoney) as sql_variant) as varchar);
go
~~START~~
varchar
123.1230
123.12
~~END~~

-- bigint
Expand Down
4 changes: 2 additions & 2 deletions test/JDBC/expected/BABEL-889.out
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ select cast(cast(cast('$123.123' as money) as sql_variant) as varchar);
go
~~START~~
varchar
123.1230
123.12
~~END~~

-- smallmoney
Expand All @@ -146,7 +146,7 @@ select cast(cast(cast('$123.123' as smallmoney) as sql_variant) as varchar);
go
~~START~~
varchar
123.1230
123.12
~~END~~

-- bigint
Expand Down
8 changes: 4 additions & 4 deletions test/JDBC/expected/babel_functions_cast.out
Original file line number Diff line number Diff line change
Expand Up @@ -127,14 +127,14 @@ select CAST(CAST(4936.56 AS MONEY) AS varchar(10));
GO
~~START~~
varchar
4936.5600
4936.56
~~END~~

select CAST(CAST(-4936.56 AS MONEY) AS varchar(10));
GO
~~START~~
varchar
-4936.5600
-4936.56
~~END~~

select CAST(4936.56 AS smallmoney);
Expand Down Expand Up @@ -955,14 +955,14 @@ select TRY_CAST(TRY_CAST(4936.56 AS MONEY) AS varchar(10));
GO
~~START~~
varchar
4936.5600
4936.56
~~END~~

select TRY_CAST(TRY_CAST(-4936.56 AS MONEY) AS varchar(10));
GO
~~START~~
varchar
-4936.5600
-4936.56
~~END~~

select TRY_CAST(4936.56 AS smallmoney);
Expand Down
Loading

0 comments on commit 34cb510

Please sign in to comment.