0002-Fix-sql-standard-style-negative-semantics.patch

text/x-patch

Filename: 0002-Fix-sql-standard-style-negative-semantics.patch
Type: text/x-patch
Part: 0
Message: Re: Fix overflow in DecodeInterval

Patch

Format: format-patch
Series: patch 0002
Subject: Fix sql standard style negative semantics
File+
src/backend/utils/adt/datetime.c 60 47
src/test/regress/expected/interval.out 14 0
src/test/regress/sql/interval.sql 5 0
From f43d27142a76fcbabf49e45b9457f8376744e759 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 2 Apr 2022 14:42:18 -0400
Subject: [PATCH 2/2] Fix sql standard style negative semantics

---
 src/backend/utils/adt/datetime.c       | 107 ++++++++++++++-----------
 src/test/regress/expected/interval.out |  14 ++++
 src/test/regress/sql/interval.sql      |   5 ++
 3 files changed, 79 insertions(+), 47 deletions(-)

diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index dae90e4a9e..5842d249ab 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -50,6 +50,8 @@ static int	DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
 static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
 						   int precision, bool fillzeros);
 static bool int64_multiply_add(int64 val, int64 multiplier, int64 *sum);
+static void AdjustForSqlStandardGlobalNegative(int64 *val, double *fval, 
+											   bool global_negative);
 static bool AdjustFractMicroseconds(double frac, int64 scale,
 									struct pg_itm_in *itm_in);
 static bool AdjustFractDays(double frac, int scale,
@@ -527,6 +529,19 @@ int64_multiply_add(int64 val, int64 multiplier, int64 *sum)
 	return true;
 }
 
+/*
+ * Adjust values sign if SQL Standard style is being used and there's a 
+ * single leading negative sign.
+ */
+static void AdjustForSqlStandardGlobalNegative(int64 *val, double *fval,
+											   bool global_negative)
+{
+	if (*val > 0 && global_negative) {
+		*val = -*val;
+		*fval = -*fval;
+	}
+}
+
 /*
  * Multiply frac by scale (to produce microseconds) and add to itm_in->tm_usec.
  * Returns true if successful, false if itm_in overflows.
@@ -3307,10 +3322,43 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 	int64		val;
 	double		fval;
 
+	bool		global_negative = false;
+
 	*dtype = DTK_DELTA;
 	type = IGNORE_DTF;
 	ClearPgItmIn(itm_in);
 
+	/*----------
+	 * The SQL standard defines the interval literal
+	 *	 '-1 1:00:00'
+	 * to mean "negative 1 days and negative 1 hours", while Postgres
+	 * traditionally treats this as meaning "negative 1 days and positive
+	 * 1 hours".  In SQL_STANDARD intervalstyle, we apply the leading sign
+	 * to all fields if there are no other explicit signs.
+	 *
+	 * We leave the signs alone if there are additional explicit signs.
+	 * This protects us against misinterpreting postgres-style dump output,
+	 * since the postgres-style output code has always put an explicit sign on
+	 * all fields following a negative field.  But note that SQL-spec output
+	 * is ambiguous and can be misinterpreted on load!	(So it's best practice
+	 * to dump in postgres style, not SQL style.)
+	 *----------
+	 */
+	if (IntervalStyle == INTSTYLE_SQL_STANDARD && *field[0] == '-')
+	{
+		/* Check for additional explicit signs */
+		bool            more_signs = false;
+		for (i = 1; i < nf; i++)
+		{
+			if (*field[i] == '-' || *field[i] == '+')
+			{
+				more_signs = true;
+				break;
+			}
+		}
+		global_negative = !more_signs;
+	}
+
 	/* read through list backwards to pick up units before values */
 	for (i = nf - 1; i >= 0; i--)
 	{
@@ -3447,18 +3495,21 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 				switch (type)
 				{
 					case DTK_MICROSEC:
+						AdjustForSqlStandardGlobalNegative(&val, &fval, global_negative);
 						if (!AdjustMicroseconds(val, fval, 1, itm_in))
 							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(MICROSECOND);
 						break;
 
 					case DTK_MILLISEC:
+						AdjustForSqlStandardGlobalNegative(&val, &fval, global_negative);
 						if (!AdjustMicroseconds(val, fval, 1000, itm_in))
 							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(MILLISECOND);
 						break;
 
 					case DTK_SECOND:
+						AdjustForSqlStandardGlobalNegative(&val, &fval, global_negative);
 						if (!AdjustMicroseconds(val, fval, USECS_PER_SEC, itm_in))
 							return DTERR_FIELD_OVERFLOW;
 
@@ -3473,12 +3524,14 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 						break;
 
 					case DTK_MINUTE:
+						AdjustForSqlStandardGlobalNegative(&val, &fval, global_negative);
 						if (!AdjustMicroseconds(val, fval, USECS_PER_MINUTE, itm_in))
 							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(MINUTE);
 						break;
 
 					case DTK_HOUR:
+						AdjustForSqlStandardGlobalNegative(&val, &fval, global_negative);
 						if (!AdjustMicroseconds(val, fval, USECS_PER_HOUR, itm_in))
 							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(HOUR);
@@ -3486,6 +3539,7 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 						break;
 
 					case DTK_DAY:
+						AdjustForSqlStandardGlobalNegative(&val, &fval, global_negative);
 						if (!AdjustDays(val, 1, itm_in) ||
 							!AdjustFractMicroseconds(fval, USECS_PER_DAY, itm_in))
 							return DTERR_FIELD_OVERFLOW;
@@ -3493,6 +3547,7 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 						break;
 
 					case DTK_WEEK:
+						AdjustForSqlStandardGlobalNegative(&val, &fval, global_negative);
 						if (!AdjustDays(val, 7, itm_in) ||
 							!AdjustFractDays(fval, 7, itm_in))
 							return DTERR_FIELD_OVERFLOW;
@@ -3500,6 +3555,7 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 						break;
 
 					case DTK_MONTH:
+						AdjustForSqlStandardGlobalNegative(&val, &fval, global_negative);
 						if (!AdjustMonths(val, itm_in) ||
 							!AdjustFractDays(fval, DAYS_PER_MONTH, itm_in))
 							return DTERR_FIELD_OVERFLOW;
@@ -3507,6 +3563,7 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 						break;
 
 					case DTK_YEAR:
+						AdjustForSqlStandardGlobalNegative(&val, &fval, global_negative);
 						if (!AdjustYears(val, 1, itm_in) ||
 							!AdjustFractYears(fval, 1, itm_in))
 							return DTERR_FIELD_OVERFLOW;
@@ -3514,6 +3571,7 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 						break;
 
 					case DTK_DECADE:
+						AdjustForSqlStandardGlobalNegative(&val, &fval, global_negative);
 						if (!AdjustYears(val, 10, itm_in) ||
 							!AdjustFractYears(fval, 10, itm_in))
 							return DTERR_FIELD_OVERFLOW;
@@ -3521,6 +3579,7 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 						break;
 
 					case DTK_CENTURY:
+						AdjustForSqlStandardGlobalNegative(&val, &fval, global_negative);
 						if (!AdjustYears(val, 100, itm_in) ||
 							!AdjustFractYears(fval, 100, itm_in))
 							return DTERR_FIELD_OVERFLOW;
@@ -3528,6 +3587,7 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 						break;
 
 					case DTK_MILLENNIUM:
+						AdjustForSqlStandardGlobalNegative(&val, &fval, global_negative);
 						if (!AdjustYears(val, 1000, itm_in) ||
 							!AdjustFractYears(fval, 1000, itm_in))
 							return DTERR_FIELD_OVERFLOW;
@@ -3580,53 +3640,6 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 	if (fmask == 0)
 		return DTERR_BAD_FORMAT;
 
-	/*----------
-	 * The SQL standard defines the interval literal
-	 *	 '-1 1:00:00'
-	 * to mean "negative 1 days and negative 1 hours", while Postgres
-	 * traditionally treats this as meaning "negative 1 days and positive
-	 * 1 hours".  In SQL_STANDARD intervalstyle, we apply the leading sign
-	 * to all fields if there are no other explicit signs.
-	 *
-	 * We leave the signs alone if there are additional explicit signs.
-	 * This protects us against misinterpreting postgres-style dump output,
-	 * since the postgres-style output code has always put an explicit sign on
-	 * all fields following a negative field.  But note that SQL-spec output
-	 * is ambiguous and can be misinterpreted on load!	(So it's best practice
-	 * to dump in postgres style, not SQL style.)
-	 *----------
-	 */
-	if (IntervalStyle == INTSTYLE_SQL_STANDARD && *field[0] == '-')
-	{
-		/* Check for additional explicit signs */
-		bool		more_signs = false;
-
-		for (i = 1; i < nf; i++)
-		{
-			if (*field[i] == '-' || *field[i] == '+')
-			{
-				more_signs = true;
-				break;
-			}
-		}
-
-		if (!more_signs)
-		{
-			/*
-			 * Rather than re-determining which field was field[0], just force
-			 * 'em all negative.
-			 */
-			if (itm_in->tm_usec > 0)
-				itm_in->tm_usec = -itm_in->tm_usec;
-			if (itm_in->tm_mday > 0)
-				itm_in->tm_mday = -itm_in->tm_mday;
-			if (itm_in->tm_mon > 0)
-				itm_in->tm_mon = -itm_in->tm_mon;
-			if (itm_in->tm_year > 0)
-				itm_in->tm_year = -itm_in->tm_year;
-		}
-	}
-
 	/* finally, AGO negates everything */
 	if (is_before)
 	{
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 00ffe0e2be..ebcc672d13 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -1690,3 +1690,17 @@ SELECT INTERVAL '-2147483648 months -2147483648 days -9223372036854775808 us';
  P-178956970Y-8M-2147483648DT-2562047788H-54.775808S
 (1 row)
 
+SET IntervalStyle to postgres;
+SELECT INTERVAL '-23 hours 45 min 12.34 sec';
+   interval   
+--------------
+ -22:14:47.66
+(1 row)
+
+SET IntervalStyle to sql_standard;
+SELECT INTERVAL '-23 hours 45 min 12.34 sec';
+   interval   
+--------------
+ -23:45:12.34
+(1 row)
+
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index fc924d5bde..bf409df6cb 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -551,3 +551,8 @@ SET IntervalStyle TO sql_standard;
 SELECT INTERVAL '-2147483648 months -2147483648 days -9223372036854775808 us';
 SET IntervalStyle to iso_8601;
 SELECT INTERVAL '-2147483648 months -2147483648 days -9223372036854775808 us';
+
+SET IntervalStyle to postgres;
+SELECT INTERVAL '-23 hours 45 min 12.34 sec';
+SET IntervalStyle to sql_standard;
+SELECT INTERVAL '-23 hours 45 min 12.34 sec';
-- 
2.25.1