From 0608caf59ad60d287f50a2b6aaf97d0a355269a8 Mon Sep 17 00:00:00 2001
From: kev <e.v.kalinin@gmail.com>
Date: Tue, 7 Jul 2020 13:30:59 +0300
Subject: [PATCH] sql: age returns normalized intervals

Release note (bug fix): The age function previous did not normalize the
duration for large day or H:M:S values in the same way PostgreSQL does.
This is now fixed.
---
 pkg/sql/logictest/testdata/logic_test/datetime  | 7 ++++++-
 pkg/sql/logictest/testdata/logic_test/timestamp | 4 ++--
 pkg/sql/opt/norm/testdata/rules/fold_constants  | 2 +-
 pkg/sql/sem/tree/eval.go                        | 8 ++++----
 pkg/util/duration/duration.go                   | 9 +++++++++
 5 files changed, 22 insertions(+), 8 deletions(-)

diff --git a/pkg/sql/logictest/testdata/logic_test/datetime b/pkg/sql/logictest/testdata/logic_test/datetime
index f97442cd5d78..a7b99caf8d58 100644
--- a/pkg/sql/logictest/testdata/logic_test/datetime
+++ b/pkg/sql/logictest/testdata/logic_test/datetime
@@ -159,13 +159,18 @@ SELECT '5874897-12-31'::date - '4714-11-24 BC'::date
 query T
 SELECT age('2001-04-10 22:06:45', '1957-06-13')
 ----
-384190:06:45
+44 years 5 mons 17 days 22:06:45
 
 query B
 SELECT age('1957-06-13') - age(now(), '1957-06-13') = interval '0s'
 ----
 true
 
+query T
+select age('2017-12-10'::timestamptz, '2017-12-01'::timestamptz)
+----
+9 days
+
 query B
 SELECT now() - timestamp '2015-06-13' > interval '100h'
 ----
diff --git a/pkg/sql/logictest/testdata/logic_test/timestamp b/pkg/sql/logictest/testdata/logic_test/timestamp
index 12d747996230..ed5dfffa5503 100644
--- a/pkg/sql/logictest/testdata/logic_test/timestamp
+++ b/pkg/sql/logictest/testdata/logic_test/timestamp
@@ -402,8 +402,8 @@ SELECT
 FROM example
 ORDER BY a
 ----
-2010-11-07 22:59:00 -0600 CST  2010-11-07 23:59:00 -0600 CST  2010-12-06 23:59:00 -0600 CST  2010-11-05 23:59:00 -0500 CDT  2010-11-05 23:59:00 -0500 CDT  2010-10-06 23:59:00 -0500 CDT  00:00:00  -25:00:00  2010-11-06 23:59:00-05:00
-2010-11-08 23:59:00 -0600 CST  2010-11-08 23:59:00 -0600 CST  2010-12-07 23:59:00 -0600 CST  2010-11-07 00:59:00 -0500 CDT  2010-11-06 23:59:00 -0500 CDT  2010-10-07 23:59:00 -0500 CDT  25:00:00  00:00:00   2010-11-07 23:59:00-06:00
+2010-11-07 22:59:00 -0600 CST  2010-11-07 23:59:00 -0600 CST  2010-12-06 23:59:00 -0600 CST  2010-11-05 23:59:00 -0500 CDT  2010-11-05 23:59:00 -0500 CDT  2010-10-06 23:59:00 -0500 CDT  00:00:00  -1 days -01:00:00  2010-11-06 23:59:00-05:00
+2010-11-08 23:59:00 -0600 CST  2010-11-08 23:59:00 -0600 CST  2010-12-07 23:59:00 -0600 CST  2010-11-07 00:59:00 -0500 CDT  2010-11-06 23:59:00 -0500 CDT  2010-10-07 23:59:00 -0500 CDT  1 day 01:00:00  00:00:00   2010-11-07 23:59:00-06:00
 
 statement ok
 DROP TABLE example
diff --git a/pkg/sql/opt/norm/testdata/rules/fold_constants b/pkg/sql/opt/norm/testdata/rules/fold_constants
index 469db12f2a2a..5df6d74f713d 100644
--- a/pkg/sql/opt/norm/testdata/rules/fold_constants
+++ b/pkg/sql/opt/norm/testdata/rules/fold_constants
@@ -366,7 +366,7 @@ values
  ├── cardinality: [1 - 1]
  ├── key: ()
  ├── fd: ()-->(1)
- └── ('-24:00:00',)
+ └── ('-1 days',)
 
 # Fold constant.
 norm expect=FoldBinary
diff --git a/pkg/sql/sem/tree/eval.go b/pkg/sql/sem/tree/eval.go
index bf30848dae24..ce207b815bf2 100644
--- a/pkg/sql/sem/tree/eval.go
+++ b/pkg/sql/sem/tree/eval.go
@@ -878,7 +878,7 @@ var BinOps = map[BinaryOperator]binOpOverload{
 			ReturnType: types.Interval,
 			Fn: func(_ *EvalContext, left Datum, right Datum) (Datum, error) {
 				nanos := left.(*DTimestamp).Sub(right.(*DTimestamp).Time).Nanoseconds()
-				return &DInterval{Duration: duration.MakeDuration(nanos, 0, 0)}, nil
+				return &DInterval{Duration: duration.MakeNormalizedDuration(nanos, 0, 0)}, nil
 			},
 		},
 		&BinOp{
@@ -887,7 +887,7 @@ var BinOps = map[BinaryOperator]binOpOverload{
 			ReturnType: types.Interval,
 			Fn: func(_ *EvalContext, left Datum, right Datum) (Datum, error) {
 				nanos := left.(*DTimestampTZ).Sub(right.(*DTimestampTZ).Time).Nanoseconds()
-				return &DInterval{Duration: duration.MakeDuration(nanos, 0, 0)}, nil
+				return &DInterval{Duration: duration.MakeNormalizedDuration(nanos, 0, 0)}, nil
 			},
 		},
 		&BinOp{
@@ -898,7 +898,7 @@ var BinOps = map[BinaryOperator]binOpOverload{
 				// These two quantities aren't directly comparable. Convert the
 				// TimestampTZ to a timestamp first.
 				nanos := left.(*DTimestamp).Sub(right.(*DTimestampTZ).stripTimeZone(ctx).Time).Nanoseconds()
-				return &DInterval{Duration: duration.MakeDuration(nanos, 0, 0)}, nil
+				return &DInterval{Duration: duration.MakeNormalizedDuration(nanos, 0, 0)}, nil
 			},
 		},
 		&BinOp{
@@ -909,7 +909,7 @@ var BinOps = map[BinaryOperator]binOpOverload{
 				// These two quantities aren't directly comparable. Convert the
 				// TimestampTZ to a timestamp first.
 				nanos := left.(*DTimestampTZ).stripTimeZone(ctx).Sub(right.(*DTimestamp).Time).Nanoseconds()
-				return &DInterval{Duration: duration.MakeDuration(nanos, 0, 0)}, nil
+				return &DInterval{Duration: duration.MakeNormalizedDuration(nanos, 0, 0)}, nil
 			},
 		},
 		&BinOp{
diff --git a/pkg/util/duration/duration.go b/pkg/util/duration/duration.go
index 2f09d08abd50..362ea9cfcd52 100644
--- a/pkg/util/duration/duration.go
+++ b/pkg/util/duration/duration.go
@@ -102,6 +102,15 @@ func MakeDuration(nanos, days, months int64) Duration {
 	}
 }
 
+// MakeNormalizedDuration returns a normalized Duration.
+func MakeNormalizedDuration(nanos, days, months int64) Duration {
+	return Duration{
+		Months: months,
+		Days:   days,
+		nanos:  rounded(nanos),
+	}.normalize()
+}
+
 // DecodeDuration returns a Duration without rounding nanos.
 func DecodeDuration(months, days, nanos int64) Duration {
 	return Duration{