Skip to content

Commit 7840f56

Browse files
Support empty string handling for date and time datatypes (#3807)
Add TSQL-compatible handling of empty string ('') for date and time types, defaulting to '1900-01-01' for date and time, similar to datetime, datetime2, datetimeoffset, smalldatetime. Changes: 1. Add helper functions to initialize the default date and time variables and to check for strings with whitespaces. 2. Modify varchar2date() and varchar2time() to handle empty/whitespace casts from varchar types to date and time types. 3. Update tsql_coerce_string_literal_hook to handle empty/whitespace casts from string literals to date and time datatypes. 4. Default empty/whitespace strings to '1900-01-01' for date and '00:00:00.0000000' for time. 5. Updated existing date-time related test cases. Added more tests cases for wider test coverage. Task: BABEL-3433 Signed-off-by: Manisha Deshpande <mmdeshp@amazon.com>
1 parent d7b9833 commit 7840f56

38 files changed

+1111
-59
lines changed

contrib/babelfishpg_common/src/babelfishpg_common.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,8 @@ get_common_utility_plugin(void)
216216
common_utility_plugin_var.TsqlUTF8LengthInUTF16 = &TsqlUTF8LengthInUTF16;
217217
common_utility_plugin_var.TsqlUTF8toUTF16StringInfo = &TsqlUTF8toUTF16StringInfo;
218218
common_utility_plugin_var.tsql_numeric_get_typmod = &tsql_numeric_get_typmod;
219+
common_utility_plugin_var.initializeToDefaultDate = &initializeToDefaultDate;
220+
common_utility_plugin_var.initializeToDefaultTime = &initializeToDefaultTime;
219221
}
220222
return &common_utility_plugin_var;
221223
}

contrib/babelfishpg_common/src/babelfishpg_common.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "fmgr.h"
44
#include "numeric.h"
5+
#include "utils/date.h"
56

67
/*
78
* Casting float < -1.0 to unsigned integer could cause issues on ARM.
@@ -95,4 +96,6 @@ typedef struct common_utility_plugin
9596
int (*TsqlUTF8LengthInUTF16) (const void *vin, int len);
9697
void (*TsqlUTF8toUTF16StringInfo) (StringInfo utf16_data, const void *data, size_t len);
9798
int32_t (*tsql_numeric_get_typmod) (Numeric num);
99+
DateADT (*initializeToDefaultDate) (void);
100+
TimeADT (*initializeToDefaultTime) (int32 typmod);
98101
} common_utility_plugin;

contrib/babelfishpg_common/src/datetime.c

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,7 @@ datetime_in_str(char *str, Node *escontext)
544544
* Set input to default '1900-01-01 00:00:00.000' if empty string
545545
* encountered
546546
*/
547-
if (*str == '\0')
547+
if (isEmptyOrWhitespace(str))
548548
{
549549
result = initializeToDefaultDatetime();
550550
PG_RETURN_TIMESTAMP(result);
@@ -1201,6 +1201,62 @@ initializeToDefaultDatetime(void)
12011201
return result;
12021202
}
12031203

1204+
/*
1205+
* Set input to default '1900-01-01' if empty string encountered
1206+
*/
1207+
DateADT
1208+
initializeToDefaultDate(void)
1209+
{
1210+
DateADT date;
1211+
struct pg_tm tt,
1212+
*tm = &tt;
1213+
tm->tm_year = 1900;
1214+
tm->tm_mon = 1;
1215+
tm->tm_mday = 1;
1216+
date = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE;
1217+
1218+
return date;
1219+
}
1220+
1221+
/*
1222+
* Set input to default '00:00:00.0000000' if empty string encountered
1223+
*/
1224+
TimeADT
1225+
initializeToDefaultTime(int32 typmod)
1226+
{
1227+
TimeADT time;
1228+
struct pg_tm tt,
1229+
*tm = &tt;
1230+
tm->tm_hour = tm->tm_min = tm->tm_sec = 0;
1231+
tm2time(tm, 0, &time);
1232+
AdjustTimeForTypmod(&time, typmod);
1233+
1234+
return time;
1235+
}
1236+
1237+
1238+
/*
1239+
* Check if string is empty or contains only whitespace
1240+
* Returns true if string is empty or contains only whitespace characters
1241+
*/
1242+
bool
1243+
isEmptyOrWhitespace(const char *str)
1244+
{
1245+
size_t i;
1246+
size_t len;
1247+
1248+
if (!str)
1249+
return true;
1250+
1251+
len = strlen(str);
1252+
for (i = 0; i < len; i++)
1253+
{
1254+
if (!isspace((unsigned char)str[i]))
1255+
return false;
1256+
}
1257+
return true;
1258+
}
1259+
12041260
Datum
12051261
datetime_pl_datetime(PG_FUNCTION_ARGS)
12061262
{

contrib/babelfishpg_common/src/datetime.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#define PLTSQL_DATETIME_H
1010

1111
#include "datatype/timestamp.h"
12+
#include "utils/date.h"
1213

1314
/* Round off to MAX_DATETIME_PRECISION decimal places. */
1415
#define DT_PREC_INV 1000
@@ -33,6 +34,10 @@
3334
#define TSQL_DEFAULT_DATETIME INT64CONST(-3155673600000000)
3435

3536
extern Timestamp initializeToDefaultDatetime(void);
37+
extern DateADT initializeToDefaultDate(void);
38+
extern TimeADT initializeToDefaultTime(int32 typmod);
39+
extern bool isEmptyOrWhitespace(const char *str);
40+
3641
/** Utility function to calculate days from '1900-01-01 00:00:00' */
3742
extern double calculateDaysFromDefaultDatetime(Timestamp timestamp_left);
3843
extern int roundFractionalSeconds(int fractseconds);

contrib/babelfishpg_common/src/datetime2.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ datetime2_in_str(char *str, int32 typmod, Node *escontext)
274274
tm->tm_mday = 0;
275275

276276
/* Set input to default '1900-01-01 00:00:00.* if empty string encountered */
277-
if (*str == '\0')
277+
if (isEmptyOrWhitespace(str))
278278
{
279279
result = initializeToDefaultDatetime();
280280
AdjustDatetime2ForTypmod(&result, typmod);

contrib/babelfishpg_common/src/datetimeoffset.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ datetimeoffset_in(PG_FUNCTION_ARGS)
114114
* Set input to default '1900-01-01 00:00:00.* 00:00' if empty string
115115
* encountered
116116
*/
117-
if (*str == '\0')
117+
if (isEmptyOrWhitespace(str))
118118
{
119119
tsql_ts = initializeToDefaultDatetime();
120120
AdjustDatetimeoffsetForTypmod(&tsql_ts, typmod);

contrib/babelfishpg_common/src/smalldatetime.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ smalldatetime_in_str(char *str, Node *escontext)
7979
DateTimeErrorExtra extra;
8080

8181
/* Set input to default '1900-01-01 00:00:00' if empty string encountered */
82-
if (*str == '\0')
82+
if (isEmptyOrWhitespace(str))
8383
{
8484
result = initializeToDefaultDatetime();
8585
AdjustTimestampForSmallDatetime(&result);

contrib/babelfishpg_common/src/varchar.c

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#include "utils/uuid.h"
4141
#include "utils/timestamp.h"
4242
#include "utils/numeric.h"
43+
#include "datetime.h"
4344
#include "typecode.h"
4445
#include "varchar.h"
4546

@@ -1062,7 +1063,14 @@ varchar2date(PG_FUNCTION_ARGS)
10621063
DateADT date;
10631064

10641065
str = varchar2cstring(source);
1065-
date = DatumGetDateADT(DirectFunctionCall1(date_in, CStringGetDatum(str)));
1066+
1067+
/*
1068+
* Set input to default '1900-01-01' if empty string encountered
1069+
*/
1070+
if (isEmptyOrWhitespace(str))
1071+
date = initializeToDefaultDate();
1072+
else
1073+
date = DatumGetDateADT(DirectFunctionCall1(date_in, CStringGetDatum(str)));
10661074
pfree(str);
10671075
PG_RETURN_DATEADT(date);
10681076
}
@@ -1079,7 +1087,14 @@ varchar2time(PG_FUNCTION_ARGS)
10791087
typmod = PG_GETARG_INT32(1);
10801088

10811089
str = varchar2cstring(source);
1082-
time = DatumGetTimeADT(DirectFunctionCall3(time_in, CStringGetDatum(str), InvalidOid, typmod));
1090+
1091+
/*
1092+
* Set input to default '00:00:00.0000000' if empty string encountered
1093+
*/
1094+
if (isEmptyOrWhitespace(str))
1095+
time = initializeToDefaultTime(typmod);
1096+
else
1097+
time = DatumGetTimeADT(DirectFunctionCall3(time_in, CStringGetDatum(str), InvalidOid, typmod));
10831098
pfree(str);
10841099
PG_RETURN_TIMEADT(time);
10851100
}

contrib/babelfishpg_tsql/src/pltsql_coerce.c

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
#include "utils/fmgroids.h"
3434
#include "common/int.h"
3535
#include "utils/numeric.h"
36+
#include "utils/date.h"
37+
#include "utils/datetime.h"
3638
#include "utils/memutils.h"
3739
#include "utils/lsyscache.h"
3840
#include "utils/syscache.h"
@@ -2656,7 +2658,7 @@ tsql_coerce_string_literal_hook(Oid targetTypeId,
26562658
*/
26572659
for (i = strlen(value) - 1; i >= 0; i--)
26582660
{
2659-
if (value[i] != ' ')
2661+
if (!isspace((unsigned char)value[i]))
26602662
break;
26612663
}
26622664

@@ -2759,6 +2761,20 @@ tsql_coerce_string_literal_hook(Oid targetTypeId,
27592761
newcon->constvalue = stringTypeDatum(baseType, value, inputTypeMod);
27602762
break;
27612763
}
2764+
case DATEOID:
2765+
{
2766+
/* Set input to default '1900-01-01' for empty strings */
2767+
DateADT date = (*common_utility_plugin_ptr->initializeToDefaultDate) ();
2768+
newcon->constvalue = DateADTGetDatum(date);
2769+
break;
2770+
}
2771+
case TIMEOID:
2772+
{
2773+
/* Set input to default '00:00:00' for empty strings */
2774+
TimeADT time = (*common_utility_plugin_ptr->initializeToDefaultTime) (inputTypeMod);
2775+
newcon->constvalue = TimeADTGetDatum(time);
2776+
break;
2777+
}
27622778
default:
27632779
newcon->constvalue = stringTypeDatum(baseType, value, inputTypeMod);
27642780
}

test/JDBC/expected/Date.out

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,23 @@ GO
1414
Declare @a date;
1515
INSERT INTO DateTest (DateCol) VALUES (@a), ('');
1616
GO
17-
~~ERROR (Code: 33557097)~~
18-
19-
~~ERROR (Message: invalid input syntax for type date: "")~~
17+
~~ROW COUNT: 2~~
2018

2119
SELECT * FROM DateTest WHERE DateCol IS NULL;
2220
GO
2321
~~START~~
2422
int#!#date
2523
1#!#<NULL>
24+
2#!#<NULL>
2625
~~END~~
2726

2827
SELECT * FROM DateTest;
2928
GO
3029
~~START~~
3130
int#!#date
3231
1#!#<NULL>
32+
2#!#<NULL>
33+
3#!#1900-01-01
3334
~~END~~
3435

3536

@@ -2049,9 +2050,10 @@ date
20492050

20502051
SELECT CAST(CAST('' AS varchar) AS DATE);
20512052
GO
2052-
~~ERROR (Code: 33557097)~~
2053-
2054-
~~ERROR (Message: invalid input syntax for type date: "")~~
2053+
~~START~~
2054+
date
2055+
1900-01-01
2056+
~~END~~
20552057

20562058

20572059
-- nchar
@@ -2650,9 +2652,10 @@ date
26502652

26512653
SELECT dbo.TestDateFunction(CAST('' AS varchar));
26522654
GO
2653-
~~ERROR (Code: 33557097)~~
2654-
2655-
~~ERROR (Message: invalid input syntax for type date: "")~~
2655+
~~START~~
2656+
date
2657+
1900-01-01
2658+
~~END~~
26562659

26572660

26582661
-- nchar

0 commit comments

Comments
 (0)