public inbox for [email protected]  
help / color / mirror / Atom feed
Re: Fix overflow in DecodeInterval
23+ messages / 3 participants
[nested] [flat]

* Re: Fix overflow in DecodeInterval
@ 2022-02-13 03:51 Andres Freund <[email protected]>
  2022-02-13 14:35 ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  0 siblings, 1 reply; 23+ messages in thread

From: Andres Freund @ 2022-02-13 03:51 UTC (permalink / raw)
  To: Joseph Koshakow <[email protected]>; +Cc: Tom Lane <[email protected]>; [email protected]

Hi,

On 2022-02-12 21:16:05 -0500, Joseph Koshakow wrote:
> I've attached the patch below.

Any reason for using int return types?


> +/* As above, but initial val produces years */
> +static int
> +AdjustYears(int val, struct pg_tm *tm, int multiplier)
> +{
> +	int			years;
> +	if (pg_mul_s32_overflow(val, multiplier, &years))
> +		return 1;
> +	if (pg_add_s32_overflow(tm->tm_year, years, &tm->tm_year))
> +		return 1;
> +	return 0;
>  }

particularly since the pg_*_overflow stuff uses bool?


Greetings,

Andres Freund






^ permalink  raw  reply  [nested|flat] 23+ messages in thread

* Re: Fix overflow in DecodeInterval
  2022-02-13 03:51 Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
@ 2022-02-13 14:35 ` Joseph Koshakow <[email protected]>
  2022-02-13 19:34   ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-13 20:30   ` Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  0 siblings, 2 replies; 23+ messages in thread

From: Joseph Koshakow @ 2022-02-13 14:35 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Tom Lane <[email protected]>; [email protected]

On Sat, Feb 12, 2022 at 10:51 PM Andres Freund <[email protected]> wrote:
> Any reason for using int return types?
>
> particularly since the pg_*_overflow stuff uses bool?

I chose int return types to keep all these methods
consistent with DecodeInterval, which returns a
non-zero int to indicate an error. Though I wasn't sure
if an int or bool would be best, so I'm happy to change
to bool if people think that's better.

Also I'm realizing now that I've incorrectly been using the
number of the patch to indicate the version, instead of just
sticking a v3 to the front. So sorry about that, all the patches
I sent in this thread are the same patch, just different versions.

- Joe Koshakow






^ permalink  raw  reply  [nested|flat] 23+ messages in thread

* Re: Fix overflow in DecodeInterval
  2022-02-13 03:51 Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-13 14:35 ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
@ 2022-02-13 19:34   ` Joseph Koshakow <[email protected]>
  1 sibling, 0 replies; 23+ messages in thread

From: Joseph Koshakow @ 2022-02-13 19:34 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Tom Lane <[email protected]>; [email protected]

Actually found an additional overflow issue in
DecodeInterval. I've updated the patch with the
fix. Specifically it prevents trying to negate a field
if it equals INT_MIN. Let me know if this is best
put into another patch.

- Joe Koshakow


Attachments:

  [text/x-patch] v4-0001-Check-for-overflow-when-decoding-an-interval.patch (14.3K, ../../CAAvxfHdyDCTbuVdHdF0kS=jCawG4ziPSntL9LWgBihRfuc+tpw@mail.gmail.com/2-v4-0001-Check-for-overflow-when-decoding-an-interval.patch)
  download | inline diff:
From 09eafa9b496c8461f2dc52ea62c9e833ab10a17f Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <[email protected]>
Date: Fri, 11 Feb 2022 14:18:32 -0500
Subject: [PATCH] Check for overflow when decoding an interval

When decoding an interval there are various date units which are
aliases for some multiple of another unit. For example a week is 7 days
and a decade is 10 years. When decoding these specific units, there is
no check for overflow, allowing the interval to overflow. This commit
adds an overflow check for all of these units.

Additionally fractional date/time units are rolled down into the next
smallest unit. For example 0.1 months is 3 days. When adding these
fraction units, there is no check for overflow, allowing the interval
to overflow. This commit adds an overflow check for all of the
fractional units.

Additionally adding the word "ago" to the interval negates every
field. However the negative of INT_MIN is still INT_MIN. This
commit adds a check to make sure that we don't try and negate
a field if it's INT_MIN.

Signed-off-by: Joseph Koshakow <[email protected]>
---
 src/backend/utils/adt/datetime.c       | 108 ++++++++++++++++++++-----
 src/test/regress/expected/interval.out |  68 ++++++++++++++++
 src/test/regress/sql/interval.sql      |  18 +++++
 3 files changed, 172 insertions(+), 22 deletions(-)

diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 7926258c06..49bd12075e 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -21,6 +21,7 @@
 #include "access/htup_details.h"
 #include "access/xact.h"
 #include "catalog/pg_type.h"
+#include "common/int.h"
 #include "common/string.h"
 #include "funcapi.h"
 #include "miscadmin.h"
@@ -44,10 +45,13 @@ static int	DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
 					   struct pg_tm *tm);
 static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
 						   int precision, bool fillzeros);
-static void AdjustFractSeconds(double frac, struct pg_tm *tm, fsec_t *fsec,
+static int AdjustFractSeconds(double frac, struct pg_tm *tm, fsec_t *fsec,
 							   int scale);
-static void AdjustFractDays(double frac, struct pg_tm *tm, fsec_t *fsec,
+static int AdjustFractDays(double frac, struct pg_tm *tm, fsec_t *fsec,
 							int scale);
+static int AdjustFractMonths(double frac, struct pg_tm *tm, int scale);
+static int AdjustDays(int val, struct pg_tm *tm, int multiplier);
+static int AdjustYears(int val, struct pg_tm *tm, int multiplier);
 static int	DetermineTimeZoneOffsetInternal(struct pg_tm *tm, pg_tz *tzp,
 											pg_time_t *tp);
 static bool DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t,
@@ -499,34 +503,76 @@ AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec)
 /*
  * Multiply frac by scale (to produce seconds) and add to *tm & *fsec.
  * We assume the input frac is less than 1 so overflow is not an issue.
+ * Returns 0 if successful, 1 if tm overflows.
  */
-static void
+static int
 AdjustFractSeconds(double frac, struct pg_tm *tm, fsec_t *fsec, int scale)
 {
 	int			sec;
 
 	if (frac == 0)
-		return;
+		return 0;
 	frac *= scale;
 	sec = (int) frac;
-	tm->tm_sec += sec;
+	if (pg_add_s32_overflow(tm->tm_sec, sec, &tm->tm_sec))
+		return 1;
 	frac -= sec;
 	*fsec += rint(frac * 1000000);
+	return 0;
 }
 
 /* As above, but initial scale produces days */
-static void
+static int
 AdjustFractDays(double frac, struct pg_tm *tm, fsec_t *fsec, int scale)
 {
 	int			extra_days;
 
 	if (frac == 0)
-		return;
+		return 0;
 	frac *= scale;
 	extra_days = (int) frac;
-	tm->tm_mday += extra_days;
+	if (pg_add_s32_overflow(tm->tm_mday, extra_days, &tm->tm_mday))
+		return 1;
 	frac -= extra_days;
-	AdjustFractSeconds(frac, tm, fsec, SECS_PER_DAY);
+	return AdjustFractSeconds(frac, tm, fsec, SECS_PER_DAY);
+}
+
+/* As above, but initial scale produces months */
+static int
+AdjustFractMonths(double frac, struct pg_tm *tm, int scale)
+{
+	int months = rint(frac * MONTHS_PER_YEAR * scale);
+
+	if (pg_add_s32_overflow(tm->tm_mon, months, &tm->tm_mon))
+		return 1;
+	return 0;
+}
+
+/*
+ * Multiply val by multiplier (to produce days) and add to *tm.
+ * Returns 0 if successful, 1 if tm overflows.
+ */
+static int
+AdjustDays(int val, struct pg_tm *tm, int multiplier)
+{
+	int			extra_days;
+	if (pg_mul_s32_overflow(val, multiplier, &extra_days))
+		return 1;
+	if (pg_add_s32_overflow(tm->tm_mday, extra_days, &tm->tm_mday))
+		return 1;
+	return 0;
+}
+
+/* As above, but initial val produces years */
+static int
+AdjustYears(int val, struct pg_tm *tm, int multiplier)
+{
+	int			years;
+	if (pg_mul_s32_overflow(val, multiplier, &years))
+		return 1;
+	if (pg_add_s32_overflow(tm->tm_year, years, &tm->tm_year))
+		return 1;
+	return 0;
 }
 
 /* Fetch a fractional-second value with suitable error checking */
@@ -3275,56 +3321,69 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 
 					case DTK_MINUTE:
 						tm->tm_min += val;
-						AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
+						if (AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(MINUTE);
 						break;
 
 					case DTK_HOUR:
 						tm->tm_hour += val;
-						AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
+						if (AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(HOUR);
 						type = DTK_DAY; /* set for next field */
 						break;
 
 					case DTK_DAY:
 						tm->tm_mday += val;
-						AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+						if (AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(DAY);
 						break;
 
 					case DTK_WEEK:
-						tm->tm_mday += val * 7;
-						AdjustFractDays(fval, tm, fsec, 7);
+						if (AdjustDays(val, tm, 7))
+							return DTERR_FIELD_OVERFLOW;
+						if (AdjustFractDays(fval, tm, fsec, 7))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(WEEK);
 						break;
 
 					case DTK_MONTH:
 						tm->tm_mon += val;
-						AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
+						if (AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(MONTH);
 						break;
 
 					case DTK_YEAR:
 						tm->tm_year += val;
-						tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
+						if (AdjustFractMonths(fval, tm, 1))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(YEAR);
 						break;
 
 					case DTK_DECADE:
-						tm->tm_year += val * 10;
-						tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 10);
+						if (AdjustYears(val, tm, 10))
+							return DTERR_FIELD_OVERFLOW;
+						if (AdjustFractMonths(fval, tm, 10))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(DECADE);
 						break;
 
 					case DTK_CENTURY:
-						tm->tm_year += val * 100;
-						tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 100);
+						if (AdjustYears(val, tm, 100))
+							return DTERR_FIELD_OVERFLOW;
+						if (AdjustFractMonths(fval, tm, 100))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(CENTURY);
 						break;
 
 					case DTK_MILLENNIUM:
-						tm->tm_year += val * 1000;
-						tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 1000);
+						if (AdjustYears(val, tm, 1000))
+							return DTERR_FIELD_OVERFLOW;
+						if (AdjustFractMonths(fval, tm, 1000))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(MILLENNIUM);
 						break;
 
@@ -3440,6 +3499,11 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 	/* finally, AGO negates everything */
 	if (is_before)
 	{
+		if (tm->tm_hour == INT_MIN ||
+			tm->tm_mday == INT_MIN ||
+			tm->tm_mon == INT_MIN)
+			return DTERR_FIELD_OVERFLOW;
+
 		*fsec = -(*fsec);
 		tm->tm_sec = -tm->tm_sec;
 		tm->tm_min = -tm->tm_min;
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index accd4a7d90..794f82b373 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -232,6 +232,74 @@ INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 years');
 ERROR:  interval out of range
 LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 years'...
                                                  ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 weeks');
+ERROR:  interval field value out of range: "2147483647 weeks"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 weeks')...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 weeks');
+ERROR:  interval field value out of range: "-2147483648 weeks"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 weeks'...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 decades');
+ERROR:  interval field value out of range: "2147483647 decades"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 decades...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 decades');
+ERROR:  interval field value out of range: "-2147483648 decades"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 decade...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 centuries');
+ERROR:  interval field value out of range: "2147483647 centuries"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 centuri...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 centuries');
+ERROR:  interval field value out of range: "-2147483648 centuries"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 centur...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 millennium');
+ERROR:  interval field value out of range: "2147483647 millennium"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 millenn...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 millennium');
+ERROR:  interval field value out of range: "-2147483648 millennium"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 millen...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 millennium 2147483647 months');
+ERROR:  interval field value out of range: "0.1 millennium 2147483647 months"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 millennium 214...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 centuries 2147483647 months');
+ERROR:  interval field value out of range: "0.1 centuries 2147483647 months"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 centuries 2147...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 decades 2147483647 months');
+ERROR:  interval field value out of range: "0.1 decades 2147483647 months"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 decades 214748...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 yrs 2147483647 months');
+ERROR:  interval field value out of range: "0.1 yrs 2147483647 months"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 yrs 2147483647...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 months 2147483647 days');
+ERROR:  interval field value out of range: "0.1 months 2147483647 days"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 months 2147483...
+                                                 ^
+SELECT INTERVAL '0.1 weeks 2147483647 hrs';
+ERROR:  interval out of range
+SELECT INTERVAL '0.1 days 2147483647 hrs';
+ERROR:  interval out of range
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 months ago');
+ERROR:  interval field value out of range: "-2147483648 months ago"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 months...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 days ago');
+ERROR:  interval field value out of range: "-2147483648 days ago"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 days a...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 hours ago');
+ERROR:  interval field value out of range: "-2147483648 hours ago"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 hours ...
+                                                 ^
 -- Test edge-case overflow detection in interval multiplication
 select extract(epoch from '256 microseconds'::interval * (2^55)::float8);
 ERROR:  interval out of range
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 6d532398bd..d524e2dfcf 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -72,6 +72,24 @@ INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 years');
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 years');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 weeks');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 weeks');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 decades');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 decades');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 centuries');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 centuries');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 millennium');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 millennium');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 millennium 2147483647 months');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 centuries 2147483647 months');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 decades 2147483647 months');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 yrs 2147483647 months');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 months 2147483647 days');
+SELECT INTERVAL '0.1 weeks 2147483647 hrs';
+SELECT INTERVAL '0.1 days 2147483647 hrs';
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 months ago');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 days ago');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 hours ago');
 
 -- Test edge-case overflow detection in interval multiplication
 select extract(epoch from '256 microseconds'::interval * (2^55)::float8);
-- 
2.25.1



^ permalink  raw  reply  [nested|flat] 23+ messages in thread

* Re: Fix overflow in DecodeInterval
  2022-02-13 03:51 Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-13 14:35 ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
@ 2022-02-13 20:30   ` Andres Freund <[email protected]>
  2022-02-13 20:38     ` Re: Fix overflow in DecodeInterval Tom Lane <[email protected]>
  1 sibling, 1 reply; 23+ messages in thread

From: Andres Freund @ 2022-02-13 20:30 UTC (permalink / raw)
  To: Joseph Koshakow <[email protected]>; +Cc: Tom Lane <[email protected]>; [email protected]

Hi,

On 2022-02-13 09:35:47 -0500, Joseph Koshakow wrote:
> I chose int return types to keep all these methods
> consistent with DecodeInterval, which returns a
> non-zero int to indicate an error.

That's different, because it actually returns different types of errors. IMO
that difference is actually reason to use a bool for the new cases, because
then it's a tad clearer that they don't return DTERR_*.

> Though I wasn't sure
> if an int or bool would be best, so I'm happy to change
> to bool if people think that's better.

+1 or bool.


> Also I'm realizing now that I've incorrectly been using the
> number of the patch to indicate the version, instead of just
> sticking a v3 to the front. So sorry about that, all the patches
> I sent in this thread are the same patch, just different versions.

No worries ;)


Do we want to consider backpatching these fixes? If so, I'd argue for skipping
10, because it doesn't have int.h stuff yet. There's also the issue with
potentially breaking indexes / constraints? Not that goes entirely away across
major versions...

Greetings,

Andres Freund






^ permalink  raw  reply  [nested|flat] 23+ messages in thread

* Re: Fix overflow in DecodeInterval
  2022-02-13 03:51 Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-13 14:35 ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-13 20:30   ` Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
@ 2022-02-13 20:38     ` Tom Lane <[email protected]>
  2022-02-13 22:12       ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  0 siblings, 1 reply; 23+ messages in thread

From: Tom Lane @ 2022-02-13 20:38 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Joseph Koshakow <[email protected]>; [email protected]

Andres Freund <[email protected]> writes:
> Do we want to consider backpatching these fixes?

I wasn't thinking that we should.  It's a behavioral change and
people might not be pleased to have it appear in minor releases.

			regards, tom lane






^ permalink  raw  reply  [nested|flat] 23+ messages in thread

* Re: Fix overflow in DecodeInterval
  2022-02-13 03:51 Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-13 14:35 ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-13 20:30   ` Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-13 20:38     ` Re: Fix overflow in DecodeInterval Tom Lane <[email protected]>
@ 2022-02-13 22:12       ` Joseph Koshakow <[email protected]>
  2022-02-15 11:44         ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  0 siblings, 1 reply; 23+ messages in thread

From: Joseph Koshakow @ 2022-02-13 22:12 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Andres Freund <[email protected]>; [email protected]

Attached is a new version switching from ints to bools, as requested.

- Joe Koshakow


Attachments:

  [text/x-patch] v5-0001-Check-for-overflow-when-decoding-an-interval.patch (14.2K, ../../CAAvxfHd3gsOkGsbu5AVG+ayHiqsWzuZsi8WknL7v4C_A_4TwOQ@mail.gmail.com/2-v5-0001-Check-for-overflow-when-decoding-an-interval.patch)
  download | inline diff:
From af8f030ad146602b4386f77b5664c6013743569b Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <[email protected]>
Date: Fri, 11 Feb 2022 14:18:32 -0500
Subject: [PATCH] Check for overflow when decoding an interval

When decoding an interval there are various date units which are
aliases for some multiple of another unit. For example a week is 7 days
and a decade is 10 years. When decoding these specific units, there is
no check for overflow, allowing the interval to overflow. This commit
adds an overflow check for all of these units.

Additionally fractional date/time units are rolled down into the next
smallest unit. For example 0.1 months is 3 days. When adding these
fraction units, there is no check for overflow, allowing the interval
to overflow. This commit adds an overflow check for all of the
fractional units.

Additionally adding the word "ago" to the interval negates every
field. However the negative of INT_MIN is still INT_MIN. This
commit adds a check to make sure that we don't try and negate
a field if it's INT_MIN.

Signed-off-by: Joseph Koshakow <[email protected]>
---
 src/backend/utils/adt/datetime.c       | 100 +++++++++++++++++++------
 src/test/regress/expected/interval.out |  68 +++++++++++++++++
 src/test/regress/sql/interval.sql      |  18 +++++
 3 files changed, 164 insertions(+), 22 deletions(-)

diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 7926258c06..15a1ebbe67 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -21,6 +21,7 @@
 #include "access/htup_details.h"
 #include "access/xact.h"
 #include "catalog/pg_type.h"
+#include "common/int.h"
 #include "common/string.h"
 #include "funcapi.h"
 #include "miscadmin.h"
@@ -44,10 +45,13 @@ static int	DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
 					   struct pg_tm *tm);
 static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
 						   int precision, bool fillzeros);
-static void AdjustFractSeconds(double frac, struct pg_tm *tm, fsec_t *fsec,
+static bool AdjustFractSeconds(double frac, struct pg_tm *tm, fsec_t *fsec,
 							   int scale);
-static void AdjustFractDays(double frac, struct pg_tm *tm, fsec_t *fsec,
+static bool AdjustFractDays(double frac, struct pg_tm *tm, fsec_t *fsec,
 							int scale);
+static bool AdjustFractMonths(double frac, struct pg_tm *tm, int scale);
+static bool AdjustDays(int val, struct pg_tm *tm, int multiplier);
+static bool AdjustYears(int val, struct pg_tm *tm, int multiplier);
 static int	DetermineTimeZoneOffsetInternal(struct pg_tm *tm, pg_tz *tzp,
 											pg_time_t *tp);
 static bool DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t,
@@ -499,34 +503,68 @@ AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec)
 /*
  * Multiply frac by scale (to produce seconds) and add to *tm & *fsec.
  * We assume the input frac is less than 1 so overflow is not an issue.
+ * Returns true if successful, false if tm overflows.
  */
-static void
+static bool
 AdjustFractSeconds(double frac, struct pg_tm *tm, fsec_t *fsec, int scale)
 {
 	int			sec;
 
 	if (frac == 0)
-		return;
+		return true;
 	frac *= scale;
 	sec = (int) frac;
-	tm->tm_sec += sec;
+	if (pg_add_s32_overflow(tm->tm_sec, sec, &tm->tm_sec))
+		return false;
 	frac -= sec;
 	*fsec += rint(frac * 1000000);
+	return true;
 }
 
 /* As above, but initial scale produces days */
-static void
+static bool
 AdjustFractDays(double frac, struct pg_tm *tm, fsec_t *fsec, int scale)
 {
 	int			extra_days;
 
 	if (frac == 0)
-		return;
+		return true;
 	frac *= scale;
 	extra_days = (int) frac;
-	tm->tm_mday += extra_days;
+	if (pg_add_s32_overflow(tm->tm_mday, extra_days, &tm->tm_mday))
+		return false;
 	frac -= extra_days;
-	AdjustFractSeconds(frac, tm, fsec, SECS_PER_DAY);
+	return AdjustFractSeconds(frac, tm, fsec, SECS_PER_DAY);
+}
+
+/* As above, but initial scale produces months */
+static bool
+AdjustFractMonths(double frac, struct pg_tm *tm, int scale)
+{
+	int months = rint(frac * MONTHS_PER_YEAR * scale);
+
+	return !pg_add_s32_overflow(tm->tm_mon, months, &tm->tm_mon);
+}
+
+/*
+ * Multiply val by multiplier (to produce days) and add to *tm.
+ * Returns true if successful, false if tm overflows.
+ */
+static bool
+AdjustDays(int val, struct pg_tm *tm, int multiplier)
+{
+	int			extra_days;
+	return !pg_mul_s32_overflow(val, multiplier, &extra_days) &&
+		!pg_add_s32_overflow(tm->tm_mday, extra_days, &tm->tm_mday);
+}
+
+/* As above, but initial val produces years */
+static bool
+AdjustYears(int val, struct pg_tm *tm, int multiplier)
+{
+	int			years;
+	return !pg_mul_s32_overflow(val, multiplier, &years) &&
+		!pg_add_s32_overflow(tm->tm_year, years, &tm->tm_year);
 }
 
 /* Fetch a fractional-second value with suitable error checking */
@@ -3275,56 +3313,69 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 
 					case DTK_MINUTE:
 						tm->tm_min += val;
-						AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
+						if (!AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(MINUTE);
 						break;
 
 					case DTK_HOUR:
 						tm->tm_hour += val;
-						AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
+						if (!AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(HOUR);
 						type = DTK_DAY; /* set for next field */
 						break;
 
 					case DTK_DAY:
 						tm->tm_mday += val;
-						AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+						if (!AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(DAY);
 						break;
 
 					case DTK_WEEK:
-						tm->tm_mday += val * 7;
-						AdjustFractDays(fval, tm, fsec, 7);
+						if (!AdjustDays(val, tm, 7))
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustFractDays(fval, tm, fsec, 7))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(WEEK);
 						break;
 
 					case DTK_MONTH:
 						tm->tm_mon += val;
-						AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
+						if (!AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(MONTH);
 						break;
 
 					case DTK_YEAR:
 						tm->tm_year += val;
-						tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
+						if (!AdjustFractMonths(fval, tm, 1))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(YEAR);
 						break;
 
 					case DTK_DECADE:
-						tm->tm_year += val * 10;
-						tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 10);
+						if (!AdjustYears(val, tm, 10))
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustFractMonths(fval, tm, 10))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(DECADE);
 						break;
 
 					case DTK_CENTURY:
-						tm->tm_year += val * 100;
-						tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 100);
+						if (!AdjustYears(val, tm, 100))
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustFractMonths(fval, tm, 100))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(CENTURY);
 						break;
 
 					case DTK_MILLENNIUM:
-						tm->tm_year += val * 1000;
-						tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 1000);
+						if (!AdjustYears(val, tm, 1000))
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustFractMonths(fval, tm, 1000))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(MILLENNIUM);
 						break;
 
@@ -3440,6 +3491,11 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 	/* finally, AGO negates everything */
 	if (is_before)
 	{
+		if (tm->tm_hour == INT_MIN ||
+			tm->tm_mday == INT_MIN ||
+			tm->tm_mon == INT_MIN)
+			return DTERR_FIELD_OVERFLOW;
+
 		*fsec = -(*fsec);
 		tm->tm_sec = -tm->tm_sec;
 		tm->tm_min = -tm->tm_min;
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index accd4a7d90..794f82b373 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -232,6 +232,74 @@ INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 years');
 ERROR:  interval out of range
 LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 years'...
                                                  ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 weeks');
+ERROR:  interval field value out of range: "2147483647 weeks"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 weeks')...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 weeks');
+ERROR:  interval field value out of range: "-2147483648 weeks"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 weeks'...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 decades');
+ERROR:  interval field value out of range: "2147483647 decades"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 decades...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 decades');
+ERROR:  interval field value out of range: "-2147483648 decades"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 decade...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 centuries');
+ERROR:  interval field value out of range: "2147483647 centuries"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 centuri...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 centuries');
+ERROR:  interval field value out of range: "-2147483648 centuries"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 centur...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 millennium');
+ERROR:  interval field value out of range: "2147483647 millennium"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 millenn...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 millennium');
+ERROR:  interval field value out of range: "-2147483648 millennium"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 millen...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 millennium 2147483647 months');
+ERROR:  interval field value out of range: "0.1 millennium 2147483647 months"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 millennium 214...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 centuries 2147483647 months');
+ERROR:  interval field value out of range: "0.1 centuries 2147483647 months"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 centuries 2147...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 decades 2147483647 months');
+ERROR:  interval field value out of range: "0.1 decades 2147483647 months"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 decades 214748...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 yrs 2147483647 months');
+ERROR:  interval field value out of range: "0.1 yrs 2147483647 months"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 yrs 2147483647...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 months 2147483647 days');
+ERROR:  interval field value out of range: "0.1 months 2147483647 days"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 months 2147483...
+                                                 ^
+SELECT INTERVAL '0.1 weeks 2147483647 hrs';
+ERROR:  interval out of range
+SELECT INTERVAL '0.1 days 2147483647 hrs';
+ERROR:  interval out of range
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 months ago');
+ERROR:  interval field value out of range: "-2147483648 months ago"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 months...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 days ago');
+ERROR:  interval field value out of range: "-2147483648 days ago"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 days a...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 hours ago');
+ERROR:  interval field value out of range: "-2147483648 hours ago"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 hours ...
+                                                 ^
 -- Test edge-case overflow detection in interval multiplication
 select extract(epoch from '256 microseconds'::interval * (2^55)::float8);
 ERROR:  interval out of range
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 6d532398bd..d524e2dfcf 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -72,6 +72,24 @@ INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 years');
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 years');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 weeks');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 weeks');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 decades');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 decades');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 centuries');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 centuries');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 millennium');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 millennium');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 millennium 2147483647 months');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 centuries 2147483647 months');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 decades 2147483647 months');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 yrs 2147483647 months');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 months 2147483647 days');
+SELECT INTERVAL '0.1 weeks 2147483647 hrs';
+SELECT INTERVAL '0.1 days 2147483647 hrs';
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 months ago');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 days ago');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 hours ago');
 
 -- Test edge-case overflow detection in interval multiplication
 select extract(epoch from '256 microseconds'::interval * (2^55)::float8);
-- 
2.25.1



^ permalink  raw  reply  [nested|flat] 23+ messages in thread

* Re: Fix overflow in DecodeInterval
  2022-02-13 03:51 Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-13 14:35 ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-13 20:30   ` Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-13 20:38     ` Re: Fix overflow in DecodeInterval Tom Lane <[email protected]>
  2022-02-13 22:12       ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
@ 2022-02-15 11:44         ` Joseph Koshakow <[email protected]>
  2022-02-15 16:28           ` Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  0 siblings, 1 reply; 23+ messages in thread

From: Joseph Koshakow @ 2022-02-15 11:44 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Andres Freund <[email protected]>; [email protected]

On Sun, Feb 13, 2022 at 5:12 PM Joseph Koshakow <[email protected]> wrote:
>
> Attached is a new version switching from ints to bools, as requested.
>
> - Joe Koshakow

Tom, Andres,

Thanks for your feedback! I'm not super familiar with the process,
should I submit this thread to the commitfest or just leave it as is?

Thanks,
Joe Koshakow






^ permalink  raw  reply  [nested|flat] 23+ messages in thread

* Re: Fix overflow in DecodeInterval
  2022-02-13 03:51 Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-13 14:35 ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-13 20:30   ` Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-13 20:38     ` Re: Fix overflow in DecodeInterval Tom Lane <[email protected]>
  2022-02-13 22:12       ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-15 11:44         ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
@ 2022-02-15 16:28           ` Andres Freund <[email protected]>
  2022-02-18 02:45             ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  0 siblings, 1 reply; 23+ messages in thread

From: Andres Freund @ 2022-02-15 16:28 UTC (permalink / raw)
  To: Joseph Koshakow <[email protected]>; +Cc: Tom Lane <[email protected]>; [email protected]

Hi,

On 2022-02-15 06:44:40 -0500, Joseph Koshakow wrote:
> Thanks for your feedback! I'm not super familiar with the process,
> should I submit this thread to the commitfest or just leave it as is?

Submit it to the CF - then we get an automatic test on a few platforms. I
think we can apply it quickly after that...

Greetings,

Andres Freund






^ permalink  raw  reply  [nested|flat] 23+ messages in thread

* Re: Fix overflow in DecodeInterval
  2022-02-13 03:51 Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-13 14:35 ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-13 20:30   ` Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-13 20:38     ` Re: Fix overflow in DecodeInterval Tom Lane <[email protected]>
  2022-02-13 22:12       ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-15 11:44         ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-15 16:28           ` Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
@ 2022-02-18 02:45             ` Joseph Koshakow <[email protected]>
  2022-02-19 19:26               ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  0 siblings, 1 reply; 23+ messages in thread

From: Joseph Koshakow @ 2022-02-18 02:45 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Tom Lane <[email protected]>; [email protected]

Ok, so I've attached a patch with my final unprompted changes. It
contains the following two changes:

1. I found some more overflows with the ISO8601 formats and have
included some fixes.
2. I reverted the overflow checks for the seconds field. It's actually a
bit more complicated than I thought. For example consider the following
query:
    postgres=# SELECT INTERVAL '0.99999999 min 2147483647 sec';
            interval
    ----------------------
    -596523:13:09.000001
    (1 row)
This query will overflow the tm_sec field of the struct pg_tm, however
it's not actually out of range for the Interval. I'm not sure the best
way to handle this right now, but I think it would be best to leave it
for a future patch. Perhaps the time related fields in struct pg_tm
need to be changed to 64 bit ints.

- Joe Koshakow


Attachments:

  [text/x-patch] v6-0001-Check-for-overflow-when-decoding-an-interval.patch (18.3K, ../../CAAvxfHfKYHw8n19s7eAcDo_B2T1SS22O1p0X4OE5_WBK230O+Q@mail.gmail.com/2-v6-0001-Check-for-overflow-when-decoding-an-interval.patch)
  download | inline diff:
From 5750dcfbc00cb1259263e9986898f1960edf9c7f Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <[email protected]>
Date: Fri, 11 Feb 2022 14:18:32 -0500
Subject: [PATCH] Check for overflow when decoding an interval

When decoding an interval there are various date units which are
aliases for some multiple of another unit. For example a week is 7 days
and a decade is 10 years. When decoding these specific units, there is
no check for overflow, allowing the interval to overflow. This commit
adds an overflow check for all of these units.

Additionally fractional date/time units are rolled down into the next
smallest unit. For example 0.1 months is 3 days. When adding these
fraction units, there is no check for overflow, allowing the interval
to overflow. This commit adds an overflow check for all of the
fractional units.

Additionally adding the word "ago" to the interval negates every
field. However the negative of INT_MIN is still INT_MIN. This
commit adds a check to make sure that we don't try and negate
a field if it's INT_MIN.

Signed-off-by: Joseph Koshakow <[email protected]>
---
 src/backend/utils/adt/datetime.c       | 115 +++++++++++++++++++------
 src/test/regress/expected/interval.out | 112 ++++++++++++++++++++++++
 src/test/regress/sql/interval.sql      |  29 +++++++
 3 files changed, 232 insertions(+), 24 deletions(-)

diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 7926258c06..1c2ab58f82 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -21,6 +21,7 @@
 #include "access/htup_details.h"
 #include "access/xact.h"
 #include "catalog/pg_type.h"
+#include "common/int.h"
 #include "common/string.h"
 #include "funcapi.h"
 #include "miscadmin.h"
@@ -46,8 +47,11 @@ static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
 						   int precision, bool fillzeros);
 static void AdjustFractSeconds(double frac, struct pg_tm *tm, fsec_t *fsec,
 							   int scale);
-static void AdjustFractDays(double frac, struct pg_tm *tm, fsec_t *fsec,
+static bool AdjustFractDays(double frac, struct pg_tm *tm, fsec_t *fsec,
 							int scale);
+static bool AdjustFractMonths(double frac, struct pg_tm *tm, int scale);
+static bool AdjustDays(int val, struct pg_tm *tm, int multiplier);
+static bool AdjustYears(int val, struct pg_tm *tm, int multiplier);
 static int	DetermineTimeZoneOffsetInternal(struct pg_tm *tm, pg_tz *tzp,
 											pg_time_t *tp);
 static bool DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t,
@@ -515,18 +519,56 @@ AdjustFractSeconds(double frac, struct pg_tm *tm, fsec_t *fsec, int scale)
 }
 
 /* As above, but initial scale produces days */
-static void
+static bool
 AdjustFractDays(double frac, struct pg_tm *tm, fsec_t *fsec, int scale)
 {
 	int			extra_days;
 
 	if (frac == 0)
-		return;
+		return true;
 	frac *= scale;
 	extra_days = (int) frac;
-	tm->tm_mday += extra_days;
+	if (pg_add_s32_overflow(tm->tm_mday, extra_days, &tm->tm_mday))
+		return false;
 	frac -= extra_days;
 	AdjustFractSeconds(frac, tm, fsec, SECS_PER_DAY);
+	return true;
+}
+
+/* As above, but initial scale produces months */
+static bool
+AdjustFractMonths(double frac, struct pg_tm *tm, int scale)
+{
+	int months = rint(frac * MONTHS_PER_YEAR * scale);
+	return !pg_add_s32_overflow(tm->tm_mon, months, &tm->tm_mon);
+}
+
+/*
+ * Multiply val by multiplier (to produce days) and add to *tm.
+ * Returns true if successful, false if tm overflows.
+ */
+static bool
+AdjustDays(int val, struct pg_tm *tm, int multiplier)
+{
+	int			extra_days;
+	return !pg_mul_s32_overflow(val, multiplier, &extra_days) &&
+		!pg_add_s32_overflow(tm->tm_mday, extra_days, &tm->tm_mday);
+}
+
+/* As above, but initial val produces months */
+static bool
+AdjustMonths(int val, struct pg_tm *tm)
+{
+	return !pg_add_s32_overflow(tm->tm_mon, val, &tm->tm_mon);
+}
+
+/* As above, but initial val produces years */
+static bool
+AdjustYears(int val, struct pg_tm *tm, int multiplier)
+{
+	int			years;
+	return !pg_mul_s32_overflow(val, multiplier, &years) &&
+		!pg_add_s32_overflow(tm->tm_year, years, &tm->tm_year);
 }
 
 /* Fetch a fractional-second value with suitable error checking */
@@ -3287,44 +3329,56 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 						break;
 
 					case DTK_DAY:
-						tm->tm_mday += val;
+						if (!AdjustDays(val, tm, 1))
+							return DTERR_FIELD_OVERFLOW;
 						AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
 						tmask = DTK_M(DAY);
 						break;
 
 					case DTK_WEEK:
-						tm->tm_mday += val * 7;
-						AdjustFractDays(fval, tm, fsec, 7);
+						if (!AdjustDays(val, tm, 7))
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustFractDays(fval, tm, fsec, 7))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(WEEK);
 						break;
 
 					case DTK_MONTH:
-						tm->tm_mon += val;
-						AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
+						if (!AdjustMonths(val, tm))
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(MONTH);
 						break;
 
 					case DTK_YEAR:
 						tm->tm_year += val;
-						tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
+						if (!AdjustFractMonths(fval, tm, 1))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(YEAR);
 						break;
 
 					case DTK_DECADE:
-						tm->tm_year += val * 10;
-						tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 10);
+						if (!AdjustYears(val, tm, 10))
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustFractMonths(fval, tm, 10))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(DECADE);
 						break;
 
 					case DTK_CENTURY:
-						tm->tm_year += val * 100;
-						tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 100);
+						if (!AdjustYears(val, tm, 100))
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustFractMonths(fval, tm, 100))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(CENTURY);
 						break;
 
 					case DTK_MILLENNIUM:
-						tm->tm_year += val * 1000;
-						tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 1000);
+						if (!AdjustYears(val, tm, 1000))
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustFractMonths(fval, tm, 1000))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(MILLENNIUM);
 						break;
 
@@ -3440,6 +3494,11 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 	/* finally, AGO negates everything */
 	if (is_before)
 	{
+		if (tm->tm_hour == INT_MIN ||
+			tm->tm_mday == INT_MIN ||
+			tm->tm_mon == INT_MIN)
+			return DTERR_FIELD_OVERFLOW;
+
 		*fsec = -(*fsec);
 		tm->tm_sec = -tm->tm_sec;
 		tm->tm_min = -tm->tm_min;
@@ -3561,18 +3620,24 @@ DecodeISO8601Interval(char *str,
 			{
 				case 'Y':
 					tm->tm_year += val;
-					tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
+					if (!AdjustFractMonths(fval, tm, 1))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case 'M':
-					tm->tm_mon += val;
-					AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
+					if (!AdjustMonths(val, tm))
+						return DTERR_FIELD_OVERFLOW;
+					if (!AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case 'W':
-					tm->tm_mday += val * 7;
-					AdjustFractDays(fval, tm, fsec, 7);
+					if (!AdjustDays(val, tm, 7))
+						return DTERR_FIELD_OVERFLOW;
+					if (!AdjustFractDays(fval, tm, fsec, 7))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case 'D':
-					tm->tm_mday += val;
+					if (!AdjustDays(val, tm, 1))
+						return DTERR_FIELD_OVERFLOW;
 					AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
 					break;
 				case 'T':		/* ISO 8601 4.4.3.3 Alternative Format / Basic */
@@ -3610,7 +3675,8 @@ DecodeISO8601Interval(char *str,
 					dterr = ParseISO8601Number(str, &str, &val, &fval);
 					if (dterr)
 						return dterr;
-					tm->tm_mon += val;
+					if (!AdjustMonths(val, tm))
+						return DTERR_FIELD_OVERFLOW;
 					AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
 					if (*str == '\0')
 						return 0;
@@ -3627,7 +3693,8 @@ DecodeISO8601Interval(char *str,
 					dterr = ParseISO8601Number(str, &str, &val, &fval);
 					if (dterr)
 						return dterr;
-					tm->tm_mday += val;
+					if (!AdjustDays(val, tm, 1))
+						return DTERR_FIELD_OVERFLOW;
 					AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
 					if (*str == '\0')
 						return 0;
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index accd4a7d90..0ec40ce968 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -232,6 +232,118 @@ INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 years');
 ERROR:  interval out of range
 LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 years'...
                                                  ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 weeks');
+ERROR:  interval field value out of range: "2147483647 weeks"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 weeks')...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 weeks');
+ERROR:  interval field value out of range: "-2147483648 weeks"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 weeks'...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 decades');
+ERROR:  interval field value out of range: "2147483647 decades"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 decades...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 decades');
+ERROR:  interval field value out of range: "-2147483648 decades"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 decade...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 centuries');
+ERROR:  interval field value out of range: "2147483647 centuries"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 centuri...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 centuries');
+ERROR:  interval field value out of range: "-2147483648 centuries"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 centur...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 millennium');
+ERROR:  interval field value out of range: "2147483647 millennium"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 millenn...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 millennium');
+ERROR:  interval field value out of range: "-2147483648 millennium"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 millen...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 millennium 2147483647 months');
+ERROR:  interval field value out of range: "0.1 millennium 2147483647 months"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 millennium 214...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 centuries 2147483647 months');
+ERROR:  interval field value out of range: "0.1 centuries 2147483647 months"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 centuries 2147...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 decades 2147483647 months');
+ERROR:  interval field value out of range: "0.1 decades 2147483647 months"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 decades 214748...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 yrs 2147483647 months');
+ERROR:  interval field value out of range: "0.1 yrs 2147483647 months"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 yrs 2147483647...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 months 0.1 yrs');
+ERROR:  interval field value out of range: "2147483647 months 0.1 yrs"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 months ...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 months 2147483647 days');
+ERROR:  interval field value out of range: "0.1 months 2147483647 days"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 months 2147483...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('1 week 2147483647 days');
+ERROR:  interval field value out of range: "1 week 2147483647 days"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('1 week 2147483647 ...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 days 1 week');
+ERROR:  interval field value out of range: "2147483647 days 1 week"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 days 1 ...
+                                                 ^
+SELECT INTERVAL '0.1 weeks 2147483647 hrs';
+ERROR:  interval out of range
+SELECT INTERVAL '0.1 days 2147483647 hrs';
+ERROR:  interval out of range
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 months ago');
+ERROR:  interval field value out of range: "-2147483648 months ago"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 months...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 days ago');
+ERROR:  interval field value out of range: "-2147483648 days ago"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 days a...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 hours ago');
+ERROR:  interval field value out of range: "-2147483648 hours ago"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 hours ...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0.1M2147483647D');
+ERROR:  interval field value out of range: "P0.1M2147483647D"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0.1M2147483647D')...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0.1Y2147483647M');
+ERROR:  interval field value out of range: "P0.1Y2147483647M"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0.1Y2147483647M')...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P2147483647M0.1Y');
+ERROR:  interval field value out of range: "P2147483647M0.1Y"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P2147483647M0.1Y')...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P1W2147483647D');
+ERROR:  interval field value out of range: "P1W2147483647D"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P1W2147483647D');
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P2147483647D1W');
+ERROR:  interval field value out of range: "P2147483647D1W"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P2147483647D1W');
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0.5W2147483647D');
+ERROR:  interval field value out of range: "P0.5W2147483647D"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0.5W2147483647D')...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0000-00.1-2147483647T00:00:00');
+ERROR:  interval field value out of range: "P0000-00.1-2147483647T00:00:00"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0000-00.1-2147483...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0000.1-2147483647-00T00:00:00');
+ERROR:  interval field value out of range: "P0000.1-2147483647-00T00:00:00"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0000.1-2147483647...
+                                                 ^
 -- Test edge-case overflow detection in interval multiplication
 select extract(epoch from '256 microseconds'::interval * (2^55)::float8);
 ERROR:  interval out of range
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 6d532398bd..dda8fd3a16 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -72,6 +72,35 @@ INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 years');
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 years');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 weeks');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 weeks');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 decades');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 decades');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 centuries');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 centuries');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 millennium');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 millennium');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 millennium 2147483647 months');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 centuries 2147483647 months');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 decades 2147483647 months');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 yrs 2147483647 months');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 months 0.1 yrs');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 months 2147483647 days');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('1 week 2147483647 days');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 days 1 week');
+SELECT INTERVAL '0.1 weeks 2147483647 hrs';
+SELECT INTERVAL '0.1 days 2147483647 hrs';
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 months ago');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 days ago');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 hours ago');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0.1M2147483647D');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0.1Y2147483647M');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P2147483647M0.1Y');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P1W2147483647D');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P2147483647D1W');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0.5W2147483647D');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0000-00.1-2147483647T00:00:00');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0000.1-2147483647-00T00:00:00');
 
 -- Test edge-case overflow detection in interval multiplication
 select extract(epoch from '256 microseconds'::interval * (2^55)::float8);
-- 
2.25.1



^ permalink  raw  reply  [nested|flat] 23+ messages in thread

* Re: Fix overflow in DecodeInterval
  2022-02-13 03:51 Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-13 14:35 ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-13 20:30   ` Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-13 20:38     ` Re: Fix overflow in DecodeInterval Tom Lane <[email protected]>
  2022-02-13 22:12       ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-15 11:44         ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-15 16:28           ` Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-18 02:45             ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
@ 2022-02-19 19:26               ` Joseph Koshakow <[email protected]>
  2022-02-20 00:16                 ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  0 siblings, 1 reply; 23+ messages in thread

From: Joseph Koshakow @ 2022-02-19 19:26 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Tom Lane <[email protected]>; [email protected]

Copying over a related thread here to have info all in one place.
It's a little fragmented so sorry about that.


On Fri, Feb 18, 2022 at 11:44 PM Tom Lane <[email protected]> wrote:
>
> Joseph Koshakow <[email protected]> writes:
> > When formatting the output of an Interval, we call abs() on the hours
> > field of the Interval. Calling abs(INT_MIN) returns back INT_MIN
> > causing the output to contain two '-' characters. The attached patch
> > fixes that issue by special casing INT_MIN hours.
>
> Good catch, but it looks to me like three out of the four formats in
> EncodeInterval have variants of this problem --- there are assumptions
> throughout that code that we can compute "-x" or "abs(x)" without
> fear.  Not much point in fixing only one symptom.
>
> Also, I notice that there's an overflow hazard upstream of here,
> in interval2tm:
>
> regression=# select interval '214748364 hours' * 11;
> ERROR:  interval out of range
> regression=# \errverbose
> ERROR:  22008: interval out of range
> LOCATION:  interval2tm, timestamp.c:1982
>
> There's no good excuse for not being able to print a value that
> we computed successfully.
>
> I wonder if the most reasonable fix would be to start using int64
> instead of int arithmetic for the values that are potentially large.
> I doubt that we'd be taking much of a performance hit on modern
> hardware.
>
>                         regards, tom lane

Joseph Koshakow <[email protected]> writes:
> On Fri, Feb 18, 2022 at 11:44 PM Tom Lane <[email protected]> wrote:
>> I wonder if the most reasonable fix would be to start using int64
>> instead of int arithmetic for the values that are potentially large.
>> I doubt that we'd be taking much of a performance hit on modern
>> hardware.

> That's an interesting idea. I've always assumed that the range of the
> time fields of Intervals was 2147483647 hours 59 minutes
> 59.999999 seconds to -2147483648 hours -59 minutes
> -59.999999 seconds. However the only reason that we can't support
> the full range of int64 microseconds is because the struct pg_tm fields
> are only ints. If we increase those fields to int64 then we'd be able to
> support the full int64 range for microseconds as well as implicitly fix
> some of the overflow issues in DecodeInterval and EncodeInterval.

On Sat, Feb 19, 2022 at 1:52 PM Tom Lane <[email protected]> wrote:
>
> Joseph Koshakow <[email protected]> writes:
> > On Sat, Feb 19, 2022 at 12:00 PM Tom Lane <[email protected]> wrote:
> >> I think that messing with struct pg_tm might have too many side-effects.
> >> However, pg_tm isn't all that well adapted to intervals in the first
> >> place, so it'd make sense to make a new struct specifically for interval
> >> decoding.
>
> > Yeah that's a good idea, pg_tm is used all over the place. Is there a
> > reason we need an intermediate structure to convert to and from?
> > We could just convert directly to an Interval in DecodeInterval and
> > print directly from an Interval in EncodeInterval.
>
> Yeah, I thought about that too, but if you look at the other callers of
> interval2tm, you'll see this same set of issues.  I'm inclined to keep
> the current code structure and just fix the data structure.  Although
> interval2tm isn't *that* big, I don't think four copies of its logic
> would be an improvement.
>
> > Also I originally created a separate thread and patch because I
> > thought this wouldn't be directly related to my other patch,
> > https://commitfest.postgresql.org/37/3541/. However I think with these
> > discussed changes it's directly related. So I think it's best to close
> > this thread and patch and copy this conversion to the other thread.
>
> Agreed.
>
>                         regards, tom lane






^ permalink  raw  reply  [nested|flat] 23+ messages in thread

* Re: Fix overflow in DecodeInterval
  2022-02-13 03:51 Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-13 14:35 ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-13 20:30   ` Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-13 20:38     ` Re: Fix overflow in DecodeInterval Tom Lane <[email protected]>
  2022-02-13 22:12       ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-15 11:44         ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-15 16:28           ` Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-18 02:45             ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-19 19:26               ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
@ 2022-02-20 00:16                 ` Joseph Koshakow <[email protected]>
  2022-02-20 23:37                   ` Re: Fix overflow in DecodeInterval Tom Lane <[email protected]>
  0 siblings, 1 reply; 23+ messages in thread

From: Joseph Koshakow @ 2022-02-20 00:16 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Tom Lane <[email protected]>; [email protected]

Attached is a patch of my first pass. The to_char method isn't finished
and I need to add a bunch of tests, but everything else is in place. It
ended up being a fairly large change in case anyone wants to take a look
the changes so far.

One thing I noticed is that interval.c has a ton of code copied and pasted
from other files. The code seemed out of date from those other files, so
I tried to bring it up to date and add my changes.

- Joe Koshakow


Attachments:

  [text/x-patch] v7-0001-Check-for-overflow-when-decoding-an-interval.patch (82.3K, ../../CAAvxfHdVaHrriTWyvsLg3POX13rOyUGysur59sOT02qmmTq0Rg@mail.gmail.com/2-v7-0001-Check-for-overflow-when-decoding-an-interval.patch)
  download | inline diff:
From f678ee1bdcd6e075f1b1c771e6d5f6b3f9089086 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <[email protected]>
Date: Fri, 11 Feb 2022 14:18:32 -0500
Subject: [PATCH] Check for overflow when decoding an interval

When decoding an interval there are various date units which are
aliases for some multiple of another unit. For example a week is 7 days
and a decade is 10 years. When decoding these specific units, there is
no check for overflow, allowing the interval to overflow. This commit
adds an overflow check for all of these units.

Additionally fractional date/time units are rolled down into the next
smallest unit. For example 0.1 months is 3 days. When adding these
fraction units, there is no check for overflow, allowing the interval
to overflow. This commit adds an overflow check for all of the
fractional units.

Additionally adding the word "ago" to the interval negates every
field. However the negative of INT_MIN is still INT_MIN. This
commit adds a check to make sure that we don't try and negate
a field if it's INT_MIN.

Additionally when encoding an interval there are numerous points
where fields are negating or passed to abs(). When any of the
fields are INT_MIN this results in undefined behavior, but
usually just leaves the value unchanged as INT_MIN. This
commit fixes this issue.

Additionally this commit changes the Interval code to use it's
own intermediate pg_tm data structure that uses int64s instead
of ints. This helps avoid numerous overflow/underflow issues.
Additionally it allows us to use the full range of int64
microseconds instead of being artificially constrained by the
int pg_tm fields.

Signed-off-by: Joseph Koshakow <[email protected]>
---
 src/backend/utils/adt/datetime.c          | 413 +++++++++------
 src/backend/utils/adt/formatting.c        |  24 +-
 src/backend/utils/adt/timestamp.c         |  54 +-
 src/common/string.c                       |  15 +
 src/include/common/string.h               |   2 +
 src/include/pgtime.h                      |  12 +
 src/include/utils/datetime.h              |   6 +-
 src/include/utils/timestamp.h             |   5 +-
 src/interfaces/ecpg/pgtypeslib/dt.h       |  20 +-
 src/interfaces/ecpg/pgtypeslib/interval.c | 584 ++++++++++++++--------
 src/test/regress/expected/interval.out    | 147 ++++++
 src/test/regress/sql/interval.sql         |  46 ++
 12 files changed, 946 insertions(+), 382 deletions(-)

diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 7926258c06..908a67648d 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -21,6 +21,7 @@
 #include "access/htup_details.h"
 #include "access/xact.h"
 #include "catalog/pg_type.h"
+#include "common/int.h"
 #include "common/string.h"
 #include "funcapi.h"
 #include "miscadmin.h"
@@ -42,12 +43,15 @@ static int	DecodeTime(char *str, int fmask, int range,
 static const datetkn *datebsearch(const char *key, const datetkn *base, int nel);
 static int	DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
 					   struct pg_tm *tm);
-static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
+static char *AppendSeconds(char *cp, int64 sec, fsec_t fsec,
 						   int precision, bool fillzeros);
-static void AdjustFractSeconds(double frac, struct pg_tm *tm, fsec_t *fsec,
+static bool AdjustFractSeconds(double frac, struct pg_itm *itm, fsec_t *fsec,
 							   int scale);
-static void AdjustFractDays(double frac, struct pg_tm *tm, fsec_t *fsec,
+static bool AdjustFractDays(double frac, struct pg_itm *itm, fsec_t *fsec,
 							int scale);
+static bool AdjustFractMonths(double frac, struct pg_itm *itm, int scale);
+static bool AdjustDays(int val, struct pg_itm *itm, int multiplier);
+static bool AdjustYears(int val, struct pg_itm *itm, int multiplier);
 static int	DetermineTimeZoneOffsetInternal(struct pg_tm *tm, pg_tz *tzp,
 											pg_time_t *tp);
 static bool DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t,
@@ -428,7 +432,7 @@ GetCurrentTimeUsec(struct pg_tm *tm, fsec_t *fsec, int *tzp)
  * Note that any sign is stripped from the input seconds values.
  */
 static char *
-AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros)
+AppendSeconds(char *cp, int64 sec, fsec_t fsec, int precision, bool fillzeros)
 {
 	Assert(precision >= 0);
 
@@ -500,33 +504,82 @@ AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec)
  * Multiply frac by scale (to produce seconds) and add to *tm & *fsec.
  * We assume the input frac is less than 1 so overflow is not an issue.
  */
-static void
-AdjustFractSeconds(double frac, struct pg_tm *tm, fsec_t *fsec, int scale)
+static bool
+AdjustFractSeconds(double frac, struct pg_itm *itm, fsec_t *fsec, int scale)
 {
 	int			sec;
 
 	if (frac == 0)
-		return;
+		return true;
 	frac *= scale;
-	sec = (int) frac;
-	tm->tm_sec += sec;
+	sec = (int64) frac;
+	if (pg_add_s64_overflow(itm->tm_sec, sec, &itm->tm_sec))
+		return false;
 	frac -= sec;
 	*fsec += rint(frac * 1000000);
+	return true;
 }
 
 /* As above, but initial scale produces days */
-static void
-AdjustFractDays(double frac, struct pg_tm *tm, fsec_t *fsec, int scale)
+static bool
+AdjustFractDays(double frac, struct pg_itm *itm, fsec_t *fsec, int scale)
 {
 	int			extra_days;
 
 	if (frac == 0)
-		return;
+		return true;
 	frac *= scale;
 	extra_days = (int) frac;
-	tm->tm_mday += extra_days;
+	if (pg_add_s32_overflow(itm->tm_mday, extra_days, &itm->tm_mday))
+		return false;
 	frac -= extra_days;
-	AdjustFractSeconds(frac, tm, fsec, SECS_PER_DAY);
+	return AdjustFractSeconds(frac, itm, fsec, SECS_PER_DAY);
+}
+
+/* As above, but initial scale produces months */
+static bool
+AdjustFractMonths(double frac, struct pg_itm *itm, int scale)
+{
+	int months = rint(frac * MONTHS_PER_YEAR * scale);
+	return !pg_add_s32_overflow(itm->tm_mon, months, &itm->tm_mon);
+}
+
+/*
+ * Add val to *tm seconds.
+ * Returns true if successful, false if tm overflows.
+ */
+static bool
+AdjustSeconds(int64 val, struct pg_itm *itm)
+{
+	return !pg_add_s64_overflow(itm->tm_sec, val, &itm->tm_sec);
+}
+
+/*
+ * Multiply val by multiplier (to produce days) and add to *tm.
+ * Returns true if successful, false if tm overflows.
+ */
+static bool
+AdjustDays(int val, struct pg_itm *itm, int multiplier)
+{
+	int			extra_days;
+	return !pg_mul_s32_overflow(val, multiplier, &extra_days) &&
+		!pg_add_s32_overflow(itm->tm_mday, extra_days, &itm->tm_mday);
+}
+
+/* As above, but initial val produces months */
+static bool
+AdjustMonths(int val, struct pg_itm *itm)
+{
+	return !pg_add_s32_overflow(itm->tm_mon, val, &itm->tm_mon);
+}
+
+/* As above, but initial val produces years */
+static bool
+AdjustYears(int val, struct pg_itm *itm, int multiplier)
+{
+	int			years;
+	return !pg_mul_s32_overflow(val, multiplier, &years) &&
+		!pg_add_s32_overflow(itm->tm_year, years, &itm->tm_year);
 }
 
 /* Fetch a fractional-second value with suitable error checking */
@@ -3064,12 +3117,28 @@ DecodeSpecial(int field, char *lowtoken, int *val)
 }
 
 
+/* ClearPgItm
+ *
+ * Zero out a pg_itm and associated fsec_t
+ */
+static inline void
+ClearPgItm(struct pg_itm *itm, fsec_t *fsec)
+{
+	itm->tm_year = 0;
+	itm->tm_mon = 0;
+	itm->tm_mday = 0;
+	itm->tm_hour = 0;
+	itm->tm_min = 0;
+	itm->tm_sec = 0;
+	*fsec = 0;
+}
+
 /* ClearPgTm
  *
- * Zero out a pg_tm and associated fsec_t
+ * Zero out a pg_tm
  */
 static inline void
-ClearPgTm(struct pg_tm *tm, fsec_t *fsec)
+ClearPgTm(struct pg_tm *tm)
 {
 	tm->tm_year = 0;
 	tm->tm_mon = 0;
@@ -3077,7 +3146,6 @@ ClearPgTm(struct pg_tm *tm, fsec_t *fsec)
 	tm->tm_hour = 0;
 	tm->tm_min = 0;
 	tm->tm_sec = 0;
-	*fsec = 0;
 }
 
 
@@ -3094,21 +3162,23 @@ ClearPgTm(struct pg_tm *tm, fsec_t *fsec)
  */
 int
 DecodeInterval(char **field, int *ftype, int nf, int range,
-			   int *dtype, struct pg_tm *tm, fsec_t *fsec)
+			   int *dtype, struct pg_itm *itm, fsec_t *fsec)
 {
-	bool		is_before = false;
-	char	   *cp;
-	int			fmask = 0,
-				tmask,
-				type;
-	int			i;
-	int			dterr;
-	int			val;
-	double		fval;
+	bool			is_before = false;
+	char	   		*cp;
+	int				fmask = 0,
+					tmask,
+					type;
+	int				i;
+	int				dterr;
+	int64			val;
+	double			fval;
+	struct pg_tm 	tm;
 
 	*dtype = DTK_DELTA;
 	type = IGNORE_DTF;
-	ClearPgTm(tm, fsec);
+	ClearPgItm(itm, fsec);
+	ClearPgTm(&tm);
 
 	/* read through list backwards to pick up units before values */
 	for (i = nf - 1; i >= 0; i--)
@@ -3117,7 +3187,8 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 		{
 			case DTK_TIME:
 				dterr = DecodeTime(field[i], fmask, range,
-								   &tmask, tm, fsec);
+								   &tmask, &tm, fsec);
+				tm2itm(&tm, itm);
 				if (dterr)
 					return dterr;
 				type = DTK_DAY;
@@ -3138,14 +3209,19 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 				 */
 				if (strchr(field[i] + 1, ':') != NULL &&
 					DecodeTime(field[i] + 1, fmask, range,
-							   &tmask, tm, fsec) == 0)
+							   &tmask, &tm, fsec) == 0)
 				{
+					tm2itm(&tm, itm);
 					if (*field[i] == '-')
 					{
 						/* flip the sign on all fields */
-						tm->tm_hour = -tm->tm_hour;
-						tm->tm_min = -tm->tm_min;
-						tm->tm_sec = -tm->tm_sec;
+						if (itm->tm_hour == PG_INT64_MIN ||
+							itm->tm_min == PG_INT64_MIN ||
+							itm->tm_mon == PG_INT64_MIN)
+							return DTERR_FIELD_OVERFLOW;
+						itm->tm_hour = -itm->tm_hour;
+						itm->tm_min = -itm->tm_min;
+						itm->tm_sec = -itm->tm_sec;
 						*fsec = -(*fsec);
 					}
 
@@ -3164,7 +3240,6 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 				 */
 
 				/* FALLTHROUGH */
-
 			case DTK_DATE:
 			case DTK_NUMBER:
 				if (type == IGNORE_DTF)
@@ -3204,7 +3279,7 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 				}
 
 				errno = 0;
-				val = strtoint(field[i], &cp, 10);
+				val = strtoint_64(field[i], &cp, 10);
 				if (errno == ERANGE)
 					return DTERR_FIELD_OVERFLOW;
 
@@ -3253,14 +3328,16 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 
 					case DTK_MILLISEC:
 						/* avoid overflowing the fsec field */
-						tm->tm_sec += val / 1000;
+						if (!AdjustSeconds(val / 1000, itm))
+							return DTERR_FIELD_OVERFLOW;
 						val -= (val / 1000) * 1000;
 						*fsec += rint((val + fval) * 1000);
 						tmask = DTK_M(MILLISECOND);
 						break;
 
 					case DTK_SECOND:
-						tm->tm_sec += val;
+						if (!AdjustSeconds(val, itm))
+							return DTERR_FIELD_OVERFLOW;
 						*fsec += rint(fval * 1000000);
 
 						/*
@@ -3274,57 +3351,86 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 						break;
 
 					case DTK_MINUTE:
-						tm->tm_min += val;
-						AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
+						itm->tm_min += val;
+						if (!AdjustFractSeconds(fval, itm, fsec, SECS_PER_MINUTE))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(MINUTE);
 						break;
 
 					case DTK_HOUR:
-						tm->tm_hour += val;
-						AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
+						itm->tm_hour += val;
+						if (!AdjustFractSeconds(fval, itm, fsec, SECS_PER_HOUR))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(HOUR);
 						type = DTK_DAY; /* set for next field */
 						break;
 
 					case DTK_DAY:
-						tm->tm_mday += val;
-						AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+						if (val < INT_MIN || val > INT_MAX)
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustDays((int)val, itm, 1))
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustFractSeconds(fval, itm, fsec, SECS_PER_DAY))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(DAY);
 						break;
 
 					case DTK_WEEK:
-						tm->tm_mday += val * 7;
-						AdjustFractDays(fval, tm, fsec, 7);
+						if (val < INT_MIN || val > INT_MAX)
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustDays((int)val, itm, 7))
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustFractDays(fval, itm, fsec, 7))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(WEEK);
 						break;
 
 					case DTK_MONTH:
-						tm->tm_mon += val;
-						AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
+						if (val < INT_MIN || val > INT_MAX)
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustMonths((int)val, itm))
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustFractDays(fval, itm, fsec, DAYS_PER_MONTH))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(MONTH);
 						break;
 
 					case DTK_YEAR:
-						tm->tm_year += val;
-						tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
+						if (val < INT_MIN || val > INT_MAX)
+							return DTERR_FIELD_OVERFLOW;
+						itm->tm_year += val;
+						if (!AdjustFractMonths(fval, itm, 1))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(YEAR);
 						break;
 
 					case DTK_DECADE:
-						tm->tm_year += val * 10;
-						tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 10);
+						if (val < INT_MIN || val > INT_MAX)
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustYears((int)val, itm, 10))
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustFractMonths(fval, itm, 10))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(DECADE);
 						break;
 
 					case DTK_CENTURY:
-						tm->tm_year += val * 100;
-						tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 100);
+						if (val < INT_MIN || val > INT_MAX)
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustYears((int)val, itm, 100))
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustFractMonths(fval, itm, 100))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(CENTURY);
 						break;
 
 					case DTK_MILLENNIUM:
-						tm->tm_year += val * 1000;
-						tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 1000);
+						if (val < INT_MIN || val > INT_MAX)
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustYears((int)val, itm, 1000))
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustFractMonths(fval, itm, 1000))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(MILLENNIUM);
 						break;
 
@@ -3335,7 +3441,7 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 
 			case DTK_STRING:
 			case DTK_SPECIAL:
-				type = DecodeUnits(i, field[i], &val);
+				type = DecodeUnits(i, field[i], (int *)&val);
 				if (type == IGNORE_DTF)
 					continue;
 
@@ -3381,7 +3487,8 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 
 		sec = *fsec / USECS_PER_SEC;
 		*fsec -= sec * USECS_PER_SEC;
-		tm->tm_sec += sec;
+		if (!AdjustSeconds(sec, itm))
+			return DTERR_FIELD_OVERFLOW;
 	}
 
 	/*----------
@@ -3422,31 +3529,39 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 			 */
 			if (*fsec > 0)
 				*fsec = -(*fsec);
-			if (tm->tm_sec > 0)
-				tm->tm_sec = -tm->tm_sec;
-			if (tm->tm_min > 0)
-				tm->tm_min = -tm->tm_min;
-			if (tm->tm_hour > 0)
-				tm->tm_hour = -tm->tm_hour;
-			if (tm->tm_mday > 0)
-				tm->tm_mday = -tm->tm_mday;
-			if (tm->tm_mon > 0)
-				tm->tm_mon = -tm->tm_mon;
-			if (tm->tm_year > 0)
-				tm->tm_year = -tm->tm_year;
+			if (itm->tm_sec > 0)
+				itm->tm_sec = -itm->tm_sec;
+			if (itm->tm_min > 0)
+				itm->tm_min = -itm->tm_min;
+			if (itm->tm_hour > 0)
+				itm->tm_hour = -itm->tm_hour;
+			if (itm->tm_mday > 0)
+				itm->tm_mday = -itm->tm_mday;
+			if (itm->tm_mon > 0)
+				itm->tm_mon = -itm->tm_mon;
+			if (itm->tm_year > 0)
+				itm->tm_year = -itm->tm_year;
 		}
 	}
 
 	/* finally, AGO negates everything */
 	if (is_before)
 	{
+		if (itm->tm_sec == PG_INT64_MIN ||
+			itm->tm_min == PG_INT64_MIN ||
+			itm->tm_hour == PG_INT64_MIN ||
+			itm->tm_mday == INT_MIN ||
+			itm->tm_mon == INT_MIN ||
+			itm->tm_year == INT_MIN)
+			return DTERR_FIELD_OVERFLOW;
+
 		*fsec = -(*fsec);
-		tm->tm_sec = -tm->tm_sec;
-		tm->tm_min = -tm->tm_min;
-		tm->tm_hour = -tm->tm_hour;
-		tm->tm_mday = -tm->tm_mday;
-		tm->tm_mon = -tm->tm_mon;
-		tm->tm_year = -tm->tm_year;
+		itm->tm_sec = -itm->tm_sec;
+		itm->tm_min = -itm->tm_min;
+		itm->tm_hour = -itm->tm_hour;
+		itm->tm_mday = -itm->tm_mday;
+		itm->tm_mon = -itm->tm_mon;
+		itm->tm_year = -itm->tm_year;
 	}
 
 	return 0;
@@ -3460,7 +3575,7 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
  * Returns 0 or DTERR code.
  */
 static int
-ParseISO8601Number(char *str, char **endptr, int *ipart, double *fpart)
+ParseISO8601Number(char *str, char **endptr, int64 *ipart, double *fpart)
 {
 	double		val;
 
@@ -3476,9 +3591,9 @@ ParseISO8601Number(char *str, char **endptr, int *ipart, double *fpart)
 		return DTERR_FIELD_OVERFLOW;
 	/* be very sure we truncate towards zero (cf dtrunc()) */
 	if (val >= 0)
-		*ipart = (int) floor(val);
+		*ipart = (int64) floor(val);
 	else
-		*ipart = (int) -floor(-val);
+		*ipart = (int64) -floor(-val);
 	*fpart = val - *ipart;
 	return 0;
 }
@@ -3516,13 +3631,13 @@ ISO8601IntegerWidth(char *fieldstart)
  */
 int
 DecodeISO8601Interval(char *str,
-					  int *dtype, struct pg_tm *tm, fsec_t *fsec)
+					  int *dtype, struct pg_itm *itm, fsec_t *fsec)
 {
 	bool		datepart = true;
 	bool		havefield = false;
 
 	*dtype = DTK_DELTA;
-	ClearPgTm(tm, fsec);
+	ClearPgItm(itm, fsec);
 
 	if (strlen(str) < 2 || str[0] != 'P')
 		return DTERR_BAD_FORMAT;
@@ -3531,7 +3646,7 @@ DecodeISO8601Interval(char *str,
 	while (*str)
 	{
 		char	   *fieldstart;
-		int			val;
+		int64		val;
 		double		fval;
 		char		unit;
 		int			dterr;
@@ -3560,29 +3675,35 @@ DecodeISO8601Interval(char *str,
 			switch (unit)		/* before T: Y M W D */
 			{
 				case 'Y':
-					tm->tm_year += val;
-					tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
+					itm->tm_year += val;
+					if (!AdjustFractMonths(fval, itm, 1))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case 'M':
-					tm->tm_mon += val;
-					AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
+					if (!AdjustMonths(val, itm))
+						return DTERR_FIELD_OVERFLOW;
+					if (!AdjustFractDays(fval, itm, fsec, DAYS_PER_MONTH))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case 'W':
-					tm->tm_mday += val * 7;
-					AdjustFractDays(fval, tm, fsec, 7);
+					if (!AdjustDays(val, itm, 7))
+						return DTERR_FIELD_OVERFLOW;
+					if (!AdjustFractDays(fval, itm, fsec, 7))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case 'D':
-					tm->tm_mday += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+					if (!AdjustDays(val, itm, 1))
+						return DTERR_FIELD_OVERFLOW;
+					AdjustFractSeconds(fval, itm, fsec, SECS_PER_DAY);
 					break;
 				case 'T':		/* ISO 8601 4.4.3.3 Alternative Format / Basic */
 				case '\0':
 					if (ISO8601IntegerWidth(fieldstart) == 8 && !havefield)
 					{
-						tm->tm_year += val / 10000;
-						tm->tm_mon += (val / 100) % 100;
-						tm->tm_mday += val % 100;
-						AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+						itm->tm_year += val / 10000;
+						itm->tm_mon += (val / 100) % 100;
+						itm->tm_mday += val % 100;
+						AdjustFractSeconds(fval, itm, fsec, SECS_PER_DAY);
 						if (unit == '\0')
 							return 0;
 						datepart = false;
@@ -3596,8 +3717,8 @@ DecodeISO8601Interval(char *str,
 					if (havefield)
 						return DTERR_BAD_FORMAT;
 
-					tm->tm_year += val;
-					tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
+					itm->tm_year += val;
+					itm->tm_mon += rint(fval * MONTHS_PER_YEAR);
 					if (unit == '\0')
 						return 0;
 					if (unit == 'T')
@@ -3610,8 +3731,9 @@ DecodeISO8601Interval(char *str,
 					dterr = ParseISO8601Number(str, &str, &val, &fval);
 					if (dterr)
 						return dterr;
-					tm->tm_mon += val;
-					AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
+					if (!AdjustMonths(val, itm))
+						return DTERR_FIELD_OVERFLOW;
+					AdjustFractDays(fval, itm, fsec, DAYS_PER_MONTH);
 					if (*str == '\0')
 						return 0;
 					if (*str == 'T')
@@ -3627,8 +3749,9 @@ DecodeISO8601Interval(char *str,
 					dterr = ParseISO8601Number(str, &str, &val, &fval);
 					if (dterr)
 						return dterr;
-					tm->tm_mday += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+					if (!AdjustDays(val, itm, 1))
+						return DTERR_FIELD_OVERFLOW;
+					AdjustFractSeconds(fval, itm, fsec, SECS_PER_DAY);
 					if (*str == '\0')
 						return 0;
 					if (*str == 'T')
@@ -3648,24 +3771,24 @@ DecodeISO8601Interval(char *str,
 			switch (unit)		/* after T: H M S */
 			{
 				case 'H':
-					tm->tm_hour += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
+					itm->tm_hour += val;
+					AdjustFractSeconds(fval, itm, fsec, SECS_PER_HOUR);
 					break;
 				case 'M':
-					tm->tm_min += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
+					itm->tm_min += val;
+					AdjustFractSeconds(fval, itm, fsec, SECS_PER_MINUTE);
 					break;
 				case 'S':
-					tm->tm_sec += val;
-					AdjustFractSeconds(fval, tm, fsec, 1);
+					itm->tm_sec += val;
+					AdjustFractSeconds(fval, itm, fsec, 1);
 					break;
 				case '\0':		/* ISO 8601 4.4.3.3 Alternative Format */
 					if (ISO8601IntegerWidth(fieldstart) == 6 && !havefield)
 					{
-						tm->tm_hour += val / 10000;
-						tm->tm_min += (val / 100) % 100;
-						tm->tm_sec += val % 100;
-						AdjustFractSeconds(fval, tm, fsec, 1);
+						itm->tm_hour += val / 10000;
+						itm->tm_min += (val / 100) % 100;
+						itm->tm_sec += val % 100;
+						AdjustFractSeconds(fval, itm, fsec, 1);
 						return 0;
 					}
 					/* Else fall through to extended alternative format */
@@ -3675,16 +3798,16 @@ DecodeISO8601Interval(char *str,
 					if (havefield)
 						return DTERR_BAD_FORMAT;
 
-					tm->tm_hour += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
+					itm->tm_hour += val;
+					AdjustFractSeconds(fval, itm, fsec, SECS_PER_HOUR);
 					if (unit == '\0')
 						return 0;
 
 					dterr = ParseISO8601Number(str, &str, &val, &fval);
 					if (dterr)
 						return dterr;
-					tm->tm_min += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
+					itm->tm_min += val;
+					AdjustFractSeconds(fval, itm, fsec, SECS_PER_MINUTE);
 					if (*str == '\0')
 						return 0;
 					if (*str != ':')
@@ -3694,8 +3817,8 @@ DecodeISO8601Interval(char *str,
 					dterr = ParseISO8601Number(str, &str, &val, &fval);
 					if (dterr)
 						return dterr;
-					tm->tm_sec += val;
-					AdjustFractSeconds(fval, tm, fsec, 1);
+					itm->tm_sec += val;
+					AdjustFractSeconds(fval, itm, fsec, 1);
 					if (*str == '\0')
 						return 0;
 					return DTERR_BAD_FORMAT;
@@ -4166,22 +4289,22 @@ EncodeDateTime(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, const char
 
 /* Append an ISO-8601-style interval field, but only if value isn't zero */
 static char *
-AddISO8601IntPart(char *cp, int value, char units)
+AddISO8601IntPart(char *cp, int64 value, char units)
 {
 	if (value == 0)
 		return cp;
-	sprintf(cp, "%d%c", value, units);
+	sprintf(cp, "%ld%c", value, units);
 	return cp + strlen(cp);
 }
 
 /* Append a postgres-style interval field, but only if value isn't zero */
 static char *
-AddPostgresIntPart(char *cp, int value, const char *units,
+AddPostgresIntPart(char *cp, int64 value, const char *units,
 				   bool *is_zero, bool *is_before)
 {
 	if (value == 0)
 		return cp;
-	sprintf(cp, "%s%s%d %s%s",
+	sprintf(cp, "%s%s%ld %s%s",
 			(!*is_zero) ? " " : "",
 			(*is_before && value > 0) ? "+" : "",
 			value,
@@ -4199,7 +4322,7 @@ AddPostgresIntPart(char *cp, int value, const char *units,
 
 /* Append a verbose-style interval field, but only if value isn't zero */
 static char *
-AddVerboseIntPart(char *cp, int value, const char *units,
+AddVerboseIntPart(char *cp, int64 value, const char *units,
 				  bool *is_zero, bool *is_before)
 {
 	if (value == 0)
@@ -4208,11 +4331,11 @@ AddVerboseIntPart(char *cp, int value, const char *units,
 	if (*is_zero)
 	{
 		*is_before = (value < 0);
-		value = abs(value);
+		value = Abs(value);
 	}
 	else if (*is_before)
 		value = -value;
-	sprintf(cp, " %d %s%s", value, units, (value == 1) ? "" : "s");
+	sprintf(cp, " %ld %s%s", value, units, (value == 1) ? "" : "s");
 	*is_zero = false;
 	return cp + strlen(cp);
 }
@@ -4238,15 +4361,15 @@ AddVerboseIntPart(char *cp, int value, const char *units,
  * "day-time literal"s (that look like ('4 5:6:7')
  */
 void
-EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
+EncodeInterval(struct pg_itm *itm, fsec_t fsec, int style, char *str)
 {
 	char	   *cp = str;
-	int			year = tm->tm_year;
-	int			mon = tm->tm_mon;
-	int			mday = tm->tm_mday;
-	int			hour = tm->tm_hour;
-	int			min = tm->tm_min;
-	int			sec = tm->tm_sec;
+	int64		year = (int64)itm->tm_year;
+	int64		mon = (int64)itm->tm_mon;
+	int64		mday = (int64)itm->tm_mday;
+	int64		hour = itm->tm_hour;
+	int64		min = itm->tm_min;
+	int64		sec = itm->tm_sec;
 	bool		is_before = false;
 	bool		is_zero = true;
 
@@ -4306,28 +4429,28 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
 					char		sec_sign = (hour < 0 || min < 0 ||
 											sec < 0 || fsec < 0) ? '-' : '+';
 
-					sprintf(cp, "%c%d-%d %c%d %c%d:%02d:",
-							year_sign, abs(year), abs(mon),
-							day_sign, abs(mday),
-							sec_sign, abs(hour), abs(min));
+					sprintf(cp, "%c%ld-%ld %c%ld %c%ld:%02ld:",
+							year_sign, Abs(year), Abs(mon),
+							day_sign, Abs(mday),
+							sec_sign, Abs(hour), Abs(min));
 					cp += strlen(cp);
 					cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
 					*cp = '\0';
 				}
 				else if (has_year_month)
 				{
-					sprintf(cp, "%d-%d", year, mon);
+					sprintf(cp, "%ld-%ld", year, mon);
 				}
 				else if (has_day)
 				{
-					sprintf(cp, "%d %d:%02d:", mday, hour, min);
+					sprintf(cp, "%ld %ld:%02ld:", mday, hour, min);
 					cp += strlen(cp);
 					cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
 					*cp = '\0';
 				}
 				else
 				{
-					sprintf(cp, "%d:%02d:", hour, min);
+					sprintf(cp, "%ld:%02ld:", hour, min);
 					cp += strlen(cp);
 					cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
 					*cp = '\0';
@@ -4377,10 +4500,10 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
 			{
 				bool		minus = (hour < 0 || min < 0 || sec < 0 || fsec < 0);
 
-				sprintf(cp, "%s%s%02d:%02d:",
+				sprintf(cp, "%s%s%02ld:%02ld:",
 						is_zero ? "" : " ",
 						(minus ? "-" : (is_before ? "+" : "")),
-						abs(hour), abs(min));
+						Abs(hour), Abs(min));
 				cp += strlen(cp);
 				cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
 				*cp = '\0';
@@ -4412,7 +4535,7 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
 				cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
 				/* We output "ago", not negatives, so use abs(). */
 				sprintf(cp, " sec%s",
-						(abs(sec) != 1 || fsec != 0) ? "s" : "");
+						(Abs(sec) != 1 || fsec != 0) ? "s" : "");
 				is_zero = false;
 			}
 			/* identically zero? then put in a unitless zero... */
@@ -4668,7 +4791,7 @@ pg_timezone_abbrevs(PG_FUNCTION_ARGS)
 	int			gmtoffset;
 	bool		is_dst;
 	unsigned char *p;
-	struct pg_tm tm;
+	struct pg_itm itm;
 	Interval   *resInterval;
 
 	/* stuff done only on the first call of the function */
@@ -4762,10 +4885,10 @@ pg_timezone_abbrevs(PG_FUNCTION_ARGS)
 	values[0] = CStringGetTextDatum(buffer);
 
 	/* Convert offset (in seconds) to an interval */
-	MemSet(&tm, 0, sizeof(struct pg_tm));
-	tm.tm_sec = gmtoffset;
+	MemSet(&itm, 0, sizeof(struct pg_itm));
+	itm.tm_sec = gmtoffset;
 	resInterval = (Interval *) palloc(sizeof(Interval));
-	tm2interval(&tm, 0, resInterval);
+	tm2interval(&itm, 0, resInterval);
 	values[1] = IntervalPGetDatum(resInterval);
 
 	values[2] = BoolGetDatum(is_dst);
@@ -4798,7 +4921,7 @@ pg_timezone_names(PG_FUNCTION_ARGS)
 	fsec_t		fsec;
 	const char *tzn;
 	Interval   *resInterval;
-	struct pg_tm itm;
+	struct pg_itm itm;
 	MemoryContext oldcontext;
 
 	/* check to see if caller supports us returning a tuplestore */
@@ -4857,7 +4980,7 @@ pg_timezone_names(PG_FUNCTION_ARGS)
 		values[0] = CStringGetTextDatum(pg_get_timezone_name(tz));
 		values[1] = CStringGetTextDatum(tzn ? tzn : "");
 
-		MemSet(&itm, 0, sizeof(struct pg_tm));
+		MemSet(&itm, 0, sizeof(struct pg_itm));
 		itm.tm_sec = -tzoff;
 		resInterval = (Interval *) palloc(sizeof(Interval));
 		tm2interval(&itm, 0, resInterval);
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index d4c2e7b069..d26aa57ec2 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -519,6 +519,13 @@ do { \
 	tmtcTzn(_X) = NULL; \
 } while(0)
 
+#define ZERO_itm(_X) \
+do {	\
+	(_X)->tm_sec  = (_X)->tm_year = (_X)->tm_min = \
+	(_X)->tm_hour = (_X)->tm_yday = 0; \
+	(_X)->tm_mday = (_X)->tm_mon  = 1; \
+} while(0)
+
 /*
  *	to_char(time) appears to to_char() as an interval, so this check
  *	is really for interval and time data types.
@@ -4156,18 +4163,31 @@ interval_to_char(PG_FUNCTION_ARGS)
 			   *res;
 	TmToChar	tmtc;
 	struct pg_tm *tm;
+	struct pg_itm itm;
 
 	if (VARSIZE_ANY_EXHDR(fmt) <= 0)
 		PG_RETURN_NULL();
 
 	ZERO_tmtc(&tmtc);
 	tm = tmtcTm(&tmtc);
+	ZERO_itm(&itm);
+
+	tm2itm(tm, &itm);
 
-	if (interval2tm(*it, tm, &tmtcFsec(&tmtc)) != 0)
+	if (interval2tm(*it, &itm, &tmtcFsec(&tmtc)) != 0)
 		PG_RETURN_NULL();
 
 	/* wday is meaningless, yday approximates the total span in days */
-	tm->tm_yday = (tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon) * DAYS_PER_MONTH + tm->tm_mday;
+	itm.tm_yday = (itm.tm_year * MONTHS_PER_YEAR + itm.tm_mon) * DAYS_PER_MONTH + itm.tm_mday;
+
+	// TODO this will not convert fields greater than ints correctly
+	tm->tm_sec = (int)itm.tm_sec;
+	tm->tm_min = (int)itm.tm_min;
+	tm->tm_hour = (int)itm.tm_hour;
+	tm->tm_mday = itm.tm_mday;
+	tm->tm_mon = itm.tm_mon;
+	tm->tm_year = itm.tm_year;
+	tm->tm_yday = itm.tm_yday;
 
 	if (!(res = datetime_to_char_body(&tmtc, fmt, true, PG_GET_COLLATION())))
 		PG_RETURN_NULL();
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 36f8a84bcc..7e930dbd6f 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -889,8 +889,8 @@ interval_in(PG_FUNCTION_ARGS)
 	int32		typmod = PG_GETARG_INT32(2);
 	Interval   *result;
 	fsec_t		fsec;
-	struct pg_tm tt,
-			   *tm = &tt;
+	struct pg_itm tt,
+			   *itm = &tt;
 	int			dtype;
 	int			nf;
 	int			range;
@@ -899,12 +899,12 @@ interval_in(PG_FUNCTION_ARGS)
 	int			ftype[MAXDATEFIELDS];
 	char		workbuf[256];
 
-	tm->tm_year = 0;
-	tm->tm_mon = 0;
-	tm->tm_mday = 0;
-	tm->tm_hour = 0;
-	tm->tm_min = 0;
-	tm->tm_sec = 0;
+	itm->tm_year = 0;
+	itm->tm_mon = 0;
+	itm->tm_mday = 0;
+	itm->tm_hour = 0;
+	itm->tm_min = 0;
+	itm->tm_sec = 0;
 	fsec = 0;
 
 	if (typmod >= 0)
@@ -916,12 +916,12 @@ interval_in(PG_FUNCTION_ARGS)
 						  ftype, MAXDATEFIELDS, &nf);
 	if (dterr == 0)
 		dterr = DecodeInterval(field, ftype, nf, range,
-							   &dtype, tm, &fsec);
+							   &dtype, itm, &fsec);
 
 	/* if those functions think it's a bad format, try ISO8601 style */
 	if (dterr == DTERR_BAD_FORMAT)
 		dterr = DecodeISO8601Interval(str,
-									  &dtype, tm, &fsec);
+									  &dtype, itm, &fsec);
 
 	if (dterr != 0)
 	{
@@ -935,7 +935,7 @@ interval_in(PG_FUNCTION_ARGS)
 	switch (dtype)
 	{
 		case DTK_DELTA:
-			if (tm2interval(tm, fsec, result) != 0)
+			if (tm2interval(itm, fsec, result) != 0)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 						 errmsg("interval out of range")));
@@ -959,15 +959,15 @@ interval_out(PG_FUNCTION_ARGS)
 {
 	Interval   *span = PG_GETARG_INTERVAL_P(0);
 	char	   *result;
-	struct pg_tm tt,
-			   *tm = &tt;
+	struct pg_itm tt,
+			   *itm = &tt;
 	fsec_t		fsec;
 	char		buf[MAXDATELEN + 1];
 
-	if (interval2tm(*span, tm, &fsec) != 0)
+	if (interval2tm(*span, itm, &fsec) != 0)
 		elog(ERROR, "could not convert interval to tm");
 
-	EncodeInterval(tm, fsec, IntervalStyle, buf);
+	EncodeInterval(itm, fsec, IntervalStyle, buf);
 
 	result = pstrdup(buf);
 	PG_RETURN_CSTRING(result);
@@ -1963,7 +1963,7 @@ tm2timestamp(struct pg_tm *tm, fsec_t fsec, int *tzp, Timestamp *result)
  * Convert an interval data type to a tm structure.
  */
 int
-interval2tm(Interval span, struct pg_tm *tm, fsec_t *fsec)
+interval2tm(Interval span, struct pg_itm *tm, fsec_t *fsec)
 {
 	TimeOffset	time;
 	TimeOffset	tfrac;
@@ -1991,7 +1991,7 @@ interval2tm(Interval span, struct pg_tm *tm, fsec_t *fsec)
 }
 
 int
-tm2interval(struct pg_tm *tm, fsec_t fsec, Interval *span)
+tm2interval(struct pg_itm *tm, fsec_t fsec, Interval *span)
 {
 	double		total_months = (double) tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon;
 
@@ -2006,6 +2006,18 @@ tm2interval(struct pg_tm *tm, fsec_t fsec, Interval *span)
 	return 0;
 }
 
+void
+tm2itm(struct pg_tm *tm, struct pg_itm *itm)
+{
+	itm->tm_sec = (int64)tm->tm_sec;
+	itm->tm_min = (int64)tm->tm_min;
+	itm->tm_hour = (int64)tm->tm_hour;
+	itm->tm_mday = tm->tm_mday;
+	itm->tm_mon = tm->tm_mon;
+	itm->tm_year = tm->tm_year;
+	itm->tm_yday = tm->tm_yday;
+}
+
 static TimeOffset
 time2t(const int hour, const int min, const int sec, const fsec_t fsec)
 {
@@ -3577,7 +3589,7 @@ timestamp_age(PG_FUNCTION_ARGS)
 	fsec_t		fsec,
 				fsec1,
 				fsec2;
-	struct pg_tm tt,
+	struct pg_itm tt,
 			   *tm = &tt;
 	struct pg_tm tt1,
 			   *tm1 = &tt1;
@@ -3696,7 +3708,7 @@ timestamptz_age(PG_FUNCTION_ARGS)
 	fsec_t		fsec,
 				fsec1,
 				fsec2;
-	struct pg_tm tt,
+	struct pg_itm tt,
 			   *tm = &tt;
 	struct pg_tm tt1,
 			   *tm1 = &tt1;
@@ -4280,7 +4292,7 @@ interval_trunc(PG_FUNCTION_ARGS)
 				val;
 	char	   *lowunits;
 	fsec_t		fsec;
-	struct pg_tm tt,
+	struct pg_itm tt,
 			   *tm = &tt;
 
 	result = (Interval *) palloc(sizeof(Interval));
@@ -5163,7 +5175,7 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
 				val;
 	char	   *lowunits;
 	fsec_t		fsec;
-	struct pg_tm tt,
+	struct pg_itm tt,
 			   *tm = &tt;
 
 	lowunits = downcase_truncate_identifier(VARDATA_ANY(units),
diff --git a/src/common/string.c b/src/common/string.c
index 16940d1fa7..14d0267919 100644
--- a/src/common/string.c
+++ b/src/common/string.c
@@ -43,6 +43,21 @@ pg_str_endswith(const char *str, const char *end)
 }
 
 
+/*
+ * strtoint_64 --- just like strtol, but returns int64 not long
+ */
+int64
+strtoint_64(const char *pg_restrict str, char **pg_restrict endptr, int base)
+{
+	long		val;
+
+	val = strtol(str, endptr, base);
+	if (val != (int64) val)
+		errno = ERANGE;
+	return (int64) val;
+}
+
+
 /*
  * strtoint --- just like strtol, but returns int not long
  */
diff --git a/src/include/common/string.h b/src/include/common/string.h
index cf00fb53cd..ea4c99befc 100644
--- a/src/include/common/string.h
+++ b/src/include/common/string.h
@@ -24,6 +24,8 @@ typedef struct PromptInterruptContext
 extern bool pg_str_endswith(const char *str, const char *end);
 extern int	strtoint(const char *pg_restrict str, char **pg_restrict endptr,
 					 int base);
+extern int64	strtoint_64(const char *pg_restrict str, char **pg_restrict endptr,
+						   int base);
 extern void pg_clean_ascii(char *str);
 extern int	pg_strip_crlf(char *str);
 extern bool pg_is_ascii(const char *str);
diff --git a/src/include/pgtime.h b/src/include/pgtime.h
index 2977b13aab..e3f42359bd 100644
--- a/src/include/pgtime.h
+++ b/src/include/pgtime.h
@@ -44,6 +44,18 @@ struct pg_tm
 	const char *tm_zone;
 };
 
+// data structure to help encode and decode intervals
+struct pg_itm
+{
+	pg_time_t	tm_sec;
+	pg_time_t	tm_min;
+	pg_time_t	tm_hour;
+	int			tm_mday;
+	int			tm_mon;			/* see above */
+	int			tm_year;		/* see above */
+	int			tm_yday;
+};
+
 typedef struct pg_tz pg_tz;
 typedef struct pg_tzenum pg_tzenum;
 
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index 0d158f3e4b..0968e66106 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -300,9 +300,9 @@ extern int	DecodeTimeOnly(char **field, int *ftype,
 						   int nf, int *dtype,
 						   struct pg_tm *tm, fsec_t *fsec, int *tzp);
 extern int	DecodeInterval(char **field, int *ftype, int nf, int range,
-						   int *dtype, struct pg_tm *tm, fsec_t *fsec);
+						   int *dtype, struct pg_itm *itm, fsec_t *fsec);
 extern int	DecodeISO8601Interval(char *str,
-								  int *dtype, struct pg_tm *tm, fsec_t *fsec);
+								  int *dtype, struct pg_itm *itm, fsec_t *fsec);
 
 extern void DateTimeParseError(int dterr, const char *str,
 							   const char *datatype) pg_attribute_noreturn();
@@ -315,7 +315,7 @@ extern int	DetermineTimeZoneAbbrevOffsetTS(TimestampTz ts, const char *abbr,
 extern void EncodeDateOnly(struct pg_tm *tm, int style, char *str);
 extern void EncodeTimeOnly(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, int style, char *str);
 extern void EncodeDateTime(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, const char *tzn, int style, char *str);
-extern void EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str);
+extern void EncodeInterval(struct pg_itm *itm, fsec_t fsec, int style, char *str);
 extern void EncodeSpecialTimestamp(Timestamp dt, char *str);
 
 extern int	ValidateDate(int fmask, bool isjulian, bool is2digits, bool bc,
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index c1a74f8e2b..17f76d40a8 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -88,8 +88,9 @@ extern int	timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm,
 						 fsec_t *fsec, const char **tzn, pg_tz *attimezone);
 extern void dt2time(Timestamp dt, int *hour, int *min, int *sec, fsec_t *fsec);
 
-extern int	interval2tm(Interval span, struct pg_tm *tm, fsec_t *fsec);
-extern int	tm2interval(struct pg_tm *tm, fsec_t fsec, Interval *span);
+extern int	interval2tm(Interval span, struct pg_itm *tm, fsec_t *fsec);
+extern int	tm2interval(struct pg_itm *tm, fsec_t fsec, Interval *span);
+extern void	tm2itm(struct pg_tm *tm, struct pg_itm *interval_tm);
 
 extern Timestamp SetEpochTimestamp(void);
 extern void GetEpochTime(struct pg_tm *tm);
diff --git a/src/interfaces/ecpg/pgtypeslib/dt.h b/src/interfaces/ecpg/pgtypeslib/dt.h
index 893c9b6194..8b11d454f5 100644
--- a/src/interfaces/ecpg/pgtypeslib/dt.h
+++ b/src/interfaces/ecpg/pgtypeslib/dt.h
@@ -115,6 +115,11 @@ typedef int32 fsec_t;
 /* generic fields to help with parsing */
 #define ISODATE 22
 #define ISOTIME 23
+/* these are only for parsing intervals */
+#define WEEK		24
+#define DECADE		25
+#define CENTURY		26
+#define MILLENNIUM	27
 /* hack for parsing two-word timezone specs "MET DST" etc */
 #define DTZMOD	28				/* "DST" as a separate word */
 /* reserved for unrecognized string values */
@@ -311,10 +316,21 @@ do { \
 #define TIMESTAMP_IS_NOEND(j)	((j) == DT_NOEND)
 #define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
 
-int			DecodeInterval(char **, int *, int, int *, struct tm *, fsec_t *);
+struct itm
+{
+	int64		tm_sec;
+	int64		tm_min;
+	int64		tm_hour;
+	int			tm_mday;
+	int			tm_mon;
+	int			tm_year;
+	int			tm_yday;
+};
+
+int			DecodeInterval(char **, int *, int, int *, struct itm *, fsec_t *);
 int			DecodeTime(char *, int *, struct tm *, fsec_t *);
 void		EncodeDateTime(struct tm *tm, fsec_t fsec, bool print_tz, int tz, const char *tzn, int style, char *str, bool EuroDates);
-void		EncodeInterval(struct tm *tm, fsec_t fsec, int style, char *str);
+void		EncodeInterval(struct itm *tm, fsec_t fsec, int style, char *str);
 int			tm2timestamp(struct tm *, fsec_t, int *, timestamp *);
 int			DecodeUnits(int field, char *lowtoken, int *val);
 bool		CheckDateTokenTables(void);
diff --git a/src/interfaces/ecpg/pgtypeslib/interval.c b/src/interfaces/ecpg/pgtypeslib/interval.c
index a7e530cb5d..a35f02c299 100644
--- a/src/interfaces/ecpg/pgtypeslib/interval.c
+++ b/src/interfaces/ecpg/pgtypeslib/interval.c
@@ -10,6 +10,7 @@
 #error -ffast-math is known to break this code
 #endif
 
+#include "common/int.h"
 #include "common/string.h"
 #include "dt.h"
 #include "pgtypes_error.h"
@@ -17,43 +18,94 @@
 #include "pgtypeslib_extern.h"
 
 /* copy&pasted from .../src/backend/utils/adt/datetime.c
- * and changed struct pg_tm to struct tm
+ * and changed struct pg_itm to struct itm
  */
+
 static void
-AdjustFractSeconds(double frac, struct /* pg_ */ tm *tm, fsec_t *fsec, int scale)
+tm2itm(struct tm *tm, struct itm *itm)
+{
+	itm->tm_sec = (int64)tm->tm_sec;
+	itm->tm_min = (int64)tm->tm_min;
+	itm->tm_hour = (int64)tm->tm_hour;
+	itm->tm_mday = tm->tm_mday;
+	itm->tm_mon = tm->tm_mon;
+	itm->tm_year = tm->tm_year;
+	itm->tm_yday = tm->tm_yday;
+}
+
+static bool
+AdjustFractSeconds(double frac, struct /* pg_ */ itm *itm, fsec_t *fsec, int scale)
 {
 	int			sec;
 
 	if (frac == 0)
-		return;
+		return true;
 	frac *= scale;
-	sec = (int) frac;
-	tm->tm_sec += sec;
+	sec = (int64) frac;
+	if (pg_add_s64_overflow(itm->tm_sec, sec, &itm->tm_sec))
+		return false;
 	frac -= sec;
 	*fsec += rint(frac * 1000000);
+	return true;
 }
 
 
 /* copy&pasted from .../src/backend/utils/adt/datetime.c
  * and changed struct pg_tm to struct tm
  */
-static void
-AdjustFractDays(double frac, struct /* pg_ */ tm *tm, fsec_t *fsec, int scale)
+static bool
+AdjustFractDays(double frac, struct /* pg_ */ itm *itm, fsec_t *fsec, int scale)
 {
 	int			extra_days;
 
 	if (frac == 0)
-		return;
+		return true;
 	frac *= scale;
-	extra_days = (int) frac;
-	tm->tm_mday += extra_days;
+	extra_days = (int64) frac;
+	if (pg_add_s32_overflow(itm->tm_mday, extra_days, &itm->tm_mday))
+		return false;
 	frac -= extra_days;
-	AdjustFractSeconds(frac, tm, fsec, SECS_PER_DAY);
+	return AdjustFractSeconds(frac, itm, fsec, SECS_PER_DAY);
+}
+
+static bool
+AdjustFractMonths(double frac, struct /* pg_ */ itm *itm, int scale)
+{
+	int months = rint(frac * MONTHS_PER_YEAR * scale);
+	return !pg_add_s32_overflow(itm->tm_mon, months, &itm->tm_mon);
+}
+
+static bool
+AdjustSeconds(int64 val, struct /* pg_ */ itm *itm)
+{
+	return !pg_add_s64_overflow(itm->tm_sec, val, &itm->tm_sec);
+}
+
+static bool
+AdjustDays(int val, struct /* pg_ */ itm *itm, int multiplier)
+{
+	int			extra_days;
+	return !pg_mul_s32_overflow(val, multiplier, &extra_days) &&
+		   !pg_add_s32_overflow(itm->tm_mday, extra_days, &itm->tm_mday);
+}
+
+static bool
+AdjustMonths(int val, struct /* pg_ */ itm *itm)
+{
+	return !pg_add_s32_overflow(itm->tm_mon, val, &itm->tm_mon);
+}
+
+static bool
+AdjustYears(int val, struct /* pg_ */ itm *itm, int multiplier)
+{
+	int			years;
+	return !pg_mul_s32_overflow(val, multiplier, &years) &&
+		   !pg_add_s32_overflow(itm->tm_year, years, &itm->tm_year);
 }
 
 /* copy&pasted from .../src/backend/utils/adt/datetime.c */
 static int
-ParseISO8601Number(const char *str, char **endptr, int *ipart, double *fpart)
+ParseISO8601Number(char *str, char **endptr, int64 *ipart, double *fpart)
 {
 	double		val;
 
@@ -69,9 +121,9 @@ ParseISO8601Number(const char *str, char **endptr, int *ipart, double *fpart)
 		return DTERR_FIELD_OVERFLOW;
 	/* be very sure we truncate towards zero (cf dtrunc()) */
 	if (val >= 0)
-		*ipart = (int) floor(val);
+		*ipart = (int64) floor(val);
 	else
-		*ipart = (int) -floor(-val);
+		*ipart = (int64) -floor(-val);
 	*fpart = val - *ipart;
 	return 0;
 }
@@ -88,10 +140,22 @@ ISO8601IntegerWidth(const char *fieldstart)
 
 
 /* copy&pasted from .../src/backend/utils/adt/datetime.c
- * and changed struct pg_tm to struct tm
+ * and changed struct pg_itm to struct itm
  */
 static inline void
-ClearPgTm(struct /* pg_ */ tm *tm, fsec_t *fsec)
+ClearPgItm(struct /* pg_ */ itm *itm, fsec_t *fsec)
+{
+	itm->tm_year = 0;
+	itm->tm_mon = 0;
+	itm->tm_mday = 0;
+	itm->tm_hour = 0;
+	itm->tm_min = 0;
+	itm->tm_sec = 0;
+	*fsec = 0;
+}
+
+static inline void
+ClearPgTm(struct /* pg_ */ tm *tm)
 {
 	tm->tm_year = 0;
 	tm->tm_mon = 0;
@@ -99,7 +163,6 @@ ClearPgTm(struct /* pg_ */ tm *tm, fsec_t *fsec)
 	tm->tm_hour = 0;
 	tm->tm_min = 0;
 	tm->tm_sec = 0;
-	*fsec = 0;
 }
 
 /* copy&pasted from .../src/backend/utils/adt/datetime.c
@@ -110,13 +173,13 @@ ClearPgTm(struct /* pg_ */ tm *tm, fsec_t *fsec)
  */
 static int
 DecodeISO8601Interval(char *str,
-					  int *dtype, struct /* pg_ */ tm *tm, fsec_t *fsec)
+					  int *dtype, struct /* pg_ */ itm *itm, fsec_t *fsec)
 {
 	bool		datepart = true;
 	bool		havefield = false;
 
 	*dtype = DTK_DELTA;
-	ClearPgTm(tm, fsec);
+	ClearPgItm(itm, fsec);
 
 	if (strlen(str) < 2 || str[0] != 'P')
 		return DTERR_BAD_FORMAT;
@@ -125,7 +188,7 @@ DecodeISO8601Interval(char *str,
 	while (*str)
 	{
 		char	   *fieldstart;
-		int			val;
+		int64		val;
 		double		fval;
 		char		unit;
 		int			dterr;
@@ -154,29 +217,35 @@ DecodeISO8601Interval(char *str,
 			switch (unit)		/* before T: Y M W D */
 			{
 				case 'Y':
-					tm->tm_year += val;
-					tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
+					itm->tm_year += val;
+					if (!AdjustFractMonths(fval, itm, 1))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case 'M':
-					tm->tm_mon += val;
-					AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
+					if (!AdjustMonths(val, itm))
+						return DTERR_FIELD_OVERFLOW;
+					if (!AdjustFractDays(fval, itm, fsec, DAYS_PER_MONTH))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case 'W':
-					tm->tm_mday += val * 7;
-					AdjustFractDays(fval, tm, fsec, 7);
+					if (!AdjustDays(val, itm, 7))
+						return DTERR_FIELD_OVERFLOW;
+					if (!AdjustFractDays(fval, itm, fsec, 7))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case 'D':
-					tm->tm_mday += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+					if (!AdjustDays(val, itm, 1))
+						return DTERR_FIELD_OVERFLOW;
+					AdjustFractSeconds(fval, itm, fsec, SECS_PER_DAY);
 					break;
 				case 'T':		/* ISO 8601 4.4.3.3 Alternative Format / Basic */
 				case '\0':
 					if (ISO8601IntegerWidth(fieldstart) == 8 && !havefield)
 					{
-						tm->tm_year += val / 10000;
-						tm->tm_mon += (val / 100) % 100;
-						tm->tm_mday += val % 100;
-						AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+						itm->tm_year += val / 10000;
+						itm->tm_mon += (val / 100) % 100;
+						itm->tm_mday += val % 100;
+						AdjustFractSeconds(fval, itm, fsec, SECS_PER_DAY);
 						if (unit == '\0')
 							return 0;
 						datepart = false;
@@ -190,8 +259,8 @@ DecodeISO8601Interval(char *str,
 					if (havefield)
 						return DTERR_BAD_FORMAT;
 
-					tm->tm_year += val;
-					tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
+					itm->tm_year += val;
+					itm->tm_mon += rint(fval * MONTHS_PER_YEAR);
 					if (unit == '\0')
 						return 0;
 					if (unit == 'T')
@@ -204,8 +273,9 @@ DecodeISO8601Interval(char *str,
 					dterr = ParseISO8601Number(str, &str, &val, &fval);
 					if (dterr)
 						return dterr;
-					tm->tm_mon += val;
-					AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
+					if (!AdjustMonths(val, itm))
+						return DTERR_FIELD_OVERFLOW;
+					AdjustFractDays(fval, itm, fsec, DAYS_PER_MONTH);
 					if (*str == '\0')
 						return 0;
 					if (*str == 'T')
@@ -221,8 +291,9 @@ DecodeISO8601Interval(char *str,
 					dterr = ParseISO8601Number(str, &str, &val, &fval);
 					if (dterr)
 						return dterr;
-					tm->tm_mday += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+					if (!AdjustDays(val, itm, 1))
+						return DTERR_FIELD_OVERFLOW;
+					AdjustFractSeconds(fval, itm, fsec, SECS_PER_DAY);
 					if (*str == '\0')
 						return 0;
 					if (*str == 'T')
@@ -242,24 +313,24 @@ DecodeISO8601Interval(char *str,
 			switch (unit)		/* after T: H M S */
 			{
 				case 'H':
-					tm->tm_hour += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
+					itm->tm_hour += val;
+					AdjustFractSeconds(fval, itm, fsec, SECS_PER_HOUR);
 					break;
 				case 'M':
-					tm->tm_min += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
+					itm->tm_min += val;
+					AdjustFractSeconds(fval, itm, fsec, SECS_PER_MINUTE);
 					break;
 				case 'S':
-					tm->tm_sec += val;
-					AdjustFractSeconds(fval, tm, fsec, 1);
+					itm->tm_sec += val;
+					AdjustFractSeconds(fval, itm, fsec, 1);
 					break;
 				case '\0':		/* ISO 8601 4.4.3.3 Alternative Format */
 					if (ISO8601IntegerWidth(fieldstart) == 6 && !havefield)
 					{
-						tm->tm_hour += val / 10000;
-						tm->tm_min += (val / 100) % 100;
-						tm->tm_sec += val % 100;
-						AdjustFractSeconds(fval, tm, fsec, 1);
+						itm->tm_hour += val / 10000;
+						itm->tm_min += (val / 100) % 100;
+						itm->tm_sec += val % 100;
+						AdjustFractSeconds(fval, itm, fsec, 1);
 						return 0;
 					}
 					/* Else fall through to extended alternative format */
@@ -269,16 +340,16 @@ DecodeISO8601Interval(char *str,
 					if (havefield)
 						return DTERR_BAD_FORMAT;
 
-					tm->tm_hour += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
+					itm->tm_hour += val;
+					AdjustFractSeconds(fval, itm, fsec, SECS_PER_HOUR);
 					if (unit == '\0')
 						return 0;
 
 					dterr = ParseISO8601Number(str, &str, &val, &fval);
 					if (dterr)
 						return dterr;
-					tm->tm_min += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
+					itm->tm_min += val;
+					AdjustFractSeconds(fval, itm, fsec, SECS_PER_MINUTE);
 					if (*str == '\0')
 						return 0;
 					if (*str != ':')
@@ -288,8 +359,8 @@ DecodeISO8601Interval(char *str,
 					dterr = ParseISO8601Number(str, &str, &val, &fval);
 					if (dterr)
 						return dterr;
-					tm->tm_sec += val;
-					AdjustFractSeconds(fval, tm, fsec, 1);
+					itm->tm_sec += val;
+					AdjustFractSeconds(fval, itm, fsec, 1);
 					if (*str == '\0')
 						return 0;
 					return DTERR_BAD_FORMAT;
@@ -324,23 +395,25 @@ DecodeISO8601Interval(char *str,
  */
 int
 DecodeInterval(char **field, int *ftype, int nf,	/* int range, */
-			   int *dtype, struct /* pg_ */ tm *tm, fsec_t *fsec)
+			   int *dtype, struct /* pg_ */ itm *itm, fsec_t *fsec)
 {
 	int			IntervalStyle = INTSTYLE_POSTGRES_VERBOSE;
 	int			range = INTERVAL_FULL_RANGE;
-	bool		is_before = false;
-	char	   *cp;
-	int			fmask = 0,
-				tmask,
-				type;
-	int			i;
-	int			dterr;
-	int			val;
-	double		fval;
+	bool			is_before = false;
+	char	   		*cp;
+	int				fmask = 0,
+					tmask,
+					type;
+	int				i;
+	int				dterr;
+	int64			val;
+	double			fval;
+	struct tm 	tm;
 
 	*dtype = DTK_DELTA;
 	type = IGNORE_DTF;
-	ClearPgTm(tm, fsec);
+	ClearPgItm(itm, fsec);
+	ClearPgTm(&tm);
 
 	/* read through list backwards to pick up units before values */
 	for (i = nf - 1; i >= 0; i--)
@@ -348,8 +421,9 @@ DecodeInterval(char **field, int *ftype, int nf,	/* int range, */
 		switch (ftype[i])
 		{
 			case DTK_TIME:
-				dterr = DecodeTime(field[i],	/* range, */
-								   &tmask, tm, fsec);
+				dterr = DecodeTime(field[i],	/* fmask, range, */
+								   &tmask, &tm, fsec);
+				tm2itm(&tm, itm);
 				if (dterr)
 					return dterr;
 				type = DTK_DAY;
@@ -358,27 +432,29 @@ DecodeInterval(char **field, int *ftype, int nf,	/* int range, */
 			case DTK_TZ:
 
 				/*
-				 * Timezone is a token with a leading sign character and at
+				 * Timezone means a token with a leading sign character and at
 				 * least one digit; there could be ':', '.', '-' embedded in
 				 * it as well.
 				 */
 				Assert(*field[i] == '-' || *field[i] == '+');
 
 				/*
-				 * Try for hh:mm or hh:mm:ss.  If not, fall through to
-				 * DTK_NUMBER case, which can handle signed float numbers and
-				 * signed year-month values.
+				 * Check for signed hh:mm or hh:mm:ss.  If so, process exactly
+				 * like DTK_TIME case above, plus handling the sign.
 				 */
 				if (strchr(field[i] + 1, ':') != NULL &&
-					DecodeTime(field[i] + 1,	/* INTERVAL_FULL_RANGE, */
-							   &tmask, tm, fsec) == 0)
-				{
-					if (*field[i] == '-')
-					{
+					DecodeTime(field[i] + 1,	/* fmask, range, */
+							   &tmask, &tm, fsec) == 0) {
+					tm2itm(&tm, itm);
+					if (*field[i] == '-') {
 						/* flip the sign on all fields */
-						tm->tm_hour = -tm->tm_hour;
-						tm->tm_min = -tm->tm_min;
-						tm->tm_sec = -tm->tm_sec;
+						if (itm->tm_hour == PG_INT64_MIN ||
+							itm->tm_min == PG_INT64_MIN ||
+							itm->tm_mon == PG_INT64_MIN)
+							return DTERR_FIELD_OVERFLOW;
+						itm->tm_hour = -itm->tm_hour;
+						itm->tm_min = -itm->tm_min;
+						itm->tm_sec = -itm->tm_sec;
 						*fsec = -(*fsec);
 					}
 
@@ -388,11 +464,15 @@ DecodeInterval(char **field, int *ftype, int nf,	/* int range, */
 					 * are reading right to left.
 					 */
 					type = DTK_DAY;
-					tmask = DTK_M(TZ);
 					break;
 				}
-				/* FALL THROUGH */
 
+				/*
+				 * Otherwise, fall through to DTK_NUMBER case, which can
+				 * handle signed float numbers and signed year-month values.
+				 */
+
+				/* FALLTHROUGH */
 			case DTK_DATE:
 			case DTK_NUMBER:
 				if (type == IGNORE_DTF)
@@ -412,17 +492,17 @@ DecodeInterval(char **field, int *ftype, int nf,	/* int range, */
 							break;
 						case INTERVAL_MASK(HOUR):
 						case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR):
-						case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE):
-						case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
 							type = DTK_HOUR;
 							break;
 						case INTERVAL_MASK(MINUTE):
 						case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE):
+						case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE):
 							type = DTK_MINUTE;
 							break;
 						case INTERVAL_MASK(SECOND):
-						case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
 						case INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
+						case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
+						case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
 							type = DTK_SECOND;
 							break;
 						default:
@@ -432,7 +512,7 @@ DecodeInterval(char **field, int *ftype, int nf,	/* int range, */
 				}
 
 				errno = 0;
-				val = strtoint(field[i], &cp, 10);
+				val = strtoint_64(field[i], &cp, 10);
 				if (errno == ERANGE)
 					return DTERR_FIELD_OVERFLOW;
 
@@ -449,6 +529,9 @@ DecodeInterval(char **field, int *ftype, int nf,	/* int range, */
 					type = DTK_MONTH;
 					if (*field[i] == '-')
 						val2 = -val2;
+					if (((double) val * MONTHS_PER_YEAR + val2) > INT_MAX ||
+						((double) val * MONTHS_PER_YEAR + val2) < INT_MIN)
+						return DTERR_FIELD_OVERFLOW;
 					val = val * MONTHS_PER_YEAR + val2;
 					fval = 0;
 				}
@@ -477,12 +560,17 @@ DecodeInterval(char **field, int *ftype, int nf,	/* int range, */
 						break;
 
 					case DTK_MILLISEC:
+						/* avoid overflowing the fsec field */
+						if (!AdjustSeconds(val / 1000, itm))
+							return DTERR_FIELD_OVERFLOW;
+						val -= (val / 1000) * 1000;
 						*fsec += rint((val + fval) * 1000);
 						tmask = DTK_M(MILLISECOND);
 						break;
 
 					case DTK_SECOND:
-						tm->tm_sec += val;
+						if (!AdjustSeconds(val, itm))
+							return DTERR_FIELD_OVERFLOW;
 						*fsec += rint(fval * 1000000);
 
 						/*
@@ -496,58 +584,87 @@ DecodeInterval(char **field, int *ftype, int nf,	/* int range, */
 						break;
 
 					case DTK_MINUTE:
-						tm->tm_min += val;
-						AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
+						itm->tm_min += val;
+						if (!AdjustFractSeconds(fval, itm, fsec, SECS_PER_MINUTE))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(MINUTE);
 						break;
 
 					case DTK_HOUR:
-						tm->tm_hour += val;
-						AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
+						itm->tm_hour += val;
+						if (!AdjustFractSeconds(fval, itm, fsec, SECS_PER_HOUR))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(HOUR);
-						type = DTK_DAY;
+						type = DTK_DAY; /* set for next field */
 						break;
 
 					case DTK_DAY:
-						tm->tm_mday += val;
-						AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
-						tmask = (fmask & DTK_M(DAY)) ? 0 : DTK_M(DAY);
+						if (val < INT_MIN || val > INT_MAX)
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustDays((int)val, itm, 1))
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustFractSeconds(fval, itm, fsec, SECS_PER_DAY))
+							return DTERR_FIELD_OVERFLOW;
+						tmask = DTK_M(DAY);
 						break;
 
 					case DTK_WEEK:
-						tm->tm_mday += val * 7;
-						AdjustFractDays(fval, tm, fsec, 7);
-						tmask = (fmask & DTK_M(DAY)) ? 0 : DTK_M(DAY);
+						if (val < INT_MIN || val > INT_MAX)
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustDays((int)val, itm, 7))
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustFractDays(fval, itm, fsec, 7))
+							return DTERR_FIELD_OVERFLOW;
+						tmask = DTK_M(WEEK);
 						break;
 
 					case DTK_MONTH:
-						tm->tm_mon += val;
-						AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
+						if (val < INT_MIN || val > INT_MAX)
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustMonths((int)val, itm))
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustFractDays(fval, itm, fsec, DAYS_PER_MONTH))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(MONTH);
 						break;
 
 					case DTK_YEAR:
-						tm->tm_year += val;
-						tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
-						tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
+						if (val < INT_MIN || val > INT_MAX)
+							return DTERR_FIELD_OVERFLOW;
+						itm->tm_year += val;
+						if (!AdjustFractMonths(fval, itm, 1))
+							return DTERR_FIELD_OVERFLOW;
+						tmask = DTK_M(YEAR);
 						break;
 
 					case DTK_DECADE:
-						tm->tm_year += val * 10;
-						tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 10);
-						tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
+						if (val < INT_MIN || val > INT_MAX)
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustYears((int)val, itm, 10))
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustFractMonths(fval, itm, 10))
+							return DTERR_FIELD_OVERFLOW;
+						tmask = DTK_M(DECADE);
 						break;
 
 					case DTK_CENTURY:
-						tm->tm_year += val * 100;
-						tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 100);
-						tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
+						if (val < INT_MIN || val > INT_MAX)
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustYears((int)val, itm, 100))
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustFractMonths(fval, itm, 100))
+							return DTERR_FIELD_OVERFLOW;
+						tmask = DTK_M(CENTURY);
 						break;
 
 					case DTK_MILLENNIUM:
-						tm->tm_year += val * 1000;
-						tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 1000);
-						tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
+						if (val < INT_MIN || val > INT_MAX)
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustYears((int)val, itm, 1000))
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustFractMonths(fval, itm, 1000))
+							return DTERR_FIELD_OVERFLOW;
+						tmask = DTK_M(MILLENNIUM);
 						break;
 
 					default:
@@ -557,7 +674,7 @@ DecodeInterval(char **field, int *ftype, int nf,	/* int range, */
 
 			case DTK_STRING:
 			case DTK_SPECIAL:
-				type = DecodeUnits(i, field[i], &val);
+				type = DecodeUnits(i, field[i], (int *)&val);
 				if (type == IGNORE_DTF)
 					continue;
 
@@ -603,7 +720,8 @@ DecodeInterval(char **field, int *ftype, int nf,	/* int range, */
 
 		sec = *fsec / USECS_PER_SEC;
 		*fsec -= sec * USECS_PER_SEC;
-		tm->tm_sec += sec;
+		if (!AdjustSeconds(sec, itm))
+			return DTERR_FIELD_OVERFLOW;
 	}
 
 	/*----------
@@ -644,31 +762,39 @@ DecodeInterval(char **field, int *ftype, int nf,	/* int range, */
 			 */
 			if (*fsec > 0)
 				*fsec = -(*fsec);
-			if (tm->tm_sec > 0)
-				tm->tm_sec = -tm->tm_sec;
-			if (tm->tm_min > 0)
-				tm->tm_min = -tm->tm_min;
-			if (tm->tm_hour > 0)
-				tm->tm_hour = -tm->tm_hour;
-			if (tm->tm_mday > 0)
-				tm->tm_mday = -tm->tm_mday;
-			if (tm->tm_mon > 0)
-				tm->tm_mon = -tm->tm_mon;
-			if (tm->tm_year > 0)
-				tm->tm_year = -tm->tm_year;
+			if (itm->tm_sec > 0)
+				itm->tm_sec = -itm->tm_sec;
+			if (itm->tm_min > 0)
+				itm->tm_min = -itm->tm_min;
+			if (itm->tm_hour > 0)
+				itm->tm_hour = -itm->tm_hour;
+			if (itm->tm_mday > 0)
+				itm->tm_mday = -itm->tm_mday;
+			if (itm->tm_mon > 0)
+				itm->tm_mon = -itm->tm_mon;
+			if (itm->tm_year > 0)
+				itm->tm_year = -itm->tm_year;
 		}
 	}
 
 	/* finally, AGO negates everything */
 	if (is_before)
 	{
+		if (itm->tm_sec == PG_INT64_MIN ||
+			itm->tm_min == PG_INT64_MIN ||
+			itm->tm_hour == PG_INT64_MIN ||
+			itm->tm_mday == INT_MIN ||
+			itm->tm_mon == INT_MIN ||
+			itm->tm_year == INT_MIN)
+			return DTERR_FIELD_OVERFLOW;
+
 		*fsec = -(*fsec);
-		tm->tm_sec = -tm->tm_sec;
-		tm->tm_min = -tm->tm_min;
-		tm->tm_hour = -tm->tm_hour;
-		tm->tm_mday = -tm->tm_mday;
-		tm->tm_mon = -tm->tm_mon;
-		tm->tm_year = -tm->tm_year;
+		itm->tm_sec = -itm->tm_sec;
+		itm->tm_min = -itm->tm_min;
+		itm->tm_hour = -itm->tm_hour;
+		itm->tm_mday = -itm->tm_mday;
+		itm->tm_mon = -itm->tm_mon;
+		itm->tm_year = -itm->tm_year;
 	}
 
 	return 0;
@@ -677,7 +803,7 @@ DecodeInterval(char **field, int *ftype, int nf,	/* int range, */
 
 /* copy&pasted from .../src/backend/utils/adt/datetime.c */
 static char *
-AddVerboseIntPart(char *cp, int value, const char *units,
+AddVerboseIntPart(char *cp, int64 value, const char *units,
 				  bool *is_zero, bool *is_before)
 {
 	if (value == 0)
@@ -686,23 +812,23 @@ AddVerboseIntPart(char *cp, int value, const char *units,
 	if (*is_zero)
 	{
 		*is_before = (value < 0);
-		value = abs(value);
+		value = Abs(value);
 	}
 	else if (*is_before)
 		value = -value;
-	sprintf(cp, " %d %s%s", value, units, (value == 1) ? "" : "s");
+	sprintf(cp, " %ld %s%s", value, units, (value == 1) ? "" : "s");
 	*is_zero = false;
 	return cp + strlen(cp);
 }
 
 /* copy&pasted from .../src/backend/utils/adt/datetime.c */
 static char *
-AddPostgresIntPart(char *cp, int value, const char *units,
+AddPostgresIntPart(char *cp, int64 value, const char *units,
 				   bool *is_zero, bool *is_before)
 {
 	if (value == 0)
 		return cp;
-	sprintf(cp, "%s%s%d %s%s",
+	sprintf(cp, "%s%s%ld %s%s",
 			(!*is_zero) ? " " : "",
 			(*is_before && value > 0) ? "+" : "",
 			value,
@@ -720,33 +846,69 @@ AddPostgresIntPart(char *cp, int value, const char *units,
 
 /* copy&pasted from .../src/backend/utils/adt/datetime.c */
 static char *
-AddISO8601IntPart(char *cp, int value, char units)
+AddISO8601IntPart(char *cp, int64 value, char units)
 {
 	if (value == 0)
 		return cp;
-	sprintf(cp, "%d%c", value, units);
+	sprintf(cp, "%ld%c", value, units);
 	return cp + strlen(cp);
 }
 
 /* copy&pasted from .../src/backend/utils/adt/datetime.c */
-static void
-AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros)
+static char *
+AppendSeconds(char *cp, int64 sec, fsec_t fsec, int precision, bool fillzeros)
 {
-	if (fsec == 0)
-	{
-		if (fillzeros)
-			sprintf(cp, "%02d", abs(sec));
-		else
-			sprintf(cp, "%d", abs(sec));
-	}
+	Assert(precision >= 0);
+
+	if (fillzeros)
+		cp = pg_ultostr_zeropad(cp, Abs(sec), 2);
 	else
+		cp = pg_ultostr(cp, Abs(sec));
+
+	/* fsec_t is just an int32 */
+	if (fsec != 0)
 	{
-		if (fillzeros)
-			sprintf(cp, "%02d.%0*d", abs(sec), precision, (int) Abs(fsec));
-		else
-			sprintf(cp, "%d.%0*d", abs(sec), precision, (int) Abs(fsec));
-		TrimTrailingZeros(cp);
+		int32		value = Abs(fsec);
+		char	   *end = &cp[precision + 1];
+		bool		gotnonzero = false;
+
+		*cp++ = '.';
+
+		/*
+		 * Append the fractional seconds part.  Note that we don't want any
+		 * trailing zeros here, so since we're building the number in reverse
+		 * we'll skip appending zeros until we've output a non-zero digit.
+		 */
+		while (precision--)
+		{
+			int32		oldval = value;
+			int32		remainder;
+
+			value /= 10;
+			remainder = oldval - value * 10;
+
+			/* check if we got a non-zero */
+			if (remainder)
+				gotnonzero = true;
+
+			if (gotnonzero)
+				cp[precision] = '0' + remainder;
+			else
+				end = &cp[precision];
+		}
+
+		/*
+		 * If we still have a non-zero value then precision must have not been
+		 * enough to print the number.  We punt the problem to pg_ltostr(),
+		 * which will generate a correct answer in the minimum valid width.
+		 */
+		if (value)
+			return pg_ultostr(cp, Abs(fsec));
+
+		return end;
 	}
+	else
+		return cp;
 }
 
 
@@ -756,15 +918,15 @@ AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros)
  */
 
 void
-EncodeInterval(struct /* pg_ */ tm *tm, fsec_t fsec, int style, char *str)
+EncodeInterval(struct /* pg_ */ itm *itm, fsec_t fsec, int style, char *str)
 {
 	char	   *cp = str;
-	int			year = tm->tm_year;
-	int			mon = tm->tm_mon;
-	int			mday = tm->tm_mday;
-	int			hour = tm->tm_hour;
-	int			min = tm->tm_min;
-	int			sec = tm->tm_sec;
+	int64		year = (int64)itm->tm_year;
+	int64		mon = (int64)itm->tm_mon;
+	int64		mday = (int64)itm->tm_mday;
+	int64		hour = itm->tm_hour;
+	int64		min = itm->tm_min;
+	int64		sec = itm->tm_sec;
 	bool		is_before = false;
 	bool		is_zero = true;
 
@@ -824,28 +986,30 @@ EncodeInterval(struct /* pg_ */ tm *tm, fsec_t fsec, int style, char *str)
 					char		sec_sign = (hour < 0 || min < 0 ||
 											sec < 0 || fsec < 0) ? '-' : '+';
 
-					sprintf(cp, "%c%d-%d %c%d %c%d:%02d:",
-							year_sign, abs(year), abs(mon),
-							day_sign, abs(mday),
-							sec_sign, abs(hour), abs(min));
+					sprintf(cp, "%c%ld-%ld %c%ld %c%ld:%02ld:",
+							year_sign, Abs(year), Abs(mon),
+							day_sign, Abs(mday),
+							sec_sign, Abs(hour), Abs(min));
 					cp += strlen(cp);
 					AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
 				}
 				else if (has_year_month)
 				{
-					sprintf(cp, "%d-%d", year, mon);
+					sprintf(cp, "%ld-%ld", year, mon);
 				}
 				else if (has_day)
 				{
-					sprintf(cp, "%d %d:%02d:", mday, hour, min);
+					sprintf(cp, "%ld %ld:%02ld:", mday, hour, min);
 					cp += strlen(cp);
 					AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+					*cp = '\0';
 				}
 				else
 				{
-					sprintf(cp, "%d:%02d:", hour, min);
+					sprintf(cp, "%ld:%02ld:", hour, min);
 					cp += strlen(cp);
 					AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+					*cp = '\0';
 				}
 			}
 			break;
@@ -881,18 +1045,25 @@ EncodeInterval(struct /* pg_ */ tm *tm, fsec_t fsec, int style, char *str)
 			/* Compatible with postgresql < 8.4 when DateStyle = 'iso' */
 		case INTSTYLE_POSTGRES:
 			cp = AddPostgresIntPart(cp, year, "year", &is_zero, &is_before);
+
+			/*
+			 * Ideally we should spell out "month" like we do for "year" and
+			 * "day".  However, for backward compatibility, we can't easily
+			 * fix this.  bjm 2011-05-24
+			 */
 			cp = AddPostgresIntPart(cp, mon, "mon", &is_zero, &is_before);
 			cp = AddPostgresIntPart(cp, mday, "day", &is_zero, &is_before);
 			if (is_zero || hour != 0 || min != 0 || sec != 0 || fsec != 0)
 			{
 				bool		minus = (hour < 0 || min < 0 || sec < 0 || fsec < 0);
 
-				sprintf(cp, "%s%s%02d:%02d:",
+				sprintf(cp, "%s%s%02ld:%02ld:",
 						is_zero ? "" : " ",
 						(minus ? "-" : (is_before ? "+" : "")),
-						abs(hour), abs(min));
+						Abs(hour), Abs(min));
 				cp += strlen(cp);
 				AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+				*cp = '\0';
 			}
 			break;
 
@@ -918,11 +1089,10 @@ EncodeInterval(struct /* pg_ */ tm *tm, fsec_t fsec, int style, char *str)
 				}
 				else if (is_before)
 					*cp++ = '-';
-				AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
-				cp += strlen(cp);
+				cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
 				/* We output "ago", not negatives, so use abs(). */
 				sprintf(cp, " sec%s",
-						(abs(sec) != 1 || fsec != 0) ? "s" : "");
+						(Abs(sec) != 1 || fsec != 0) ? "s" : "");
 				is_zero = false;
 			}
 			/* identically zero? then put in a unitless zero... */
@@ -939,47 +1109,47 @@ EncodeInterval(struct /* pg_ */ tm *tm, fsec_t fsec, int style, char *str)
  * Convert an interval data type to a tm structure.
  */
 static int
-interval2tm(interval span, struct tm *tm, fsec_t *fsec)
+interval2tm(interval span, struct itm *itm, fsec_t *fsec)
 {
 	int64		time;
 
 	if (span.month != 0)
 	{
-		tm->tm_year = span.month / MONTHS_PER_YEAR;
-		tm->tm_mon = span.month % MONTHS_PER_YEAR;
+		itm->tm_year = span.month / MONTHS_PER_YEAR;
+		itm->tm_mon = span.month % MONTHS_PER_YEAR;
 
 	}
 	else
 	{
-		tm->tm_year = 0;
-		tm->tm_mon = 0;
+		itm->tm_year = 0;
+		itm->tm_mon = 0;
 	}
 
 	time = span.time;
 
-	tm->tm_mday = time / USECS_PER_DAY;
-	time -= tm->tm_mday * USECS_PER_DAY;
-	tm->tm_hour = time / USECS_PER_HOUR;
-	time -= tm->tm_hour * USECS_PER_HOUR;
-	tm->tm_min = time / USECS_PER_MINUTE;
-	time -= tm->tm_min * USECS_PER_MINUTE;
-	tm->tm_sec = time / USECS_PER_SEC;
-	*fsec = time - (tm->tm_sec * USECS_PER_SEC);
+	itm->tm_mday = time / USECS_PER_DAY;
+	time -= itm->tm_mday * USECS_PER_DAY;
+	itm->tm_hour = time / USECS_PER_HOUR;
+	time -= itm->tm_hour * USECS_PER_HOUR;
+	itm->tm_min = time / USECS_PER_MINUTE;
+	time -= itm->tm_min * USECS_PER_MINUTE;
+	itm->tm_sec = time / USECS_PER_SEC;
+	*fsec = time - (itm->tm_sec * USECS_PER_SEC);
 
 	return 0;
 }								/* interval2tm() */
 
 static int
-tm2interval(struct tm *tm, fsec_t fsec, interval * span)
+tm2interval(struct itm *itm, fsec_t fsec, interval * span)
 {
-	if ((double) tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon > INT_MAX ||
-		(double) tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon < INT_MIN)
+	if ((double) itm->tm_year * MONTHS_PER_YEAR + itm->tm_mon > INT_MAX ||
+		(double) itm->tm_year * MONTHS_PER_YEAR + itm->tm_mon < INT_MIN)
 		return -1;
-	span->month = tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon;
-	span->time = (((((((tm->tm_mday * INT64CONST(24)) +
-					   tm->tm_hour) * INT64CONST(60)) +
-					 tm->tm_min) * INT64CONST(60)) +
-				   tm->tm_sec) * USECS_PER_SEC) + fsec;
+	span->month = itm->tm_year * MONTHS_PER_YEAR + itm->tm_mon;
+	span->time = (((((((itm->tm_mday * INT64CONST(24)) +
+					   itm->tm_hour) * INT64CONST(60)) +
+					 itm->tm_min) * INT64CONST(60)) +
+				   itm->tm_sec) * USECS_PER_SEC) + fsec;
 
 	return 0;
 }								/* tm2interval() */
@@ -1005,8 +1175,8 @@ PGTYPESinterval_from_asc(char *str, char **endptr)
 {
 	interval   *result = NULL;
 	fsec_t		fsec;
-	struct tm	tt,
-			   *tm = &tt;
+	struct itm	tt,
+			   *itm = &tt;
 	int			dtype;
 	int			nf;
 	char	   *field[MAXDATEFIELDS];
@@ -1015,12 +1185,12 @@ PGTYPESinterval_from_asc(char *str, char **endptr)
 	char	   *realptr;
 	char	  **ptr = (endptr != NULL) ? endptr : &realptr;
 
-	tm->tm_year = 0;
-	tm->tm_mon = 0;
-	tm->tm_mday = 0;
-	tm->tm_hour = 0;
-	tm->tm_min = 0;
-	tm->tm_sec = 0;
+	itm->tm_year = 0;
+	itm->tm_mon = 0;
+	itm->tm_mday = 0;
+	itm->tm_hour = 0;
+	itm->tm_min = 0;
+	itm->tm_sec = 0;
 	fsec = 0;
 
 	if (strlen(str) > MAXDATELEN)
@@ -1030,8 +1200,8 @@ PGTYPESinterval_from_asc(char *str, char **endptr)
 	}
 
 	if (ParseDateTime(str, lowstr, field, ftype, &nf, ptr) != 0 ||
-		(DecodeInterval(field, ftype, nf, &dtype, tm, &fsec) != 0 &&
-		 DecodeISO8601Interval(str, &dtype, tm, &fsec) != 0))
+		(DecodeInterval(field, ftype, nf, &dtype, itm, &fsec) != 0 &&
+		 DecodeISO8601Interval(str, &dtype, itm, &fsec) != 0))
 	{
 		errno = PGTYPES_INTVL_BAD_INTERVAL;
 		return NULL;
@@ -1048,7 +1218,7 @@ PGTYPESinterval_from_asc(char *str, char **endptr)
 		return NULL;
 	}
 
-	if (tm2interval(tm, fsec, result) != 0)
+	if (tm2interval(itm, fsec, result) != 0)
 	{
 		errno = PGTYPES_INTVL_BAD_INTERVAL;
 		free(result);
@@ -1062,19 +1232,19 @@ PGTYPESinterval_from_asc(char *str, char **endptr)
 char *
 PGTYPESinterval_to_asc(interval * span)
 {
-	struct tm	tt,
-			   *tm = &tt;
+	struct itm	tt,
+			   *itm = &tt;
 	fsec_t		fsec;
 	char		buf[MAXDATELEN + 1];
 	int			IntervalStyle = INTSTYLE_POSTGRES_VERBOSE;
 
-	if (interval2tm(*span, tm, &fsec) != 0)
+	if (interval2tm(*span, itm, &fsec) != 0)
 	{
 		errno = PGTYPES_INTVL_BAD_INTERVAL;
 		return NULL;
 	}
 
-	EncodeInterval(tm, fsec, IntervalStyle, buf);
+	EncodeInterval(itm, fsec, IntervalStyle, buf);
 
 	return pgtypes_strdup(buf);
 }
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index accd4a7d90..3ff78a0724 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -232,6 +232,115 @@ INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 years');
 ERROR:  interval out of range
 LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 years'...
                                                  ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 weeks');
+ERROR:  interval field value out of range: "2147483647 weeks"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 weeks')...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 weeks');
+ERROR:  interval field value out of range: "-2147483648 weeks"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 weeks'...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 decades');
+ERROR:  interval field value out of range: "2147483647 decades"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 decades...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 decades');
+ERROR:  interval field value out of range: "-2147483648 decades"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 decade...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 centuries');
+ERROR:  interval field value out of range: "2147483647 centuries"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 centuri...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 centuries');
+ERROR:  interval field value out of range: "-2147483648 centuries"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 centur...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 millennium');
+ERROR:  interval field value out of range: "2147483647 millennium"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 millenn...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 millennium');
+ERROR:  interval field value out of range: "-2147483648 millennium"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 millen...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 millennium 2147483647 months');
+ERROR:  interval field value out of range: "0.1 millennium 2147483647 months"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 millennium 214...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 centuries 2147483647 months');
+ERROR:  interval field value out of range: "0.1 centuries 2147483647 months"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 centuries 2147...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 decades 2147483647 months');
+ERROR:  interval field value out of range: "0.1 decades 2147483647 months"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 decades 214748...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 yrs 2147483647 months');
+ERROR:  interval field value out of range: "0.1 yrs 2147483647 months"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 yrs 2147483647...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 months 0.1 yrs');
+ERROR:  interval field value out of range: "2147483647 months 0.1 yrs"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 months ...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 months 2147483647 days');
+ERROR:  interval field value out of range: "0.1 months 2147483647 days"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 months 2147483...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('1 week 2147483647 days');
+ERROR:  interval field value out of range: "1 week 2147483647 days"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('1 week 2147483647 ...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 days 1 week');
+ERROR:  interval field value out of range: "2147483647 days 1 week"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 days 1 ...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 months ago');
+ERROR:  interval field value out of range: "-2147483648 months ago"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 months...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 days ago');
+ERROR:  interval field value out of range: "-2147483648 days ago"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 days a...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0.1M2147483647D');
+ERROR:  interval field value out of range: "P0.1M2147483647D"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0.1M2147483647D')...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0.1Y2147483647M');
+ERROR:  interval field value out of range: "P0.1Y2147483647M"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0.1Y2147483647M')...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P2147483647M0.1Y');
+ERROR:  interval field value out of range: "P2147483647M0.1Y"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P2147483647M0.1Y')...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P1W2147483647D');
+ERROR:  interval field value out of range: "P1W2147483647D"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P1W2147483647D');
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P2147483647D1W');
+ERROR:  interval field value out of range: "P2147483647D1W"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P2147483647D1W');
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0.5W2147483647D');
+ERROR:  interval field value out of range: "P0.5W2147483647D"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0.5W2147483647D')...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0000-00.1-2147483647T00:00:00');
+ERROR:  interval field value out of range: "P0000-00.1-2147483647T00:00:00"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0000-00.1-2147483...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0000.1-2147483647-00T00:00:00');
+ERROR:  interval field value out of range: "P0000.1-2147483647-00T00:00:00"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0000.1-2147483647...
+                                                 ^
+-- TODO: Tests to add:
+--      overflow time parts
+--      overflow time parts with fractional day/week parts
+--      overflow time parts with ago
+--      printing INT_MIN/INT64_MIN fields
 -- Test edge-case overflow detection in interval multiplication
 select extract(epoch from '256 microseconds'::interval * (2^55)::float8);
 ERROR:  interval out of range
@@ -1043,3 +1152,41 @@ SELECT extract(epoch from interval '1000000000 days');
  86400000000000.000000
 (1 row)
 
+-- int64 time fields
+-- TODO add more tests
+SELECT INTERVAL '214748364 hours' * 11;
+      ?column?      
+--------------------
+ @ 2362232004 hours
+(1 row)
+
+-- test that INT_MIN number of hours is formatted properly
+-- TODO add more tests
+SET IntervalStyle to postgres;
+SELECT INTERVAL '-2147483648 months -2147483648 days -2147483648 hrs';
+                          interval                           
+-------------------------------------------------------------
+ -178956970 years -8 mons -2147483648 days -2147483648:00:00
+(1 row)
+
+SET IntervalStyle to postgres_verbose;
+SELECT INTERVAL '-2147483648 months -2147483648 days -2147483648 hrs';
+                           interval                            
+---------------------------------------------------------------
+ @ 178956970 years 8 mons 2147483648 days 2147483648 hours ago
+(1 row)
+
+SET IntervalStyle TO sql_standard;
+SELECT INTERVAL '-2147483648 months -2147483648 days -2147483648 hrs';
+                  interval                  
+--------------------------------------------
+ -178956970-8 -2147483648 -2147483648:00:00
+(1 row)
+
+SET IntervalStyle to iso_8601;
+SELECT INTERVAL '-2147483648 months -2147483648 days -2147483648 hrs';
+                 interval                 
+------------------------------------------
+ P-178956970Y-8M-2147483648DT-2147483648H
+(1 row)
+
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 6d532398bd..1a637c7e2c 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -72,6 +72,37 @@ INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 years');
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 years');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 weeks');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 weeks');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 decades');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 decades');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 centuries');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 centuries');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 millennium');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 millennium');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 millennium 2147483647 months');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 centuries 2147483647 months');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 decades 2147483647 months');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 yrs 2147483647 months');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 months 0.1 yrs');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 months 2147483647 days');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('1 week 2147483647 days');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 days 1 week');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 months ago');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 days ago');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0.1M2147483647D');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0.1Y2147483647M');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P2147483647M0.1Y');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P1W2147483647D');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P2147483647D1W');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0.5W2147483647D');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0000-00.1-2147483647T00:00:00');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0000.1-2147483647-00T00:00:00');
+-- TODO: Tests to add:
+--      overflow time parts
+--      overflow time parts with fractional day/week parts
+--      overflow time parts with ago
+--      printing INT_MIN/INT64_MIN fields
 
 -- Test edge-case overflow detection in interval multiplication
 select extract(epoch from '256 microseconds'::interval * (2^55)::float8);
@@ -355,3 +386,18 @@ SELECT f1,
 
 -- internal overflow test case
 SELECT extract(epoch from interval '1000000000 days');
+
+-- int64 time fields
+-- TODO add more tests
+SELECT INTERVAL '214748364 hours' * 11;
+
+-- test that INT_MIN number of hours is formatted properly
+-- TODO add more tests
+SET IntervalStyle to postgres;
+SELECT INTERVAL '-2147483648 months -2147483648 days -2147483648 hrs';
+SET IntervalStyle to postgres_verbose;
+SELECT INTERVAL '-2147483648 months -2147483648 days -2147483648 hrs';
+SET IntervalStyle TO sql_standard;
+SELECT INTERVAL '-2147483648 months -2147483648 days -2147483648 hrs';
+SET IntervalStyle to iso_8601;
+SELECT INTERVAL '-2147483648 months -2147483648 days -2147483648 hrs';
-- 
2.25.1



^ permalink  raw  reply  [nested|flat] 23+ messages in thread

* Re: Fix overflow in DecodeInterval
  2022-02-13 03:51 Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-13 14:35 ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-13 20:30   ` Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-13 20:38     ` Re: Fix overflow in DecodeInterval Tom Lane <[email protected]>
  2022-02-13 22:12       ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-15 11:44         ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-15 16:28           ` Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-18 02:45             ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-19 19:26               ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-20 00:16                 ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
@ 2022-02-20 23:37                   ` Tom Lane <[email protected]>
  2022-02-21 02:53                     ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  0 siblings, 1 reply; 23+ messages in thread

From: Tom Lane @ 2022-02-20 23:37 UTC (permalink / raw)
  To: Joseph Koshakow <[email protected]>; +Cc: Andres Freund <[email protected]>; [email protected]

Joseph Koshakow <[email protected]> writes:
> Attached is a patch of my first pass. The to_char method isn't finished
> and I need to add a bunch of tests, but everything else is in place. It
> ended up being a fairly large change in case anyone wants to take a look
> the changes so far.

Couple of quick comments:

* You've assumed in a number of places that long == int64.  This is wrong
on many platforms.  Current project style for use of int64 in printf-type
calls is to cast the argument to (long long) explicitly and use "%lld" (or
"%llu", etc).  As for strtoint_64, surely that functionality exists
somewhere already (hopefully with fewer bugs than this has).

* I think that tools like Coverity will complain about how you've
got both calls that check the result of AdjustFractSeconds (etc)
and calls that don't.  You might consider coding the latter like

+            /* this can't overflow: */
+            (void) AdjustFractSeconds(fval, itm, fsec, SECS_PER_DAY);

in hopes of (a) silencing those warnings and (b) making the implicit
assumption clear to readers.

* I'm not entirely buying use of pg_time_t, rather than just int64,
in struct itm.  I think the point of this struct is to get away
from datetime-specific datatypes.  Also, if pg_time_t ever changed
width, it'd create issues for users of this struct.  I also note
a number of places in the patch that are assuming that these fields
are int64 not something else.

* I'm a little inclined to rename interval2tm/tm2interval to
interval2itm/itm2interval, as I think it'd be confusing for those
function names to refer to a typedef they no longer use.

* The uses of tm2itm make me a bit itchy.  Is that sweeping
upstream-of-there overflow problems under the rug?

* // comments are not project style, either.  While pgindent will
convert them to /* ... */ style, the results might not be pleasing.

> One thing I noticed is that interval.c has a ton of code copied and pasted
> from other files. The code seemed out of date from those other files, so
> I tried to bring it up to date and add my changes.

We haven't actually maintained ecpg's copies of backend datatype-specific
code in many years.  While bringing that stuff up to speed might be
worthwhile (or perhaps not, given the lack of complaints), I'd see it
as a separate project.

			regards, tom lane






^ permalink  raw  reply  [nested|flat] 23+ messages in thread

* Re: Fix overflow in DecodeInterval
  2022-02-13 03:51 Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-13 14:35 ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-13 20:30   ` Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-13 20:38     ` Re: Fix overflow in DecodeInterval Tom Lane <[email protected]>
  2022-02-13 22:12       ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-15 11:44         ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-15 16:28           ` Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-18 02:45             ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-19 19:26               ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-20 00:16                 ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-20 23:37                   ` Re: Fix overflow in DecodeInterval Tom Lane <[email protected]>
@ 2022-02-21 02:53                     ` Joseph Koshakow <[email protected]>
  2022-03-06 16:14                       ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-03-22 00:31                       ` Re: Fix overflow in DecodeInterval Tom Lane <[email protected]>
  2022-04-02 19:08                       ` Re: Fix overflow in DecodeInterval Tom Lane <[email protected]>
  0 siblings, 3 replies; 23+ messages in thread

From: Joseph Koshakow @ 2022-02-21 02:53 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Andres Freund <[email protected]>; [email protected]

On Sun, Feb 20, 2022 at 6:37 PM Tom Lane <[email protected]> wrote:
> Couple of quick comments:

Thanks for the comments Tom, I'll work on fixing these and submit a
new patch.

> * The uses of tm2itm make me a bit itchy.  Is that sweeping
> upstream-of-there overflow problems under the rug?

I agree, I'm not super happy with that approach. In fact
I'm pretty sure it will cause queries like
    SELECT INTERVAL '2147483648:00:00';
to overflow upstream, even though queries like
    SELECT INTERVAL '2147483648 hours';
would not. The places tm2itm is being used are
 * After DecodeTime
 * In interval_to_char.
The more general issue is how to share code with
functions that are doing almost identical things but use
pg_tm instead of the new pg_itm? I'm not really sure what
the best solution is right now but I will think about it. If
anyone has suggestions though, feel free to chime in.

- Joe Koshakow






^ permalink  raw  reply  [nested|flat] 23+ messages in thread

* Re: Fix overflow in DecodeInterval
  2022-02-13 03:51 Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-13 14:35 ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-13 20:30   ` Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-13 20:38     ` Re: Fix overflow in DecodeInterval Tom Lane <[email protected]>
  2022-02-13 22:12       ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-15 11:44         ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-15 16:28           ` Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-18 02:45             ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-19 19:26               ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-20 00:16                 ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-20 23:37                   ` Re: Fix overflow in DecodeInterval Tom Lane <[email protected]>
  2022-02-21 02:53                     ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
@ 2022-03-06 16:14                       ` Joseph Koshakow <[email protected]>
  2022-03-08 00:00                         ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2 siblings, 1 reply; 23+ messages in thread

From: Joseph Koshakow @ 2022-03-06 16:14 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Andres Freund <[email protected]>; PostgreSQL Hackers <[email protected]>

Hi All,

Sorry for the delay in the new patch, I've attached my most recent
patch to this email. I ended up reworking a good portion of my previous
patch so below I've included some reasons why, notes on my current
approach, and some pro/cons to the approach.

* The main reason for the rework had to do with double conversions and
shared code.

* The existing code for rounding had a lot of int to double
casting and vice versa. I *think* that doubles are able to completely
represent the range of ints. However doubles are not able to represent
the full range of int64. After making the change I started noticing
a lot of lossy behavior. One thought I had was to change the doubles
to long doubles, but I wasn't able to figure out if long doubles could
completely represent the range of int64. Especially since their size
varies depending on the architecture. Does anyone know the answer to
this?

* I ended up creating two intermediate data structures for Intervals.
One for decoding and one for everything else. I'll go into more detail
below.
* One common benefit was that they both contain a usec field which
means that the Interval methods no longer need to carry around a
separate fsec argument.
* The obvious con here is that Intervals require two unique
intermediate data structures, while all other date/time types
can share a single intermediate data structure. I find this to
be a bit clunky.

* pg_itm_in is the struct used for Interval decoding. It's very similar
to pg_tm, except all of the time related fields are collapsed into a
single `int64 usec` field.
* The biggest benefit of this was that all int64-double conversions
are limited to a single function, AdjustFractMicroseconds. Instead
of fractional units flowing down over every single time field, they
only need to flow down into the single `int64 usec` field.
* Overflows are caught much earlier in the decoding process which
helps avoid wasted work.
* I found that the decoding code became simpler for time fields,
though this is a bit subjective.

* pg_itm is the struct used for all other Interval functionality. It's
very similar to pg_tm, except the tm_hour field is converted from int
to int64 and an `int tm_usec` field was added.
* When encoding and working with Intervals, we almost always want
to break the time field out into hours, min, sec, usec. So it's
helpful to have a common place to do this, instead of every
function duplicating this code.
* When breaking the time fields out, a single field will never
contain a value greater than could have fit in the next unit
higher. Meaning that minutes will never be greater than 60, seconds
will be never greater than 60, and usec will never be greater than
1,000. So hours is actually the only field that needs to be int64
and the rest can be an int.
* This also helps limit the impact to shared code (see below).

* There's some shared code between Intervals and other date/time types.
Specifically the DecodeTime function and the datetime_to_char_body
function. These functions take in a `struct pg_tm` and a `fsec_t fsec`
(fsec_t is just an alias for int32) which allows them to be re-used by
all date/time types. The only difference now between pg_tm and pg_itm
is the tm_hour field size (the tm_usec field in pg_itm can be used as
the fsec). So to get around this I changed the function signatures to
take a `struct pg_tm`, `fsec_t fsec`, and an `int64 hour` argument.
It's up to the caller to provide to correct hour field. Intervals can
easily convert pg_itm to a pg_tm, fsec, and hour. It's honestly a bit
error-prone since those functions have to explicitly ignore the
pg_tm->tm_hour field and use the provided hour argument instead, but I
couldn't think of a better less intrusive solution. If anyone has a
better idea, please don't hesitate to bring it up.

* This partly existed in the previous patch, but I just wanted to
restate it. All modifications to pg_itm_in during decoding is done via
helper functions that check for overflow. All invocations of these
functions either return an error on overflow or explicitly state why an
overflow is impossible.

* I completely rewrote the ParseISO8601Number function to try and avoid
double to int64 conversions. I tried to model it after the parsing done
in DecodeInterval, though I would appreciate extra scrutiny here.

- Joe Koshakow


Attachments:

  [text/x-patch] v8-0001-Check-for-overflow-when-decoding-an-interval.patch (102.1K, ../../CAAvxfHd+8GAZSPxcuJa+Z3TG2WoPUmJ-ER9NxjAhKwQaDzgdKg@mail.gmail.com/2-v8-0001-Check-for-overflow-when-decoding-an-interval.patch)
  download | inline diff:
From a2afce720fb65b87638a634078067a796a639ddc Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <[email protected]>
Date: Mon, 28 Feb 2022 22:52:55 -0500
Subject: [PATCH] Rework Interval encoding and decoding

The current Interval encoding and decoding has the following issues:
  * The decoding functions have many uncaught errors that allow the
    Interval value to overflow/underflow.
  * Both the decoding and encoding functions do not protect against
    taking the absolute value or negating INT_MIN which leads to
    undefined behavior (usually it just leaves the value unchanged at
    INT_MIN, which is not the desired result).
  * The encoding and decoding process arbitrarily limits the range of
    Interval values by what can fit into the intermediate data
    structures, and not by what can fit into the Interval data
    structure.

This commit attempts to solve all of those issues by creating new
Interval specific intermediate data structures and explicitly check for
overflow, underflow, and undefined behavior.
---
 src/backend/utils/adt/datetime.c       | 602 ++++++++++++++----------
 src/backend/utils/adt/formatting.c     |  39 +-
 src/backend/utils/adt/numutils.c       |  12 +
 src/backend/utils/adt/timestamp.c      | 323 ++++++-------
 src/include/datatype/timestamp.h       |   9 +-
 src/include/pgtime.h                   |  25 +
 src/include/utils/builtins.h           |   1 +
 src/include/utils/datetime.h           |   8 +-
 src/include/utils/timestamp.h          |   5 +-
 src/test/regress/expected/interval.out | 611 +++++++++++++++++++++++++
 src/test/regress/sql/interval.sql      | 184 ++++++++
 11 files changed, 1410 insertions(+), 409 deletions(-)

diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 7926258c06..3690fbbc63 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -21,6 +21,7 @@
 #include "access/htup_details.h"
 #include "access/xact.h"
 #include "catalog/pg_type.h"
+#include "common/int.h"
 #include "common/string.h"
 #include "funcapi.h"
 #include "miscadmin.h"
@@ -38,16 +39,20 @@ static int	DecodeNumberField(int len, char *str,
 							  int fmask, int *tmask,
 							  struct pg_tm *tm, fsec_t *fsec, bool *is2digits);
 static int	DecodeTime(char *str, int fmask, int range,
-					   int *tmask, struct pg_tm *tm, fsec_t *fsec);
+					   int *tmask, struct pg_tm *tm, int64 *hour, fsec_t *fsec);
+static int DecodeTimeForInterval(char *str, int fmask, int range,
+								 int *tmask, struct pg_itm_in *itm_in);
 static const datetkn *datebsearch(const char *key, const datetkn *base, int nel);
 static int	DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
 					   struct pg_tm *tm);
-static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
+static char *AppendSeconds(char *cp, int sec, int64 fsec,
 						   int precision, bool fillzeros);
-static void AdjustFractSeconds(double frac, struct pg_tm *tm, fsec_t *fsec,
-							   int scale);
-static void AdjustFractDays(double frac, struct pg_tm *tm, fsec_t *fsec,
-							int scale);
+static bool AdjustFractMicroseconds(long double frac, struct pg_itm_in *itm_in, int64 scale);
+static bool AdjustFractDays(double frac, struct pg_itm_in *pg_itm_in, int scale);
+static bool AdjustFractMonths(double frac, struct pg_itm_in *itm_in, int scale);
+static bool AdjustMicroseconds(int64 val, struct pg_itm_in *itm_in, int64 multiplier, double fval);
+static bool AdjustDays(int val, struct pg_itm_in *itm_in, int multiplier);
+static bool AdjustYears(int val, struct pg_itm_in *itm_in, int multiplier);
 static int	DetermineTimeZoneOffsetInternal(struct pg_tm *tm, pg_tz *tzp,
 											pg_time_t *tp);
 static bool DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t,
@@ -428,7 +433,7 @@ GetCurrentTimeUsec(struct pg_tm *tm, fsec_t *fsec, int *tzp)
  * Note that any sign is stripped from the input seconds values.
  */
 static char *
-AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros)
+AppendSeconds(char *cp, int sec, int64 fsec, int precision, bool fillzeros)
 {
 	Assert(precision >= 0);
 
@@ -437,10 +442,9 @@ AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros)
 	else
 		cp = pg_ultostr(cp, Abs(sec));
 
-	/* fsec_t is just an int32 */
 	if (fsec != 0)
 	{
-		int32		value = Abs(fsec);
+		int64		value = Abs(fsec);
 		char	   *end = &cp[precision + 1];
 		bool		gotnonzero = false;
 
@@ -453,8 +457,8 @@ AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros)
 		 */
 		while (precision--)
 		{
-			int32		oldval = value;
-			int32		remainder;
+			int64		oldval = value;
+			int64		remainder;
 
 			value /= 10;
 			remainder = oldval - value * 10;
@@ -475,7 +479,7 @@ AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros)
 		 * which will generate a correct answer in the minimum valid width.
 		 */
 		if (value)
-			return pg_ultostr(cp, Abs(fsec));
+			return pg_ulltostr(cp, Abs(fsec));
 
 		return end;
 	}
@@ -497,36 +501,96 @@ AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec)
 }
 
 /*
- * Multiply frac by scale (to produce seconds) and add to *tm & *fsec.
- * We assume the input frac is less than 1 so overflow is not an issue.
+ * Multiply frac by scale (to produce microseconds) and add to *itm.
+ * We assume the input frac is less than 1 so overflow of frac is not an issue.
  */
-static void
-AdjustFractSeconds(double frac, struct pg_tm *tm, fsec_t *fsec, int scale)
+static bool
+AdjustFractMicroseconds(long double frac, struct pg_itm_in *itm_in, int64 scale)
 {
-	int			sec;
+	int64		usec;
+	int64		round = 0;
 
 	if (frac == 0)
-		return;
+		return true;
 	frac *= scale;
-	sec = (int) frac;
-	tm->tm_sec += sec;
-	frac -= sec;
-	*fsec += rint(frac * 1000000);
+	usec = (int64) frac;
+	if (pg_add_s64_overflow(itm_in->tm_usec, usec, &itm_in->tm_usec))
+		return false;
+	
+	frac = frac - usec;
+	if (frac > 0.5)
+		round = 1;
+	else if (frac < -0.5)
+		round = -1;
+
+	return !pg_add_s64_overflow(itm_in->tm_usec, round, &itm_in->tm_usec);
 }
 
 /* As above, but initial scale produces days */
-static void
-AdjustFractDays(double frac, struct pg_tm *tm, fsec_t *fsec, int scale)
+static bool
+AdjustFractDays(double frac, struct pg_itm_in *itm_in, int scale)
 {
 	int			extra_days;
 
 	if (frac == 0)
-		return;
+		return true;
 	frac *= scale;
 	extra_days = (int) frac;
-	tm->tm_mday += extra_days;
+	if (pg_add_s32_overflow(itm_in->tm_mday, extra_days, &itm_in->tm_mday))
+		return false;
 	frac -= extra_days;
-	AdjustFractSeconds(frac, tm, fsec, SECS_PER_DAY);
+	return AdjustFractMicroseconds(frac, itm_in, USECS_PER_DAY);
+}
+
+/* As above, but initial scale produces months */
+static bool
+AdjustFractMonths(double frac, struct pg_itm_in *itm_in, int scale)
+{
+	int extra_months = rint(frac * MONTHS_PER_YEAR * scale);
+	return !pg_add_s32_overflow(itm_in->tm_mon, extra_months, &itm_in->tm_mon);
+}
+
+/*
+ * Multiply val by multiplier (to produce microseconds) and add to *itm.
+ * Returns true if successful, false if tm overflows.
+ */
+static bool
+AdjustMicroseconds(int64 val, struct pg_itm_in *itm_in, int64 multiplier, double fval)
+{
+	int64		usecs;
+	if (pg_mul_s64_overflow(val, multiplier, &usecs) ||
+		pg_add_s64_overflow(itm_in->tm_usec, usecs, &itm_in->tm_usec))
+		return false;
+
+	return AdjustFractMicroseconds(fval, itm_in, multiplier);
+}
+
+/*
+ * Multiply val by multiplier (to produce days) and add to *itm.
+ * Returns true if successful, false if tm overflows.
+ */
+static bool
+AdjustDays(int val, struct pg_itm_in *itm_in, int multiplier)
+{
+	int			extra_days;
+	return !pg_mul_s32_overflow(val, multiplier, &extra_days) &&
+		!pg_add_s32_overflow(itm_in->tm_mday, extra_days, &itm_in->tm_mday);
+}
+
+/* As above, but initial val produces months */
+static bool
+AdjustMonths(int val, struct pg_itm_in *itm_in)
+{
+	return !pg_add_s32_overflow(itm_in->tm_mon, val, &itm_in->tm_mon);
+}
+
+/* As above, but initial val produces years */
+static bool
+AdjustYears(int val, struct pg_itm_in *itm_in, int multiplier)
+{
+	int			years;
+	return !pg_mul_s32_overflow(val, multiplier, &years) &&
+		!pg_add_s32_overflow(itm_in->tm_year, years, &itm_in->tm_year);
 }
 
 /* Fetch a fractional-second value with suitable error checking */
@@ -965,7 +1029,8 @@ DecodeDateTime(char **field, int *ftype, int nf,
 				break;
 
 			case DTK_TIME:
-
+			{
+				int64 hour;
 				/*
 				 * This might be an ISO time following a "t" field.
 				 */
@@ -977,16 +1042,19 @@ DecodeDateTime(char **field, int *ftype, int nf,
 					ptype = 0;
 				}
 				dterr = DecodeTime(field[i], fmask, INTERVAL_FULL_RANGE,
-								   &tmask, tm, fsec);
+								   &tmask, tm, &hour, fsec);
 				if (dterr)
 					return dterr;
+				if (hour > INT_MAX || hour < INT_MIN)
+					return DTERR_FIELD_OVERFLOW;
+				tm->tm_hour = (int) hour;
 
 				/* check for time overflow */
 				if (time_overflows(tm->tm_hour, tm->tm_min, tm->tm_sec,
 								   *fsec))
 					return DTERR_FIELD_OVERFLOW;
 				break;
-
+			}
 			case DTK_TZ:
 				{
 					int			tz;
@@ -1866,13 +1934,18 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
 				break;
 
 			case DTK_TIME:
+			{
+				int64 hour;
 				dterr = DecodeTime(field[i], (fmask | DTK_DATE_M),
 								   INTERVAL_FULL_RANGE,
-								   &tmask, tm, fsec);
+								   &tmask, tm, &hour, fsec);
 				if (dterr)
 					return dterr;
+				if (hour > INT_MAX || hour < INT_MIN)
+					return DTERR_FIELD_OVERFLOW;
+				tm->tm_hour = (int) hour;
 				break;
-
+			}
 			case DTK_TZ:
 				{
 					int			tz;
@@ -2554,10 +2627,13 @@ ValidateDate(int fmask, bool isjulian, bool is2digits, bool bc,
  *
  * Only check the lower limit on hours, since this same code can be
  * used to represent time spans.
+ *
+ * Different consumers of this function have different requirements on the size
+ * of hours. So we take in an *int64 hour and let the consumer check the result.
  */
 static int
 DecodeTime(char *str, int fmask, int range,
-		   int *tmask, struct pg_tm *tm, fsec_t *fsec)
+		   int *tmask, struct pg_tm *tm, int64 *hour, fsec_t *fsec)
 {
 	char	   *cp;
 	int			dterr;
@@ -2565,7 +2641,7 @@ DecodeTime(char *str, int fmask, int range,
 	*tmask = DTK_TIME_M;
 
 	errno = 0;
-	tm->tm_hour = strtoint(str, &cp, 10);
+	*hour = strtoi64(str, &cp, 10);
 	if (errno == ERANGE)
 		return DTERR_FIELD_OVERFLOW;
 	if (*cp != ':')
@@ -2581,9 +2657,11 @@ DecodeTime(char *str, int fmask, int range,
 		/* If it's a MINUTE TO SECOND interval, take 2 fields as being mm:ss */
 		if (range == (INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND)))
 		{
+			if (*hour > INT_MAX || *hour < INT_MIN)
+				return DTERR_FIELD_OVERFLOW;
 			tm->tm_sec = tm->tm_min;
-			tm->tm_min = tm->tm_hour;
-			tm->tm_hour = 0;
+			tm->tm_min = (int) *hour;
+			*hour = 0;
 		}
 	}
 	else if (*cp == '.')
@@ -2592,9 +2670,11 @@ DecodeTime(char *str, int fmask, int range,
 		dterr = ParseFractionalSecond(cp, fsec);
 		if (dterr)
 			return dterr;
+		if (*hour > INT_MAX || *hour < INT_MIN)
+			return DTERR_FIELD_OVERFLOW;
 		tm->tm_sec = tm->tm_min;
-		tm->tm_min = tm->tm_hour;
-		tm->tm_hour = 0;
+		tm->tm_min = (int) *hour;
+		*hour = 0;
 	}
 	else if (*cp == ':')
 	{
@@ -2617,7 +2697,7 @@ DecodeTime(char *str, int fmask, int range,
 		return DTERR_BAD_FORMAT;
 
 	/* do a sanity check */
-	if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_min > MINS_PER_HOUR - 1 ||
+	if (*hour < 0 || tm->tm_min < 0 || tm->tm_min > MINS_PER_HOUR - 1 ||
 		tm->tm_sec < 0 || tm->tm_sec > SECS_PER_MINUTE ||
 		*fsec < INT64CONST(0) ||
 		*fsec > USECS_PER_SEC)
@@ -2626,6 +2706,30 @@ DecodeTime(char *str, int fmask, int range,
 	return 0;
 }
 
+/* DecodeTimeForInterval()
+ * Decode time string which includes delimiters for Interval decoding.
+ * Return 0 if okay, a DTERR code if not.
+ */
+static int
+DecodeTimeForInterval(char *str, int fmask, int range,
+								 int *tmask, struct pg_itm_in *itm_in)
+{
+	int dterr;
+	int64 hour;
+	struct pg_tm tt,
+			*tm = &tt;
+	dterr = DecodeTime(str, fmask, range,
+					   tmask, tm, &hour, (fsec_t *)&itm_in->tm_usec);
+	if (dterr)
+		return dterr;
+	
+	if (!AdjustMicroseconds(hour, itm_in, USECS_PER_HOUR, 0) ||
+		!AdjustMicroseconds(tm->tm_min, itm_in, USECS_PER_MINUTE, 0) ||
+		!AdjustMicroseconds(tm->tm_sec, itm_in, USECS_PER_SEC, 0))
+		return DTERR_FIELD_OVERFLOW;
+
+	return 0;
+}
 
 /* DecodeNumber()
  * Interpret plain numeric field as a date value in context.
@@ -3063,28 +3167,24 @@ DecodeSpecial(int field, char *lowtoken, int *val)
 	return type;
 }
 
-
-/* ClearPgTm
+/* ClearPgItmIn
  *
- * Zero out a pg_tm and associated fsec_t
+ * Zero out a pg_itm_in
  */
 static inline void
-ClearPgTm(struct pg_tm *tm, fsec_t *fsec)
+ClearPgItmIn(struct pg_itm_in *itm_in)
 {
-	tm->tm_year = 0;
-	tm->tm_mon = 0;
-	tm->tm_mday = 0;
-	tm->tm_hour = 0;
-	tm->tm_min = 0;
-	tm->tm_sec = 0;
-	*fsec = 0;
+	itm_in->tm_year = 0;
+	itm_in->tm_mon = 0;
+	itm_in->tm_mday = 0;
+	itm_in->tm_usec = 0;
 }
 
 
 /* DecodeInterval()
  * Interpret previously parsed fields for general time interval.
  * Returns 0 if successful, DTERR code if bogus input detected.
- * dtype, tm, fsec are output parameters.
+ * dtype and itm_in are output parameters.
  *
  * Allow "date" field DTK_DATE since this could be just
  *	an unsigned floating point number. - thomas 1997-11-16
@@ -3094,21 +3194,22 @@ ClearPgTm(struct pg_tm *tm, fsec_t *fsec)
  */
 int
 DecodeInterval(char **field, int *ftype, int nf, int range,
-			   int *dtype, struct pg_tm *tm, fsec_t *fsec)
+			   int *dtype, struct pg_itm_in *itm_in)
 {
 	bool		is_before = false;
 	char	   *cp;
 	int			fmask = 0,
 				tmask,
-				type;
+				type,
+				tval;
 	int			i;
 	int			dterr;
-	int			val;
+	int64		val;
 	double		fval;
 
 	*dtype = DTK_DELTA;
 	type = IGNORE_DTF;
-	ClearPgTm(tm, fsec);
+	ClearPgItmIn(itm_in);
 
 	/* read through list backwards to pick up units before values */
 	for (i = nf - 1; i >= 0; i--)
@@ -3116,8 +3217,8 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 		switch (ftype[i])
 		{
 			case DTK_TIME:
-				dterr = DecodeTime(field[i], fmask, range,
-								   &tmask, tm, fsec);
+				dterr = DecodeTimeForInterval(field[i], fmask, range,
+											  &tmask, itm_in);
 				if (dterr)
 					return dterr;
 				type = DTK_DAY;
@@ -3137,16 +3238,15 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 				 * like DTK_TIME case above, plus handling the sign.
 				 */
 				if (strchr(field[i] + 1, ':') != NULL &&
-					DecodeTime(field[i] + 1, fmask, range,
-							   &tmask, tm, fsec) == 0)
+					DecodeTimeForInterval(field[i] + 1, fmask, range,
+							   &tmask, itm_in) == 0)
 				{
 					if (*field[i] == '-')
 					{
-						/* flip the sign on all fields */
-						tm->tm_hour = -tm->tm_hour;
-						tm->tm_min = -tm->tm_min;
-						tm->tm_sec = -tm->tm_sec;
-						*fsec = -(*fsec);
+						/* flip the sign on time field */
+						if (itm_in->tm_usec == PG_INT64_MIN)
+							return DTERR_FIELD_OVERFLOW;
+						itm_in->tm_usec = -itm_in->tm_usec;
 					}
 
 					/*
@@ -3204,7 +3304,7 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 				}
 
 				errno = 0;
-				val = strtoint(field[i], &cp, 10);
+				val = strtoi64(field[i], &cp, 10);
 				if (errno == ERANGE)
 					return DTERR_FIELD_OVERFLOW;
 
@@ -3221,8 +3321,8 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 					type = DTK_MONTH;
 					if (*field[i] == '-')
 						val2 = -val2;
-					if (((double) val * MONTHS_PER_YEAR + val2) > INT_MAX ||
-						((double) val * MONTHS_PER_YEAR + val2) < INT_MIN)
+					if ((val * MONTHS_PER_YEAR + val2) > INT_MAX ||
+						(val * MONTHS_PER_YEAR + val2) < INT_MIN)
 						return DTERR_FIELD_OVERFLOW;
 					val = val * MONTHS_PER_YEAR + val2;
 					fval = 0;
@@ -3247,21 +3347,20 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 				switch (type)
 				{
 					case DTK_MICROSEC:
-						*fsec += rint(val + fval);
+						if (!AdjustMicroseconds(val, itm_in, 1, fval))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(MICROSECOND);
 						break;
 
 					case DTK_MILLISEC:
-						/* avoid overflowing the fsec field */
-						tm->tm_sec += val / 1000;
-						val -= (val / 1000) * 1000;
-						*fsec += rint((val + fval) * 1000);
+						if (!AdjustMicroseconds(val, itm_in, USECS_PER_MSEC, fval))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(MILLISECOND);
 						break;
 
 					case DTK_SECOND:
-						tm->tm_sec += val;
-						*fsec += rint(fval * 1000000);
+						if (!AdjustMicroseconds(val, itm_in, USECS_PER_SEC, fval))
+							return DTERR_FIELD_OVERFLOW;
 
 						/*
 						 * If any subseconds were specified, consider this
@@ -3274,57 +3373,71 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 						break;
 
 					case DTK_MINUTE:
-						tm->tm_min += val;
-						AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
+						if (!AdjustMicroseconds(val, itm_in, USECS_PER_MINUTE, fval))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(MINUTE);
 						break;
 
 					case DTK_HOUR:
-						tm->tm_hour += val;
-						AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
+						if (!AdjustMicroseconds(val, itm_in, USECS_PER_HOUR, fval))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(HOUR);
 						type = DTK_DAY; /* set for next field */
 						break;
 
 					case DTK_DAY:
-						tm->tm_mday += val;
-						AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+						if (val < INT_MIN || val > INT_MAX ||
+							!AdjustDays((int) val, itm_in, 1) ||
+							!AdjustFractMicroseconds(fval, itm_in, USECS_PER_DAY))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(DAY);
 						break;
 
 					case DTK_WEEK:
-						tm->tm_mday += val * 7;
-						AdjustFractDays(fval, tm, fsec, 7);
+						if (val < INT_MIN || val > INT_MAX ||
+							!AdjustDays((int) val, itm_in, 7) ||
+							!AdjustFractDays(fval, itm_in, 7))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(WEEK);
 						break;
 
 					case DTK_MONTH:
-						tm->tm_mon += val;
-						AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
+						if (val < INT_MIN || val > INT_MAX ||
+							!AdjustMonths((int) val, itm_in) ||
+							!AdjustFractDays(fval, itm_in, DAYS_PER_MONTH))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(MONTH);
 						break;
 
 					case DTK_YEAR:
-						tm->tm_year += val;
-						tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
+						if (val < INT_MIN || val > INT_MAX ||
+							!AdjustYears((int) val, itm_in, 1) ||
+							!AdjustFractMonths(fval, itm_in, 1))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(YEAR);
 						break;
 
 					case DTK_DECADE:
-						tm->tm_year += val * 10;
-						tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 10);
+						if (val < INT_MIN || val > INT_MAX ||
+							!AdjustYears((int) val, itm_in, YEARS_PER_DECADE) ||
+							!AdjustFractMonths(fval, itm_in, YEARS_PER_DECADE))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(DECADE);
 						break;
 
 					case DTK_CENTURY:
-						tm->tm_year += val * 100;
-						tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 100);
+						if (val < INT_MIN || val > INT_MAX ||
+							!AdjustYears((int) val, itm_in, YEARS_PER_CENTURY) ||
+							!AdjustFractMonths(fval, itm_in, YEARS_PER_CENTURY))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(CENTURY);
 						break;
 
 					case DTK_MILLENNIUM:
-						tm->tm_year += val * 1000;
-						tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 1000);
+						if (val < INT_MIN || val > INT_MAX ||
+							!AdjustYears((int) val, itm_in, YEARS_PER_MILLENNIUM) ||
+							!AdjustFractMonths(fval, itm_in, YEARS_PER_MILLENNIUM))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(MILLENNIUM);
 						break;
 
@@ -3335,7 +3448,7 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 
 			case DTK_STRING:
 			case DTK_SPECIAL:
-				type = DecodeUnits(i, field[i], &val);
+				type = DecodeUnits(i, field[i], &tval);
 				if (type == IGNORE_DTF)
 					continue;
 
@@ -3343,17 +3456,17 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 				switch (type)
 				{
 					case UNITS:
-						type = val;
+						type = tval;
 						break;
 
 					case AGO:
 						is_before = true;
-						type = val;
+						type = tval;
 						break;
 
 					case RESERV:
 						tmask = (DTK_DATE_M | DTK_TIME_M);
-						*dtype = val;
+						*dtype = tval;
 						break;
 
 					default:
@@ -3374,16 +3487,6 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 	if (fmask == 0)
 		return DTERR_BAD_FORMAT;
 
-	/* ensure fractional seconds are fractional */
-	if (*fsec != 0)
-	{
-		int			sec;
-
-		sec = *fsec / USECS_PER_SEC;
-		*fsec -= sec * USECS_PER_SEC;
-		tm->tm_sec += sec;
-	}
-
 	/*----------
 	 * The SQL standard defines the interval literal
 	 *	 '-1 1:00:00'
@@ -3420,33 +3523,30 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 			 * Rather than re-determining which field was field[0], just force
 			 * 'em all negative.
 			 */
-			if (*fsec > 0)
-				*fsec = -(*fsec);
-			if (tm->tm_sec > 0)
-				tm->tm_sec = -tm->tm_sec;
-			if (tm->tm_min > 0)
-				tm->tm_min = -tm->tm_min;
-			if (tm->tm_hour > 0)
-				tm->tm_hour = -tm->tm_hour;
-			if (tm->tm_mday > 0)
-				tm->tm_mday = -tm->tm_mday;
-			if (tm->tm_mon > 0)
-				tm->tm_mon = -tm->tm_mon;
-			if (tm->tm_year > 0)
-				tm->tm_year = -tm->tm_year;
+			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)
 	{
-		*fsec = -(*fsec);
-		tm->tm_sec = -tm->tm_sec;
-		tm->tm_min = -tm->tm_min;
-		tm->tm_hour = -tm->tm_hour;
-		tm->tm_mday = -tm->tm_mday;
-		tm->tm_mon = -tm->tm_mon;
-		tm->tm_year = -tm->tm_year;
+		if (itm_in->tm_usec == PG_INT64_MIN ||
+			itm_in->tm_mday == INT_MIN ||
+			itm_in->tm_mon == INT_MIN ||
+			itm_in->tm_year == INT_MIN)
+			return DTERR_FIELD_OVERFLOW;
+
+		itm_in->tm_usec = -itm_in->tm_usec;
+		itm_in->tm_mday = -itm_in->tm_mday;
+		itm_in->tm_mon = -itm_in->tm_mon;
+		itm_in->tm_year = -itm_in->tm_year;
 	}
 
 	return 0;
@@ -3460,26 +3560,37 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
  * Returns 0 or DTERR code.
  */
 static int
-ParseISO8601Number(char *str, char **endptr, int *ipart, double *fpart)
+ParseISO8601Number(char *str, char **endptr, int64 *ipart, double *fpart)
 {
-	double		val;
+	int sign = 1;
+	*ipart = 0;
+	*fpart = 0.0;
 
 	if (!(isdigit((unsigned char) *str) || *str == '-' || *str == '.'))
 		return DTERR_BAD_FORMAT;
 	errno = 0;
-	val = strtod(str, endptr);
+
+	/* Parse sign if there is any */
+	if (*str == '-')
+	{
+		sign = -1;
+		str++;
+		*endptr = str;
+	}
+
+	/* Parse int64 part if there is any */
+	if (isdigit((unsigned char) **endptr))
+		*ipart = strtoi64(*endptr, endptr, 10) * sign;
+
+	/* Parse decimal part if there is any */
+	if (**endptr == '.') {
+		*fpart = strtod(*endptr, endptr) * sign;
+	}
+
 	/* did we not see anything that looks like a double? */
 	if (*endptr == str || errno != 0)
 		return DTERR_BAD_FORMAT;
-	/* watch out for overflow */
-	if (val < INT_MIN || val > INT_MAX)
-		return DTERR_FIELD_OVERFLOW;
-	/* be very sure we truncate towards zero (cf dtrunc()) */
-	if (val >= 0)
-		*ipart = (int) floor(val);
-	else
-		*ipart = (int) -floor(-val);
-	*fpart = val - *ipart;
+
 	return 0;
 }
 
@@ -3508,21 +3619,20 @@ ISO8601IntegerWidth(char *fieldstart)
  * Returns 0 if successful, DTERR code if bogus input detected.
  * Note: error code should be DTERR_BAD_FORMAT if input doesn't look like
  * ISO8601, otherwise this could cause unexpected error messages.
- * dtype, tm, fsec are output parameters.
+ * dtype and itm_in are output parameters.
  *
  *	A couple exceptions from the spec:
  *	 - a week field ('W') may coexist with other units
  *	 - allows decimals in fields other than the least significant unit.
  */
 int
-DecodeISO8601Interval(char *str,
-					  int *dtype, struct pg_tm *tm, fsec_t *fsec)
+DecodeISO8601Interval(char *str, int *dtype, struct pg_itm_in *itm_in)
 {
 	bool		datepart = true;
 	bool		havefield = false;
 
 	*dtype = DTK_DELTA;
-	ClearPgTm(tm, fsec);
+	ClearPgItmIn(itm_in);
 
 	if (strlen(str) < 2 || str[0] != 'P')
 		return DTERR_BAD_FORMAT;
@@ -3531,7 +3641,7 @@ DecodeISO8601Interval(char *str,
 	while (*str)
 	{
 		char	   *fieldstart;
-		int			val;
+		int64		val;
 		double		fval;
 		char		unit;
 		int			dterr;
@@ -3557,32 +3667,50 @@ DecodeISO8601Interval(char *str,
 
 		if (datepart)
 		{
+			/* Date parts cannot be bigger than int */
+			if (val < INT_MIN || val > INT_MAX)
+				return DTERR_FIELD_OVERFLOW;
 			switch (unit)		/* before T: Y M W D */
 			{
 				case 'Y':
-					tm->tm_year += val;
-					tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
+					/* 
+					 * Not possible to overflow years in this format since
+					 * there's no year aliases and can't have fractional
+					 * years
+					 */
+					(void) AdjustYears((int) val, itm_in, 1);
+					if (!AdjustFractMonths(fval, itm_in, 1))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case 'M':
-					tm->tm_mon += val;
-					AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
+					if (!AdjustMonths((int) val, itm_in) ||
+						!AdjustFractDays(fval, itm_in, DAYS_PER_MONTH))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case 'W':
-					tm->tm_mday += val * 7;
-					AdjustFractDays(fval, tm, fsec, 7);
+					if (!AdjustDays((int) val, itm_in, 7) ||
+						!AdjustFractDays(fval, itm_in, 7))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case 'D':
-					tm->tm_mday += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+					if (!AdjustDays((int) val, itm_in, 1) ||
+						!AdjustFractMicroseconds(fval, itm_in, USECS_PER_DAY))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case 'T':		/* ISO 8601 4.4.3.3 Alternative Format / Basic */
 				case '\0':
 					if (ISO8601IntegerWidth(fieldstart) == 8 && !havefield)
 					{
-						tm->tm_year += val / 10000;
-						tm->tm_mon += (val / 100) % 100;
-						tm->tm_mday += val % 100;
-						AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+						/* None of the date fields can overflow because val is
+						 * within the bounds of an int from the check above
+						 */
+						(void) AdjustYears((int) val / 10000, itm_in, 1);
+						(void) AdjustMonths((int) (val / 100) % 100, itm_in);
+						(void) AdjustDays((int) val % 100, itm_in, 1);
+						/* Can't overflow because date fields must come before
+						 * time fields
+						 */
+						(void) AdjustFractMicroseconds(fval, itm_in, USECS_PER_DAY);
 						if (unit == '\0')
 							return 0;
 						datepart = false;
@@ -3596,8 +3724,14 @@ DecodeISO8601Interval(char *str,
 					if (havefield)
 						return DTERR_BAD_FORMAT;
 
-					tm->tm_year += val;
-					tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
+					/* 
+					 * Not possible to overflow years in this format since
+					 * there's no year aliases and can't have fractional
+					 * years
+					 */
+					(void) AdjustYears((int) val, itm_in, 1);
+					/* Can't overflow because years must come before months */
+					(void) AdjustFractMonths(fval, itm_in, 1);
 					if (unit == '\0')
 						return 0;
 					if (unit == 'T')
@@ -3610,8 +3744,10 @@ DecodeISO8601Interval(char *str,
 					dterr = ParseISO8601Number(str, &str, &val, &fval);
 					if (dterr)
 						return dterr;
-					tm->tm_mon += val;
-					AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
+					if (!AdjustMonths((int) val, itm_in))
+						return DTERR_FIELD_OVERFLOW;
+					/* Can't overflow because months must come before days */
+					(void) AdjustFractDays(fval, itm_in, DAYS_PER_MONTH);
 					if (*str == '\0')
 						return 0;
 					if (*str == 'T')
@@ -3627,8 +3763,10 @@ DecodeISO8601Interval(char *str,
 					dterr = ParseISO8601Number(str, &str, &val, &fval);
 					if (dterr)
 						return dterr;
-					tm->tm_mday += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+					if (!AdjustDays((int) val, itm_in, 1))
+						return DTERR_FIELD_OVERFLOW;
+					/* Can't overflow because days must come before time fields */
+					(void) AdjustFractMicroseconds(fval, itm_in, USECS_PER_DAY);
 					if (*str == '\0')
 						return 0;
 					if (*str == 'T')
@@ -3648,24 +3786,25 @@ DecodeISO8601Interval(char *str,
 			switch (unit)		/* after T: H M S */
 			{
 				case 'H':
-					tm->tm_hour += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
+					if (!AdjustMicroseconds(val, itm_in, USECS_PER_HOUR, fval))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case 'M':
-					tm->tm_min += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
+					if (!AdjustMicroseconds(val, itm_in, USECS_PER_MINUTE, fval))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case 'S':
-					tm->tm_sec += val;
-					AdjustFractSeconds(fval, tm, fsec, 1);
+					if (!AdjustMicroseconds(val, itm_in, USECS_PER_SEC, fval))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case '\0':		/* ISO 8601 4.4.3.3 Alternative Format */
 					if (ISO8601IntegerWidth(fieldstart) == 6 && !havefield)
 					{
-						tm->tm_hour += val / 10000;
-						tm->tm_min += (val / 100) % 100;
-						tm->tm_sec += val % 100;
-						AdjustFractSeconds(fval, tm, fsec, 1);
+						if (!AdjustMicroseconds(val / 10000, itm_in, USECS_PER_HOUR, 0) ||
+							!AdjustMicroseconds((val / 100) % 100, itm_in, USECS_PER_MINUTE, 0) ||
+							!AdjustMicroseconds(val % 100, itm_in, USECS_PER_SEC, 0) ||
+							!AdjustFractMicroseconds(fval, itm_in, 1))
+							return DTERR_FIELD_OVERFLOW;
 						return 0;
 					}
 					/* Else fall through to extended alternative format */
@@ -3675,16 +3814,16 @@ DecodeISO8601Interval(char *str,
 					if (havefield)
 						return DTERR_BAD_FORMAT;
 
-					tm->tm_hour += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
+					if (!AdjustMicroseconds(val, itm_in, USECS_PER_HOUR, fval))
+						return DTERR_FIELD_OVERFLOW;
 					if (unit == '\0')
 						return 0;
 
 					dterr = ParseISO8601Number(str, &str, &val, &fval);
 					if (dterr)
 						return dterr;
-					tm->tm_min += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
+					if (!AdjustMicroseconds(val, itm_in, USECS_PER_MINUTE, fval))
+						return DTERR_FIELD_OVERFLOW;
 					if (*str == '\0')
 						return 0;
 					if (*str != ':')
@@ -3694,8 +3833,8 @@ DecodeISO8601Interval(char *str,
 					dterr = ParseISO8601Number(str, &str, &val, &fval);
 					if (dterr)
 						return dterr;
-					tm->tm_sec += val;
-					AdjustFractSeconds(fval, tm, fsec, 1);
+					if (!AdjustMicroseconds(val, itm_in, USECS_PER_SEC, fval))
+						return DTERR_FIELD_OVERFLOW;
 					if (*str == '\0')
 						return 0;
 					return DTERR_BAD_FORMAT;
@@ -4166,25 +4305,25 @@ EncodeDateTime(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, const char
 
 /* Append an ISO-8601-style interval field, but only if value isn't zero */
 static char *
-AddISO8601IntPart(char *cp, int value, char units)
+AddISO8601IntPart(char *cp, int64 value, char units)
 {
 	if (value == 0)
 		return cp;
-	sprintf(cp, "%d%c", value, units);
+	sprintf(cp, "%lld%c", (long long) value, units);
 	return cp + strlen(cp);
 }
 
 /* Append a postgres-style interval field, but only if value isn't zero */
 static char *
-AddPostgresIntPart(char *cp, int value, const char *units,
+AddPostgresIntPart(char *cp, int64 value, const char *units,
 				   bool *is_zero, bool *is_before)
 {
 	if (value == 0)
 		return cp;
-	sprintf(cp, "%s%s%d %s%s",
+	sprintf(cp, "%s%s%lld %s%s",
 			(!*is_zero) ? " " : "",
 			(*is_before && value > 0) ? "+" : "",
-			value,
+			(long long) value,
 			units,
 			(value != 1) ? "s" : "");
 
@@ -4199,7 +4338,7 @@ AddPostgresIntPart(char *cp, int value, const char *units,
 
 /* Append a verbose-style interval field, but only if value isn't zero */
 static char *
-AddVerboseIntPart(char *cp, int value, const char *units,
+AddVerboseIntPart(char *cp, int64 value, const char *units,
 				  bool *is_zero, bool *is_before)
 {
 	if (value == 0)
@@ -4208,11 +4347,11 @@ AddVerboseIntPart(char *cp, int value, const char *units,
 	if (*is_zero)
 	{
 		*is_before = (value < 0);
-		value = abs(value);
+		value = Abs(value);
 	}
 	else if (*is_before)
 		value = -value;
-	sprintf(cp, " %d %s%s", value, units, (value == 1) ? "" : "s");
+	sprintf(cp, " %lld %s%s", (long long) value, units, (value == 1) ? "" : "s");
 	*is_zero = false;
 	return cp + strlen(cp);
 }
@@ -4238,15 +4377,16 @@ AddVerboseIntPart(char *cp, int value, const char *units,
  * "day-time literal"s (that look like ('4 5:6:7')
  */
 void
-EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
+EncodeInterval(struct pg_itm *itm, int style, char *str)
 {
 	char	   *cp = str;
-	int			year = tm->tm_year;
-	int			mon = tm->tm_mon;
-	int			mday = tm->tm_mday;
-	int			hour = tm->tm_hour;
-	int			min = tm->tm_min;
-	int			sec = tm->tm_sec;
+	int64		year = (int64) itm->tm_year;
+	int64		mon = (int64) itm->tm_mon;
+	int64		mday = (int64) itm->tm_mday;
+	int64		hour = itm->tm_hour;
+	int			min = itm->tm_min;
+	int			sec = itm->tm_sec;
+	int 		usec = itm->tm_usec;
 	bool		is_before = false;
 	bool		is_zero = true;
 
@@ -4263,13 +4403,13 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
 			{
 				bool		has_negative = year < 0 || mon < 0 ||
 				mday < 0 || hour < 0 ||
-				min < 0 || sec < 0 || fsec < 0;
+				min < 0 || sec < 0 || usec < 0;
 				bool		has_positive = year > 0 || mon > 0 ||
 				mday > 0 || hour > 0 ||
-				min > 0 || sec > 0 || fsec > 0;
+				min > 0 || sec > 0 || usec > 0;
 				bool		has_year_month = year != 0 || mon != 0;
 				bool		has_day_time = mday != 0 || hour != 0 ||
-				min != 0 || sec != 0 || fsec != 0;
+				min != 0 || sec != 0 || usec != 0;
 				bool		has_day = mday != 0;
 				bool		sql_standard_value = !(has_negative && has_positive) &&
 				!(has_year_month && has_day_time);
@@ -4287,7 +4427,7 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
 					hour = -hour;
 					min = -min;
 					sec = -sec;
-					fsec = -fsec;
+					usec = -usec;
 				}
 
 				if (!has_negative && !has_positive)
@@ -4304,32 +4444,34 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
 					char		year_sign = (year < 0 || mon < 0) ? '-' : '+';
 					char		day_sign = (mday < 0) ? '-' : '+';
 					char		sec_sign = (hour < 0 || min < 0 ||
-											sec < 0 || fsec < 0) ? '-' : '+';
+											sec < 0 || usec < 0) ? '-' : '+';
 
-					sprintf(cp, "%c%d-%d %c%d %c%d:%02d:",
-							year_sign, abs(year), abs(mon),
-							day_sign, abs(mday),
-							sec_sign, abs(hour), abs(min));
+					sprintf(cp, "%c%lld-%lld %c%lld %c%lld:%02d:",
+							year_sign, (long long) Abs(year), (long long) Abs(mon),
+							day_sign, (long long) Abs(mday),
+							sec_sign, (long long) Abs(hour), abs(min));
 					cp += strlen(cp);
-					cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+					cp = AppendSeconds(cp, sec, usec, MAX_INTERVAL_PRECISION, true);
 					*cp = '\0';
 				}
 				else if (has_year_month)
 				{
-					sprintf(cp, "%d-%d", year, mon);
+					sprintf(cp, "%lld-%lld",
+							(long long) year, (long long) mon);
 				}
 				else if (has_day)
 				{
-					sprintf(cp, "%d %d:%02d:", mday, hour, min);
+					sprintf(cp, "%lld %lld:%02d:",
+							(long long) mday, (long long) hour, min);
 					cp += strlen(cp);
-					cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+					cp = AppendSeconds(cp, sec, usec, MAX_INTERVAL_PRECISION, true);
 					*cp = '\0';
 				}
 				else
 				{
-					sprintf(cp, "%d:%02d:", hour, min);
+					sprintf(cp, "%lld:%02d:", (long long) hour, min);
 					cp += strlen(cp);
-					cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+					cp = AppendSeconds(cp, sec, usec, MAX_INTERVAL_PRECISION, true);
 					*cp = '\0';
 				}
 			}
@@ -4339,7 +4481,7 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
 		case INTSTYLE_ISO_8601:
 			/* special-case zero to avoid printing nothing */
 			if (year == 0 && mon == 0 && mday == 0 &&
-				hour == 0 && min == 0 && sec == 0 && fsec == 0)
+				hour == 0 && min == 0 && sec == 0 && usec == 0)
 			{
 				sprintf(cp, "PT0S");
 				break;
@@ -4348,15 +4490,15 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
 			cp = AddISO8601IntPart(cp, year, 'Y');
 			cp = AddISO8601IntPart(cp, mon, 'M');
 			cp = AddISO8601IntPart(cp, mday, 'D');
-			if (hour != 0 || min != 0 || sec != 0 || fsec != 0)
+			if (hour != 0 || min != 0 || sec != 0 || usec != 0)
 				*cp++ = 'T';
 			cp = AddISO8601IntPart(cp, hour, 'H');
 			cp = AddISO8601IntPart(cp, min, 'M');
-			if (sec != 0 || fsec != 0)
+			if (sec != 0 || usec != 0)
 			{
-				if (sec < 0 || fsec < 0)
+				if (sec < 0 || usec < 0)
 					*cp++ = '-';
-				cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
+				cp = AppendSeconds(cp, sec, usec, MAX_INTERVAL_PRECISION, false);
 				*cp++ = 'S';
 				*cp++ = '\0';
 			}
@@ -4373,16 +4515,16 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
 			 */
 			cp = AddPostgresIntPart(cp, mon, "mon", &is_zero, &is_before);
 			cp = AddPostgresIntPart(cp, mday, "day", &is_zero, &is_before);
-			if (is_zero || hour != 0 || min != 0 || sec != 0 || fsec != 0)
+			if (is_zero || hour != 0 || min != 0 || sec != 0 || usec != 0)
 			{
-				bool		minus = (hour < 0 || min < 0 || sec < 0 || fsec < 0);
+				bool		minus = (hour < 0 || min < 0 || sec < 0 || usec < 0);
 
-				sprintf(cp, "%s%s%02d:%02d:",
+				sprintf(cp, "%s%s%02lld:%02d:",
 						is_zero ? "" : " ",
 						(minus ? "-" : (is_before ? "+" : "")),
-						abs(hour), abs(min));
+						(long long) Abs(hour), abs(min));
 				cp += strlen(cp);
-				cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+				cp = AppendSeconds(cp, sec, usec, MAX_INTERVAL_PRECISION, true);
 				*cp = '\0';
 			}
 			break;
@@ -4397,10 +4539,10 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
 			cp = AddVerboseIntPart(cp, mday, "day", &is_zero, &is_before);
 			cp = AddVerboseIntPart(cp, hour, "hour", &is_zero, &is_before);
 			cp = AddVerboseIntPart(cp, min, "min", &is_zero, &is_before);
-			if (sec != 0 || fsec != 0)
+			if (sec != 0 || usec != 0)
 			{
 				*cp++ = ' ';
-				if (sec < 0 || (sec == 0 && fsec < 0))
+				if (sec < 0 || (sec == 0 && usec < 0))
 				{
 					if (is_zero)
 						is_before = true;
@@ -4409,10 +4551,10 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
 				}
 				else if (is_before)
 					*cp++ = '-';
-				cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
+				cp = AppendSeconds(cp, sec, usec, MAX_INTERVAL_PRECISION, false);
 				/* We output "ago", not negatives, so use abs(). */
 				sprintf(cp, " sec%s",
-						(abs(sec) != 1 || fsec != 0) ? "s" : "");
+						(abs(sec) != 1 || usec != 0) ? "s" : "");
 				is_zero = false;
 			}
 			/* identically zero? then put in a unitless zero... */
@@ -4668,7 +4810,7 @@ pg_timezone_abbrevs(PG_FUNCTION_ARGS)
 	int			gmtoffset;
 	bool		is_dst;
 	unsigned char *p;
-	struct pg_tm tm;
+	struct pg_itm_in itm_in;
 	Interval   *resInterval;
 
 	/* stuff done only on the first call of the function */
@@ -4762,10 +4904,10 @@ pg_timezone_abbrevs(PG_FUNCTION_ARGS)
 	values[0] = CStringGetTextDatum(buffer);
 
 	/* Convert offset (in seconds) to an interval */
-	MemSet(&tm, 0, sizeof(struct pg_tm));
-	tm.tm_sec = gmtoffset;
+	MemSet(&itm_in, 0, sizeof(struct pg_itm_in));
+	itm_in.tm_usec = (int64) gmtoffset * USECS_PER_SEC;
 	resInterval = (Interval *) palloc(sizeof(Interval));
-	tm2interval(&tm, 0, resInterval);
+	itmin2interval(&itm_in, resInterval);
 	values[1] = IntervalPGetDatum(resInterval);
 
 	values[2] = BoolGetDatum(is_dst);
@@ -4798,7 +4940,7 @@ pg_timezone_names(PG_FUNCTION_ARGS)
 	fsec_t		fsec;
 	const char *tzn;
 	Interval   *resInterval;
-	struct pg_tm itm;
+	struct pg_itm_in itm_in;
 	MemoryContext oldcontext;
 
 	/* check to see if caller supports us returning a tuplestore */
@@ -4857,10 +4999,10 @@ pg_timezone_names(PG_FUNCTION_ARGS)
 		values[0] = CStringGetTextDatum(pg_get_timezone_name(tz));
 		values[1] = CStringGetTextDatum(tzn ? tzn : "");
 
-		MemSet(&itm, 0, sizeof(struct pg_tm));
-		itm.tm_sec = -tzoff;
+		MemSet(&itm_in, 0, sizeof(struct pg_itm_in));
+		itm_in.tm_usec = (int64) -tzoff * USECS_PER_SEC;
 		resInterval = (Interval *) palloc(sizeof(Interval));
-		tm2interval(&itm, 0, resInterval);
+		itmin2interval(&itm_in, resInterval);
 		values[2] = IntervalPGetDatum(resInterval);
 
 		values[3] = BoolGetDatum(tm.tm_isdst > 0);
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index d4c2e7b069..71d820f7ed 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -496,6 +496,10 @@ typedef struct
 typedef struct TmToChar
 {
 	struct pg_tm tm;			/* classic 'tm' struct */
+	/* Different date/time types have different requirements on the size of the
+	 * hour field. So we take in a separate int64 hour field.
+	 */
+	int64		tm_hour;		/* hours */
 	fsec_t		fsec;			/* fractional seconds */
 	const char *tzn;			/* timezone */
 } TmToChar;
@@ -2649,6 +2653,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 	FormatNode *n;
 	char	   *s;
 	struct pg_tm *tm = &in->tm;
+	int64 tm_hour = in->tm_hour;
 	int			i;
 
 	/* cache localized days and months */
@@ -2668,25 +2673,25 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 		{
 			case DCH_A_M:
 			case DCH_P_M:
-				strcpy(s, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
+				strcpy(s, (tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
 					   ? P_M_STR : A_M_STR);
 				s += strlen(s);
 				break;
 			case DCH_AM:
 			case DCH_PM:
-				strcpy(s, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
+				strcpy(s, (tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
 					   ? PM_STR : AM_STR);
 				s += strlen(s);
 				break;
 			case DCH_a_m:
 			case DCH_p_m:
-				strcpy(s, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
+				strcpy(s, (tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
 					   ? p_m_STR : a_m_STR);
 				s += strlen(s);
 				break;
 			case DCH_am:
 			case DCH_pm:
-				strcpy(s, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
+				strcpy(s, (tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
 					   ? pm_STR : am_STR);
 				s += strlen(s);
 				break;
@@ -2697,16 +2702,16 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 				 * display time as shown on a 12-hour clock, even for
 				 * intervals
 				 */
-				sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : (tm->tm_hour >= 0) ? 2 : 3,
-						tm->tm_hour % (HOURS_PER_DAY / 2) == 0 ? HOURS_PER_DAY / 2 :
-						tm->tm_hour % (HOURS_PER_DAY / 2));
+				sprintf(s, "%0*lld", S_FM(n->suffix) ? 0 : (tm_hour >= 0) ? 2 : 3,
+						tm_hour % (HOURS_PER_DAY / 2) == 0 ? (long long) HOURS_PER_DAY / 2 :
+						(long long) tm_hour % (HOURS_PER_DAY / 2));
 				if (S_THth(n->suffix))
 					str_numth(s, s, S_TH_TYPE(n->suffix));
 				s += strlen(s);
 				break;
 			case DCH_HH24:
-				sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : (tm->tm_hour >= 0) ? 2 : 3,
-						tm->tm_hour);
+				sprintf(s, "%0*lld", S_FM(n->suffix) ? 0 : (tm_hour >= 0) ? 2 : 3,
+						(long long) tm_hour);
 				if (S_THth(n->suffix))
 					str_numth(s, s, S_TH_TYPE(n->suffix));
 				s += strlen(s);
@@ -2754,7 +2759,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 				break;
 #undef DCH_to_char_fsec
 			case DCH_SSSS:
-				sprintf(s, "%d", tm->tm_hour * SECS_PER_HOUR +
+				sprintf(s, "%lld", (long long) tm_hour * SECS_PER_HOUR +
 						tm->tm_min * SECS_PER_MINUTE +
 						tm->tm_sec);
 				if (S_THth(n->suffix))
@@ -4100,6 +4105,7 @@ timestamp_to_char(PG_FUNCTION_ARGS)
 		ereport(ERROR,
 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 				 errmsg("timestamp out of range")));
+	tmtc.tm_hour = (int64) tm->tm_hour;
 
 	thisdate = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday);
 	tm->tm_wday = (thisdate + 1) % 7;
@@ -4132,6 +4138,7 @@ timestamptz_to_char(PG_FUNCTION_ARGS)
 		ereport(ERROR,
 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 				 errmsg("timestamp out of range")));
+	tmtc.tm_hour = (int64) tm->tm_hour;
 
 	thisdate = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday);
 	tm->tm_wday = (thisdate + 1) % 7;
@@ -4156,6 +4163,8 @@ interval_to_char(PG_FUNCTION_ARGS)
 			   *res;
 	TmToChar	tmtc;
 	struct pg_tm *tm;
+	struct pg_itm tt,
+			*itm = &tt;
 
 	if (VARSIZE_ANY_EXHDR(fmt) <= 0)
 		PG_RETURN_NULL();
@@ -4163,8 +4172,16 @@ interval_to_char(PG_FUNCTION_ARGS)
 	ZERO_tmtc(&tmtc);
 	tm = tmtcTm(&tmtc);
 
-	if (interval2tm(*it, tm, &tmtcFsec(&tmtc)) != 0)
+	if (interval2itm(*it, itm))
 		PG_RETURN_NULL();
+	tmtc.fsec = itm->tm_usec;
+	tmtc.tm_hour = itm->tm_hour;
+	tm->tm_sec = itm->tm_sec;
+	tm->tm_min = itm->tm_min;
+	tm->tm_mday = itm->tm_mday;
+	tm->tm_mon = itm->tm_mon;
+	tm->tm_year = itm->tm_year;
+	tm->tm_yday = itm->tm_yday;
 
 	/* wday is meaningless, yday approximates the total span in days */
 	tm->tm_yday = (tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon) * DAYS_PER_MONTH + tm->tm_mday;
diff --git a/src/backend/utils/adt/numutils.c b/src/backend/utils/adt/numutils.c
index cc3f95d399..8bdcdee328 100644
--- a/src/backend/utils/adt/numutils.c
+++ b/src/backend/utils/adt/numutils.c
@@ -602,3 +602,15 @@ pg_ultostr(char *str, uint32 value)
 
 	return str + len;
 }
+
+/*
+ * pg_ulltostr
+ *		See above
+ */
+char *
+pg_ulltostr(char *str, uint64 value)
+{
+	int			len = pg_ulltoa_n(value, str);
+
+	return str + len;
+}
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index ae36ff3328..77cc730b9d 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -888,9 +888,8 @@ interval_in(PG_FUNCTION_ARGS)
 #endif
 	int32		typmod = PG_GETARG_INT32(2);
 	Interval   *result;
-	fsec_t		fsec;
-	struct pg_tm tt,
-			   *tm = &tt;
+	struct pg_itm_in tt,
+			   *itm_in = &tt;
 	int			dtype;
 	int			nf;
 	int			range;
@@ -899,13 +898,10 @@ interval_in(PG_FUNCTION_ARGS)
 	int			ftype[MAXDATEFIELDS];
 	char		workbuf[256];
 
-	tm->tm_year = 0;
-	tm->tm_mon = 0;
-	tm->tm_mday = 0;
-	tm->tm_hour = 0;
-	tm->tm_min = 0;
-	tm->tm_sec = 0;
-	fsec = 0;
+	itm_in->tm_year = 0;
+	itm_in->tm_mon = 0;
+	itm_in->tm_mday = 0;
+	itm_in->tm_usec = 0;
 
 	if (typmod >= 0)
 		range = INTERVAL_RANGE(typmod);
@@ -916,12 +912,12 @@ interval_in(PG_FUNCTION_ARGS)
 						  ftype, MAXDATEFIELDS, &nf);
 	if (dterr == 0)
 		dterr = DecodeInterval(field, ftype, nf, range,
-							   &dtype, tm, &fsec);
+							   &dtype, itm_in);
 
 	/* if those functions think it's a bad format, try ISO8601 style */
 	if (dterr == DTERR_BAD_FORMAT)
 		dterr = DecodeISO8601Interval(str,
-									  &dtype, tm, &fsec);
+									  &dtype, itm_in);
 
 	if (dterr != 0)
 	{
@@ -935,7 +931,7 @@ interval_in(PG_FUNCTION_ARGS)
 	switch (dtype)
 	{
 		case DTK_DELTA:
-			if (tm2interval(tm, fsec, result) != 0)
+			if (itmin2interval(itm_in, result) != 0)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 						 errmsg("interval out of range")));
@@ -959,15 +955,14 @@ interval_out(PG_FUNCTION_ARGS)
 {
 	Interval   *span = PG_GETARG_INTERVAL_P(0);
 	char	   *result;
-	struct pg_tm tt,
-			   *tm = &tt;
-	fsec_t		fsec;
+	struct pg_itm tt,
+			   *itm = &tt;
 	char		buf[MAXDATELEN + 1];
 
-	if (interval2tm(*span, tm, &fsec) != 0)
+	if (interval2itm(*span, itm) != 0)
 		elog(ERROR, "could not convert interval to tm");
 
-	EncodeInterval(tm, fsec, IntervalStyle, buf);
+	EncodeInterval(itm, IntervalStyle, buf);
 
 	result = pstrdup(buf);
 	PG_RETURN_CSTRING(result);
@@ -1963,45 +1958,59 @@ tm2timestamp(struct pg_tm *tm, fsec_t fsec, int *tzp, Timestamp *result)
  * Convert an interval data type to a tm structure.
  */
 int
-interval2tm(Interval span, struct pg_tm *tm, fsec_t *fsec)
+interval2itm(Interval span, struct pg_itm *itm)
 {
 	TimeOffset	time;
 	TimeOffset	tfrac;
 
-	tm->tm_year = span.month / MONTHS_PER_YEAR;
-	tm->tm_mon = span.month % MONTHS_PER_YEAR;
-	tm->tm_mday = span.day;
+	itm->tm_year = span.month / MONTHS_PER_YEAR;
+	itm->tm_mon = span.month % MONTHS_PER_YEAR;
+	itm->tm_mday = span.day;
 	time = span.time;
 
 	tfrac = time / USECS_PER_HOUR;
 	time -= tfrac * USECS_PER_HOUR;
-	tm->tm_hour = tfrac;
-	if (!SAMESIGN(tm->tm_hour, tfrac))
+	itm->tm_hour = tfrac;
+	if (!SAMESIGN(itm->tm_hour, tfrac))
 		ereport(ERROR,
 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 				 errmsg("interval out of range")));
 	tfrac = time / USECS_PER_MINUTE;
 	time -= tfrac * USECS_PER_MINUTE;
-	tm->tm_min = tfrac;
+	itm->tm_min = tfrac;
 	tfrac = time / USECS_PER_SEC;
-	*fsec = time - (tfrac * USECS_PER_SEC);
-	tm->tm_sec = tfrac;
+	itm->tm_usec = time - (tfrac * USECS_PER_SEC);
+	itm->tm_sec = tfrac;
+
+	return 0;
+}
+
+int
+itm2interval(struct pg_itm *itm, Interval *span)
+{
+	int64		total_months = (int64) itm->tm_year * MONTHS_PER_YEAR + itm->tm_mon;
+
+	if (total_months > INT_MAX || total_months < INT_MIN)
+		return -1;
+	span->month = (int) total_months;
+	span->day = itm->tm_mday;
+	span->time = (((((itm->tm_hour * INT64CONST(60)) +
+					 itm->tm_min) * INT64CONST(60)) +
+				   itm->tm_sec) * USECS_PER_SEC) + itm->tm_usec;
 
 	return 0;
 }
 
 int
-tm2interval(struct pg_tm *tm, fsec_t fsec, Interval *span)
+itmin2interval(struct pg_itm_in *itm_in, Interval *span)
 {
-	double		total_months = (double) tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon;
+	double		total_months = (double) itm_in->tm_year * MONTHS_PER_YEAR + itm_in->tm_mon;
 
 	if (total_months > INT_MAX || total_months < INT_MIN)
 		return -1;
 	span->month = total_months;
-	span->day = tm->tm_mday;
-	span->time = (((((tm->tm_hour * INT64CONST(60)) +
-					 tm->tm_min) * INT64CONST(60)) +
-				   tm->tm_sec) * USECS_PER_SEC) + fsec;
+	span->day = itm_in->tm_mday;
+	span->time = itm_in->tm_usec;
 
 	return 0;
 }
@@ -3601,11 +3610,10 @@ timestamp_age(PG_FUNCTION_ARGS)
 	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
 	Timestamp	dt2 = PG_GETARG_TIMESTAMP(1);
 	Interval   *result;
-	fsec_t		fsec,
-				fsec1,
+	fsec_t		fsec1,
 				fsec2;
-	struct pg_tm tt,
-			   *tm = &tt;
+	struct pg_itm tt,
+			   *itm = &tt;
 	struct pg_tm tt1,
 			   *tm1 = &tt1;
 	struct pg_tm tt2,
@@ -3617,84 +3625,84 @@ timestamp_age(PG_FUNCTION_ARGS)
 		timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
 	{
 		/* form the symbolic difference */
-		fsec = fsec1 - fsec2;
-		tm->tm_sec = tm1->tm_sec - tm2->tm_sec;
-		tm->tm_min = tm1->tm_min - tm2->tm_min;
-		tm->tm_hour = tm1->tm_hour - tm2->tm_hour;
-		tm->tm_mday = tm1->tm_mday - tm2->tm_mday;
-		tm->tm_mon = tm1->tm_mon - tm2->tm_mon;
-		tm->tm_year = tm1->tm_year - tm2->tm_year;
+		itm->tm_usec = fsec1 - fsec2;
+		itm->tm_sec = tm1->tm_sec - tm2->tm_sec;
+		itm->tm_min = tm1->tm_min - tm2->tm_min;
+		itm->tm_hour = tm1->tm_hour - tm2->tm_hour;
+		itm->tm_mday = tm1->tm_mday - tm2->tm_mday;
+		itm->tm_mon = tm1->tm_mon - tm2->tm_mon;
+		itm->tm_year = tm1->tm_year - tm2->tm_year;
 
 		/* flip sign if necessary... */
 		if (dt1 < dt2)
 		{
-			fsec = -fsec;
-			tm->tm_sec = -tm->tm_sec;
-			tm->tm_min = -tm->tm_min;
-			tm->tm_hour = -tm->tm_hour;
-			tm->tm_mday = -tm->tm_mday;
-			tm->tm_mon = -tm->tm_mon;
-			tm->tm_year = -tm->tm_year;
+			itm->tm_usec = -itm->tm_usec;
+			itm->tm_sec = -itm->tm_sec;
+			itm->tm_min = -itm->tm_min;
+			itm->tm_hour = -itm->tm_hour;
+			itm->tm_mday = -itm->tm_mday;
+			itm->tm_mon = -itm->tm_mon;
+			itm->tm_year = -itm->tm_year;
 		}
 
 		/* propagate any negative fields into the next higher field */
-		while (fsec < 0)
+		while (itm->tm_usec < 0)
 		{
-			fsec += USECS_PER_SEC;
-			tm->tm_sec--;
+			itm->tm_usec += USECS_PER_SEC;
+			itm->tm_sec--;
 		}
 
-		while (tm->tm_sec < 0)
+		while (itm->tm_sec < 0)
 		{
-			tm->tm_sec += SECS_PER_MINUTE;
-			tm->tm_min--;
+			itm->tm_sec += SECS_PER_MINUTE;
+			itm->tm_min--;
 		}
 
-		while (tm->tm_min < 0)
+		while (itm->tm_min < 0)
 		{
-			tm->tm_min += MINS_PER_HOUR;
-			tm->tm_hour--;
+			itm->tm_min += MINS_PER_HOUR;
+			itm->tm_hour--;
 		}
 
-		while (tm->tm_hour < 0)
+		while (itm->tm_hour < 0)
 		{
-			tm->tm_hour += HOURS_PER_DAY;
-			tm->tm_mday--;
+			itm->tm_hour += HOURS_PER_DAY;
+			itm->tm_mday--;
 		}
 
-		while (tm->tm_mday < 0)
+		while (itm->tm_mday < 0)
 		{
 			if (dt1 < dt2)
 			{
-				tm->tm_mday += day_tab[isleap(tm1->tm_year)][tm1->tm_mon - 1];
-				tm->tm_mon--;
+				itm->tm_mday += day_tab[isleap(tm1->tm_year)][tm1->tm_mon - 1];
+				itm->tm_mon--;
 			}
 			else
 			{
-				tm->tm_mday += day_tab[isleap(tm2->tm_year)][tm2->tm_mon - 1];
-				tm->tm_mon--;
+				itm->tm_mday += day_tab[isleap(tm2->tm_year)][tm2->tm_mon - 1];
+				itm->tm_mon--;
 			}
 		}
 
-		while (tm->tm_mon < 0)
+		while (itm->tm_mon < 0)
 		{
-			tm->tm_mon += MONTHS_PER_YEAR;
-			tm->tm_year--;
+			itm->tm_mon += MONTHS_PER_YEAR;
+			itm->tm_year--;
 		}
 
 		/* recover sign if necessary... */
 		if (dt1 < dt2)
 		{
-			fsec = -fsec;
-			tm->tm_sec = -tm->tm_sec;
-			tm->tm_min = -tm->tm_min;
-			tm->tm_hour = -tm->tm_hour;
-			tm->tm_mday = -tm->tm_mday;
-			tm->tm_mon = -tm->tm_mon;
-			tm->tm_year = -tm->tm_year;
+			itm->tm_usec = -itm->tm_usec;
+			itm->tm_sec = -itm->tm_sec;
+			itm->tm_min = -itm->tm_min;
+			itm->tm_hour = -itm->tm_hour;
+			itm->tm_mday = -itm->tm_mday;
+			itm->tm_mon = -itm->tm_mon;
+			itm->tm_year = -itm->tm_year;
 		}
 
-		if (tm2interval(tm, fsec, result) != 0)
+		if (itm2interval(itm, result) != 0)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 					 errmsg("interval out of range")));
@@ -3720,11 +3728,10 @@ timestamptz_age(PG_FUNCTION_ARGS)
 	TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0);
 	TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1);
 	Interval   *result;
-	fsec_t		fsec,
-				fsec1,
+	fsec_t		fsec1,
 				fsec2;
-	struct pg_tm tt,
-			   *tm = &tt;
+	struct pg_itm tt,
+			   *itm = &tt;
 	struct pg_tm tt1,
 			   *tm1 = &tt1;
 	struct pg_tm tt2,
@@ -3738,69 +3745,69 @@ timestamptz_age(PG_FUNCTION_ARGS)
 		timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
 	{
 		/* form the symbolic difference */
-		fsec = fsec1 - fsec2;
-		tm->tm_sec = tm1->tm_sec - tm2->tm_sec;
-		tm->tm_min = tm1->tm_min - tm2->tm_min;
-		tm->tm_hour = tm1->tm_hour - tm2->tm_hour;
-		tm->tm_mday = tm1->tm_mday - tm2->tm_mday;
-		tm->tm_mon = tm1->tm_mon - tm2->tm_mon;
-		tm->tm_year = tm1->tm_year - tm2->tm_year;
+		itm->tm_usec = fsec1 - fsec2;
+		itm->tm_sec = tm1->tm_sec - tm2->tm_sec;
+		itm->tm_min = tm1->tm_min - tm2->tm_min;
+		itm->tm_hour = tm1->tm_hour - tm2->tm_hour;
+		itm->tm_mday = tm1->tm_mday - tm2->tm_mday;
+		itm->tm_mon = tm1->tm_mon - tm2->tm_mon;
+		itm->tm_year = tm1->tm_year - tm2->tm_year;
 
 		/* flip sign if necessary... */
 		if (dt1 < dt2)
 		{
-			fsec = -fsec;
-			tm->tm_sec = -tm->tm_sec;
-			tm->tm_min = -tm->tm_min;
-			tm->tm_hour = -tm->tm_hour;
-			tm->tm_mday = -tm->tm_mday;
-			tm->tm_mon = -tm->tm_mon;
-			tm->tm_year = -tm->tm_year;
+			itm->tm_usec = -itm->tm_usec;
+			itm->tm_sec = -itm->tm_sec;
+			itm->tm_min = -itm->tm_min;
+			itm->tm_hour = -itm->tm_hour;
+			itm->tm_mday = -itm->tm_mday;
+			itm->tm_mon = -itm->tm_mon;
+			itm->tm_year = -itm->tm_year;
 		}
 
 		/* propagate any negative fields into the next higher field */
-		while (fsec < 0)
+		while (itm->tm_usec < 0)
 		{
-			fsec += USECS_PER_SEC;
-			tm->tm_sec--;
+			itm->tm_usec += USECS_PER_SEC;
+			itm->tm_sec--;
 		}
 
-		while (tm->tm_sec < 0)
+		while (itm->tm_sec < 0)
 		{
-			tm->tm_sec += SECS_PER_MINUTE;
-			tm->tm_min--;
+			itm->tm_sec += SECS_PER_MINUTE;
+			itm->tm_min--;
 		}
 
-		while (tm->tm_min < 0)
+		while (itm->tm_min < 0)
 		{
-			tm->tm_min += MINS_PER_HOUR;
-			tm->tm_hour--;
+			itm->tm_min += MINS_PER_HOUR;
+			itm->tm_hour--;
 		}
 
-		while (tm->tm_hour < 0)
+		while (itm->tm_hour < 0)
 		{
-			tm->tm_hour += HOURS_PER_DAY;
-			tm->tm_mday--;
+			itm->tm_hour += HOURS_PER_DAY;
+			itm->tm_mday--;
 		}
 
-		while (tm->tm_mday < 0)
+		while (itm->tm_mday < 0)
 		{
 			if (dt1 < dt2)
 			{
-				tm->tm_mday += day_tab[isleap(tm1->tm_year)][tm1->tm_mon - 1];
-				tm->tm_mon--;
+				itm->tm_mday += day_tab[isleap(tm1->tm_year)][tm1->tm_mon - 1];
+				itm->tm_mon--;
 			}
 			else
 			{
-				tm->tm_mday += day_tab[isleap(tm2->tm_year)][tm2->tm_mon - 1];
-				tm->tm_mon--;
+				itm->tm_mday += day_tab[isleap(tm2->tm_year)][tm2->tm_mon - 1];
+				itm->tm_mon--;
 			}
 		}
 
-		while (tm->tm_mon < 0)
+		while (itm->tm_mon < 0)
 		{
-			tm->tm_mon += MONTHS_PER_YEAR;
-			tm->tm_year--;
+			itm->tm_mon += MONTHS_PER_YEAR;
+			itm->tm_year--;
 		}
 
 		/*
@@ -3810,16 +3817,16 @@ timestamptz_age(PG_FUNCTION_ARGS)
 		/* recover sign if necessary... */
 		if (dt1 < dt2)
 		{
-			fsec = -fsec;
-			tm->tm_sec = -tm->tm_sec;
-			tm->tm_min = -tm->tm_min;
-			tm->tm_hour = -tm->tm_hour;
-			tm->tm_mday = -tm->tm_mday;
-			tm->tm_mon = -tm->tm_mon;
-			tm->tm_year = -tm->tm_year;
+			itm->tm_usec = -itm->tm_usec;
+			itm->tm_sec = -itm->tm_sec;
+			itm->tm_min = -itm->tm_min;
+			itm->tm_hour = -itm->tm_hour;
+			itm->tm_mday = -itm->tm_mday;
+			itm->tm_mon = -itm->tm_mon;
+			itm->tm_year = -itm->tm_year;
 		}
 
-		if (tm2interval(tm, fsec, result) != 0)
+		if (itm2interval(itm, result) != 0)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 					 errmsg("interval out of range")));
@@ -4306,9 +4313,8 @@ interval_trunc(PG_FUNCTION_ARGS)
 	int			type,
 				val;
 	char	   *lowunits;
-	fsec_t		fsec;
-	struct pg_tm tt,
-			   *tm = &tt;
+	struct pg_itm tt,
+			   *itm = &tt;
 
 	result = (Interval *) palloc(sizeof(Interval));
 
@@ -4320,45 +4326,45 @@ interval_trunc(PG_FUNCTION_ARGS)
 
 	if (type == UNITS)
 	{
-		if (interval2tm(*interval, tm, &fsec) == 0)
+		if (interval2itm(*interval, itm) == 0)
 		{
 			switch (val)
 			{
 				case DTK_MILLENNIUM:
 					/* caution: C division may have negative remainder */
-					tm->tm_year = (tm->tm_year / 1000) * 1000;
+					itm->tm_year = (itm->tm_year / 1000) * 1000;
 					/* FALL THRU */
 				case DTK_CENTURY:
 					/* caution: C division may have negative remainder */
-					tm->tm_year = (tm->tm_year / 100) * 100;
+					itm->tm_year = (itm->tm_year / 100) * 100;
 					/* FALL THRU */
 				case DTK_DECADE:
 					/* caution: C division may have negative remainder */
-					tm->tm_year = (tm->tm_year / 10) * 10;
+					itm->tm_year = (itm->tm_year / 10) * 10;
 					/* FALL THRU */
 				case DTK_YEAR:
-					tm->tm_mon = 0;
+					itm->tm_mon = 0;
 					/* FALL THRU */
 				case DTK_QUARTER:
-					tm->tm_mon = 3 * (tm->tm_mon / 3);
+					itm->tm_mon = 3 * (itm->tm_mon / 3);
 					/* FALL THRU */
 				case DTK_MONTH:
-					tm->tm_mday = 0;
+					itm->tm_mday = 0;
 					/* FALL THRU */
 				case DTK_DAY:
-					tm->tm_hour = 0;
+					itm->tm_hour = 0;
 					/* FALL THRU */
 				case DTK_HOUR:
-					tm->tm_min = 0;
+					itm->tm_min = 0;
 					/* FALL THRU */
 				case DTK_MINUTE:
-					tm->tm_sec = 0;
+					itm->tm_sec = 0;
 					/* FALL THRU */
 				case DTK_SECOND:
-					fsec = 0;
+					itm->tm_usec = 0;
 					break;
 				case DTK_MILLISEC:
-					fsec = (fsec / 1000) * 1000;
+					itm->tm_usec = (itm->tm_usec / 1000) * 1000;
 					break;
 				case DTK_MICROSEC:
 					break;
@@ -4371,7 +4377,7 @@ interval_trunc(PG_FUNCTION_ARGS)
 							 (val == DTK_WEEK) ? errdetail("Months usually have fractional weeks.") : 0));
 			}
 
-			if (tm2interval(tm, fsec, result) != 0)
+			if (itm2interval(itm, result) != 0)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 						 errmsg("interval out of range")));
@@ -5189,9 +5195,8 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
 	int			type,
 				val;
 	char	   *lowunits;
-	fsec_t		fsec;
-	struct pg_tm tt,
-			   *tm = &tt;
+	struct pg_itm tt,
+			   *itm = &tt;
 
 	lowunits = downcase_truncate_identifier(VARDATA_ANY(units),
 											VARSIZE_ANY_EXHDR(units),
@@ -5203,12 +5208,12 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
 
 	if (type == UNITS)
 	{
-		if (interval2tm(*interval, tm, &fsec) == 0)
+		if (interval2itm(*interval, itm) == 0)
 		{
 			switch (val)
 			{
 				case DTK_MICROSEC:
-					intresult = tm->tm_sec * INT64CONST(1000000) + fsec;
+					intresult = itm->tm_sec * INT64CONST(1000000) + itm->tm_usec;
 					break;
 
 				case DTK_MILLISEC:
@@ -5217,9 +5222,9 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
 						 * tm->tm_sec * 1000 + fsec / 1000
 						 * = (tm->tm_sec * 1'000'000 + fsec) / 1000
 						 */
-						PG_RETURN_NUMERIC(int64_div_fast_to_numeric(tm->tm_sec * INT64CONST(1000000) + fsec, 3));
+						PG_RETURN_NUMERIC(int64_div_fast_to_numeric(itm->tm_sec * INT64CONST(1000000) + + itm->tm_usec, 3));
 					else
-						PG_RETURN_FLOAT8(tm->tm_sec * 1000.0 + fsec / 1000.0);
+						PG_RETURN_FLOAT8(itm->tm_sec * 1000.0 + itm->tm_usec / 1000.0);
 					break;
 
 				case DTK_SECOND:
@@ -5228,48 +5233,48 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
 						 * tm->tm_sec + fsec / 1'000'000
 						 * = (tm->tm_sec * 1'000'000 + fsec) / 1'000'000
 						 */
-						PG_RETURN_NUMERIC(int64_div_fast_to_numeric(tm->tm_sec * INT64CONST(1000000) + fsec, 6));
+						PG_RETURN_NUMERIC(int64_div_fast_to_numeric(itm->tm_sec * INT64CONST(1000000) + + itm->tm_usec, 6));
 					else
-						PG_RETURN_FLOAT8(tm->tm_sec + fsec / 1000000.0);
+						PG_RETURN_FLOAT8(itm->tm_sec + itm->tm_usec / 1000000.0);
 					break;
 
 				case DTK_MINUTE:
-					intresult = tm->tm_min;
+					intresult = itm->tm_min;
 					break;
 
 				case DTK_HOUR:
-					intresult = tm->tm_hour;
+					intresult = itm->tm_hour;
 					break;
 
 				case DTK_DAY:
-					intresult = tm->tm_mday;
+					intresult = itm->tm_mday;
 					break;
 
 				case DTK_MONTH:
-					intresult = tm->tm_mon;
+					intresult = itm->tm_mon;
 					break;
 
 				case DTK_QUARTER:
-					intresult = (tm->tm_mon / 3) + 1;
+					intresult = (itm->tm_mon / 3) + 1;
 					break;
 
 				case DTK_YEAR:
-					intresult = tm->tm_year;
+					intresult = itm->tm_year;
 					break;
 
 				case DTK_DECADE:
 					/* caution: C division may have negative remainder */
-					intresult = tm->tm_year / 10;
+					intresult = itm->tm_year / 10;
 					break;
 
 				case DTK_CENTURY:
 					/* caution: C division may have negative remainder */
-					intresult = tm->tm_year / 100;
+					intresult = itm->tm_year / 100;
 					break;
 
 				case DTK_MILLENNIUM:
 					/* caution: C division may have negative remainder */
-					intresult = tm->tm_year / 1000;
+					intresult = itm->tm_year / 1000;
 					break;
 
 				default:
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 5fa38d20d8..ba918a2b22 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -64,9 +64,11 @@ typedef struct
 /*
  * Assorted constants for datetime-related calculations
  */
-
-#define DAYS_PER_YEAR	365.25	/* assumes leap year every four years */
-#define MONTHS_PER_YEAR 12
+#define YEARS_PER_MILLENNIUM	1000
+#define YEARS_PER_CENTURY		100
+#define YEARS_PER_DECADE		10
+#define DAYS_PER_YEAR			365.25	/* assumes leap year every four years */
+#define MONTHS_PER_YEAR			12
 /*
  *	DAYS_PER_MONTH is very imprecise.  The more accurate value is
  *	365.2425/12 = 30.436875, or '30 days 10:29:06'.  Right now we only
@@ -92,6 +94,7 @@ typedef struct
 #define USECS_PER_HOUR	INT64CONST(3600000000)
 #define USECS_PER_MINUTE INT64CONST(60000000)
 #define USECS_PER_SEC	INT64CONST(1000000)
+#define USECS_PER_MSEC	INT64CONST(1000)
 
 /*
  * We allow numeric timezone offsets up to 15:59:59 either way from Greenwich.
diff --git a/src/include/pgtime.h b/src/include/pgtime.h
index 2977b13aab..708d1e0cc9 100644
--- a/src/include/pgtime.h
+++ b/src/include/pgtime.h
@@ -44,6 +44,31 @@ struct pg_tm
 	const char *tm_zone;
 };
 
+/* data structure to help decode intervals */
+struct pg_itm_in
+{
+	int64		tm_usec;
+	int			tm_mday;
+	int			tm_mon;			/* see above */
+	int			tm_year;		/* see above */
+};
+
+/* data structure to help encode and manipulate intervals */
+struct pg_itm
+{
+	/* time units smaller than hours only have values less than 1 hour and can
+	 * fit into an int
+	 */
+	int			tm_usec;
+	int			tm_sec;
+	int			tm_min;
+	int64		tm_hour;
+	int			tm_mday;
+	int			tm_mon;			/* see above */
+	int			tm_year;		/* see above */
+	int			tm_yday;
+};
+
 typedef struct pg_tz pg_tz;
 typedef struct pg_tzenum pg_tzenum;
 
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 666e545496..bf3e2a9336 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -53,6 +53,7 @@ extern int	pg_ltoa(int32 l, char *a);
 extern int	pg_lltoa(int64 ll, char *a);
 extern char *pg_ultostr_zeropad(char *str, uint32 value, int32 minwidth);
 extern char *pg_ultostr(char *str, uint32 value);
+extern char *pg_ulltostr(char *str, uint64 value);
 
 /* oid.c */
 extern oidvector *buildoidvector(const Oid *oids, int n);
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index 0d158f3e4b..d4d699c8f7 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -300,9 +300,9 @@ extern int	DecodeTimeOnly(char **field, int *ftype,
 						   int nf, int *dtype,
 						   struct pg_tm *tm, fsec_t *fsec, int *tzp);
 extern int	DecodeInterval(char **field, int *ftype, int nf, int range,
-						   int *dtype, struct pg_tm *tm, fsec_t *fsec);
-extern int	DecodeISO8601Interval(char *str,
-								  int *dtype, struct pg_tm *tm, fsec_t *fsec);
+						   int *dtype, struct pg_itm_in *tm);
+extern int	DecodeISO8601Interval(char *str, int *dtype,
+								  struct pg_itm_in *itm_in);
 
 extern void DateTimeParseError(int dterr, const char *str,
 							   const char *datatype) pg_attribute_noreturn();
@@ -315,7 +315,7 @@ extern int	DetermineTimeZoneAbbrevOffsetTS(TimestampTz ts, const char *abbr,
 extern void EncodeDateOnly(struct pg_tm *tm, int style, char *str);
 extern void EncodeTimeOnly(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, int style, char *str);
 extern void EncodeDateTime(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, const char *tzn, int style, char *str);
-extern void EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str);
+extern void EncodeInterval(struct pg_itm *itm, int style, char *str);
 extern void EncodeSpecialTimestamp(Timestamp dt, char *str);
 
 extern int	ValidateDate(int fmask, bool isjulian, bool is2digits, bool bc,
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index c1a74f8e2b..eea429dc5f 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -88,8 +88,9 @@ extern int	timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm,
 						 fsec_t *fsec, const char **tzn, pg_tz *attimezone);
 extern void dt2time(Timestamp dt, int *hour, int *min, int *sec, fsec_t *fsec);
 
-extern int	interval2tm(Interval span, struct pg_tm *tm, fsec_t *fsec);
-extern int	tm2interval(struct pg_tm *tm, fsec_t fsec, Interval *span);
+extern int	interval2itm(Interval span, struct pg_itm *itm);
+extern int	itm2interval(struct pg_itm *itm, Interval *span);
+extern int	itmin2interval(struct pg_itm_in *itm_in, Interval *span);
 
 extern Timestamp SetEpochTimestamp(void);
 extern void GetEpochTime(struct pg_tm *tm);
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 146f7c55d0..00ffe0e2be 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -1079,3 +1079,614 @@ SELECT extract(epoch from interval '1000000000 days');
  86400000000000.000000
 (1 row)
 
+-- test time fields using entire 64 bit microseconds range
+SELECT INTERVAL '2562047788.01521550194 hours';
+             interval              
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+SELECT INTERVAL '-2562047788.01521550222 hours';
+               interval                
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+SELECT INTERVAL '153722867280.912930117 minutes';
+             interval              
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+SELECT INTERVAL '-153722867280.912930133 minutes';
+               interval                
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+SELECT INTERVAL '9223372036854.775807 seconds';
+             interval              
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+SELECT INTERVAL '-9223372036854.775808 seconds';
+               interval                
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+SELECT INTERVAL '9223372036854775.807 milliseconds';
+             interval              
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+SELECT INTERVAL '-9223372036854775.808 milliseconds';
+               interval                
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+SELECT INTERVAL '9223372036854775807 microseconds';
+             interval              
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+SELECT INTERVAL '-9223372036854775808 microseconds';
+               interval                
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+SELECT INTERVAL 'PT2562047788H54.775807S';
+             interval              
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+SELECT INTERVAL 'PT-2562047788H-54.775808S';
+               interval                
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+SELECT INTERVAL 'PT2562047788:00:54.775807';
+             interval              
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+SELECT INTERVAL 'PT2562047788.0152155019444';
+             interval              
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+SELECT INTERVAL 'PT-2562047788.0152155022222';
+               interval                
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+-- overflow each date/time field
+SELECT INTERVAL '2147483648 years';
+ERROR:  interval field value out of range: "2147483648 years"
+LINE 1: SELECT INTERVAL '2147483648 years';
+                        ^
+SELECT INTERVAL '-2147483649 years';
+ERROR:  interval field value out of range: "-2147483649 years"
+LINE 1: SELECT INTERVAL '-2147483649 years';
+                        ^
+SELECT INTERVAL '2147483648 months';
+ERROR:  interval field value out of range: "2147483648 months"
+LINE 1: SELECT INTERVAL '2147483648 months';
+                        ^
+SELECT INTERVAL '-2147483649 months';
+ERROR:  interval field value out of range: "-2147483649 months"
+LINE 1: SELECT INTERVAL '-2147483649 months';
+                        ^
+SELECT INTERVAL '2147483648 days';
+ERROR:  interval field value out of range: "2147483648 days"
+LINE 1: SELECT INTERVAL '2147483648 days';
+                        ^
+SELECT INTERVAL '-2147483649 days';
+ERROR:  interval field value out of range: "-2147483649 days"
+LINE 1: SELECT INTERVAL '-2147483649 days';
+                        ^
+SELECT INTERVAL '2562047789 hours';
+ERROR:  interval field value out of range: "2562047789 hours"
+LINE 1: SELECT INTERVAL '2562047789 hours';
+                        ^
+SELECT INTERVAL '-2562047789 hours';
+ERROR:  interval field value out of range: "-2562047789 hours"
+LINE 1: SELECT INTERVAL '-2562047789 hours';
+                        ^
+SELECT INTERVAL '153722867281 minutes';
+ERROR:  interval field value out of range: "153722867281 minutes"
+LINE 1: SELECT INTERVAL '153722867281 minutes';
+                        ^
+SELECT INTERVAL '-153722867281 minutes';
+ERROR:  interval field value out of range: "-153722867281 minutes"
+LINE 1: SELECT INTERVAL '-153722867281 minutes';
+                        ^
+SELECT INTERVAL '9223372036855 seconds';
+ERROR:  interval field value out of range: "9223372036855 seconds"
+LINE 1: SELECT INTERVAL '9223372036855 seconds';
+                        ^
+SELECT INTERVAL '-9223372036855 seconds';
+ERROR:  interval field value out of range: "-9223372036855 seconds"
+LINE 1: SELECT INTERVAL '-9223372036855 seconds';
+                        ^
+SELECT INTERVAL '9223372036854777 millisecond';
+ERROR:  interval field value out of range: "9223372036854777 millisecond"
+LINE 1: SELECT INTERVAL '9223372036854777 millisecond';
+                        ^
+SELECT INTERVAL '-9223372036854777 millisecond';
+ERROR:  interval field value out of range: "-9223372036854777 millisecond"
+LINE 1: SELECT INTERVAL '-9223372036854777 millisecond';
+                        ^
+SELECT INTERVAL '9223372036854775808 microsecond';
+ERROR:  interval field value out of range: "9223372036854775808 microsecond"
+LINE 1: SELECT INTERVAL '9223372036854775808 microsecond';
+                        ^
+SELECT INTERVAL '-9223372036854775809 microsecond';
+ERROR:  interval field value out of range: "-9223372036854775809 microsecond"
+LINE 1: SELECT INTERVAL '-9223372036854775809 microsecond';
+                        ^
+SELECT INTERVAL 'P2147483648';
+ERROR:  interval field value out of range: "P2147483648"
+LINE 1: SELECT INTERVAL 'P2147483648';
+                        ^
+SELECT INTERVAL 'P-2147483649';
+ERROR:  interval field value out of range: "P-2147483649"
+LINE 1: SELECT INTERVAL 'P-2147483649';
+                        ^
+SELECT INTERVAL 'P1-2147483647-2147483647';
+ERROR:  interval out of range
+LINE 1: SELECT INTERVAL 'P1-2147483647-2147483647';
+                        ^
+SELECT INTERVAL 'PT2562047789';
+ERROR:  interval field value out of range: "PT2562047789"
+LINE 1: SELECT INTERVAL 'PT2562047789';
+                        ^
+SELECT INTERVAL 'PT-2562047789';
+ERROR:  interval field value out of range: "PT-2562047789"
+LINE 1: SELECT INTERVAL 'PT-2562047789';
+                        ^
+-- overflow with date/time unit aliases
+SELECT INTERVAL '2147483647 weeks';
+ERROR:  interval field value out of range: "2147483647 weeks"
+LINE 1: SELECT INTERVAL '2147483647 weeks';
+                        ^
+SELECT INTERVAL '-2147483648 weeks';
+ERROR:  interval field value out of range: "-2147483648 weeks"
+LINE 1: SELECT INTERVAL '-2147483648 weeks';
+                        ^
+SELECT INTERVAL '2147483647 decades';
+ERROR:  interval field value out of range: "2147483647 decades"
+LINE 1: SELECT INTERVAL '2147483647 decades';
+                        ^
+SELECT INTERVAL '-2147483648 decades';
+ERROR:  interval field value out of range: "-2147483648 decades"
+LINE 1: SELECT INTERVAL '-2147483648 decades';
+                        ^
+SELECT INTERVAL '2147483647 centuries';
+ERROR:  interval field value out of range: "2147483647 centuries"
+LINE 1: SELECT INTERVAL '2147483647 centuries';
+                        ^
+SELECT INTERVAL '-2147483648 centuries';
+ERROR:  interval field value out of range: "-2147483648 centuries"
+LINE 1: SELECT INTERVAL '-2147483648 centuries';
+                        ^
+SELECT INTERVAL '2147483647 millennium';
+ERROR:  interval field value out of range: "2147483647 millennium"
+LINE 1: SELECT INTERVAL '2147483647 millennium';
+                        ^
+SELECT INTERVAL '-2147483648 millennium';
+ERROR:  interval field value out of range: "-2147483648 millennium"
+LINE 1: SELECT INTERVAL '-2147483648 millennium';
+                        ^
+SELECT INTERVAL '1 week 2147483647 days';
+ERROR:  interval field value out of range: "1 week 2147483647 days"
+LINE 1: SELECT INTERVAL '1 week 2147483647 days';
+                        ^
+SELECT INTERVAL '-1 week -2147483648 days';
+ERROR:  interval field value out of range: "-1 week -2147483648 days"
+LINE 1: SELECT INTERVAL '-1 week -2147483648 days';
+                        ^
+SELECT INTERVAL '2147483647 days 1 week';
+ERROR:  interval field value out of range: "2147483647 days 1 week"
+LINE 1: SELECT INTERVAL '2147483647 days 1 week';
+                        ^
+SELECT INTERVAL '-2147483648 days -1 week';
+ERROR:  interval field value out of range: "-2147483648 days -1 week"
+LINE 1: SELECT INTERVAL '-2147483648 days -1 week';
+                        ^
+SELECT INTERVAL 'P1W2147483647D';
+ERROR:  interval field value out of range: "P1W2147483647D"
+LINE 1: SELECT INTERVAL 'P1W2147483647D';
+                        ^
+SELECT INTERVAL 'P-1W-2147483648D';
+ERROR:  interval field value out of range: "P-1W-2147483648D"
+LINE 1: SELECT INTERVAL 'P-1W-2147483648D';
+                        ^
+SELECT INTERVAL 'P2147483647D1W';
+ERROR:  interval field value out of range: "P2147483647D1W"
+LINE 1: SELECT INTERVAL 'P2147483647D1W';
+                        ^
+SELECT INTERVAL 'P-2147483648D-1W';
+ERROR:  interval field value out of range: "P-2147483648D-1W"
+LINE 1: SELECT INTERVAL 'P-2147483648D-1W';
+                        ^
+SELECT INTERVAL '1 decade 2147483647 years';
+ERROR:  interval field value out of range: "1 decade 2147483647 years"
+LINE 1: SELECT INTERVAL '1 decade 2147483647 years';
+                        ^
+SELECT INTERVAL '1 century 2147483647 years';
+ERROR:  interval field value out of range: "1 century 2147483647 years"
+LINE 1: SELECT INTERVAL '1 century 2147483647 years';
+                        ^
+SELECT INTERVAL '1 millennium 2147483647 years';
+ERROR:  interval field value out of range: "1 millennium 2147483647 years"
+LINE 1: SELECT INTERVAL '1 millennium 2147483647 years';
+                        ^
+SELECT INTERVAL '-1 decade -2147483648 years';
+ERROR:  interval field value out of range: "-1 decade -2147483648 years"
+LINE 1: SELECT INTERVAL '-1 decade -2147483648 years';
+                        ^
+SELECT INTERVAL '-1 century -2147483648 years';
+ERROR:  interval field value out of range: "-1 century -2147483648 years"
+LINE 1: SELECT INTERVAL '-1 century -2147483648 years';
+                        ^
+SELECT INTERVAL '-1 millennium -2147483648 years';
+ERROR:  interval field value out of range: "-1 millennium -2147483648 years"
+LINE 1: SELECT INTERVAL '-1 millennium -2147483648 years';
+                        ^
+SELECT INTERVAL '2147483647 years 1 decade';
+ERROR:  interval field value out of range: "2147483647 years 1 decade"
+LINE 1: SELECT INTERVAL '2147483647 years 1 decade';
+                        ^
+SELECT INTERVAL '2147483647 years 1 century';
+ERROR:  interval field value out of range: "2147483647 years 1 century"
+LINE 1: SELECT INTERVAL '2147483647 years 1 century';
+                        ^
+SELECT INTERVAL '2147483647 years 1 millennium';
+ERROR:  interval field value out of range: "2147483647 years 1 millennium"
+LINE 1: SELECT INTERVAL '2147483647 years 1 millennium';
+                        ^
+SELECT INTERVAL '-2147483648 years -1 decade';
+ERROR:  interval field value out of range: "-2147483648 years -1 decade"
+LINE 1: SELECT INTERVAL '-2147483648 years -1 decade';
+                        ^
+SELECT INTERVAL '-2147483648 years -1 century';
+ERROR:  interval field value out of range: "-2147483648 years -1 century"
+LINE 1: SELECT INTERVAL '-2147483648 years -1 century';
+                        ^
+SELECT INTERVAL '-2147483648 years -1 millennium';
+ERROR:  interval field value out of range: "-2147483648 years -1 millennium"
+LINE 1: SELECT INTERVAL '-2147483648 years -1 millennium';
+                        ^
+-- overflowing with fractional fields - postgres format
+SELECT INTERVAL '0.1 millennium 2147483647 months';
+ERROR:  interval field value out of range: "0.1 millennium 2147483647 months"
+LINE 1: SELECT INTERVAL '0.1 millennium 2147483647 months';
+                        ^
+SELECT INTERVAL '0.1 centuries 2147483647 months';
+ERROR:  interval field value out of range: "0.1 centuries 2147483647 months"
+LINE 1: SELECT INTERVAL '0.1 centuries 2147483647 months';
+                        ^
+SELECT INTERVAL '0.1 decades 2147483647 months';
+ERROR:  interval field value out of range: "0.1 decades 2147483647 months"
+LINE 1: SELECT INTERVAL '0.1 decades 2147483647 months';
+                        ^
+SELECT INTERVAL '0.1 yrs 2147483647 months';
+ERROR:  interval field value out of range: "0.1 yrs 2147483647 months"
+LINE 1: SELECT INTERVAL '0.1 yrs 2147483647 months';
+                        ^
+SELECT INTERVAL '-0.1 millennium -2147483648 months';
+ERROR:  interval field value out of range: "-0.1 millennium -2147483648 months"
+LINE 1: SELECT INTERVAL '-0.1 millennium -2147483648 months';
+                        ^
+SELECT INTERVAL '-0.1 centuries -2147483648 months';
+ERROR:  interval field value out of range: "-0.1 centuries -2147483648 months"
+LINE 1: SELECT INTERVAL '-0.1 centuries -2147483648 months';
+                        ^
+SELECT INTERVAL '-0.1 decades -2147483648 months';
+ERROR:  interval field value out of range: "-0.1 decades -2147483648 months"
+LINE 1: SELECT INTERVAL '-0.1 decades -2147483648 months';
+                        ^
+SELECT INTERVAL '-0.1 yrs -2147483648 months';
+ERROR:  interval field value out of range: "-0.1 yrs -2147483648 months"
+LINE 1: SELECT INTERVAL '-0.1 yrs -2147483648 months';
+                        ^
+SELECT INTERVAL '2147483647 months 0.1 millennium';
+ERROR:  interval field value out of range: "2147483647 months 0.1 millennium"
+LINE 1: SELECT INTERVAL '2147483647 months 0.1 millennium';
+                        ^
+SELECT INTERVAL '2147483647 months 0.1 centuries';
+ERROR:  interval field value out of range: "2147483647 months 0.1 centuries"
+LINE 1: SELECT INTERVAL '2147483647 months 0.1 centuries';
+                        ^
+SELECT INTERVAL '2147483647 months 0.1 decades';
+ERROR:  interval field value out of range: "2147483647 months 0.1 decades"
+LINE 1: SELECT INTERVAL '2147483647 months 0.1 decades';
+                        ^
+SELECT INTERVAL '2147483647 months 0.1 yrs';
+ERROR:  interval field value out of range: "2147483647 months 0.1 yrs"
+LINE 1: SELECT INTERVAL '2147483647 months 0.1 yrs';
+                        ^
+SELECT INTERVAL '-2147483648 months -0.1 millennium';
+ERROR:  interval field value out of range: "-2147483648 months -0.1 millennium"
+LINE 1: SELECT INTERVAL '-2147483648 months -0.1 millennium';
+                        ^
+SELECT INTERVAL '-2147483648 months -0.1 centuries';
+ERROR:  interval field value out of range: "-2147483648 months -0.1 centuries"
+LINE 1: SELECT INTERVAL '-2147483648 months -0.1 centuries';
+                        ^
+SELECT INTERVAL '-2147483648 months -0.1 decades';
+ERROR:  interval field value out of range: "-2147483648 months -0.1 decades"
+LINE 1: SELECT INTERVAL '-2147483648 months -0.1 decades';
+                        ^
+SELECT INTERVAL '-2147483648 months -0.1 yrs';
+ERROR:  interval field value out of range: "-2147483648 months -0.1 yrs"
+LINE 1: SELECT INTERVAL '-2147483648 months -0.1 yrs';
+                        ^
+SELECT INTERVAL '0.1 months 2147483647 days';
+ERROR:  interval field value out of range: "0.1 months 2147483647 days"
+LINE 1: SELECT INTERVAL '0.1 months 2147483647 days';
+                        ^
+SELECT INTERVAL '-0.1 months -2147483648 days';
+ERROR:  interval field value out of range: "-0.1 months -2147483648 days"
+LINE 1: SELECT INTERVAL '-0.1 months -2147483648 days';
+                        ^
+SELECT INTERVAL '2147483647 days 0.1 months';
+ERROR:  interval field value out of range: "2147483647 days 0.1 months"
+LINE 1: SELECT INTERVAL '2147483647 days 0.1 months';
+                        ^
+SELECT INTERVAL '-2147483648 days -0.1 months';
+ERROR:  interval field value out of range: "-2147483648 days -0.1 months"
+LINE 1: SELECT INTERVAL '-2147483648 days -0.1 months';
+                        ^
+SELECT INTERVAL '0.5 weeks 2147483647 days';
+ERROR:  interval field value out of range: "0.5 weeks 2147483647 days"
+LINE 1: SELECT INTERVAL '0.5 weeks 2147483647 days';
+                        ^
+SELECT INTERVAL '-0.5 weeks -2147483648 days';
+ERROR:  interval field value out of range: "-0.5 weeks -2147483648 days"
+LINE 1: SELECT INTERVAL '-0.5 weeks -2147483648 days';
+                        ^
+SELECT INTERVAL '2147483647 days 0.5 weeks';
+ERROR:  interval field value out of range: "2147483647 days 0.5 weeks"
+LINE 1: SELECT INTERVAL '2147483647 days 0.5 weeks';
+                        ^
+SELECT INTERVAL '-2147483648 days -0.5 weeks';
+ERROR:  interval field value out of range: "-2147483648 days -0.5 weeks"
+LINE 1: SELECT INTERVAL '-2147483648 days -0.5 weeks';
+                        ^
+SELECT INTERVAL '0.01 months 9223372036854775807 microseconds';
+ERROR:  interval field value out of range: "0.01 months 9223372036854775807 microseconds"
+LINE 1: SELECT INTERVAL '0.01 months 9223372036854775807 microsecond...
+                        ^
+SELECT INTERVAL '-0.01 months -9223372036854775808 microseconds';
+ERROR:  interval field value out of range: "-0.01 months -9223372036854775808 microseconds"
+LINE 1: SELECT INTERVAL '-0.01 months -9223372036854775808 microseco...
+                        ^
+SELECT INTERVAL '9223372036854775807 microseconds 0.01 months';
+ERROR:  interval field value out of range: "9223372036854775807 microseconds 0.01 months"
+LINE 1: SELECT INTERVAL '9223372036854775807 microseconds 0.01 month...
+                        ^
+SELECT INTERVAL '-9223372036854775808 microseconds -0.01 months';
+ERROR:  interval field value out of range: "-9223372036854775808 microseconds -0.01 months"
+LINE 1: SELECT INTERVAL '-9223372036854775808 microseconds -0.01 mon...
+                        ^
+SELECT INTERVAL '0.1 weeks 9223372036854775807 microseconds';
+ERROR:  interval field value out of range: "0.1 weeks 9223372036854775807 microseconds"
+LINE 1: SELECT INTERVAL '0.1 weeks 9223372036854775807 microseconds'...
+                        ^
+SELECT INTERVAL '-0.1 weeks -9223372036854775808 microseconds';
+ERROR:  interval field value out of range: "-0.1 weeks -9223372036854775808 microseconds"
+LINE 1: SELECT INTERVAL '-0.1 weeks -9223372036854775808 microsecond...
+                        ^
+SELECT INTERVAL '9223372036854775807 microseconds 0.1 weeks';
+ERROR:  interval field value out of range: "9223372036854775807 microseconds 0.1 weeks"
+LINE 1: SELECT INTERVAL '9223372036854775807 microseconds 0.1 weeks'...
+                        ^
+SELECT INTERVAL '-9223372036854775808 microseconds -0.1 weeks';
+ERROR:  interval field value out of range: "-9223372036854775808 microseconds -0.1 weeks"
+LINE 1: SELECT INTERVAL '-9223372036854775808 microseconds -0.1 week...
+                        ^
+SELECT INTERVAL '0.1 days 9223372036854775807 microseconds';
+ERROR:  interval field value out of range: "0.1 days 9223372036854775807 microseconds"
+LINE 1: SELECT INTERVAL '0.1 days 9223372036854775807 microseconds';
+                        ^
+SELECT INTERVAL '-0.1 days -9223372036854775808 microseconds';
+ERROR:  interval field value out of range: "-0.1 days -9223372036854775808 microseconds"
+LINE 1: SELECT INTERVAL '-0.1 days -9223372036854775808 microseconds...
+                        ^
+SELECT INTERVAL '9223372036854775807 microseconds 0.1 days';
+ERROR:  interval field value out of range: "9223372036854775807 microseconds 0.1 days"
+LINE 1: SELECT INTERVAL '9223372036854775807 microseconds 0.1 days';
+                        ^
+SELECT INTERVAL '-9223372036854775808 microseconds -0.1 days';
+ERROR:  interval field value out of range: "-9223372036854775808 microseconds -0.1 days"
+LINE 1: SELECT INTERVAL '-9223372036854775808 microseconds -0.1 days...
+                        ^
+-- overflowing with fractional fields - ISO8601 format
+SELECT INTERVAL 'P0.1Y2147483647M';
+ERROR:  interval field value out of range: "P0.1Y2147483647M"
+LINE 1: SELECT INTERVAL 'P0.1Y2147483647M';
+                        ^
+SELECT INTERVAL 'P-0.1Y-2147483648M';
+ERROR:  interval field value out of range: "P-0.1Y-2147483648M"
+LINE 1: SELECT INTERVAL 'P-0.1Y-2147483648M';
+                        ^
+SELECT INTERVAL 'P2147483647M0.1Y';
+ERROR:  interval field value out of range: "P2147483647M0.1Y"
+LINE 1: SELECT INTERVAL 'P2147483647M0.1Y';
+                        ^
+SELECT INTERVAL 'P-2147483648M-0.1Y';
+ERROR:  interval field value out of range: "P-2147483648M-0.1Y"
+LINE 1: SELECT INTERVAL 'P-2147483648M-0.1Y';
+                        ^
+SELECT INTERVAL 'P0.1M2147483647D';
+ERROR:  interval field value out of range: "P0.1M2147483647D"
+LINE 1: SELECT INTERVAL 'P0.1M2147483647D';
+                        ^
+SELECT INTERVAL 'P-0.1M-2147483648D';
+ERROR:  interval field value out of range: "P-0.1M-2147483648D"
+LINE 1: SELECT INTERVAL 'P-0.1M-2147483648D';
+                        ^
+SELECT INTERVAL 'P2147483647D0.1M';
+ERROR:  interval field value out of range: "P2147483647D0.1M"
+LINE 1: SELECT INTERVAL 'P2147483647D0.1M';
+                        ^
+SELECT INTERVAL 'P-2147483648D-0.1M';
+ERROR:  interval field value out of range: "P-2147483648D-0.1M"
+LINE 1: SELECT INTERVAL 'P-2147483648D-0.1M';
+                        ^
+SELECT INTERVAL 'P0.5W2147483647D';
+ERROR:  interval field value out of range: "P0.5W2147483647D"
+LINE 1: SELECT INTERVAL 'P0.5W2147483647D';
+                        ^
+SELECT INTERVAL 'P-0.5W-2147483648D';
+ERROR:  interval field value out of range: "P-0.5W-2147483648D"
+LINE 1: SELECT INTERVAL 'P-0.5W-2147483648D';
+                        ^
+SELECT INTERVAL 'P2147483647D0.5W';
+ERROR:  interval field value out of range: "P2147483647D0.5W"
+LINE 1: SELECT INTERVAL 'P2147483647D0.5W';
+                        ^
+SELECT INTERVAL 'P-2147483648D-0.5W';
+ERROR:  interval field value out of range: "P-2147483648D-0.5W"
+LINE 1: SELECT INTERVAL 'P-2147483648D-0.5W';
+                        ^
+SELECT INTERVAL 'P0.01MT2562047788H54.775807S';
+ERROR:  interval field value out of range: "P0.01MT2562047788H54.775807S"
+LINE 1: SELECT INTERVAL 'P0.01MT2562047788H54.775807S';
+                        ^
+SELECT INTERVAL 'P-0.01MT-2562047788H-54.775808S';
+ERROR:  interval field value out of range: "P-0.01MT-2562047788H-54.775808S"
+LINE 1: SELECT INTERVAL 'P-0.01MT-2562047788H-54.775808S';
+                        ^
+SELECT INTERVAL 'P0.1DT2562047788H54.775807S';
+ERROR:  interval field value out of range: "P0.1DT2562047788H54.775807S"
+LINE 1: SELECT INTERVAL 'P0.1DT2562047788H54.775807S';
+                        ^
+SELECT INTERVAL 'P-0.1DT-2562047788H-54.775808S';
+ERROR:  interval field value out of range: "P-0.1DT-2562047788H-54.775808S"
+LINE 1: SELECT INTERVAL 'P-0.1DT-2562047788H-54.775808S';
+                        ^
+SELECT INTERVAL 'PT2562047788.1H54.775807S';
+ERROR:  interval field value out of range: "PT2562047788.1H54.775807S"
+LINE 1: SELECT INTERVAL 'PT2562047788.1H54.775807S';
+                        ^
+SELECT INTERVAL 'PT-2562047788.1H-54.775808S';
+ERROR:  interval field value out of range: "PT-2562047788.1H-54.775808S"
+LINE 1: SELECT INTERVAL 'PT-2562047788.1H-54.775808S';
+                        ^
+SELECT INTERVAL 'PT2562047788H0.1M54.775807S';
+ERROR:  interval field value out of range: "PT2562047788H0.1M54.775807S"
+LINE 1: SELECT INTERVAL 'PT2562047788H0.1M54.775807S';
+                        ^
+SELECT INTERVAL 'PT-2562047788H-0.1M-54.775808S';
+ERROR:  interval field value out of range: "PT-2562047788H-0.1M-54.775808S"
+LINE 1: SELECT INTERVAL 'PT-2562047788H-0.1M-54.775808S';
+                        ^
+-- overflowing with fractional fields - ISO8601 alternative format
+SELECT INTERVAL 'P0.1-2147483647-00';
+ERROR:  interval field value out of range: "P0.1-2147483647-00"
+LINE 1: SELECT INTERVAL 'P0.1-2147483647-00';
+                        ^
+SELECT INTERVAL 'P00-0.1-2147483647';
+ERROR:  interval field value out of range: "P00-0.1-2147483647"
+LINE 1: SELECT INTERVAL 'P00-0.1-2147483647';
+                        ^
+SELECT INTERVAL 'P00-0.01-00T2562047788:00:54.775807';
+ERROR:  interval field value out of range: "P00-0.01-00T2562047788:00:54.775807"
+LINE 1: SELECT INTERVAL 'P00-0.01-00T2562047788:00:54.775807';
+                        ^
+SELECT INTERVAL 'P00-00-0.1T2562047788:00:54.775807';
+ERROR:  interval field value out of range: "P00-00-0.1T2562047788:00:54.775807"
+LINE 1: SELECT INTERVAL 'P00-00-0.1T2562047788:00:54.775807';
+                        ^
+SELECT INTERVAL 'PT2562047788.1:00:54.775807';
+ERROR:  interval field value out of range: "PT2562047788.1:00:54.775807"
+LINE 1: SELECT INTERVAL 'PT2562047788.1:00:54.775807';
+                        ^
+SELECT INTERVAL 'PT2562047788:01.:54.775807';
+ERROR:  interval field value out of range: "PT2562047788:01.:54.775807"
+LINE 1: SELECT INTERVAL 'PT2562047788:01.:54.775807';
+                        ^
+-- overflowing with fractional fields - SQL standard format
+SELECT INTERVAL '0.1 2562047788:0:54.775807';
+ERROR:  interval field value out of range: "0.1 2562047788:0:54.775807"
+LINE 1: SELECT INTERVAL '0.1 2562047788:0:54.775807';
+                        ^
+SELECT INTERVAL '0.1 2562047788:0:54.775808 ago';
+ERROR:  interval field value out of range: "0.1 2562047788:0:54.775808 ago"
+LINE 1: SELECT INTERVAL '0.1 2562047788:0:54.775808 ago';
+                        ^
+SELECT INTERVAL '2562047788.1:0:54.775807';
+ERROR:  interval field value out of range: "2562047788.1:0:54.775807"
+LINE 1: SELECT INTERVAL '2562047788.1:0:54.775807';
+                        ^
+SELECT INTERVAL '2562047788.1:0:54.775808 ago';
+ERROR:  interval field value out of range: "2562047788.1:0:54.775808 ago"
+LINE 1: SELECT INTERVAL '2562047788.1:0:54.775808 ago';
+                        ^
+SELECT INTERVAL '2562047788:0.1:54.775807';
+ERROR:  invalid input syntax for type interval: "2562047788:0.1:54.775807"
+LINE 1: SELECT INTERVAL '2562047788:0.1:54.775807';
+                        ^
+SELECT INTERVAL '2562047788:0.1:54.775808 ago';
+ERROR:  invalid input syntax for type interval: "2562047788:0.1:54.775808 ago"
+LINE 1: SELECT INTERVAL '2562047788:0.1:54.775808 ago';
+                        ^
+-- overflowing using AGO with INT_MIN
+SELECT INTERVAL '-2147483648 months ago';
+ERROR:  interval field value out of range: "-2147483648 months ago"
+LINE 1: SELECT INTERVAL '-2147483648 months ago';
+                        ^
+SELECT INTERVAL '-2147483648 days ago';
+ERROR:  interval field value out of range: "-2147483648 days ago"
+LINE 1: SELECT INTERVAL '-2147483648 days ago';
+                        ^
+SELECT INTERVAL '-9223372036854775808 microseconds ago';
+ERROR:  interval field value out of range: "-9223372036854775808 microseconds ago"
+LINE 1: SELECT INTERVAL '-9223372036854775808 microseconds ago';
+                        ^
+SELECT INTERVAL '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+ERROR:  interval field value out of range: "-2147483648 months -2147483648 days -9223372036854775808 microseconds ago"
+LINE 1: SELECT INTERVAL '-2147483648 months -2147483648 days -922337...
+                        ^
+-- test that INT_MIN number is formatted properly
+SET IntervalStyle to postgres;
+SELECT INTERVAL '-2147483648 months -2147483648 days -9223372036854775808 us';
+                              interval                              
+--------------------------------------------------------------------
+ -178956970 years -8 mons -2147483648 days -2562047788:00:54.775808
+(1 row)
+
+SET IntervalStyle to postgres_verbose;
+SELECT INTERVAL '-2147483648 months -2147483648 days -9223372036854775808 us';
+                                   interval                                   
+------------------------------------------------------------------------------
+ @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775808 secs ago
+(1 row)
+
+SET IntervalStyle TO sql_standard;
+SELECT INTERVAL '-2147483648 months -2147483648 days -9223372036854775808 us';
+                     interval                      
+---------------------------------------------------
+ -178956970-8 -2147483648 -2562047788:00:54.775808
+(1 row)
+
+SET IntervalStyle to iso_8601;
+SELECT INTERVAL '-2147483648 months -2147483648 days -9223372036854775808 us';
+                      interval                       
+-----------------------------------------------------
+ P-178956970Y-8M-2147483648DT-2562047788H-54.775808S
+(1 row)
+
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index c31f0eec05..fc924d5bde 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -367,3 +367,187 @@ SELECT f1,
 
 -- internal overflow test case
 SELECT extract(epoch from interval '1000000000 days');
+
+-- test time fields using entire 64 bit microseconds range
+SELECT INTERVAL '2562047788.01521550194 hours';
+SELECT INTERVAL '-2562047788.01521550222 hours';
+SELECT INTERVAL '153722867280.912930117 minutes';
+SELECT INTERVAL '-153722867280.912930133 minutes';
+SELECT INTERVAL '9223372036854.775807 seconds';
+SELECT INTERVAL '-9223372036854.775808 seconds';
+SELECT INTERVAL '9223372036854775.807 milliseconds';
+SELECT INTERVAL '-9223372036854775.808 milliseconds';
+SELECT INTERVAL '9223372036854775807 microseconds';
+SELECT INTERVAL '-9223372036854775808 microseconds';
+
+SELECT INTERVAL 'PT2562047788H54.775807S';
+SELECT INTERVAL 'PT-2562047788H-54.775808S';
+
+SELECT INTERVAL 'PT2562047788:00:54.775807';
+
+SELECT INTERVAL 'PT2562047788.0152155019444';
+SELECT INTERVAL 'PT-2562047788.0152155022222';
+
+-- overflow each date/time field
+SELECT INTERVAL '2147483648 years';
+SELECT INTERVAL '-2147483649 years';
+SELECT INTERVAL '2147483648 months';
+SELECT INTERVAL '-2147483649 months';
+SELECT INTERVAL '2147483648 days';
+SELECT INTERVAL '-2147483649 days';
+SELECT INTERVAL '2562047789 hours';
+SELECT INTERVAL '-2562047789 hours';
+SELECT INTERVAL '153722867281 minutes';
+SELECT INTERVAL '-153722867281 minutes';
+SELECT INTERVAL '9223372036855 seconds';
+SELECT INTERVAL '-9223372036855 seconds';
+SELECT INTERVAL '9223372036854777 millisecond';
+SELECT INTERVAL '-9223372036854777 millisecond';
+SELECT INTERVAL '9223372036854775808 microsecond';
+SELECT INTERVAL '-9223372036854775809 microsecond';
+
+SELECT INTERVAL 'P2147483648';
+SELECT INTERVAL 'P-2147483649';
+SELECT INTERVAL 'P1-2147483647-2147483647';
+SELECT INTERVAL 'PT2562047789';
+SELECT INTERVAL 'PT-2562047789';
+
+-- overflow with date/time unit aliases
+SELECT INTERVAL '2147483647 weeks';
+SELECT INTERVAL '-2147483648 weeks';
+SELECT INTERVAL '2147483647 decades';
+SELECT INTERVAL '-2147483648 decades';
+SELECT INTERVAL '2147483647 centuries';
+SELECT INTERVAL '-2147483648 centuries';
+SELECT INTERVAL '2147483647 millennium';
+SELECT INTERVAL '-2147483648 millennium';
+
+SELECT INTERVAL '1 week 2147483647 days';
+SELECT INTERVAL '-1 week -2147483648 days';
+SELECT INTERVAL '2147483647 days 1 week';
+SELECT INTERVAL '-2147483648 days -1 week';
+
+SELECT INTERVAL 'P1W2147483647D';
+SELECT INTERVAL 'P-1W-2147483648D';
+SELECT INTERVAL 'P2147483647D1W';
+SELECT INTERVAL 'P-2147483648D-1W';
+
+SELECT INTERVAL '1 decade 2147483647 years';
+SELECT INTERVAL '1 century 2147483647 years';
+SELECT INTERVAL '1 millennium 2147483647 years';
+SELECT INTERVAL '-1 decade -2147483648 years';
+SELECT INTERVAL '-1 century -2147483648 years';
+SELECT INTERVAL '-1 millennium -2147483648 years';
+
+SELECT INTERVAL '2147483647 years 1 decade';
+SELECT INTERVAL '2147483647 years 1 century';
+SELECT INTERVAL '2147483647 years 1 millennium';
+SELECT INTERVAL '-2147483648 years -1 decade';
+SELECT INTERVAL '-2147483648 years -1 century';
+SELECT INTERVAL '-2147483648 years -1 millennium';
+
+-- overflowing with fractional fields - postgres format
+SELECT INTERVAL '0.1 millennium 2147483647 months';
+SELECT INTERVAL '0.1 centuries 2147483647 months';
+SELECT INTERVAL '0.1 decades 2147483647 months';
+SELECT INTERVAL '0.1 yrs 2147483647 months';
+SELECT INTERVAL '-0.1 millennium -2147483648 months';
+SELECT INTERVAL '-0.1 centuries -2147483648 months';
+SELECT INTERVAL '-0.1 decades -2147483648 months';
+SELECT INTERVAL '-0.1 yrs -2147483648 months';
+
+SELECT INTERVAL '2147483647 months 0.1 millennium';
+SELECT INTERVAL '2147483647 months 0.1 centuries';
+SELECT INTERVAL '2147483647 months 0.1 decades';
+SELECT INTERVAL '2147483647 months 0.1 yrs';
+SELECT INTERVAL '-2147483648 months -0.1 millennium';
+SELECT INTERVAL '-2147483648 months -0.1 centuries';
+SELECT INTERVAL '-2147483648 months -0.1 decades';
+SELECT INTERVAL '-2147483648 months -0.1 yrs';
+
+SELECT INTERVAL '0.1 months 2147483647 days';
+SELECT INTERVAL '-0.1 months -2147483648 days';
+SELECT INTERVAL '2147483647 days 0.1 months';
+SELECT INTERVAL '-2147483648 days -0.1 months';
+
+SELECT INTERVAL '0.5 weeks 2147483647 days';
+SELECT INTERVAL '-0.5 weeks -2147483648 days';
+SELECT INTERVAL '2147483647 days 0.5 weeks';
+SELECT INTERVAL '-2147483648 days -0.5 weeks';
+
+SELECT INTERVAL '0.01 months 9223372036854775807 microseconds';
+SELECT INTERVAL '-0.01 months -9223372036854775808 microseconds';
+SELECT INTERVAL '9223372036854775807 microseconds 0.01 months';
+SELECT INTERVAL '-9223372036854775808 microseconds -0.01 months';
+
+SELECT INTERVAL '0.1 weeks 9223372036854775807 microseconds';
+SELECT INTERVAL '-0.1 weeks -9223372036854775808 microseconds';
+SELECT INTERVAL '9223372036854775807 microseconds 0.1 weeks';
+SELECT INTERVAL '-9223372036854775808 microseconds -0.1 weeks';
+
+SELECT INTERVAL '0.1 days 9223372036854775807 microseconds';
+SELECT INTERVAL '-0.1 days -9223372036854775808 microseconds';
+SELECT INTERVAL '9223372036854775807 microseconds 0.1 days';
+SELECT INTERVAL '-9223372036854775808 microseconds -0.1 days';
+
+-- overflowing with fractional fields - ISO8601 format
+SELECT INTERVAL 'P0.1Y2147483647M';
+SELECT INTERVAL 'P-0.1Y-2147483648M';
+SELECT INTERVAL 'P2147483647M0.1Y';
+SELECT INTERVAL 'P-2147483648M-0.1Y';
+
+SELECT INTERVAL 'P0.1M2147483647D';
+SELECT INTERVAL 'P-0.1M-2147483648D';
+SELECT INTERVAL 'P2147483647D0.1M';
+SELECT INTERVAL 'P-2147483648D-0.1M';
+
+SELECT INTERVAL 'P0.5W2147483647D';
+SELECT INTERVAL 'P-0.5W-2147483648D';
+SELECT INTERVAL 'P2147483647D0.5W';
+SELECT INTERVAL 'P-2147483648D-0.5W';
+
+SELECT INTERVAL 'P0.01MT2562047788H54.775807S';
+SELECT INTERVAL 'P-0.01MT-2562047788H-54.775808S';
+
+SELECT INTERVAL 'P0.1DT2562047788H54.775807S';
+SELECT INTERVAL 'P-0.1DT-2562047788H-54.775808S';
+
+SELECT INTERVAL 'PT2562047788.1H54.775807S';
+SELECT INTERVAL 'PT-2562047788.1H-54.775808S';
+
+SELECT INTERVAL 'PT2562047788H0.1M54.775807S';
+SELECT INTERVAL 'PT-2562047788H-0.1M-54.775808S';
+
+-- overflowing with fractional fields - ISO8601 alternative format
+SELECT INTERVAL 'P0.1-2147483647-00';
+SELECT INTERVAL 'P00-0.1-2147483647';
+SELECT INTERVAL 'P00-0.01-00T2562047788:00:54.775807';
+SELECT INTERVAL 'P00-00-0.1T2562047788:00:54.775807';
+SELECT INTERVAL 'PT2562047788.1:00:54.775807';
+SELECT INTERVAL 'PT2562047788:01.:54.775807';
+
+-- overflowing with fractional fields - SQL standard format
+SELECT INTERVAL '0.1 2562047788:0:54.775807';
+SELECT INTERVAL '0.1 2562047788:0:54.775808 ago';
+
+SELECT INTERVAL '2562047788.1:0:54.775807';
+SELECT INTERVAL '2562047788.1:0:54.775808 ago';
+
+SELECT INTERVAL '2562047788:0.1:54.775807';
+SELECT INTERVAL '2562047788:0.1:54.775808 ago';
+
+-- overflowing using AGO with INT_MIN
+SELECT INTERVAL '-2147483648 months ago';
+SELECT INTERVAL '-2147483648 days ago';
+SELECT INTERVAL '-9223372036854775808 microseconds ago';
+SELECT INTERVAL '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+
+-- test that INT_MIN number is formatted properly
+SET IntervalStyle to postgres;
+SELECT INTERVAL '-2147483648 months -2147483648 days -9223372036854775808 us';
+SET IntervalStyle to postgres_verbose;
+SELECT INTERVAL '-2147483648 months -2147483648 days -9223372036854775808 us';
+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';
-- 
2.25.1



^ permalink  raw  reply  [nested|flat] 23+ messages in thread

* Re: Fix overflow in DecodeInterval
  2022-02-13 03:51 Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-13 14:35 ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-13 20:30   ` Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-13 20:38     ` Re: Fix overflow in DecodeInterval Tom Lane <[email protected]>
  2022-02-13 22:12       ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-15 11:44         ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-15 16:28           ` Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-18 02:45             ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-19 19:26               ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-20 00:16                 ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-20 23:37                   ` Re: Fix overflow in DecodeInterval Tom Lane <[email protected]>
  2022-02-21 02:53                     ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-03-06 16:14                       ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
@ 2022-03-08 00:00                         ` Joseph Koshakow <[email protected]>
  0 siblings, 0 replies; 23+ messages in thread

From: Joseph Koshakow @ 2022-03-08 00:00 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Andres Freund <[email protected]>; PostgreSQL Hackers <[email protected]>

I just realized another issue today. It may have been obvious from one
of Tom's earlier messages, but I'm just now putting the pieces
together.
On Fri, Feb 18, 2022 at 11:44 PM Tom Lane <[email protected]> wrote:
> Also, I notice that there's an overflow hazard upstream of here,
> in interval2tm:
>
> regression=# select interval '214748364 hours' * 11;
> ERROR: interval out of range
> regression=# \errverbose
> ERROR: 22008: interval out of range
> LOCATION: interval2tm, timestamp.c:1982
>
> There's no good excuse for not being able to print a value that
> we computed successfully.

Scenarios like this can properly decode the interval, but actually
error out when encoding the interval. As a consequence you can insert
the value successfully into a table, but any attempt to query the table
that includes the "bad interval" value in the result will cause an
error. Below I've demonstrated an example:

postgres=# CREATE TABLE tbl (i INTERVAL);
CREATE TABLE
postgres=# INSERT INTO tbl VALUES ('1 day'), ('3 months'), ('2 years');
INSERT 0 3
postgres=# SELECT * FROM tbl;
i
---------
1 day
3 mons
2 years
(3 rows)

postgres=# INSERT INTO tbl VALUES ('2147483647 hours 60 minutes');
INSERT 0 1
postgres=# SELECT * FROM tbl;
ERROR: interval out of range

This would seriously reduce the usable of any table that contains one
of these "bad interval" values.

My patch actually fixes this issue, but I just wanted to call it out
because it might be relevant when reviewing.






^ permalink  raw  reply  [nested|flat] 23+ messages in thread

* Re: Fix overflow in DecodeInterval
  2022-02-13 03:51 Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-13 14:35 ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-13 20:30   ` Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-13 20:38     ` Re: Fix overflow in DecodeInterval Tom Lane <[email protected]>
  2022-02-13 22:12       ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-15 11:44         ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-15 16:28           ` Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-18 02:45             ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-19 19:26               ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-20 00:16                 ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-20 23:37                   ` Re: Fix overflow in DecodeInterval Tom Lane <[email protected]>
  2022-02-21 02:53                     ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
@ 2022-03-22 00:31                       ` Tom Lane <[email protected]>
  2022-03-24 00:27                         ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2 siblings, 1 reply; 23+ messages in thread

From: Tom Lane @ 2022-03-22 00:31 UTC (permalink / raw)
  To: Joseph Koshakow <[email protected]>; +Cc: Andres Freund <[email protected]>; PostgreSQL Hackers <[email protected]>

Joseph Koshakow <[email protected]> writes:
> [ v8-0001-Check-for-overflow-when-decoding-an-interval.patch ]

This isn't applying per the cfbot; looks like it got sideswiped
by 9e9858389.  Here's a quick rebase.  I've not reviewed it, but
I did notice (because git was in my face about this) that it's
got whitespace issues.  Please try to avoid unnecessary whitespace
changes ... pgindent will clean those up, but it makes reviewing
harder.

			regards, tom lane



Attachments:

  [text/x-diff] v9-0001-Check-for-overflow-when-decoding-an-interval.patch (100.3K, ../../[email protected]/2-v9-0001-Check-for-overflow-when-decoding-an-interval.patch)
  download | inline diff:
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index ba0ec35ac5..014ec88e0d 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -21,6 +21,7 @@
 #include "access/htup_details.h"
 #include "access/xact.h"
 #include "catalog/pg_type.h"
+#include "common/int.h"
 #include "common/string.h"
 #include "funcapi.h"
 #include "miscadmin.h"
@@ -38,16 +39,20 @@ static int	DecodeNumberField(int len, char *str,
 							  int fmask, int *tmask,
 							  struct pg_tm *tm, fsec_t *fsec, bool *is2digits);
 static int	DecodeTime(char *str, int fmask, int range,
-					   int *tmask, struct pg_tm *tm, fsec_t *fsec);
+					   int *tmask, struct pg_tm *tm, int64 *hour, fsec_t *fsec);
+static int DecodeTimeForInterval(char *str, int fmask, int range,
+								 int *tmask, struct pg_itm_in *itm_in);
 static const datetkn *datebsearch(const char *key, const datetkn *base, int nel);
 static int	DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
 					   struct pg_tm *tm);
-static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
+static char *AppendSeconds(char *cp, int sec, int64 fsec,
 						   int precision, bool fillzeros);
-static void AdjustFractSeconds(double frac, struct pg_tm *tm, fsec_t *fsec,
-							   int scale);
-static void AdjustFractDays(double frac, struct pg_tm *tm, fsec_t *fsec,
-							int scale);
+static bool AdjustFractMicroseconds(long double frac, struct pg_itm_in *itm_in, int64 scale);
+static bool AdjustFractDays(double frac, struct pg_itm_in *pg_itm_in, int scale);
+static bool AdjustFractMonths(double frac, struct pg_itm_in *itm_in, int scale);
+static bool AdjustMicroseconds(int64 val, struct pg_itm_in *itm_in, int64 multiplier, double fval);
+static bool AdjustDays(int val, struct pg_itm_in *itm_in, int multiplier);
+static bool AdjustYears(int val, struct pg_itm_in *itm_in, int multiplier);
 static int	DetermineTimeZoneOffsetInternal(struct pg_tm *tm, pg_tz *tzp,
 											pg_time_t *tp);
 static bool DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t,
@@ -428,7 +433,7 @@ GetCurrentTimeUsec(struct pg_tm *tm, fsec_t *fsec, int *tzp)
  * Note that any sign is stripped from the input seconds values.
  */
 static char *
-AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros)
+AppendSeconds(char *cp, int sec, int64 fsec, int precision, bool fillzeros)
 {
 	Assert(precision >= 0);
 
@@ -437,10 +442,9 @@ AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros)
 	else
 		cp = pg_ultostr(cp, Abs(sec));
 
-	/* fsec_t is just an int32 */
 	if (fsec != 0)
 	{
-		int32		value = Abs(fsec);
+		int64		value = Abs(fsec);
 		char	   *end = &cp[precision + 1];
 		bool		gotnonzero = false;
 
@@ -453,8 +457,8 @@ AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros)
 		 */
 		while (precision--)
 		{
-			int32		oldval = value;
-			int32		remainder;
+			int64		oldval = value;
+			int64		remainder;
 
 			value /= 10;
 			remainder = oldval - value * 10;
@@ -475,7 +479,7 @@ AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros)
 		 * which will generate a correct answer in the minimum valid width.
 		 */
 		if (value)
-			return pg_ultostr(cp, Abs(fsec));
+			return pg_ulltostr(cp, Abs(fsec));
 
 		return end;
 	}
@@ -497,36 +501,96 @@ AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec)
 }
 
 /*
- * Multiply frac by scale (to produce seconds) and add to *tm & *fsec.
- * We assume the input frac is less than 1 so overflow is not an issue.
+ * Multiply frac by scale (to produce microseconds) and add to *itm.
+ * We assume the input frac is less than 1 so overflow of frac is not an issue.
  */
-static void
-AdjustFractSeconds(double frac, struct pg_tm *tm, fsec_t *fsec, int scale)
+static bool
+AdjustFractMicroseconds(long double frac, struct pg_itm_in *itm_in, int64 scale)
 {
-	int			sec;
+	int64		usec;
+	int64		round = 0;
 
 	if (frac == 0)
-		return;
+		return true;
 	frac *= scale;
-	sec = (int) frac;
-	tm->tm_sec += sec;
-	frac -= sec;
-	*fsec += rint(frac * 1000000);
+	usec = (int64) frac;
+	if (pg_add_s64_overflow(itm_in->tm_usec, usec, &itm_in->tm_usec))
+		return false;
+	
+	frac = frac - usec;
+	if (frac > 0.5)
+		round = 1;
+	else if (frac < -0.5)
+		round = -1;
+
+	return !pg_add_s64_overflow(itm_in->tm_usec, round, &itm_in->tm_usec);
 }
 
 /* As above, but initial scale produces days */
-static void
-AdjustFractDays(double frac, struct pg_tm *tm, fsec_t *fsec, int scale)
+static bool
+AdjustFractDays(double frac, struct pg_itm_in *itm_in, int scale)
 {
 	int			extra_days;
 
 	if (frac == 0)
-		return;
+		return true;
 	frac *= scale;
 	extra_days = (int) frac;
-	tm->tm_mday += extra_days;
+	if (pg_add_s32_overflow(itm_in->tm_mday, extra_days, &itm_in->tm_mday))
+		return false;
 	frac -= extra_days;
-	AdjustFractSeconds(frac, tm, fsec, SECS_PER_DAY);
+	return AdjustFractMicroseconds(frac, itm_in, USECS_PER_DAY);
+}
+
+/* As above, but initial scale produces months */
+static bool
+AdjustFractMonths(double frac, struct pg_itm_in *itm_in, int scale)
+{
+	int extra_months = rint(frac * MONTHS_PER_YEAR * scale);
+	return !pg_add_s32_overflow(itm_in->tm_mon, extra_months, &itm_in->tm_mon);
+}
+
+/*
+ * Multiply val by multiplier (to produce microseconds) and add to *itm.
+ * Returns true if successful, false if tm overflows.
+ */
+static bool
+AdjustMicroseconds(int64 val, struct pg_itm_in *itm_in, int64 multiplier, double fval)
+{
+	int64		usecs;
+	if (pg_mul_s64_overflow(val, multiplier, &usecs) ||
+		pg_add_s64_overflow(itm_in->tm_usec, usecs, &itm_in->tm_usec))
+		return false;
+
+	return AdjustFractMicroseconds(fval, itm_in, multiplier);
+}
+
+/*
+ * Multiply val by multiplier (to produce days) and add to *itm.
+ * Returns true if successful, false if tm overflows.
+ */
+static bool
+AdjustDays(int val, struct pg_itm_in *itm_in, int multiplier)
+{
+	int			extra_days;
+	return !pg_mul_s32_overflow(val, multiplier, &extra_days) &&
+		!pg_add_s32_overflow(itm_in->tm_mday, extra_days, &itm_in->tm_mday);
+}
+
+/* As above, but initial val produces months */
+static bool
+AdjustMonths(int val, struct pg_itm_in *itm_in)
+{
+	return !pg_add_s32_overflow(itm_in->tm_mon, val, &itm_in->tm_mon);
+}
+
+/* As above, but initial val produces years */
+static bool
+AdjustYears(int val, struct pg_itm_in *itm_in, int multiplier)
+{
+	int			years;
+	return !pg_mul_s32_overflow(val, multiplier, &years) &&
+		!pg_add_s32_overflow(itm_in->tm_year, years, &itm_in->tm_year);
 }
 
 /* Fetch a fractional-second value with suitable error checking */
@@ -965,7 +1029,8 @@ DecodeDateTime(char **field, int *ftype, int nf,
 				break;
 
 			case DTK_TIME:
-
+			{
+				int64 hour;
 				/*
 				 * This might be an ISO time following a "t" field.
 				 */
@@ -977,16 +1042,19 @@ DecodeDateTime(char **field, int *ftype, int nf,
 					ptype = 0;
 				}
 				dterr = DecodeTime(field[i], fmask, INTERVAL_FULL_RANGE,
-								   &tmask, tm, fsec);
+								   &tmask, tm, &hour, fsec);
 				if (dterr)
 					return dterr;
+				if (hour > INT_MAX || hour < INT_MIN)
+					return DTERR_FIELD_OVERFLOW;
+				tm->tm_hour = (int) hour;
 
 				/* check for time overflow */
 				if (time_overflows(tm->tm_hour, tm->tm_min, tm->tm_sec,
 								   *fsec))
 					return DTERR_FIELD_OVERFLOW;
 				break;
-
+			}
 			case DTK_TZ:
 				{
 					int			tz;
@@ -1866,13 +1934,18 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
 				break;
 
 			case DTK_TIME:
+			{
+				int64 hour;
 				dterr = DecodeTime(field[i], (fmask | DTK_DATE_M),
 								   INTERVAL_FULL_RANGE,
-								   &tmask, tm, fsec);
+								   &tmask, tm, &hour, fsec);
 				if (dterr)
 					return dterr;
+				if (hour > INT_MAX || hour < INT_MIN)
+					return DTERR_FIELD_OVERFLOW;
+				tm->tm_hour = (int) hour;
 				break;
-
+			}
 			case DTK_TZ:
 				{
 					int			tz;
@@ -2554,10 +2627,13 @@ ValidateDate(int fmask, bool isjulian, bool is2digits, bool bc,
  *
  * Only check the lower limit on hours, since this same code can be
  * used to represent time spans.
+ *
+ * Different consumers of this function have different requirements on the size
+ * of hours. So we take in an *int64 hour and let the consumer check the result.
  */
 static int
 DecodeTime(char *str, int fmask, int range,
-		   int *tmask, struct pg_tm *tm, fsec_t *fsec)
+		   int *tmask, struct pg_tm *tm, int64 *hour, fsec_t *fsec)
 {
 	char	   *cp;
 	int			dterr;
@@ -2565,7 +2641,7 @@ DecodeTime(char *str, int fmask, int range,
 	*tmask = DTK_TIME_M;
 
 	errno = 0;
-	tm->tm_hour = strtoint(str, &cp, 10);
+	*hour = strtoi64(str, &cp, 10);
 	if (errno == ERANGE)
 		return DTERR_FIELD_OVERFLOW;
 	if (*cp != ':')
@@ -2581,9 +2657,11 @@ DecodeTime(char *str, int fmask, int range,
 		/* If it's a MINUTE TO SECOND interval, take 2 fields as being mm:ss */
 		if (range == (INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND)))
 		{
+			if (*hour > INT_MAX || *hour < INT_MIN)
+				return DTERR_FIELD_OVERFLOW;
 			tm->tm_sec = tm->tm_min;
-			tm->tm_min = tm->tm_hour;
-			tm->tm_hour = 0;
+			tm->tm_min = (int) *hour;
+			*hour = 0;
 		}
 	}
 	else if (*cp == '.')
@@ -2592,9 +2670,11 @@ DecodeTime(char *str, int fmask, int range,
 		dterr = ParseFractionalSecond(cp, fsec);
 		if (dterr)
 			return dterr;
+		if (*hour > INT_MAX || *hour < INT_MIN)
+			return DTERR_FIELD_OVERFLOW;
 		tm->tm_sec = tm->tm_min;
-		tm->tm_min = tm->tm_hour;
-		tm->tm_hour = 0;
+		tm->tm_min = (int) *hour;
+		*hour = 0;
 	}
 	else if (*cp == ':')
 	{
@@ -2617,7 +2697,7 @@ DecodeTime(char *str, int fmask, int range,
 		return DTERR_BAD_FORMAT;
 
 	/* do a sanity check */
-	if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_min > MINS_PER_HOUR - 1 ||
+	if (*hour < 0 || tm->tm_min < 0 || tm->tm_min > MINS_PER_HOUR - 1 ||
 		tm->tm_sec < 0 || tm->tm_sec > SECS_PER_MINUTE ||
 		*fsec < INT64CONST(0) ||
 		*fsec > USECS_PER_SEC)
@@ -2626,6 +2706,30 @@ DecodeTime(char *str, int fmask, int range,
 	return 0;
 }
 
+/* DecodeTimeForInterval()
+ * Decode time string which includes delimiters for Interval decoding.
+ * Return 0 if okay, a DTERR code if not.
+ */
+static int
+DecodeTimeForInterval(char *str, int fmask, int range,
+								 int *tmask, struct pg_itm_in *itm_in)
+{
+	int dterr;
+	int64 hour;
+	struct pg_tm tt,
+			*tm = &tt;
+	dterr = DecodeTime(str, fmask, range,
+					   tmask, tm, &hour, (fsec_t *)&itm_in->tm_usec);
+	if (dterr)
+		return dterr;
+	
+	if (!AdjustMicroseconds(hour, itm_in, USECS_PER_HOUR, 0) ||
+		!AdjustMicroseconds(tm->tm_min, itm_in, USECS_PER_MINUTE, 0) ||
+		!AdjustMicroseconds(tm->tm_sec, itm_in, USECS_PER_SEC, 0))
+		return DTERR_FIELD_OVERFLOW;
+
+	return 0;
+}
 
 /* DecodeNumber()
  * Interpret plain numeric field as a date value in context.
@@ -3063,28 +3167,24 @@ DecodeSpecial(int field, char *lowtoken, int *val)
 	return type;
 }
 
-
-/* ClearPgTm
+/* ClearPgItmIn
  *
- * Zero out a pg_tm and associated fsec_t
+ * Zero out a pg_itm_in
  */
 static inline void
-ClearPgTm(struct pg_tm *tm, fsec_t *fsec)
+ClearPgItmIn(struct pg_itm_in *itm_in)
 {
-	tm->tm_year = 0;
-	tm->tm_mon = 0;
-	tm->tm_mday = 0;
-	tm->tm_hour = 0;
-	tm->tm_min = 0;
-	tm->tm_sec = 0;
-	*fsec = 0;
+	itm_in->tm_year = 0;
+	itm_in->tm_mon = 0;
+	itm_in->tm_mday = 0;
+	itm_in->tm_usec = 0;
 }
 
 
 /* DecodeInterval()
  * Interpret previously parsed fields for general time interval.
  * Returns 0 if successful, DTERR code if bogus input detected.
- * dtype, tm, fsec are output parameters.
+ * dtype and itm_in are output parameters.
  *
  * Allow "date" field DTK_DATE since this could be just
  *	an unsigned floating point number. - thomas 1997-11-16
@@ -3094,21 +3194,22 @@ ClearPgTm(struct pg_tm *tm, fsec_t *fsec)
  */
 int
 DecodeInterval(char **field, int *ftype, int nf, int range,
-			   int *dtype, struct pg_tm *tm, fsec_t *fsec)
+			   int *dtype, struct pg_itm_in *itm_in)
 {
 	bool		is_before = false;
 	char	   *cp;
 	int			fmask = 0,
 				tmask,
-				type;
+				type,
+				tval;
 	int			i;
 	int			dterr;
-	int			val;
+	int64		val;
 	double		fval;
 
 	*dtype = DTK_DELTA;
 	type = IGNORE_DTF;
-	ClearPgTm(tm, fsec);
+	ClearPgItmIn(itm_in);
 
 	/* read through list backwards to pick up units before values */
 	for (i = nf - 1; i >= 0; i--)
@@ -3116,8 +3217,8 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 		switch (ftype[i])
 		{
 			case DTK_TIME:
-				dterr = DecodeTime(field[i], fmask, range,
-								   &tmask, tm, fsec);
+				dterr = DecodeTimeForInterval(field[i], fmask, range,
+											  &tmask, itm_in);
 				if (dterr)
 					return dterr;
 				type = DTK_DAY;
@@ -3137,16 +3238,15 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 				 * like DTK_TIME case above, plus handling the sign.
 				 */
 				if (strchr(field[i] + 1, ':') != NULL &&
-					DecodeTime(field[i] + 1, fmask, range,
-							   &tmask, tm, fsec) == 0)
+					DecodeTimeForInterval(field[i] + 1, fmask, range,
+							   &tmask, itm_in) == 0)
 				{
 					if (*field[i] == '-')
 					{
-						/* flip the sign on all fields */
-						tm->tm_hour = -tm->tm_hour;
-						tm->tm_min = -tm->tm_min;
-						tm->tm_sec = -tm->tm_sec;
-						*fsec = -(*fsec);
+						/* flip the sign on time field */
+						if (itm_in->tm_usec == PG_INT64_MIN)
+							return DTERR_FIELD_OVERFLOW;
+						itm_in->tm_usec = -itm_in->tm_usec;
 					}
 
 					/*
@@ -3204,7 +3304,7 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 				}
 
 				errno = 0;
-				val = strtoint(field[i], &cp, 10);
+				val = strtoi64(field[i], &cp, 10);
 				if (errno == ERANGE)
 					return DTERR_FIELD_OVERFLOW;
 
@@ -3221,8 +3321,8 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 					type = DTK_MONTH;
 					if (*field[i] == '-')
 						val2 = -val2;
-					if (((double) val * MONTHS_PER_YEAR + val2) > INT_MAX ||
-						((double) val * MONTHS_PER_YEAR + val2) < INT_MIN)
+					if ((val * MONTHS_PER_YEAR + val2) > INT_MAX ||
+						(val * MONTHS_PER_YEAR + val2) < INT_MIN)
 						return DTERR_FIELD_OVERFLOW;
 					val = val * MONTHS_PER_YEAR + val2;
 					fval = 0;
@@ -3247,21 +3347,20 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 				switch (type)
 				{
 					case DTK_MICROSEC:
-						*fsec += rint(val + fval);
+						if (!AdjustMicroseconds(val, itm_in, 1, fval))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(MICROSECOND);
 						break;
 
 					case DTK_MILLISEC:
-						/* avoid overflowing the fsec field */
-						tm->tm_sec += val / 1000;
-						val -= (val / 1000) * 1000;
-						*fsec += rint((val + fval) * 1000);
+						if (!AdjustMicroseconds(val, itm_in, USECS_PER_MSEC, fval))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(MILLISECOND);
 						break;
 
 					case DTK_SECOND:
-						tm->tm_sec += val;
-						*fsec += rint(fval * 1000000);
+						if (!AdjustMicroseconds(val, itm_in, USECS_PER_SEC, fval))
+							return DTERR_FIELD_OVERFLOW;
 
 						/*
 						 * If any subseconds were specified, consider this
@@ -3274,57 +3373,71 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 						break;
 
 					case DTK_MINUTE:
-						tm->tm_min += val;
-						AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
+						if (!AdjustMicroseconds(val, itm_in, USECS_PER_MINUTE, fval))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(MINUTE);
 						break;
 
 					case DTK_HOUR:
-						tm->tm_hour += val;
-						AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
+						if (!AdjustMicroseconds(val, itm_in, USECS_PER_HOUR, fval))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(HOUR);
 						type = DTK_DAY; /* set for next field */
 						break;
 
 					case DTK_DAY:
-						tm->tm_mday += val;
-						AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+						if (val < INT_MIN || val > INT_MAX ||
+							!AdjustDays((int) val, itm_in, 1) ||
+							!AdjustFractMicroseconds(fval, itm_in, USECS_PER_DAY))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(DAY);
 						break;
 
 					case DTK_WEEK:
-						tm->tm_mday += val * 7;
-						AdjustFractDays(fval, tm, fsec, 7);
+						if (val < INT_MIN || val > INT_MAX ||
+							!AdjustDays((int) val, itm_in, 7) ||
+							!AdjustFractDays(fval, itm_in, 7))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(WEEK);
 						break;
 
 					case DTK_MONTH:
-						tm->tm_mon += val;
-						AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
+						if (val < INT_MIN || val > INT_MAX ||
+							!AdjustMonths((int) val, itm_in) ||
+							!AdjustFractDays(fval, itm_in, DAYS_PER_MONTH))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(MONTH);
 						break;
 
 					case DTK_YEAR:
-						tm->tm_year += val;
-						tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
+						if (val < INT_MIN || val > INT_MAX ||
+							!AdjustYears((int) val, itm_in, 1) ||
+							!AdjustFractMonths(fval, itm_in, 1))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(YEAR);
 						break;
 
 					case DTK_DECADE:
-						tm->tm_year += val * 10;
-						tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 10);
+						if (val < INT_MIN || val > INT_MAX ||
+							!AdjustYears((int) val, itm_in, YEARS_PER_DECADE) ||
+							!AdjustFractMonths(fval, itm_in, YEARS_PER_DECADE))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(DECADE);
 						break;
 
 					case DTK_CENTURY:
-						tm->tm_year += val * 100;
-						tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 100);
+						if (val < INT_MIN || val > INT_MAX ||
+							!AdjustYears((int) val, itm_in, YEARS_PER_CENTURY) ||
+							!AdjustFractMonths(fval, itm_in, YEARS_PER_CENTURY))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(CENTURY);
 						break;
 
 					case DTK_MILLENNIUM:
-						tm->tm_year += val * 1000;
-						tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 1000);
+						if (val < INT_MIN || val > INT_MAX ||
+							!AdjustYears((int) val, itm_in, YEARS_PER_MILLENNIUM) ||
+							!AdjustFractMonths(fval, itm_in, YEARS_PER_MILLENNIUM))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(MILLENNIUM);
 						break;
 
@@ -3335,7 +3448,7 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 
 			case DTK_STRING:
 			case DTK_SPECIAL:
-				type = DecodeUnits(i, field[i], &val);
+				type = DecodeUnits(i, field[i], &tval);
 				if (type == IGNORE_DTF)
 					continue;
 
@@ -3343,17 +3456,17 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 				switch (type)
 				{
 					case UNITS:
-						type = val;
+						type = tval;
 						break;
 
 					case AGO:
 						is_before = true;
-						type = val;
+						type = tval;
 						break;
 
 					case RESERV:
 						tmask = (DTK_DATE_M | DTK_TIME_M);
-						*dtype = val;
+						*dtype = tval;
 						break;
 
 					default:
@@ -3374,16 +3487,6 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 	if (fmask == 0)
 		return DTERR_BAD_FORMAT;
 
-	/* ensure fractional seconds are fractional */
-	if (*fsec != 0)
-	{
-		int			sec;
-
-		sec = *fsec / USECS_PER_SEC;
-		*fsec -= sec * USECS_PER_SEC;
-		tm->tm_sec += sec;
-	}
-
 	/*----------
 	 * The SQL standard defines the interval literal
 	 *	 '-1 1:00:00'
@@ -3420,33 +3523,30 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 			 * Rather than re-determining which field was field[0], just force
 			 * 'em all negative.
 			 */
-			if (*fsec > 0)
-				*fsec = -(*fsec);
-			if (tm->tm_sec > 0)
-				tm->tm_sec = -tm->tm_sec;
-			if (tm->tm_min > 0)
-				tm->tm_min = -tm->tm_min;
-			if (tm->tm_hour > 0)
-				tm->tm_hour = -tm->tm_hour;
-			if (tm->tm_mday > 0)
-				tm->tm_mday = -tm->tm_mday;
-			if (tm->tm_mon > 0)
-				tm->tm_mon = -tm->tm_mon;
-			if (tm->tm_year > 0)
-				tm->tm_year = -tm->tm_year;
+			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)
 	{
-		*fsec = -(*fsec);
-		tm->tm_sec = -tm->tm_sec;
-		tm->tm_min = -tm->tm_min;
-		tm->tm_hour = -tm->tm_hour;
-		tm->tm_mday = -tm->tm_mday;
-		tm->tm_mon = -tm->tm_mon;
-		tm->tm_year = -tm->tm_year;
+		if (itm_in->tm_usec == PG_INT64_MIN ||
+			itm_in->tm_mday == INT_MIN ||
+			itm_in->tm_mon == INT_MIN ||
+			itm_in->tm_year == INT_MIN)
+			return DTERR_FIELD_OVERFLOW;
+
+		itm_in->tm_usec = -itm_in->tm_usec;
+		itm_in->tm_mday = -itm_in->tm_mday;
+		itm_in->tm_mon = -itm_in->tm_mon;
+		itm_in->tm_year = -itm_in->tm_year;
 	}
 
 	return 0;
@@ -3460,26 +3560,37 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
  * Returns 0 or DTERR code.
  */
 static int
-ParseISO8601Number(char *str, char **endptr, int *ipart, double *fpart)
+ParseISO8601Number(char *str, char **endptr, int64 *ipart, double *fpart)
 {
-	double		val;
+	int sign = 1;
+	*ipart = 0;
+	*fpart = 0.0;
 
 	if (!(isdigit((unsigned char) *str) || *str == '-' || *str == '.'))
 		return DTERR_BAD_FORMAT;
 	errno = 0;
-	val = strtod(str, endptr);
+
+	/* Parse sign if there is any */
+	if (*str == '-')
+	{
+		sign = -1;
+		str++;
+		*endptr = str;
+	}
+
+	/* Parse int64 part if there is any */
+	if (isdigit((unsigned char) **endptr))
+		*ipart = strtoi64(*endptr, endptr, 10) * sign;
+
+	/* Parse decimal part if there is any */
+	if (**endptr == '.') {
+		*fpart = strtod(*endptr, endptr) * sign;
+	}
+
 	/* did we not see anything that looks like a double? */
 	if (*endptr == str || errno != 0)
 		return DTERR_BAD_FORMAT;
-	/* watch out for overflow */
-	if (val < INT_MIN || val > INT_MAX)
-		return DTERR_FIELD_OVERFLOW;
-	/* be very sure we truncate towards zero (cf dtrunc()) */
-	if (val >= 0)
-		*ipart = (int) floor(val);
-	else
-		*ipart = (int) -floor(-val);
-	*fpart = val - *ipart;
+
 	return 0;
 }
 
@@ -3508,21 +3619,20 @@ ISO8601IntegerWidth(char *fieldstart)
  * Returns 0 if successful, DTERR code if bogus input detected.
  * Note: error code should be DTERR_BAD_FORMAT if input doesn't look like
  * ISO8601, otherwise this could cause unexpected error messages.
- * dtype, tm, fsec are output parameters.
+ * dtype and itm_in are output parameters.
  *
  *	A couple exceptions from the spec:
  *	 - a week field ('W') may coexist with other units
  *	 - allows decimals in fields other than the least significant unit.
  */
 int
-DecodeISO8601Interval(char *str,
-					  int *dtype, struct pg_tm *tm, fsec_t *fsec)
+DecodeISO8601Interval(char *str, int *dtype, struct pg_itm_in *itm_in)
 {
 	bool		datepart = true;
 	bool		havefield = false;
 
 	*dtype = DTK_DELTA;
-	ClearPgTm(tm, fsec);
+	ClearPgItmIn(itm_in);
 
 	if (strlen(str) < 2 || str[0] != 'P')
 		return DTERR_BAD_FORMAT;
@@ -3531,7 +3641,7 @@ DecodeISO8601Interval(char *str,
 	while (*str)
 	{
 		char	   *fieldstart;
-		int			val;
+		int64		val;
 		double		fval;
 		char		unit;
 		int			dterr;
@@ -3557,32 +3667,50 @@ DecodeISO8601Interval(char *str,
 
 		if (datepart)
 		{
+			/* Date parts cannot be bigger than int */
+			if (val < INT_MIN || val > INT_MAX)
+				return DTERR_FIELD_OVERFLOW;
 			switch (unit)		/* before T: Y M W D */
 			{
 				case 'Y':
-					tm->tm_year += val;
-					tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
+					/* 
+					 * Not possible to overflow years in this format since
+					 * there's no year aliases and can't have fractional
+					 * years
+					 */
+					(void) AdjustYears((int) val, itm_in, 1);
+					if (!AdjustFractMonths(fval, itm_in, 1))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case 'M':
-					tm->tm_mon += val;
-					AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
+					if (!AdjustMonths((int) val, itm_in) ||
+						!AdjustFractDays(fval, itm_in, DAYS_PER_MONTH))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case 'W':
-					tm->tm_mday += val * 7;
-					AdjustFractDays(fval, tm, fsec, 7);
+					if (!AdjustDays((int) val, itm_in, 7) ||
+						!AdjustFractDays(fval, itm_in, 7))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case 'D':
-					tm->tm_mday += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+					if (!AdjustDays((int) val, itm_in, 1) ||
+						!AdjustFractMicroseconds(fval, itm_in, USECS_PER_DAY))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case 'T':		/* ISO 8601 4.4.3.3 Alternative Format / Basic */
 				case '\0':
 					if (ISO8601IntegerWidth(fieldstart) == 8 && !havefield)
 					{
-						tm->tm_year += val / 10000;
-						tm->tm_mon += (val / 100) % 100;
-						tm->tm_mday += val % 100;
-						AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+						/* None of the date fields can overflow because val is
+						 * within the bounds of an int from the check above
+						 */
+						(void) AdjustYears((int) val / 10000, itm_in, 1);
+						(void) AdjustMonths((int) (val / 100) % 100, itm_in);
+						(void) AdjustDays((int) val % 100, itm_in, 1);
+						/* Can't overflow because date fields must come before
+						 * time fields
+						 */
+						(void) AdjustFractMicroseconds(fval, itm_in, USECS_PER_DAY);
 						if (unit == '\0')
 							return 0;
 						datepart = false;
@@ -3596,8 +3724,14 @@ DecodeISO8601Interval(char *str,
 					if (havefield)
 						return DTERR_BAD_FORMAT;
 
-					tm->tm_year += val;
-					tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
+					/* 
+					 * Not possible to overflow years in this format since
+					 * there's no year aliases and can't have fractional
+					 * years
+					 */
+					(void) AdjustYears((int) val, itm_in, 1);
+					/* Can't overflow because years must come before months */
+					(void) AdjustFractMonths(fval, itm_in, 1);
 					if (unit == '\0')
 						return 0;
 					if (unit == 'T')
@@ -3610,8 +3744,10 @@ DecodeISO8601Interval(char *str,
 					dterr = ParseISO8601Number(str, &str, &val, &fval);
 					if (dterr)
 						return dterr;
-					tm->tm_mon += val;
-					AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
+					if (!AdjustMonths((int) val, itm_in))
+						return DTERR_FIELD_OVERFLOW;
+					/* Can't overflow because months must come before days */
+					(void) AdjustFractDays(fval, itm_in, DAYS_PER_MONTH);
 					if (*str == '\0')
 						return 0;
 					if (*str == 'T')
@@ -3627,8 +3763,10 @@ DecodeISO8601Interval(char *str,
 					dterr = ParseISO8601Number(str, &str, &val, &fval);
 					if (dterr)
 						return dterr;
-					tm->tm_mday += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+					if (!AdjustDays((int) val, itm_in, 1))
+						return DTERR_FIELD_OVERFLOW;
+					/* Can't overflow because days must come before time fields */
+					(void) AdjustFractMicroseconds(fval, itm_in, USECS_PER_DAY);
 					if (*str == '\0')
 						return 0;
 					if (*str == 'T')
@@ -3648,24 +3786,25 @@ DecodeISO8601Interval(char *str,
 			switch (unit)		/* after T: H M S */
 			{
 				case 'H':
-					tm->tm_hour += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
+					if (!AdjustMicroseconds(val, itm_in, USECS_PER_HOUR, fval))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case 'M':
-					tm->tm_min += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
+					if (!AdjustMicroseconds(val, itm_in, USECS_PER_MINUTE, fval))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case 'S':
-					tm->tm_sec += val;
-					AdjustFractSeconds(fval, tm, fsec, 1);
+					if (!AdjustMicroseconds(val, itm_in, USECS_PER_SEC, fval))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case '\0':		/* ISO 8601 4.4.3.3 Alternative Format */
 					if (ISO8601IntegerWidth(fieldstart) == 6 && !havefield)
 					{
-						tm->tm_hour += val / 10000;
-						tm->tm_min += (val / 100) % 100;
-						tm->tm_sec += val % 100;
-						AdjustFractSeconds(fval, tm, fsec, 1);
+						if (!AdjustMicroseconds(val / 10000, itm_in, USECS_PER_HOUR, 0) ||
+							!AdjustMicroseconds((val / 100) % 100, itm_in, USECS_PER_MINUTE, 0) ||
+							!AdjustMicroseconds(val % 100, itm_in, USECS_PER_SEC, 0) ||
+							!AdjustFractMicroseconds(fval, itm_in, 1))
+							return DTERR_FIELD_OVERFLOW;
 						return 0;
 					}
 					/* Else fall through to extended alternative format */
@@ -3675,16 +3814,16 @@ DecodeISO8601Interval(char *str,
 					if (havefield)
 						return DTERR_BAD_FORMAT;
 
-					tm->tm_hour += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
+					if (!AdjustMicroseconds(val, itm_in, USECS_PER_HOUR, fval))
+						return DTERR_FIELD_OVERFLOW;
 					if (unit == '\0')
 						return 0;
 
 					dterr = ParseISO8601Number(str, &str, &val, &fval);
 					if (dterr)
 						return dterr;
-					tm->tm_min += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
+					if (!AdjustMicroseconds(val, itm_in, USECS_PER_MINUTE, fval))
+						return DTERR_FIELD_OVERFLOW;
 					if (*str == '\0')
 						return 0;
 					if (*str != ':')
@@ -3694,8 +3833,8 @@ DecodeISO8601Interval(char *str,
 					dterr = ParseISO8601Number(str, &str, &val, &fval);
 					if (dterr)
 						return dterr;
-					tm->tm_sec += val;
-					AdjustFractSeconds(fval, tm, fsec, 1);
+					if (!AdjustMicroseconds(val, itm_in, USECS_PER_SEC, fval))
+						return DTERR_FIELD_OVERFLOW;
 					if (*str == '\0')
 						return 0;
 					return DTERR_BAD_FORMAT;
@@ -4166,25 +4305,25 @@ EncodeDateTime(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, const char
 
 /* Append an ISO-8601-style interval field, but only if value isn't zero */
 static char *
-AddISO8601IntPart(char *cp, int value, char units)
+AddISO8601IntPart(char *cp, int64 value, char units)
 {
 	if (value == 0)
 		return cp;
-	sprintf(cp, "%d%c", value, units);
+	sprintf(cp, "%lld%c", (long long) value, units);
 	return cp + strlen(cp);
 }
 
 /* Append a postgres-style interval field, but only if value isn't zero */
 static char *
-AddPostgresIntPart(char *cp, int value, const char *units,
+AddPostgresIntPart(char *cp, int64 value, const char *units,
 				   bool *is_zero, bool *is_before)
 {
 	if (value == 0)
 		return cp;
-	sprintf(cp, "%s%s%d %s%s",
+	sprintf(cp, "%s%s%lld %s%s",
 			(!*is_zero) ? " " : "",
 			(*is_before && value > 0) ? "+" : "",
-			value,
+			(long long) value,
 			units,
 			(value != 1) ? "s" : "");
 
@@ -4199,7 +4338,7 @@ AddPostgresIntPart(char *cp, int value, const char *units,
 
 /* Append a verbose-style interval field, but only if value isn't zero */
 static char *
-AddVerboseIntPart(char *cp, int value, const char *units,
+AddVerboseIntPart(char *cp, int64 value, const char *units,
 				  bool *is_zero, bool *is_before)
 {
 	if (value == 0)
@@ -4208,11 +4347,11 @@ AddVerboseIntPart(char *cp, int value, const char *units,
 	if (*is_zero)
 	{
 		*is_before = (value < 0);
-		value = abs(value);
+		value = Abs(value);
 	}
 	else if (*is_before)
 		value = -value;
-	sprintf(cp, " %d %s%s", value, units, (value == 1) ? "" : "s");
+	sprintf(cp, " %lld %s%s", (long long) value, units, (value == 1) ? "" : "s");
 	*is_zero = false;
 	return cp + strlen(cp);
 }
@@ -4238,15 +4377,16 @@ AddVerboseIntPart(char *cp, int value, const char *units,
  * "day-time literal"s (that look like ('4 5:6:7')
  */
 void
-EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
+EncodeInterval(struct pg_itm *itm, int style, char *str)
 {
 	char	   *cp = str;
-	int			year = tm->tm_year;
-	int			mon = tm->tm_mon;
-	int			mday = tm->tm_mday;
-	int			hour = tm->tm_hour;
-	int			min = tm->tm_min;
-	int			sec = tm->tm_sec;
+	int64		year = (int64) itm->tm_year;
+	int64		mon = (int64) itm->tm_mon;
+	int64		mday = (int64) itm->tm_mday;
+	int64		hour = itm->tm_hour;
+	int			min = itm->tm_min;
+	int			sec = itm->tm_sec;
+	int 		usec = itm->tm_usec;
 	bool		is_before = false;
 	bool		is_zero = true;
 
@@ -4263,13 +4403,13 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
 			{
 				bool		has_negative = year < 0 || mon < 0 ||
 				mday < 0 || hour < 0 ||
-				min < 0 || sec < 0 || fsec < 0;
+				min < 0 || sec < 0 || usec < 0;
 				bool		has_positive = year > 0 || mon > 0 ||
 				mday > 0 || hour > 0 ||
-				min > 0 || sec > 0 || fsec > 0;
+				min > 0 || sec > 0 || usec > 0;
 				bool		has_year_month = year != 0 || mon != 0;
 				bool		has_day_time = mday != 0 || hour != 0 ||
-				min != 0 || sec != 0 || fsec != 0;
+				min != 0 || sec != 0 || usec != 0;
 				bool		has_day = mday != 0;
 				bool		sql_standard_value = !(has_negative && has_positive) &&
 				!(has_year_month && has_day_time);
@@ -4287,7 +4427,7 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
 					hour = -hour;
 					min = -min;
 					sec = -sec;
-					fsec = -fsec;
+					usec = -usec;
 				}
 
 				if (!has_negative && !has_positive)
@@ -4304,32 +4444,34 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
 					char		year_sign = (year < 0 || mon < 0) ? '-' : '+';
 					char		day_sign = (mday < 0) ? '-' : '+';
 					char		sec_sign = (hour < 0 || min < 0 ||
-											sec < 0 || fsec < 0) ? '-' : '+';
+											sec < 0 || usec < 0) ? '-' : '+';
 
-					sprintf(cp, "%c%d-%d %c%d %c%d:%02d:",
-							year_sign, abs(year), abs(mon),
-							day_sign, abs(mday),
-							sec_sign, abs(hour), abs(min));
+					sprintf(cp, "%c%lld-%lld %c%lld %c%lld:%02d:",
+							year_sign, (long long) Abs(year), (long long) Abs(mon),
+							day_sign, (long long) Abs(mday),
+							sec_sign, (long long) Abs(hour), abs(min));
 					cp += strlen(cp);
-					cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+					cp = AppendSeconds(cp, sec, usec, MAX_INTERVAL_PRECISION, true);
 					*cp = '\0';
 				}
 				else if (has_year_month)
 				{
-					sprintf(cp, "%d-%d", year, mon);
+					sprintf(cp, "%lld-%lld",
+							(long long) year, (long long) mon);
 				}
 				else if (has_day)
 				{
-					sprintf(cp, "%d %d:%02d:", mday, hour, min);
+					sprintf(cp, "%lld %lld:%02d:",
+							(long long) mday, (long long) hour, min);
 					cp += strlen(cp);
-					cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+					cp = AppendSeconds(cp, sec, usec, MAX_INTERVAL_PRECISION, true);
 					*cp = '\0';
 				}
 				else
 				{
-					sprintf(cp, "%d:%02d:", hour, min);
+					sprintf(cp, "%lld:%02d:", (long long) hour, min);
 					cp += strlen(cp);
-					cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+					cp = AppendSeconds(cp, sec, usec, MAX_INTERVAL_PRECISION, true);
 					*cp = '\0';
 				}
 			}
@@ -4339,7 +4481,7 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
 		case INTSTYLE_ISO_8601:
 			/* special-case zero to avoid printing nothing */
 			if (year == 0 && mon == 0 && mday == 0 &&
-				hour == 0 && min == 0 && sec == 0 && fsec == 0)
+				hour == 0 && min == 0 && sec == 0 && usec == 0)
 			{
 				sprintf(cp, "PT0S");
 				break;
@@ -4348,15 +4490,15 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
 			cp = AddISO8601IntPart(cp, year, 'Y');
 			cp = AddISO8601IntPart(cp, mon, 'M');
 			cp = AddISO8601IntPart(cp, mday, 'D');
-			if (hour != 0 || min != 0 || sec != 0 || fsec != 0)
+			if (hour != 0 || min != 0 || sec != 0 || usec != 0)
 				*cp++ = 'T';
 			cp = AddISO8601IntPart(cp, hour, 'H');
 			cp = AddISO8601IntPart(cp, min, 'M');
-			if (sec != 0 || fsec != 0)
+			if (sec != 0 || usec != 0)
 			{
-				if (sec < 0 || fsec < 0)
+				if (sec < 0 || usec < 0)
 					*cp++ = '-';
-				cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
+				cp = AppendSeconds(cp, sec, usec, MAX_INTERVAL_PRECISION, false);
 				*cp++ = 'S';
 				*cp++ = '\0';
 			}
@@ -4373,16 +4515,16 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
 			 */
 			cp = AddPostgresIntPart(cp, mon, "mon", &is_zero, &is_before);
 			cp = AddPostgresIntPart(cp, mday, "day", &is_zero, &is_before);
-			if (is_zero || hour != 0 || min != 0 || sec != 0 || fsec != 0)
+			if (is_zero || hour != 0 || min != 0 || sec != 0 || usec != 0)
 			{
-				bool		minus = (hour < 0 || min < 0 || sec < 0 || fsec < 0);
+				bool		minus = (hour < 0 || min < 0 || sec < 0 || usec < 0);
 
-				sprintf(cp, "%s%s%02d:%02d:",
+				sprintf(cp, "%s%s%02lld:%02d:",
 						is_zero ? "" : " ",
 						(minus ? "-" : (is_before ? "+" : "")),
-						abs(hour), abs(min));
+						(long long) Abs(hour), abs(min));
 				cp += strlen(cp);
-				cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+				cp = AppendSeconds(cp, sec, usec, MAX_INTERVAL_PRECISION, true);
 				*cp = '\0';
 			}
 			break;
@@ -4397,10 +4539,10 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
 			cp = AddVerboseIntPart(cp, mday, "day", &is_zero, &is_before);
 			cp = AddVerboseIntPart(cp, hour, "hour", &is_zero, &is_before);
 			cp = AddVerboseIntPart(cp, min, "min", &is_zero, &is_before);
-			if (sec != 0 || fsec != 0)
+			if (sec != 0 || usec != 0)
 			{
 				*cp++ = ' ';
-				if (sec < 0 || (sec == 0 && fsec < 0))
+				if (sec < 0 || (sec == 0 && usec < 0))
 				{
 					if (is_zero)
 						is_before = true;
@@ -4409,10 +4551,10 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
 				}
 				else if (is_before)
 					*cp++ = '-';
-				cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
+				cp = AppendSeconds(cp, sec, usec, MAX_INTERVAL_PRECISION, false);
 				/* We output "ago", not negatives, so use abs(). */
 				sprintf(cp, " sec%s",
-						(abs(sec) != 1 || fsec != 0) ? "s" : "");
+						(abs(sec) != 1 || usec != 0) ? "s" : "");
 				is_zero = false;
 			}
 			/* identically zero? then put in a unitless zero... */
@@ -4668,7 +4810,7 @@ pg_timezone_abbrevs(PG_FUNCTION_ARGS)
 	int			gmtoffset;
 	bool		is_dst;
 	unsigned char *p;
-	struct pg_tm tm;
+	struct pg_itm_in itm_in;
 	Interval   *resInterval;
 
 	/* stuff done only on the first call of the function */
@@ -4762,10 +4904,10 @@ pg_timezone_abbrevs(PG_FUNCTION_ARGS)
 	values[0] = CStringGetTextDatum(buffer);
 
 	/* Convert offset (in seconds) to an interval */
-	MemSet(&tm, 0, sizeof(struct pg_tm));
-	tm.tm_sec = gmtoffset;
+	MemSet(&itm_in, 0, sizeof(struct pg_itm_in));
+	itm_in.tm_usec = (int64) gmtoffset * USECS_PER_SEC;
 	resInterval = (Interval *) palloc(sizeof(Interval));
-	tm2interval(&tm, 0, resInterval);
+	itmin2interval(&itm_in, resInterval);
 	values[1] = IntervalPGetDatum(resInterval);
 
 	values[2] = BoolGetDatum(is_dst);
@@ -4795,7 +4937,7 @@ pg_timezone_names(PG_FUNCTION_ARGS)
 	fsec_t		fsec;
 	const char *tzn;
 	Interval   *resInterval;
-	struct pg_tm itm;
+	struct pg_itm_in itm_in;
 
 	SetSingleFuncCall(fcinfo, 0);
 
@@ -4831,10 +4973,10 @@ pg_timezone_names(PG_FUNCTION_ARGS)
 		values[0] = CStringGetTextDatum(pg_get_timezone_name(tz));
 		values[1] = CStringGetTextDatum(tzn ? tzn : "");
 
-		MemSet(&itm, 0, sizeof(struct pg_tm));
-		itm.tm_sec = -tzoff;
+		MemSet(&itm_in, 0, sizeof(struct pg_itm_in));
+		itm_in.tm_usec = (int64) -tzoff * USECS_PER_SEC;
 		resInterval = (Interval *) palloc(sizeof(Interval));
-		tm2interval(&itm, 0, resInterval);
+		itmin2interval(&itm_in, resInterval);
 		values[2] = IntervalPGetDatum(resInterval);
 
 		values[3] = BoolGetDatum(tm.tm_isdst > 0);
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index ed698f788d..4db0860ad3 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -496,6 +496,10 @@ typedef struct
 typedef struct TmToChar
 {
 	struct pg_tm tm;			/* classic 'tm' struct */
+	/* Different date/time types have different requirements on the size of the
+	 * hour field. So we take in a separate int64 hour field.
+	 */
+	int64		tm_hour;		/* hours */
 	fsec_t		fsec;			/* fractional seconds */
 	const char *tzn;			/* timezone */
 } TmToChar;
@@ -2655,6 +2659,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 	FormatNode *n;
 	char	   *s;
 	struct pg_tm *tm = &in->tm;
+	int64 tm_hour = in->tm_hour;
 	int			i;
 
 	/* cache localized days and months */
@@ -2674,25 +2679,25 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 		{
 			case DCH_A_M:
 			case DCH_P_M:
-				strcpy(s, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
+				strcpy(s, (tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
 					   ? P_M_STR : A_M_STR);
 				s += strlen(s);
 				break;
 			case DCH_AM:
 			case DCH_PM:
-				strcpy(s, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
+				strcpy(s, (tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
 					   ? PM_STR : AM_STR);
 				s += strlen(s);
 				break;
 			case DCH_a_m:
 			case DCH_p_m:
-				strcpy(s, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
+				strcpy(s, (tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
 					   ? p_m_STR : a_m_STR);
 				s += strlen(s);
 				break;
 			case DCH_am:
 			case DCH_pm:
-				strcpy(s, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
+				strcpy(s, (tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
 					   ? pm_STR : am_STR);
 				s += strlen(s);
 				break;
@@ -2703,16 +2708,16 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 				 * display time as shown on a 12-hour clock, even for
 				 * intervals
 				 */
-				sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : (tm->tm_hour >= 0) ? 2 : 3,
-						tm->tm_hour % (HOURS_PER_DAY / 2) == 0 ? HOURS_PER_DAY / 2 :
-						tm->tm_hour % (HOURS_PER_DAY / 2));
+				sprintf(s, "%0*lld", S_FM(n->suffix) ? 0 : (tm_hour >= 0) ? 2 : 3,
+						tm_hour % (HOURS_PER_DAY / 2) == 0 ? (long long) HOURS_PER_DAY / 2 :
+						(long long) tm_hour % (HOURS_PER_DAY / 2));
 				if (S_THth(n->suffix))
 					str_numth(s, s, S_TH_TYPE(n->suffix));
 				s += strlen(s);
 				break;
 			case DCH_HH24:
-				sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : (tm->tm_hour >= 0) ? 2 : 3,
-						tm->tm_hour);
+				sprintf(s, "%0*lld", S_FM(n->suffix) ? 0 : (tm_hour >= 0) ? 2 : 3,
+						(long long) tm_hour);
 				if (S_THth(n->suffix))
 					str_numth(s, s, S_TH_TYPE(n->suffix));
 				s += strlen(s);
@@ -2760,7 +2765,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 				break;
 #undef DCH_to_char_fsec
 			case DCH_SSSS:
-				sprintf(s, "%d", tm->tm_hour * SECS_PER_HOUR +
+				sprintf(s, "%lld", (long long) tm_hour * SECS_PER_HOUR +
 						tm->tm_min * SECS_PER_MINUTE +
 						tm->tm_sec);
 				if (S_THth(n->suffix))
@@ -4106,6 +4111,7 @@ timestamp_to_char(PG_FUNCTION_ARGS)
 		ereport(ERROR,
 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 				 errmsg("timestamp out of range")));
+	tmtc.tm_hour = (int64) tm->tm_hour;
 
 	thisdate = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday);
 	tm->tm_wday = (thisdate + 1) % 7;
@@ -4138,6 +4144,7 @@ timestamptz_to_char(PG_FUNCTION_ARGS)
 		ereport(ERROR,
 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 				 errmsg("timestamp out of range")));
+	tmtc.tm_hour = (int64) tm->tm_hour;
 
 	thisdate = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday);
 	tm->tm_wday = (thisdate + 1) % 7;
@@ -4162,6 +4169,8 @@ interval_to_char(PG_FUNCTION_ARGS)
 			   *res;
 	TmToChar	tmtc;
 	struct pg_tm *tm;
+	struct pg_itm tt,
+			*itm = &tt;
 
 	if (VARSIZE_ANY_EXHDR(fmt) <= 0)
 		PG_RETURN_NULL();
@@ -4169,8 +4178,16 @@ interval_to_char(PG_FUNCTION_ARGS)
 	ZERO_tmtc(&tmtc);
 	tm = tmtcTm(&tmtc);
 
-	if (interval2tm(*it, tm, &tmtcFsec(&tmtc)) != 0)
+	if (interval2itm(*it, itm))
 		PG_RETURN_NULL();
+	tmtc.fsec = itm->tm_usec;
+	tmtc.tm_hour = itm->tm_hour;
+	tm->tm_sec = itm->tm_sec;
+	tm->tm_min = itm->tm_min;
+	tm->tm_mday = itm->tm_mday;
+	tm->tm_mon = itm->tm_mon;
+	tm->tm_year = itm->tm_year;
+	tm->tm_yday = itm->tm_yday;
 
 	/* wday is meaningless, yday approximates the total span in days */
 	tm->tm_yday = (tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon) * DAYS_PER_MONTH + tm->tm_mday;
diff --git a/src/backend/utils/adt/numutils.c b/src/backend/utils/adt/numutils.c
index cc3f95d399..8bdcdee328 100644
--- a/src/backend/utils/adt/numutils.c
+++ b/src/backend/utils/adt/numutils.c
@@ -602,3 +602,15 @@ pg_ultostr(char *str, uint32 value)
 
 	return str + len;
 }
+
+/*
+ * pg_ulltostr
+ *		See above
+ */
+char *
+pg_ulltostr(char *str, uint64 value)
+{
+	int			len = pg_ulltoa_n(value, str);
+
+	return str + len;
+}
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index ae36ff3328..77cc730b9d 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -888,9 +888,8 @@ interval_in(PG_FUNCTION_ARGS)
 #endif
 	int32		typmod = PG_GETARG_INT32(2);
 	Interval   *result;
-	fsec_t		fsec;
-	struct pg_tm tt,
-			   *tm = &tt;
+	struct pg_itm_in tt,
+			   *itm_in = &tt;
 	int			dtype;
 	int			nf;
 	int			range;
@@ -899,13 +898,10 @@ interval_in(PG_FUNCTION_ARGS)
 	int			ftype[MAXDATEFIELDS];
 	char		workbuf[256];
 
-	tm->tm_year = 0;
-	tm->tm_mon = 0;
-	tm->tm_mday = 0;
-	tm->tm_hour = 0;
-	tm->tm_min = 0;
-	tm->tm_sec = 0;
-	fsec = 0;
+	itm_in->tm_year = 0;
+	itm_in->tm_mon = 0;
+	itm_in->tm_mday = 0;
+	itm_in->tm_usec = 0;
 
 	if (typmod >= 0)
 		range = INTERVAL_RANGE(typmod);
@@ -916,12 +912,12 @@ interval_in(PG_FUNCTION_ARGS)
 						  ftype, MAXDATEFIELDS, &nf);
 	if (dterr == 0)
 		dterr = DecodeInterval(field, ftype, nf, range,
-							   &dtype, tm, &fsec);
+							   &dtype, itm_in);
 
 	/* if those functions think it's a bad format, try ISO8601 style */
 	if (dterr == DTERR_BAD_FORMAT)
 		dterr = DecodeISO8601Interval(str,
-									  &dtype, tm, &fsec);
+									  &dtype, itm_in);
 
 	if (dterr != 0)
 	{
@@ -935,7 +931,7 @@ interval_in(PG_FUNCTION_ARGS)
 	switch (dtype)
 	{
 		case DTK_DELTA:
-			if (tm2interval(tm, fsec, result) != 0)
+			if (itmin2interval(itm_in, result) != 0)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 						 errmsg("interval out of range")));
@@ -959,15 +955,14 @@ interval_out(PG_FUNCTION_ARGS)
 {
 	Interval   *span = PG_GETARG_INTERVAL_P(0);
 	char	   *result;
-	struct pg_tm tt,
-			   *tm = &tt;
-	fsec_t		fsec;
+	struct pg_itm tt,
+			   *itm = &tt;
 	char		buf[MAXDATELEN + 1];
 
-	if (interval2tm(*span, tm, &fsec) != 0)
+	if (interval2itm(*span, itm) != 0)
 		elog(ERROR, "could not convert interval to tm");
 
-	EncodeInterval(tm, fsec, IntervalStyle, buf);
+	EncodeInterval(itm, IntervalStyle, buf);
 
 	result = pstrdup(buf);
 	PG_RETURN_CSTRING(result);
@@ -1963,45 +1958,59 @@ tm2timestamp(struct pg_tm *tm, fsec_t fsec, int *tzp, Timestamp *result)
  * Convert an interval data type to a tm structure.
  */
 int
-interval2tm(Interval span, struct pg_tm *tm, fsec_t *fsec)
+interval2itm(Interval span, struct pg_itm *itm)
 {
 	TimeOffset	time;
 	TimeOffset	tfrac;
 
-	tm->tm_year = span.month / MONTHS_PER_YEAR;
-	tm->tm_mon = span.month % MONTHS_PER_YEAR;
-	tm->tm_mday = span.day;
+	itm->tm_year = span.month / MONTHS_PER_YEAR;
+	itm->tm_mon = span.month % MONTHS_PER_YEAR;
+	itm->tm_mday = span.day;
 	time = span.time;
 
 	tfrac = time / USECS_PER_HOUR;
 	time -= tfrac * USECS_PER_HOUR;
-	tm->tm_hour = tfrac;
-	if (!SAMESIGN(tm->tm_hour, tfrac))
+	itm->tm_hour = tfrac;
+	if (!SAMESIGN(itm->tm_hour, tfrac))
 		ereport(ERROR,
 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 				 errmsg("interval out of range")));
 	tfrac = time / USECS_PER_MINUTE;
 	time -= tfrac * USECS_PER_MINUTE;
-	tm->tm_min = tfrac;
+	itm->tm_min = tfrac;
 	tfrac = time / USECS_PER_SEC;
-	*fsec = time - (tfrac * USECS_PER_SEC);
-	tm->tm_sec = tfrac;
+	itm->tm_usec = time - (tfrac * USECS_PER_SEC);
+	itm->tm_sec = tfrac;
+
+	return 0;
+}
+
+int
+itm2interval(struct pg_itm *itm, Interval *span)
+{
+	int64		total_months = (int64) itm->tm_year * MONTHS_PER_YEAR + itm->tm_mon;
+
+	if (total_months > INT_MAX || total_months < INT_MIN)
+		return -1;
+	span->month = (int) total_months;
+	span->day = itm->tm_mday;
+	span->time = (((((itm->tm_hour * INT64CONST(60)) +
+					 itm->tm_min) * INT64CONST(60)) +
+				   itm->tm_sec) * USECS_PER_SEC) + itm->tm_usec;
 
 	return 0;
 }
 
 int
-tm2interval(struct pg_tm *tm, fsec_t fsec, Interval *span)
+itmin2interval(struct pg_itm_in *itm_in, Interval *span)
 {
-	double		total_months = (double) tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon;
+	double		total_months = (double) itm_in->tm_year * MONTHS_PER_YEAR + itm_in->tm_mon;
 
 	if (total_months > INT_MAX || total_months < INT_MIN)
 		return -1;
 	span->month = total_months;
-	span->day = tm->tm_mday;
-	span->time = (((((tm->tm_hour * INT64CONST(60)) +
-					 tm->tm_min) * INT64CONST(60)) +
-				   tm->tm_sec) * USECS_PER_SEC) + fsec;
+	span->day = itm_in->tm_mday;
+	span->time = itm_in->tm_usec;
 
 	return 0;
 }
@@ -3601,11 +3610,10 @@ timestamp_age(PG_FUNCTION_ARGS)
 	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
 	Timestamp	dt2 = PG_GETARG_TIMESTAMP(1);
 	Interval   *result;
-	fsec_t		fsec,
-				fsec1,
+	fsec_t		fsec1,
 				fsec2;
-	struct pg_tm tt,
-			   *tm = &tt;
+	struct pg_itm tt,
+			   *itm = &tt;
 	struct pg_tm tt1,
 			   *tm1 = &tt1;
 	struct pg_tm tt2,
@@ -3617,84 +3625,84 @@ timestamp_age(PG_FUNCTION_ARGS)
 		timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
 	{
 		/* form the symbolic difference */
-		fsec = fsec1 - fsec2;
-		tm->tm_sec = tm1->tm_sec - tm2->tm_sec;
-		tm->tm_min = tm1->tm_min - tm2->tm_min;
-		tm->tm_hour = tm1->tm_hour - tm2->tm_hour;
-		tm->tm_mday = tm1->tm_mday - tm2->tm_mday;
-		tm->tm_mon = tm1->tm_mon - tm2->tm_mon;
-		tm->tm_year = tm1->tm_year - tm2->tm_year;
+		itm->tm_usec = fsec1 - fsec2;
+		itm->tm_sec = tm1->tm_sec - tm2->tm_sec;
+		itm->tm_min = tm1->tm_min - tm2->tm_min;
+		itm->tm_hour = tm1->tm_hour - tm2->tm_hour;
+		itm->tm_mday = tm1->tm_mday - tm2->tm_mday;
+		itm->tm_mon = tm1->tm_mon - tm2->tm_mon;
+		itm->tm_year = tm1->tm_year - tm2->tm_year;
 
 		/* flip sign if necessary... */
 		if (dt1 < dt2)
 		{
-			fsec = -fsec;
-			tm->tm_sec = -tm->tm_sec;
-			tm->tm_min = -tm->tm_min;
-			tm->tm_hour = -tm->tm_hour;
-			tm->tm_mday = -tm->tm_mday;
-			tm->tm_mon = -tm->tm_mon;
-			tm->tm_year = -tm->tm_year;
+			itm->tm_usec = -itm->tm_usec;
+			itm->tm_sec = -itm->tm_sec;
+			itm->tm_min = -itm->tm_min;
+			itm->tm_hour = -itm->tm_hour;
+			itm->tm_mday = -itm->tm_mday;
+			itm->tm_mon = -itm->tm_mon;
+			itm->tm_year = -itm->tm_year;
 		}
 
 		/* propagate any negative fields into the next higher field */
-		while (fsec < 0)
+		while (itm->tm_usec < 0)
 		{
-			fsec += USECS_PER_SEC;
-			tm->tm_sec--;
+			itm->tm_usec += USECS_PER_SEC;
+			itm->tm_sec--;
 		}
 
-		while (tm->tm_sec < 0)
+		while (itm->tm_sec < 0)
 		{
-			tm->tm_sec += SECS_PER_MINUTE;
-			tm->tm_min--;
+			itm->tm_sec += SECS_PER_MINUTE;
+			itm->tm_min--;
 		}
 
-		while (tm->tm_min < 0)
+		while (itm->tm_min < 0)
 		{
-			tm->tm_min += MINS_PER_HOUR;
-			tm->tm_hour--;
+			itm->tm_min += MINS_PER_HOUR;
+			itm->tm_hour--;
 		}
 
-		while (tm->tm_hour < 0)
+		while (itm->tm_hour < 0)
 		{
-			tm->tm_hour += HOURS_PER_DAY;
-			tm->tm_mday--;
+			itm->tm_hour += HOURS_PER_DAY;
+			itm->tm_mday--;
 		}
 
-		while (tm->tm_mday < 0)
+		while (itm->tm_mday < 0)
 		{
 			if (dt1 < dt2)
 			{
-				tm->tm_mday += day_tab[isleap(tm1->tm_year)][tm1->tm_mon - 1];
-				tm->tm_mon--;
+				itm->tm_mday += day_tab[isleap(tm1->tm_year)][tm1->tm_mon - 1];
+				itm->tm_mon--;
 			}
 			else
 			{
-				tm->tm_mday += day_tab[isleap(tm2->tm_year)][tm2->tm_mon - 1];
-				tm->tm_mon--;
+				itm->tm_mday += day_tab[isleap(tm2->tm_year)][tm2->tm_mon - 1];
+				itm->tm_mon--;
 			}
 		}
 
-		while (tm->tm_mon < 0)
+		while (itm->tm_mon < 0)
 		{
-			tm->tm_mon += MONTHS_PER_YEAR;
-			tm->tm_year--;
+			itm->tm_mon += MONTHS_PER_YEAR;
+			itm->tm_year--;
 		}
 
 		/* recover sign if necessary... */
 		if (dt1 < dt2)
 		{
-			fsec = -fsec;
-			tm->tm_sec = -tm->tm_sec;
-			tm->tm_min = -tm->tm_min;
-			tm->tm_hour = -tm->tm_hour;
-			tm->tm_mday = -tm->tm_mday;
-			tm->tm_mon = -tm->tm_mon;
-			tm->tm_year = -tm->tm_year;
+			itm->tm_usec = -itm->tm_usec;
+			itm->tm_sec = -itm->tm_sec;
+			itm->tm_min = -itm->tm_min;
+			itm->tm_hour = -itm->tm_hour;
+			itm->tm_mday = -itm->tm_mday;
+			itm->tm_mon = -itm->tm_mon;
+			itm->tm_year = -itm->tm_year;
 		}
 
-		if (tm2interval(tm, fsec, result) != 0)
+		if (itm2interval(itm, result) != 0)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 					 errmsg("interval out of range")));
@@ -3720,11 +3728,10 @@ timestamptz_age(PG_FUNCTION_ARGS)
 	TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0);
 	TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1);
 	Interval   *result;
-	fsec_t		fsec,
-				fsec1,
+	fsec_t		fsec1,
 				fsec2;
-	struct pg_tm tt,
-			   *tm = &tt;
+	struct pg_itm tt,
+			   *itm = &tt;
 	struct pg_tm tt1,
 			   *tm1 = &tt1;
 	struct pg_tm tt2,
@@ -3738,69 +3745,69 @@ timestamptz_age(PG_FUNCTION_ARGS)
 		timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
 	{
 		/* form the symbolic difference */
-		fsec = fsec1 - fsec2;
-		tm->tm_sec = tm1->tm_sec - tm2->tm_sec;
-		tm->tm_min = tm1->tm_min - tm2->tm_min;
-		tm->tm_hour = tm1->tm_hour - tm2->tm_hour;
-		tm->tm_mday = tm1->tm_mday - tm2->tm_mday;
-		tm->tm_mon = tm1->tm_mon - tm2->tm_mon;
-		tm->tm_year = tm1->tm_year - tm2->tm_year;
+		itm->tm_usec = fsec1 - fsec2;
+		itm->tm_sec = tm1->tm_sec - tm2->tm_sec;
+		itm->tm_min = tm1->tm_min - tm2->tm_min;
+		itm->tm_hour = tm1->tm_hour - tm2->tm_hour;
+		itm->tm_mday = tm1->tm_mday - tm2->tm_mday;
+		itm->tm_mon = tm1->tm_mon - tm2->tm_mon;
+		itm->tm_year = tm1->tm_year - tm2->tm_year;
 
 		/* flip sign if necessary... */
 		if (dt1 < dt2)
 		{
-			fsec = -fsec;
-			tm->tm_sec = -tm->tm_sec;
-			tm->tm_min = -tm->tm_min;
-			tm->tm_hour = -tm->tm_hour;
-			tm->tm_mday = -tm->tm_mday;
-			tm->tm_mon = -tm->tm_mon;
-			tm->tm_year = -tm->tm_year;
+			itm->tm_usec = -itm->tm_usec;
+			itm->tm_sec = -itm->tm_sec;
+			itm->tm_min = -itm->tm_min;
+			itm->tm_hour = -itm->tm_hour;
+			itm->tm_mday = -itm->tm_mday;
+			itm->tm_mon = -itm->tm_mon;
+			itm->tm_year = -itm->tm_year;
 		}
 
 		/* propagate any negative fields into the next higher field */
-		while (fsec < 0)
+		while (itm->tm_usec < 0)
 		{
-			fsec += USECS_PER_SEC;
-			tm->tm_sec--;
+			itm->tm_usec += USECS_PER_SEC;
+			itm->tm_sec--;
 		}
 
-		while (tm->tm_sec < 0)
+		while (itm->tm_sec < 0)
 		{
-			tm->tm_sec += SECS_PER_MINUTE;
-			tm->tm_min--;
+			itm->tm_sec += SECS_PER_MINUTE;
+			itm->tm_min--;
 		}
 
-		while (tm->tm_min < 0)
+		while (itm->tm_min < 0)
 		{
-			tm->tm_min += MINS_PER_HOUR;
-			tm->tm_hour--;
+			itm->tm_min += MINS_PER_HOUR;
+			itm->tm_hour--;
 		}
 
-		while (tm->tm_hour < 0)
+		while (itm->tm_hour < 0)
 		{
-			tm->tm_hour += HOURS_PER_DAY;
-			tm->tm_mday--;
+			itm->tm_hour += HOURS_PER_DAY;
+			itm->tm_mday--;
 		}
 
-		while (tm->tm_mday < 0)
+		while (itm->tm_mday < 0)
 		{
 			if (dt1 < dt2)
 			{
-				tm->tm_mday += day_tab[isleap(tm1->tm_year)][tm1->tm_mon - 1];
-				tm->tm_mon--;
+				itm->tm_mday += day_tab[isleap(tm1->tm_year)][tm1->tm_mon - 1];
+				itm->tm_mon--;
 			}
 			else
 			{
-				tm->tm_mday += day_tab[isleap(tm2->tm_year)][tm2->tm_mon - 1];
-				tm->tm_mon--;
+				itm->tm_mday += day_tab[isleap(tm2->tm_year)][tm2->tm_mon - 1];
+				itm->tm_mon--;
 			}
 		}
 
-		while (tm->tm_mon < 0)
+		while (itm->tm_mon < 0)
 		{
-			tm->tm_mon += MONTHS_PER_YEAR;
-			tm->tm_year--;
+			itm->tm_mon += MONTHS_PER_YEAR;
+			itm->tm_year--;
 		}
 
 		/*
@@ -3810,16 +3817,16 @@ timestamptz_age(PG_FUNCTION_ARGS)
 		/* recover sign if necessary... */
 		if (dt1 < dt2)
 		{
-			fsec = -fsec;
-			tm->tm_sec = -tm->tm_sec;
-			tm->tm_min = -tm->tm_min;
-			tm->tm_hour = -tm->tm_hour;
-			tm->tm_mday = -tm->tm_mday;
-			tm->tm_mon = -tm->tm_mon;
-			tm->tm_year = -tm->tm_year;
+			itm->tm_usec = -itm->tm_usec;
+			itm->tm_sec = -itm->tm_sec;
+			itm->tm_min = -itm->tm_min;
+			itm->tm_hour = -itm->tm_hour;
+			itm->tm_mday = -itm->tm_mday;
+			itm->tm_mon = -itm->tm_mon;
+			itm->tm_year = -itm->tm_year;
 		}
 
-		if (tm2interval(tm, fsec, result) != 0)
+		if (itm2interval(itm, result) != 0)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 					 errmsg("interval out of range")));
@@ -4306,9 +4313,8 @@ interval_trunc(PG_FUNCTION_ARGS)
 	int			type,
 				val;
 	char	   *lowunits;
-	fsec_t		fsec;
-	struct pg_tm tt,
-			   *tm = &tt;
+	struct pg_itm tt,
+			   *itm = &tt;
 
 	result = (Interval *) palloc(sizeof(Interval));
 
@@ -4320,45 +4326,45 @@ interval_trunc(PG_FUNCTION_ARGS)
 
 	if (type == UNITS)
 	{
-		if (interval2tm(*interval, tm, &fsec) == 0)
+		if (interval2itm(*interval, itm) == 0)
 		{
 			switch (val)
 			{
 				case DTK_MILLENNIUM:
 					/* caution: C division may have negative remainder */
-					tm->tm_year = (tm->tm_year / 1000) * 1000;
+					itm->tm_year = (itm->tm_year / 1000) * 1000;
 					/* FALL THRU */
 				case DTK_CENTURY:
 					/* caution: C division may have negative remainder */
-					tm->tm_year = (tm->tm_year / 100) * 100;
+					itm->tm_year = (itm->tm_year / 100) * 100;
 					/* FALL THRU */
 				case DTK_DECADE:
 					/* caution: C division may have negative remainder */
-					tm->tm_year = (tm->tm_year / 10) * 10;
+					itm->tm_year = (itm->tm_year / 10) * 10;
 					/* FALL THRU */
 				case DTK_YEAR:
-					tm->tm_mon = 0;
+					itm->tm_mon = 0;
 					/* FALL THRU */
 				case DTK_QUARTER:
-					tm->tm_mon = 3 * (tm->tm_mon / 3);
+					itm->tm_mon = 3 * (itm->tm_mon / 3);
 					/* FALL THRU */
 				case DTK_MONTH:
-					tm->tm_mday = 0;
+					itm->tm_mday = 0;
 					/* FALL THRU */
 				case DTK_DAY:
-					tm->tm_hour = 0;
+					itm->tm_hour = 0;
 					/* FALL THRU */
 				case DTK_HOUR:
-					tm->tm_min = 0;
+					itm->tm_min = 0;
 					/* FALL THRU */
 				case DTK_MINUTE:
-					tm->tm_sec = 0;
+					itm->tm_sec = 0;
 					/* FALL THRU */
 				case DTK_SECOND:
-					fsec = 0;
+					itm->tm_usec = 0;
 					break;
 				case DTK_MILLISEC:
-					fsec = (fsec / 1000) * 1000;
+					itm->tm_usec = (itm->tm_usec / 1000) * 1000;
 					break;
 				case DTK_MICROSEC:
 					break;
@@ -4371,7 +4377,7 @@ interval_trunc(PG_FUNCTION_ARGS)
 							 (val == DTK_WEEK) ? errdetail("Months usually have fractional weeks.") : 0));
 			}
 
-			if (tm2interval(tm, fsec, result) != 0)
+			if (itm2interval(itm, result) != 0)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 						 errmsg("interval out of range")));
@@ -5189,9 +5195,8 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
 	int			type,
 				val;
 	char	   *lowunits;
-	fsec_t		fsec;
-	struct pg_tm tt,
-			   *tm = &tt;
+	struct pg_itm tt,
+			   *itm = &tt;
 
 	lowunits = downcase_truncate_identifier(VARDATA_ANY(units),
 											VARSIZE_ANY_EXHDR(units),
@@ -5203,12 +5208,12 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
 
 	if (type == UNITS)
 	{
-		if (interval2tm(*interval, tm, &fsec) == 0)
+		if (interval2itm(*interval, itm) == 0)
 		{
 			switch (val)
 			{
 				case DTK_MICROSEC:
-					intresult = tm->tm_sec * INT64CONST(1000000) + fsec;
+					intresult = itm->tm_sec * INT64CONST(1000000) + itm->tm_usec;
 					break;
 
 				case DTK_MILLISEC:
@@ -5217,9 +5222,9 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
 						 * tm->tm_sec * 1000 + fsec / 1000
 						 * = (tm->tm_sec * 1'000'000 + fsec) / 1000
 						 */
-						PG_RETURN_NUMERIC(int64_div_fast_to_numeric(tm->tm_sec * INT64CONST(1000000) + fsec, 3));
+						PG_RETURN_NUMERIC(int64_div_fast_to_numeric(itm->tm_sec * INT64CONST(1000000) + + itm->tm_usec, 3));
 					else
-						PG_RETURN_FLOAT8(tm->tm_sec * 1000.0 + fsec / 1000.0);
+						PG_RETURN_FLOAT8(itm->tm_sec * 1000.0 + itm->tm_usec / 1000.0);
 					break;
 
 				case DTK_SECOND:
@@ -5228,48 +5233,48 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
 						 * tm->tm_sec + fsec / 1'000'000
 						 * = (tm->tm_sec * 1'000'000 + fsec) / 1'000'000
 						 */
-						PG_RETURN_NUMERIC(int64_div_fast_to_numeric(tm->tm_sec * INT64CONST(1000000) + fsec, 6));
+						PG_RETURN_NUMERIC(int64_div_fast_to_numeric(itm->tm_sec * INT64CONST(1000000) + + itm->tm_usec, 6));
 					else
-						PG_RETURN_FLOAT8(tm->tm_sec + fsec / 1000000.0);
+						PG_RETURN_FLOAT8(itm->tm_sec + itm->tm_usec / 1000000.0);
 					break;
 
 				case DTK_MINUTE:
-					intresult = tm->tm_min;
+					intresult = itm->tm_min;
 					break;
 
 				case DTK_HOUR:
-					intresult = tm->tm_hour;
+					intresult = itm->tm_hour;
 					break;
 
 				case DTK_DAY:
-					intresult = tm->tm_mday;
+					intresult = itm->tm_mday;
 					break;
 
 				case DTK_MONTH:
-					intresult = tm->tm_mon;
+					intresult = itm->tm_mon;
 					break;
 
 				case DTK_QUARTER:
-					intresult = (tm->tm_mon / 3) + 1;
+					intresult = (itm->tm_mon / 3) + 1;
 					break;
 
 				case DTK_YEAR:
-					intresult = tm->tm_year;
+					intresult = itm->tm_year;
 					break;
 
 				case DTK_DECADE:
 					/* caution: C division may have negative remainder */
-					intresult = tm->tm_year / 10;
+					intresult = itm->tm_year / 10;
 					break;
 
 				case DTK_CENTURY:
 					/* caution: C division may have negative remainder */
-					intresult = tm->tm_year / 100;
+					intresult = itm->tm_year / 100;
 					break;
 
 				case DTK_MILLENNIUM:
 					/* caution: C division may have negative remainder */
-					intresult = tm->tm_year / 1000;
+					intresult = itm->tm_year / 1000;
 					break;
 
 				default:
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 5fa38d20d8..ba918a2b22 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -64,9 +64,11 @@ typedef struct
 /*
  * Assorted constants for datetime-related calculations
  */
-
-#define DAYS_PER_YEAR	365.25	/* assumes leap year every four years */
-#define MONTHS_PER_YEAR 12
+#define YEARS_PER_MILLENNIUM	1000
+#define YEARS_PER_CENTURY		100
+#define YEARS_PER_DECADE		10
+#define DAYS_PER_YEAR			365.25	/* assumes leap year every four years */
+#define MONTHS_PER_YEAR			12
 /*
  *	DAYS_PER_MONTH is very imprecise.  The more accurate value is
  *	365.2425/12 = 30.436875, or '30 days 10:29:06'.  Right now we only
@@ -92,6 +94,7 @@ typedef struct
 #define USECS_PER_HOUR	INT64CONST(3600000000)
 #define USECS_PER_MINUTE INT64CONST(60000000)
 #define USECS_PER_SEC	INT64CONST(1000000)
+#define USECS_PER_MSEC	INT64CONST(1000)
 
 /*
  * We allow numeric timezone offsets up to 15:59:59 either way from Greenwich.
diff --git a/src/include/pgtime.h b/src/include/pgtime.h
index 2977b13aab..708d1e0cc9 100644
--- a/src/include/pgtime.h
+++ b/src/include/pgtime.h
@@ -44,6 +44,31 @@ struct pg_tm
 	const char *tm_zone;
 };
 
+/* data structure to help decode intervals */
+struct pg_itm_in
+{
+	int64		tm_usec;
+	int			tm_mday;
+	int			tm_mon;			/* see above */
+	int			tm_year;		/* see above */
+};
+
+/* data structure to help encode and manipulate intervals */
+struct pg_itm
+{
+	/* time units smaller than hours only have values less than 1 hour and can
+	 * fit into an int
+	 */
+	int			tm_usec;
+	int			tm_sec;
+	int			tm_min;
+	int64		tm_hour;
+	int			tm_mday;
+	int			tm_mon;			/* see above */
+	int			tm_year;		/* see above */
+	int			tm_yday;
+};
+
 typedef struct pg_tz pg_tz;
 typedef struct pg_tzenum pg_tzenum;
 
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 666e545496..bf3e2a9336 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -53,6 +53,7 @@ extern int	pg_ltoa(int32 l, char *a);
 extern int	pg_lltoa(int64 ll, char *a);
 extern char *pg_ultostr_zeropad(char *str, uint32 value, int32 minwidth);
 extern char *pg_ultostr(char *str, uint32 value);
+extern char *pg_ulltostr(char *str, uint64 value);
 
 /* oid.c */
 extern oidvector *buildoidvector(const Oid *oids, int n);
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index 0d158f3e4b..d4d699c8f7 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -300,9 +300,9 @@ extern int	DecodeTimeOnly(char **field, int *ftype,
 						   int nf, int *dtype,
 						   struct pg_tm *tm, fsec_t *fsec, int *tzp);
 extern int	DecodeInterval(char **field, int *ftype, int nf, int range,
-						   int *dtype, struct pg_tm *tm, fsec_t *fsec);
-extern int	DecodeISO8601Interval(char *str,
-								  int *dtype, struct pg_tm *tm, fsec_t *fsec);
+						   int *dtype, struct pg_itm_in *tm);
+extern int	DecodeISO8601Interval(char *str, int *dtype,
+								  struct pg_itm_in *itm_in);
 
 extern void DateTimeParseError(int dterr, const char *str,
 							   const char *datatype) pg_attribute_noreturn();
@@ -315,7 +315,7 @@ extern int	DetermineTimeZoneAbbrevOffsetTS(TimestampTz ts, const char *abbr,
 extern void EncodeDateOnly(struct pg_tm *tm, int style, char *str);
 extern void EncodeTimeOnly(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, int style, char *str);
 extern void EncodeDateTime(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, const char *tzn, int style, char *str);
-extern void EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str);
+extern void EncodeInterval(struct pg_itm *itm, int style, char *str);
 extern void EncodeSpecialTimestamp(Timestamp dt, char *str);
 
 extern int	ValidateDate(int fmask, bool isjulian, bool is2digits, bool bc,
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index c1a74f8e2b..eea429dc5f 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -88,8 +88,9 @@ extern int	timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm,
 						 fsec_t *fsec, const char **tzn, pg_tz *attimezone);
 extern void dt2time(Timestamp dt, int *hour, int *min, int *sec, fsec_t *fsec);
 
-extern int	interval2tm(Interval span, struct pg_tm *tm, fsec_t *fsec);
-extern int	tm2interval(struct pg_tm *tm, fsec_t fsec, Interval *span);
+extern int	interval2itm(Interval span, struct pg_itm *itm);
+extern int	itm2interval(struct pg_itm *itm, Interval *span);
+extern int	itmin2interval(struct pg_itm_in *itm_in, Interval *span);
 
 extern Timestamp SetEpochTimestamp(void);
 extern void GetEpochTime(struct pg_tm *tm);
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 146f7c55d0..00ffe0e2be 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -1079,3 +1079,614 @@ SELECT extract(epoch from interval '1000000000 days');
  86400000000000.000000
 (1 row)
 
+-- test time fields using entire 64 bit microseconds range
+SELECT INTERVAL '2562047788.01521550194 hours';
+             interval              
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+SELECT INTERVAL '-2562047788.01521550222 hours';
+               interval                
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+SELECT INTERVAL '153722867280.912930117 minutes';
+             interval              
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+SELECT INTERVAL '-153722867280.912930133 minutes';
+               interval                
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+SELECT INTERVAL '9223372036854.775807 seconds';
+             interval              
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+SELECT INTERVAL '-9223372036854.775808 seconds';
+               interval                
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+SELECT INTERVAL '9223372036854775.807 milliseconds';
+             interval              
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+SELECT INTERVAL '-9223372036854775.808 milliseconds';
+               interval                
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+SELECT INTERVAL '9223372036854775807 microseconds';
+             interval              
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+SELECT INTERVAL '-9223372036854775808 microseconds';
+               interval                
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+SELECT INTERVAL 'PT2562047788H54.775807S';
+             interval              
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+SELECT INTERVAL 'PT-2562047788H-54.775808S';
+               interval                
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+SELECT INTERVAL 'PT2562047788:00:54.775807';
+             interval              
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+SELECT INTERVAL 'PT2562047788.0152155019444';
+             interval              
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+SELECT INTERVAL 'PT-2562047788.0152155022222';
+               interval                
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+-- overflow each date/time field
+SELECT INTERVAL '2147483648 years';
+ERROR:  interval field value out of range: "2147483648 years"
+LINE 1: SELECT INTERVAL '2147483648 years';
+                        ^
+SELECT INTERVAL '-2147483649 years';
+ERROR:  interval field value out of range: "-2147483649 years"
+LINE 1: SELECT INTERVAL '-2147483649 years';
+                        ^
+SELECT INTERVAL '2147483648 months';
+ERROR:  interval field value out of range: "2147483648 months"
+LINE 1: SELECT INTERVAL '2147483648 months';
+                        ^
+SELECT INTERVAL '-2147483649 months';
+ERROR:  interval field value out of range: "-2147483649 months"
+LINE 1: SELECT INTERVAL '-2147483649 months';
+                        ^
+SELECT INTERVAL '2147483648 days';
+ERROR:  interval field value out of range: "2147483648 days"
+LINE 1: SELECT INTERVAL '2147483648 days';
+                        ^
+SELECT INTERVAL '-2147483649 days';
+ERROR:  interval field value out of range: "-2147483649 days"
+LINE 1: SELECT INTERVAL '-2147483649 days';
+                        ^
+SELECT INTERVAL '2562047789 hours';
+ERROR:  interval field value out of range: "2562047789 hours"
+LINE 1: SELECT INTERVAL '2562047789 hours';
+                        ^
+SELECT INTERVAL '-2562047789 hours';
+ERROR:  interval field value out of range: "-2562047789 hours"
+LINE 1: SELECT INTERVAL '-2562047789 hours';
+                        ^
+SELECT INTERVAL '153722867281 minutes';
+ERROR:  interval field value out of range: "153722867281 minutes"
+LINE 1: SELECT INTERVAL '153722867281 minutes';
+                        ^
+SELECT INTERVAL '-153722867281 minutes';
+ERROR:  interval field value out of range: "-153722867281 minutes"
+LINE 1: SELECT INTERVAL '-153722867281 minutes';
+                        ^
+SELECT INTERVAL '9223372036855 seconds';
+ERROR:  interval field value out of range: "9223372036855 seconds"
+LINE 1: SELECT INTERVAL '9223372036855 seconds';
+                        ^
+SELECT INTERVAL '-9223372036855 seconds';
+ERROR:  interval field value out of range: "-9223372036855 seconds"
+LINE 1: SELECT INTERVAL '-9223372036855 seconds';
+                        ^
+SELECT INTERVAL '9223372036854777 millisecond';
+ERROR:  interval field value out of range: "9223372036854777 millisecond"
+LINE 1: SELECT INTERVAL '9223372036854777 millisecond';
+                        ^
+SELECT INTERVAL '-9223372036854777 millisecond';
+ERROR:  interval field value out of range: "-9223372036854777 millisecond"
+LINE 1: SELECT INTERVAL '-9223372036854777 millisecond';
+                        ^
+SELECT INTERVAL '9223372036854775808 microsecond';
+ERROR:  interval field value out of range: "9223372036854775808 microsecond"
+LINE 1: SELECT INTERVAL '9223372036854775808 microsecond';
+                        ^
+SELECT INTERVAL '-9223372036854775809 microsecond';
+ERROR:  interval field value out of range: "-9223372036854775809 microsecond"
+LINE 1: SELECT INTERVAL '-9223372036854775809 microsecond';
+                        ^
+SELECT INTERVAL 'P2147483648';
+ERROR:  interval field value out of range: "P2147483648"
+LINE 1: SELECT INTERVAL 'P2147483648';
+                        ^
+SELECT INTERVAL 'P-2147483649';
+ERROR:  interval field value out of range: "P-2147483649"
+LINE 1: SELECT INTERVAL 'P-2147483649';
+                        ^
+SELECT INTERVAL 'P1-2147483647-2147483647';
+ERROR:  interval out of range
+LINE 1: SELECT INTERVAL 'P1-2147483647-2147483647';
+                        ^
+SELECT INTERVAL 'PT2562047789';
+ERROR:  interval field value out of range: "PT2562047789"
+LINE 1: SELECT INTERVAL 'PT2562047789';
+                        ^
+SELECT INTERVAL 'PT-2562047789';
+ERROR:  interval field value out of range: "PT-2562047789"
+LINE 1: SELECT INTERVAL 'PT-2562047789';
+                        ^
+-- overflow with date/time unit aliases
+SELECT INTERVAL '2147483647 weeks';
+ERROR:  interval field value out of range: "2147483647 weeks"
+LINE 1: SELECT INTERVAL '2147483647 weeks';
+                        ^
+SELECT INTERVAL '-2147483648 weeks';
+ERROR:  interval field value out of range: "-2147483648 weeks"
+LINE 1: SELECT INTERVAL '-2147483648 weeks';
+                        ^
+SELECT INTERVAL '2147483647 decades';
+ERROR:  interval field value out of range: "2147483647 decades"
+LINE 1: SELECT INTERVAL '2147483647 decades';
+                        ^
+SELECT INTERVAL '-2147483648 decades';
+ERROR:  interval field value out of range: "-2147483648 decades"
+LINE 1: SELECT INTERVAL '-2147483648 decades';
+                        ^
+SELECT INTERVAL '2147483647 centuries';
+ERROR:  interval field value out of range: "2147483647 centuries"
+LINE 1: SELECT INTERVAL '2147483647 centuries';
+                        ^
+SELECT INTERVAL '-2147483648 centuries';
+ERROR:  interval field value out of range: "-2147483648 centuries"
+LINE 1: SELECT INTERVAL '-2147483648 centuries';
+                        ^
+SELECT INTERVAL '2147483647 millennium';
+ERROR:  interval field value out of range: "2147483647 millennium"
+LINE 1: SELECT INTERVAL '2147483647 millennium';
+                        ^
+SELECT INTERVAL '-2147483648 millennium';
+ERROR:  interval field value out of range: "-2147483648 millennium"
+LINE 1: SELECT INTERVAL '-2147483648 millennium';
+                        ^
+SELECT INTERVAL '1 week 2147483647 days';
+ERROR:  interval field value out of range: "1 week 2147483647 days"
+LINE 1: SELECT INTERVAL '1 week 2147483647 days';
+                        ^
+SELECT INTERVAL '-1 week -2147483648 days';
+ERROR:  interval field value out of range: "-1 week -2147483648 days"
+LINE 1: SELECT INTERVAL '-1 week -2147483648 days';
+                        ^
+SELECT INTERVAL '2147483647 days 1 week';
+ERROR:  interval field value out of range: "2147483647 days 1 week"
+LINE 1: SELECT INTERVAL '2147483647 days 1 week';
+                        ^
+SELECT INTERVAL '-2147483648 days -1 week';
+ERROR:  interval field value out of range: "-2147483648 days -1 week"
+LINE 1: SELECT INTERVAL '-2147483648 days -1 week';
+                        ^
+SELECT INTERVAL 'P1W2147483647D';
+ERROR:  interval field value out of range: "P1W2147483647D"
+LINE 1: SELECT INTERVAL 'P1W2147483647D';
+                        ^
+SELECT INTERVAL 'P-1W-2147483648D';
+ERROR:  interval field value out of range: "P-1W-2147483648D"
+LINE 1: SELECT INTERVAL 'P-1W-2147483648D';
+                        ^
+SELECT INTERVAL 'P2147483647D1W';
+ERROR:  interval field value out of range: "P2147483647D1W"
+LINE 1: SELECT INTERVAL 'P2147483647D1W';
+                        ^
+SELECT INTERVAL 'P-2147483648D-1W';
+ERROR:  interval field value out of range: "P-2147483648D-1W"
+LINE 1: SELECT INTERVAL 'P-2147483648D-1W';
+                        ^
+SELECT INTERVAL '1 decade 2147483647 years';
+ERROR:  interval field value out of range: "1 decade 2147483647 years"
+LINE 1: SELECT INTERVAL '1 decade 2147483647 years';
+                        ^
+SELECT INTERVAL '1 century 2147483647 years';
+ERROR:  interval field value out of range: "1 century 2147483647 years"
+LINE 1: SELECT INTERVAL '1 century 2147483647 years';
+                        ^
+SELECT INTERVAL '1 millennium 2147483647 years';
+ERROR:  interval field value out of range: "1 millennium 2147483647 years"
+LINE 1: SELECT INTERVAL '1 millennium 2147483647 years';
+                        ^
+SELECT INTERVAL '-1 decade -2147483648 years';
+ERROR:  interval field value out of range: "-1 decade -2147483648 years"
+LINE 1: SELECT INTERVAL '-1 decade -2147483648 years';
+                        ^
+SELECT INTERVAL '-1 century -2147483648 years';
+ERROR:  interval field value out of range: "-1 century -2147483648 years"
+LINE 1: SELECT INTERVAL '-1 century -2147483648 years';
+                        ^
+SELECT INTERVAL '-1 millennium -2147483648 years';
+ERROR:  interval field value out of range: "-1 millennium -2147483648 years"
+LINE 1: SELECT INTERVAL '-1 millennium -2147483648 years';
+                        ^
+SELECT INTERVAL '2147483647 years 1 decade';
+ERROR:  interval field value out of range: "2147483647 years 1 decade"
+LINE 1: SELECT INTERVAL '2147483647 years 1 decade';
+                        ^
+SELECT INTERVAL '2147483647 years 1 century';
+ERROR:  interval field value out of range: "2147483647 years 1 century"
+LINE 1: SELECT INTERVAL '2147483647 years 1 century';
+                        ^
+SELECT INTERVAL '2147483647 years 1 millennium';
+ERROR:  interval field value out of range: "2147483647 years 1 millennium"
+LINE 1: SELECT INTERVAL '2147483647 years 1 millennium';
+                        ^
+SELECT INTERVAL '-2147483648 years -1 decade';
+ERROR:  interval field value out of range: "-2147483648 years -1 decade"
+LINE 1: SELECT INTERVAL '-2147483648 years -1 decade';
+                        ^
+SELECT INTERVAL '-2147483648 years -1 century';
+ERROR:  interval field value out of range: "-2147483648 years -1 century"
+LINE 1: SELECT INTERVAL '-2147483648 years -1 century';
+                        ^
+SELECT INTERVAL '-2147483648 years -1 millennium';
+ERROR:  interval field value out of range: "-2147483648 years -1 millennium"
+LINE 1: SELECT INTERVAL '-2147483648 years -1 millennium';
+                        ^
+-- overflowing with fractional fields - postgres format
+SELECT INTERVAL '0.1 millennium 2147483647 months';
+ERROR:  interval field value out of range: "0.1 millennium 2147483647 months"
+LINE 1: SELECT INTERVAL '0.1 millennium 2147483647 months';
+                        ^
+SELECT INTERVAL '0.1 centuries 2147483647 months';
+ERROR:  interval field value out of range: "0.1 centuries 2147483647 months"
+LINE 1: SELECT INTERVAL '0.1 centuries 2147483647 months';
+                        ^
+SELECT INTERVAL '0.1 decades 2147483647 months';
+ERROR:  interval field value out of range: "0.1 decades 2147483647 months"
+LINE 1: SELECT INTERVAL '0.1 decades 2147483647 months';
+                        ^
+SELECT INTERVAL '0.1 yrs 2147483647 months';
+ERROR:  interval field value out of range: "0.1 yrs 2147483647 months"
+LINE 1: SELECT INTERVAL '0.1 yrs 2147483647 months';
+                        ^
+SELECT INTERVAL '-0.1 millennium -2147483648 months';
+ERROR:  interval field value out of range: "-0.1 millennium -2147483648 months"
+LINE 1: SELECT INTERVAL '-0.1 millennium -2147483648 months';
+                        ^
+SELECT INTERVAL '-0.1 centuries -2147483648 months';
+ERROR:  interval field value out of range: "-0.1 centuries -2147483648 months"
+LINE 1: SELECT INTERVAL '-0.1 centuries -2147483648 months';
+                        ^
+SELECT INTERVAL '-0.1 decades -2147483648 months';
+ERROR:  interval field value out of range: "-0.1 decades -2147483648 months"
+LINE 1: SELECT INTERVAL '-0.1 decades -2147483648 months';
+                        ^
+SELECT INTERVAL '-0.1 yrs -2147483648 months';
+ERROR:  interval field value out of range: "-0.1 yrs -2147483648 months"
+LINE 1: SELECT INTERVAL '-0.1 yrs -2147483648 months';
+                        ^
+SELECT INTERVAL '2147483647 months 0.1 millennium';
+ERROR:  interval field value out of range: "2147483647 months 0.1 millennium"
+LINE 1: SELECT INTERVAL '2147483647 months 0.1 millennium';
+                        ^
+SELECT INTERVAL '2147483647 months 0.1 centuries';
+ERROR:  interval field value out of range: "2147483647 months 0.1 centuries"
+LINE 1: SELECT INTERVAL '2147483647 months 0.1 centuries';
+                        ^
+SELECT INTERVAL '2147483647 months 0.1 decades';
+ERROR:  interval field value out of range: "2147483647 months 0.1 decades"
+LINE 1: SELECT INTERVAL '2147483647 months 0.1 decades';
+                        ^
+SELECT INTERVAL '2147483647 months 0.1 yrs';
+ERROR:  interval field value out of range: "2147483647 months 0.1 yrs"
+LINE 1: SELECT INTERVAL '2147483647 months 0.1 yrs';
+                        ^
+SELECT INTERVAL '-2147483648 months -0.1 millennium';
+ERROR:  interval field value out of range: "-2147483648 months -0.1 millennium"
+LINE 1: SELECT INTERVAL '-2147483648 months -0.1 millennium';
+                        ^
+SELECT INTERVAL '-2147483648 months -0.1 centuries';
+ERROR:  interval field value out of range: "-2147483648 months -0.1 centuries"
+LINE 1: SELECT INTERVAL '-2147483648 months -0.1 centuries';
+                        ^
+SELECT INTERVAL '-2147483648 months -0.1 decades';
+ERROR:  interval field value out of range: "-2147483648 months -0.1 decades"
+LINE 1: SELECT INTERVAL '-2147483648 months -0.1 decades';
+                        ^
+SELECT INTERVAL '-2147483648 months -0.1 yrs';
+ERROR:  interval field value out of range: "-2147483648 months -0.1 yrs"
+LINE 1: SELECT INTERVAL '-2147483648 months -0.1 yrs';
+                        ^
+SELECT INTERVAL '0.1 months 2147483647 days';
+ERROR:  interval field value out of range: "0.1 months 2147483647 days"
+LINE 1: SELECT INTERVAL '0.1 months 2147483647 days';
+                        ^
+SELECT INTERVAL '-0.1 months -2147483648 days';
+ERROR:  interval field value out of range: "-0.1 months -2147483648 days"
+LINE 1: SELECT INTERVAL '-0.1 months -2147483648 days';
+                        ^
+SELECT INTERVAL '2147483647 days 0.1 months';
+ERROR:  interval field value out of range: "2147483647 days 0.1 months"
+LINE 1: SELECT INTERVAL '2147483647 days 0.1 months';
+                        ^
+SELECT INTERVAL '-2147483648 days -0.1 months';
+ERROR:  interval field value out of range: "-2147483648 days -0.1 months"
+LINE 1: SELECT INTERVAL '-2147483648 days -0.1 months';
+                        ^
+SELECT INTERVAL '0.5 weeks 2147483647 days';
+ERROR:  interval field value out of range: "0.5 weeks 2147483647 days"
+LINE 1: SELECT INTERVAL '0.5 weeks 2147483647 days';
+                        ^
+SELECT INTERVAL '-0.5 weeks -2147483648 days';
+ERROR:  interval field value out of range: "-0.5 weeks -2147483648 days"
+LINE 1: SELECT INTERVAL '-0.5 weeks -2147483648 days';
+                        ^
+SELECT INTERVAL '2147483647 days 0.5 weeks';
+ERROR:  interval field value out of range: "2147483647 days 0.5 weeks"
+LINE 1: SELECT INTERVAL '2147483647 days 0.5 weeks';
+                        ^
+SELECT INTERVAL '-2147483648 days -0.5 weeks';
+ERROR:  interval field value out of range: "-2147483648 days -0.5 weeks"
+LINE 1: SELECT INTERVAL '-2147483648 days -0.5 weeks';
+                        ^
+SELECT INTERVAL '0.01 months 9223372036854775807 microseconds';
+ERROR:  interval field value out of range: "0.01 months 9223372036854775807 microseconds"
+LINE 1: SELECT INTERVAL '0.01 months 9223372036854775807 microsecond...
+                        ^
+SELECT INTERVAL '-0.01 months -9223372036854775808 microseconds';
+ERROR:  interval field value out of range: "-0.01 months -9223372036854775808 microseconds"
+LINE 1: SELECT INTERVAL '-0.01 months -9223372036854775808 microseco...
+                        ^
+SELECT INTERVAL '9223372036854775807 microseconds 0.01 months';
+ERROR:  interval field value out of range: "9223372036854775807 microseconds 0.01 months"
+LINE 1: SELECT INTERVAL '9223372036854775807 microseconds 0.01 month...
+                        ^
+SELECT INTERVAL '-9223372036854775808 microseconds -0.01 months';
+ERROR:  interval field value out of range: "-9223372036854775808 microseconds -0.01 months"
+LINE 1: SELECT INTERVAL '-9223372036854775808 microseconds -0.01 mon...
+                        ^
+SELECT INTERVAL '0.1 weeks 9223372036854775807 microseconds';
+ERROR:  interval field value out of range: "0.1 weeks 9223372036854775807 microseconds"
+LINE 1: SELECT INTERVAL '0.1 weeks 9223372036854775807 microseconds'...
+                        ^
+SELECT INTERVAL '-0.1 weeks -9223372036854775808 microseconds';
+ERROR:  interval field value out of range: "-0.1 weeks -9223372036854775808 microseconds"
+LINE 1: SELECT INTERVAL '-0.1 weeks -9223372036854775808 microsecond...
+                        ^
+SELECT INTERVAL '9223372036854775807 microseconds 0.1 weeks';
+ERROR:  interval field value out of range: "9223372036854775807 microseconds 0.1 weeks"
+LINE 1: SELECT INTERVAL '9223372036854775807 microseconds 0.1 weeks'...
+                        ^
+SELECT INTERVAL '-9223372036854775808 microseconds -0.1 weeks';
+ERROR:  interval field value out of range: "-9223372036854775808 microseconds -0.1 weeks"
+LINE 1: SELECT INTERVAL '-9223372036854775808 microseconds -0.1 week...
+                        ^
+SELECT INTERVAL '0.1 days 9223372036854775807 microseconds';
+ERROR:  interval field value out of range: "0.1 days 9223372036854775807 microseconds"
+LINE 1: SELECT INTERVAL '0.1 days 9223372036854775807 microseconds';
+                        ^
+SELECT INTERVAL '-0.1 days -9223372036854775808 microseconds';
+ERROR:  interval field value out of range: "-0.1 days -9223372036854775808 microseconds"
+LINE 1: SELECT INTERVAL '-0.1 days -9223372036854775808 microseconds...
+                        ^
+SELECT INTERVAL '9223372036854775807 microseconds 0.1 days';
+ERROR:  interval field value out of range: "9223372036854775807 microseconds 0.1 days"
+LINE 1: SELECT INTERVAL '9223372036854775807 microseconds 0.1 days';
+                        ^
+SELECT INTERVAL '-9223372036854775808 microseconds -0.1 days';
+ERROR:  interval field value out of range: "-9223372036854775808 microseconds -0.1 days"
+LINE 1: SELECT INTERVAL '-9223372036854775808 microseconds -0.1 days...
+                        ^
+-- overflowing with fractional fields - ISO8601 format
+SELECT INTERVAL 'P0.1Y2147483647M';
+ERROR:  interval field value out of range: "P0.1Y2147483647M"
+LINE 1: SELECT INTERVAL 'P0.1Y2147483647M';
+                        ^
+SELECT INTERVAL 'P-0.1Y-2147483648M';
+ERROR:  interval field value out of range: "P-0.1Y-2147483648M"
+LINE 1: SELECT INTERVAL 'P-0.1Y-2147483648M';
+                        ^
+SELECT INTERVAL 'P2147483647M0.1Y';
+ERROR:  interval field value out of range: "P2147483647M0.1Y"
+LINE 1: SELECT INTERVAL 'P2147483647M0.1Y';
+                        ^
+SELECT INTERVAL 'P-2147483648M-0.1Y';
+ERROR:  interval field value out of range: "P-2147483648M-0.1Y"
+LINE 1: SELECT INTERVAL 'P-2147483648M-0.1Y';
+                        ^
+SELECT INTERVAL 'P0.1M2147483647D';
+ERROR:  interval field value out of range: "P0.1M2147483647D"
+LINE 1: SELECT INTERVAL 'P0.1M2147483647D';
+                        ^
+SELECT INTERVAL 'P-0.1M-2147483648D';
+ERROR:  interval field value out of range: "P-0.1M-2147483648D"
+LINE 1: SELECT INTERVAL 'P-0.1M-2147483648D';
+                        ^
+SELECT INTERVAL 'P2147483647D0.1M';
+ERROR:  interval field value out of range: "P2147483647D0.1M"
+LINE 1: SELECT INTERVAL 'P2147483647D0.1M';
+                        ^
+SELECT INTERVAL 'P-2147483648D-0.1M';
+ERROR:  interval field value out of range: "P-2147483648D-0.1M"
+LINE 1: SELECT INTERVAL 'P-2147483648D-0.1M';
+                        ^
+SELECT INTERVAL 'P0.5W2147483647D';
+ERROR:  interval field value out of range: "P0.5W2147483647D"
+LINE 1: SELECT INTERVAL 'P0.5W2147483647D';
+                        ^
+SELECT INTERVAL 'P-0.5W-2147483648D';
+ERROR:  interval field value out of range: "P-0.5W-2147483648D"
+LINE 1: SELECT INTERVAL 'P-0.5W-2147483648D';
+                        ^
+SELECT INTERVAL 'P2147483647D0.5W';
+ERROR:  interval field value out of range: "P2147483647D0.5W"
+LINE 1: SELECT INTERVAL 'P2147483647D0.5W';
+                        ^
+SELECT INTERVAL 'P-2147483648D-0.5W';
+ERROR:  interval field value out of range: "P-2147483648D-0.5W"
+LINE 1: SELECT INTERVAL 'P-2147483648D-0.5W';
+                        ^
+SELECT INTERVAL 'P0.01MT2562047788H54.775807S';
+ERROR:  interval field value out of range: "P0.01MT2562047788H54.775807S"
+LINE 1: SELECT INTERVAL 'P0.01MT2562047788H54.775807S';
+                        ^
+SELECT INTERVAL 'P-0.01MT-2562047788H-54.775808S';
+ERROR:  interval field value out of range: "P-0.01MT-2562047788H-54.775808S"
+LINE 1: SELECT INTERVAL 'P-0.01MT-2562047788H-54.775808S';
+                        ^
+SELECT INTERVAL 'P0.1DT2562047788H54.775807S';
+ERROR:  interval field value out of range: "P0.1DT2562047788H54.775807S"
+LINE 1: SELECT INTERVAL 'P0.1DT2562047788H54.775807S';
+                        ^
+SELECT INTERVAL 'P-0.1DT-2562047788H-54.775808S';
+ERROR:  interval field value out of range: "P-0.1DT-2562047788H-54.775808S"
+LINE 1: SELECT INTERVAL 'P-0.1DT-2562047788H-54.775808S';
+                        ^
+SELECT INTERVAL 'PT2562047788.1H54.775807S';
+ERROR:  interval field value out of range: "PT2562047788.1H54.775807S"
+LINE 1: SELECT INTERVAL 'PT2562047788.1H54.775807S';
+                        ^
+SELECT INTERVAL 'PT-2562047788.1H-54.775808S';
+ERROR:  interval field value out of range: "PT-2562047788.1H-54.775808S"
+LINE 1: SELECT INTERVAL 'PT-2562047788.1H-54.775808S';
+                        ^
+SELECT INTERVAL 'PT2562047788H0.1M54.775807S';
+ERROR:  interval field value out of range: "PT2562047788H0.1M54.775807S"
+LINE 1: SELECT INTERVAL 'PT2562047788H0.1M54.775807S';
+                        ^
+SELECT INTERVAL 'PT-2562047788H-0.1M-54.775808S';
+ERROR:  interval field value out of range: "PT-2562047788H-0.1M-54.775808S"
+LINE 1: SELECT INTERVAL 'PT-2562047788H-0.1M-54.775808S';
+                        ^
+-- overflowing with fractional fields - ISO8601 alternative format
+SELECT INTERVAL 'P0.1-2147483647-00';
+ERROR:  interval field value out of range: "P0.1-2147483647-00"
+LINE 1: SELECT INTERVAL 'P0.1-2147483647-00';
+                        ^
+SELECT INTERVAL 'P00-0.1-2147483647';
+ERROR:  interval field value out of range: "P00-0.1-2147483647"
+LINE 1: SELECT INTERVAL 'P00-0.1-2147483647';
+                        ^
+SELECT INTERVAL 'P00-0.01-00T2562047788:00:54.775807';
+ERROR:  interval field value out of range: "P00-0.01-00T2562047788:00:54.775807"
+LINE 1: SELECT INTERVAL 'P00-0.01-00T2562047788:00:54.775807';
+                        ^
+SELECT INTERVAL 'P00-00-0.1T2562047788:00:54.775807';
+ERROR:  interval field value out of range: "P00-00-0.1T2562047788:00:54.775807"
+LINE 1: SELECT INTERVAL 'P00-00-0.1T2562047788:00:54.775807';
+                        ^
+SELECT INTERVAL 'PT2562047788.1:00:54.775807';
+ERROR:  interval field value out of range: "PT2562047788.1:00:54.775807"
+LINE 1: SELECT INTERVAL 'PT2562047788.1:00:54.775807';
+                        ^
+SELECT INTERVAL 'PT2562047788:01.:54.775807';
+ERROR:  interval field value out of range: "PT2562047788:01.:54.775807"
+LINE 1: SELECT INTERVAL 'PT2562047788:01.:54.775807';
+                        ^
+-- overflowing with fractional fields - SQL standard format
+SELECT INTERVAL '0.1 2562047788:0:54.775807';
+ERROR:  interval field value out of range: "0.1 2562047788:0:54.775807"
+LINE 1: SELECT INTERVAL '0.1 2562047788:0:54.775807';
+                        ^
+SELECT INTERVAL '0.1 2562047788:0:54.775808 ago';
+ERROR:  interval field value out of range: "0.1 2562047788:0:54.775808 ago"
+LINE 1: SELECT INTERVAL '0.1 2562047788:0:54.775808 ago';
+                        ^
+SELECT INTERVAL '2562047788.1:0:54.775807';
+ERROR:  interval field value out of range: "2562047788.1:0:54.775807"
+LINE 1: SELECT INTERVAL '2562047788.1:0:54.775807';
+                        ^
+SELECT INTERVAL '2562047788.1:0:54.775808 ago';
+ERROR:  interval field value out of range: "2562047788.1:0:54.775808 ago"
+LINE 1: SELECT INTERVAL '2562047788.1:0:54.775808 ago';
+                        ^
+SELECT INTERVAL '2562047788:0.1:54.775807';
+ERROR:  invalid input syntax for type interval: "2562047788:0.1:54.775807"
+LINE 1: SELECT INTERVAL '2562047788:0.1:54.775807';
+                        ^
+SELECT INTERVAL '2562047788:0.1:54.775808 ago';
+ERROR:  invalid input syntax for type interval: "2562047788:0.1:54.775808 ago"
+LINE 1: SELECT INTERVAL '2562047788:0.1:54.775808 ago';
+                        ^
+-- overflowing using AGO with INT_MIN
+SELECT INTERVAL '-2147483648 months ago';
+ERROR:  interval field value out of range: "-2147483648 months ago"
+LINE 1: SELECT INTERVAL '-2147483648 months ago';
+                        ^
+SELECT INTERVAL '-2147483648 days ago';
+ERROR:  interval field value out of range: "-2147483648 days ago"
+LINE 1: SELECT INTERVAL '-2147483648 days ago';
+                        ^
+SELECT INTERVAL '-9223372036854775808 microseconds ago';
+ERROR:  interval field value out of range: "-9223372036854775808 microseconds ago"
+LINE 1: SELECT INTERVAL '-9223372036854775808 microseconds ago';
+                        ^
+SELECT INTERVAL '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+ERROR:  interval field value out of range: "-2147483648 months -2147483648 days -9223372036854775808 microseconds ago"
+LINE 1: SELECT INTERVAL '-2147483648 months -2147483648 days -922337...
+                        ^
+-- test that INT_MIN number is formatted properly
+SET IntervalStyle to postgres;
+SELECT INTERVAL '-2147483648 months -2147483648 days -9223372036854775808 us';
+                              interval                              
+--------------------------------------------------------------------
+ -178956970 years -8 mons -2147483648 days -2562047788:00:54.775808
+(1 row)
+
+SET IntervalStyle to postgres_verbose;
+SELECT INTERVAL '-2147483648 months -2147483648 days -9223372036854775808 us';
+                                   interval                                   
+------------------------------------------------------------------------------
+ @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775808 secs ago
+(1 row)
+
+SET IntervalStyle TO sql_standard;
+SELECT INTERVAL '-2147483648 months -2147483648 days -9223372036854775808 us';
+                     interval                      
+---------------------------------------------------
+ -178956970-8 -2147483648 -2562047788:00:54.775808
+(1 row)
+
+SET IntervalStyle to iso_8601;
+SELECT INTERVAL '-2147483648 months -2147483648 days -9223372036854775808 us';
+                      interval                       
+-----------------------------------------------------
+ P-178956970Y-8M-2147483648DT-2562047788H-54.775808S
+(1 row)
+
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index c31f0eec05..fc924d5bde 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -367,3 +367,187 @@ SELECT f1,
 
 -- internal overflow test case
 SELECT extract(epoch from interval '1000000000 days');
+
+-- test time fields using entire 64 bit microseconds range
+SELECT INTERVAL '2562047788.01521550194 hours';
+SELECT INTERVAL '-2562047788.01521550222 hours';
+SELECT INTERVAL '153722867280.912930117 minutes';
+SELECT INTERVAL '-153722867280.912930133 minutes';
+SELECT INTERVAL '9223372036854.775807 seconds';
+SELECT INTERVAL '-9223372036854.775808 seconds';
+SELECT INTERVAL '9223372036854775.807 milliseconds';
+SELECT INTERVAL '-9223372036854775.808 milliseconds';
+SELECT INTERVAL '9223372036854775807 microseconds';
+SELECT INTERVAL '-9223372036854775808 microseconds';
+
+SELECT INTERVAL 'PT2562047788H54.775807S';
+SELECT INTERVAL 'PT-2562047788H-54.775808S';
+
+SELECT INTERVAL 'PT2562047788:00:54.775807';
+
+SELECT INTERVAL 'PT2562047788.0152155019444';
+SELECT INTERVAL 'PT-2562047788.0152155022222';
+
+-- overflow each date/time field
+SELECT INTERVAL '2147483648 years';
+SELECT INTERVAL '-2147483649 years';
+SELECT INTERVAL '2147483648 months';
+SELECT INTERVAL '-2147483649 months';
+SELECT INTERVAL '2147483648 days';
+SELECT INTERVAL '-2147483649 days';
+SELECT INTERVAL '2562047789 hours';
+SELECT INTERVAL '-2562047789 hours';
+SELECT INTERVAL '153722867281 minutes';
+SELECT INTERVAL '-153722867281 minutes';
+SELECT INTERVAL '9223372036855 seconds';
+SELECT INTERVAL '-9223372036855 seconds';
+SELECT INTERVAL '9223372036854777 millisecond';
+SELECT INTERVAL '-9223372036854777 millisecond';
+SELECT INTERVAL '9223372036854775808 microsecond';
+SELECT INTERVAL '-9223372036854775809 microsecond';
+
+SELECT INTERVAL 'P2147483648';
+SELECT INTERVAL 'P-2147483649';
+SELECT INTERVAL 'P1-2147483647-2147483647';
+SELECT INTERVAL 'PT2562047789';
+SELECT INTERVAL 'PT-2562047789';
+
+-- overflow with date/time unit aliases
+SELECT INTERVAL '2147483647 weeks';
+SELECT INTERVAL '-2147483648 weeks';
+SELECT INTERVAL '2147483647 decades';
+SELECT INTERVAL '-2147483648 decades';
+SELECT INTERVAL '2147483647 centuries';
+SELECT INTERVAL '-2147483648 centuries';
+SELECT INTERVAL '2147483647 millennium';
+SELECT INTERVAL '-2147483648 millennium';
+
+SELECT INTERVAL '1 week 2147483647 days';
+SELECT INTERVAL '-1 week -2147483648 days';
+SELECT INTERVAL '2147483647 days 1 week';
+SELECT INTERVAL '-2147483648 days -1 week';
+
+SELECT INTERVAL 'P1W2147483647D';
+SELECT INTERVAL 'P-1W-2147483648D';
+SELECT INTERVAL 'P2147483647D1W';
+SELECT INTERVAL 'P-2147483648D-1W';
+
+SELECT INTERVAL '1 decade 2147483647 years';
+SELECT INTERVAL '1 century 2147483647 years';
+SELECT INTERVAL '1 millennium 2147483647 years';
+SELECT INTERVAL '-1 decade -2147483648 years';
+SELECT INTERVAL '-1 century -2147483648 years';
+SELECT INTERVAL '-1 millennium -2147483648 years';
+
+SELECT INTERVAL '2147483647 years 1 decade';
+SELECT INTERVAL '2147483647 years 1 century';
+SELECT INTERVAL '2147483647 years 1 millennium';
+SELECT INTERVAL '-2147483648 years -1 decade';
+SELECT INTERVAL '-2147483648 years -1 century';
+SELECT INTERVAL '-2147483648 years -1 millennium';
+
+-- overflowing with fractional fields - postgres format
+SELECT INTERVAL '0.1 millennium 2147483647 months';
+SELECT INTERVAL '0.1 centuries 2147483647 months';
+SELECT INTERVAL '0.1 decades 2147483647 months';
+SELECT INTERVAL '0.1 yrs 2147483647 months';
+SELECT INTERVAL '-0.1 millennium -2147483648 months';
+SELECT INTERVAL '-0.1 centuries -2147483648 months';
+SELECT INTERVAL '-0.1 decades -2147483648 months';
+SELECT INTERVAL '-0.1 yrs -2147483648 months';
+
+SELECT INTERVAL '2147483647 months 0.1 millennium';
+SELECT INTERVAL '2147483647 months 0.1 centuries';
+SELECT INTERVAL '2147483647 months 0.1 decades';
+SELECT INTERVAL '2147483647 months 0.1 yrs';
+SELECT INTERVAL '-2147483648 months -0.1 millennium';
+SELECT INTERVAL '-2147483648 months -0.1 centuries';
+SELECT INTERVAL '-2147483648 months -0.1 decades';
+SELECT INTERVAL '-2147483648 months -0.1 yrs';
+
+SELECT INTERVAL '0.1 months 2147483647 days';
+SELECT INTERVAL '-0.1 months -2147483648 days';
+SELECT INTERVAL '2147483647 days 0.1 months';
+SELECT INTERVAL '-2147483648 days -0.1 months';
+
+SELECT INTERVAL '0.5 weeks 2147483647 days';
+SELECT INTERVAL '-0.5 weeks -2147483648 days';
+SELECT INTERVAL '2147483647 days 0.5 weeks';
+SELECT INTERVAL '-2147483648 days -0.5 weeks';
+
+SELECT INTERVAL '0.01 months 9223372036854775807 microseconds';
+SELECT INTERVAL '-0.01 months -9223372036854775808 microseconds';
+SELECT INTERVAL '9223372036854775807 microseconds 0.01 months';
+SELECT INTERVAL '-9223372036854775808 microseconds -0.01 months';
+
+SELECT INTERVAL '0.1 weeks 9223372036854775807 microseconds';
+SELECT INTERVAL '-0.1 weeks -9223372036854775808 microseconds';
+SELECT INTERVAL '9223372036854775807 microseconds 0.1 weeks';
+SELECT INTERVAL '-9223372036854775808 microseconds -0.1 weeks';
+
+SELECT INTERVAL '0.1 days 9223372036854775807 microseconds';
+SELECT INTERVAL '-0.1 days -9223372036854775808 microseconds';
+SELECT INTERVAL '9223372036854775807 microseconds 0.1 days';
+SELECT INTERVAL '-9223372036854775808 microseconds -0.1 days';
+
+-- overflowing with fractional fields - ISO8601 format
+SELECT INTERVAL 'P0.1Y2147483647M';
+SELECT INTERVAL 'P-0.1Y-2147483648M';
+SELECT INTERVAL 'P2147483647M0.1Y';
+SELECT INTERVAL 'P-2147483648M-0.1Y';
+
+SELECT INTERVAL 'P0.1M2147483647D';
+SELECT INTERVAL 'P-0.1M-2147483648D';
+SELECT INTERVAL 'P2147483647D0.1M';
+SELECT INTERVAL 'P-2147483648D-0.1M';
+
+SELECT INTERVAL 'P0.5W2147483647D';
+SELECT INTERVAL 'P-0.5W-2147483648D';
+SELECT INTERVAL 'P2147483647D0.5W';
+SELECT INTERVAL 'P-2147483648D-0.5W';
+
+SELECT INTERVAL 'P0.01MT2562047788H54.775807S';
+SELECT INTERVAL 'P-0.01MT-2562047788H-54.775808S';
+
+SELECT INTERVAL 'P0.1DT2562047788H54.775807S';
+SELECT INTERVAL 'P-0.1DT-2562047788H-54.775808S';
+
+SELECT INTERVAL 'PT2562047788.1H54.775807S';
+SELECT INTERVAL 'PT-2562047788.1H-54.775808S';
+
+SELECT INTERVAL 'PT2562047788H0.1M54.775807S';
+SELECT INTERVAL 'PT-2562047788H-0.1M-54.775808S';
+
+-- overflowing with fractional fields - ISO8601 alternative format
+SELECT INTERVAL 'P0.1-2147483647-00';
+SELECT INTERVAL 'P00-0.1-2147483647';
+SELECT INTERVAL 'P00-0.01-00T2562047788:00:54.775807';
+SELECT INTERVAL 'P00-00-0.1T2562047788:00:54.775807';
+SELECT INTERVAL 'PT2562047788.1:00:54.775807';
+SELECT INTERVAL 'PT2562047788:01.:54.775807';
+
+-- overflowing with fractional fields - SQL standard format
+SELECT INTERVAL '0.1 2562047788:0:54.775807';
+SELECT INTERVAL '0.1 2562047788:0:54.775808 ago';
+
+SELECT INTERVAL '2562047788.1:0:54.775807';
+SELECT INTERVAL '2562047788.1:0:54.775808 ago';
+
+SELECT INTERVAL '2562047788:0.1:54.775807';
+SELECT INTERVAL '2562047788:0.1:54.775808 ago';
+
+-- overflowing using AGO with INT_MIN
+SELECT INTERVAL '-2147483648 months ago';
+SELECT INTERVAL '-2147483648 days ago';
+SELECT INTERVAL '-9223372036854775808 microseconds ago';
+SELECT INTERVAL '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+
+-- test that INT_MIN number is formatted properly
+SET IntervalStyle to postgres;
+SELECT INTERVAL '-2147483648 months -2147483648 days -9223372036854775808 us';
+SET IntervalStyle to postgres_verbose;
+SELECT INTERVAL '-2147483648 months -2147483648 days -9223372036854775808 us';
+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';


^ permalink  raw  reply  [nested|flat] 23+ messages in thread

* Re: Fix overflow in DecodeInterval
  2022-02-13 03:51 Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-13 14:35 ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-13 20:30   ` Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-13 20:38     ` Re: Fix overflow in DecodeInterval Tom Lane <[email protected]>
  2022-02-13 22:12       ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-15 11:44         ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-15 16:28           ` Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-18 02:45             ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-19 19:26               ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-20 00:16                 ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-20 23:37                   ` Re: Fix overflow in DecodeInterval Tom Lane <[email protected]>
  2022-02-21 02:53                     ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-03-22 00:31                       ` Re: Fix overflow in DecodeInterval Tom Lane <[email protected]>
@ 2022-03-24 00:27                         ` Joseph Koshakow <[email protected]>
  2022-03-24 01:20                           ` Re: Fix overflow in DecodeInterval Tom Lane <[email protected]>
  0 siblings, 1 reply; 23+ messages in thread

From: Joseph Koshakow @ 2022-03-24 00:27 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Andres Freund <[email protected]>; PostgreSQL Hackers <[email protected]>

On Mon, Mar 21, 2022 at 8:31 PM Tom Lane <[email protected]> wrote:
> This isn't applying per the cfbot; looks like it got sideswiped
> by 9e9858389.  Here's a quick rebase.  I've not reviewed it, but
> I did notice (because git was in my face about this) that it's
> got whitespace issues.  Please try to avoid unnecessary whitespace
> changes ... pgindent will clean those up, but it makes reviewing
> harder.

Sorry about that, I didn't have my IDE set up quite right and
noticed a little too late that I had some auto-formatting turned
on. Thanks for doing the rebase, did it end up fixing
the whitespace issues? If not I'll go through the patch and try
and fix them all.

- Joe Koshakow





^ permalink  raw  reply  [nested|flat] 23+ messages in thread

* Re: Fix overflow in DecodeInterval
  2022-02-13 03:51 Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-13 14:35 ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-13 20:30   ` Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-13 20:38     ` Re: Fix overflow in DecodeInterval Tom Lane <[email protected]>
  2022-02-13 22:12       ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-15 11:44         ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-15 16:28           ` Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-18 02:45             ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-19 19:26               ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-20 00:16                 ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-20 23:37                   ` Re: Fix overflow in DecodeInterval Tom Lane <[email protected]>
  2022-02-21 02:53                     ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-03-22 00:31                       ` Re: Fix overflow in DecodeInterval Tom Lane <[email protected]>
  2022-03-24 00:27                         ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
@ 2022-03-24 01:20                           ` Tom Lane <[email protected]>
  0 siblings, 0 replies; 23+ messages in thread

From: Tom Lane @ 2022-03-24 01:20 UTC (permalink / raw)
  To: Joseph Koshakow <[email protected]>; +Cc: Andres Freund <[email protected]>; PostgreSQL Hackers <[email protected]>

Joseph Koshakow <[email protected]> writes:
> Sorry about that, I didn't have my IDE set up quite right and
> noticed a little too late that I had some auto-formatting turned
> on. Thanks for doing the rebase, did it end up fixing
> the whitespace issues? If not I'll go through the patch and try
> and fix them all.

No, I just fixed the merge failure.

Our standard way to clean up whitespace issues and make sure code
meets our layout conventions is to run pgindent over it [1].
For this particular patch, that might be too much, because it will
reindent the sections that you added braces around, making the patch
harder to review.  So maybe the best bet is to leave well enough
alone and expect the committer to re-pgindent before pushing it.
However, if you spot any diff hunks where there's just a whitespace
change, getting rid of those would be appreciated.

			regards, tom lane

[1] https://wiki.postgresql.org/wiki/Running_pgindent_on_non-core_code_or_development_code





^ permalink  raw  reply  [nested|flat] 23+ messages in thread

* Re: Fix overflow in DecodeInterval
  2022-02-13 03:51 Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-13 14:35 ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-13 20:30   ` Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-13 20:38     ` Re: Fix overflow in DecodeInterval Tom Lane <[email protected]>
  2022-02-13 22:12       ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-15 11:44         ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-15 16:28           ` Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
  2022-02-18 02:45             ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-19 19:26               ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-20 00:16                 ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
  2022-02-20 23:37                   ` Re: Fix overflow in DecodeInterval Tom Lane <[email protected]>
  2022-02-21 02:53                     ` Re: Fix overflow in DecodeInterval Joseph Koshakow <[email protected]>
@ 2022-04-02 19:08                       ` Tom Lane <[email protected]>
  2 siblings, 0 replies; 23+ messages in thread

From: Tom Lane @ 2022-04-02 19:08 UTC (permalink / raw)
  To: Joseph Koshakow <[email protected]>; +Cc: Andres Freund <[email protected]>; PostgreSQL Hackers <[email protected]>

Joseph Koshakow <[email protected]> writes:
> Ok I actually remember now, the issue is with the rounding
> code in AdjustFractMicroseconds.
> ...
> I believe it's possible for `frac -= usec;` to result in a value greater
> than 1 or less than -1 due to the lossiness of int64 to double
> conversions.

I think it's not, at least not for the interesting range of possible
values in this code.  Given that abs(frac) < 1 to start with, the
abs value of usec can't exceed the value of scale, which is at most 
USECS_PER_DAY so it's at most 37 or so bits, which is well within
the exact range for any sane implementation of double.  It would
take a very poor floating-point implementation to not get the right
answer here.  (And we're largely assuming IEEE-compliant floats these
days.)

Anyway, the other issue indeed turns out to be easy to fix.
Attached is a v11 that deals with that.  If the cfbot doesn't
complain about it, I'll push this later today.

			regards, tom lane



Attachments:

  [text/x-diff] v11-0001-Check-for-overflow-when-decoding-an-interval.patch (96.0K, ../../[email protected]/2-v11-0001-Check-for-overflow-when-decoding-an-interval.patch)
  download | inline diff:
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index ba0ec35ac5..462f2ed7a8 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -21,6 +21,7 @@
 #include "access/htup_details.h"
 #include "access/xact.h"
 #include "catalog/pg_type.h"
+#include "common/int.h"
 #include "common/string.h"
 #include "funcapi.h"
 #include "miscadmin.h"
@@ -37,17 +38,31 @@ static int	DecodeNumber(int flen, char *field, bool haveTextMonth,
 static int	DecodeNumberField(int len, char *str,
 							  int fmask, int *tmask,
 							  struct pg_tm *tm, fsec_t *fsec, bool *is2digits);
+static int	DecodeTimeCommon(char *str, int fmask, int range,
+							 int *tmask, struct pg_itm *itm);
 static int	DecodeTime(char *str, int fmask, int range,
 					   int *tmask, struct pg_tm *tm, fsec_t *fsec);
+static int	DecodeTimeForInterval(char *str, int fmask, int range,
+								  int *tmask, struct pg_itm_in *itm_in);
 static const datetkn *datebsearch(const char *key, const datetkn *base, int nel);
 static int	DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
 					   struct pg_tm *tm);
 static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
 						   int precision, bool fillzeros);
-static void AdjustFractSeconds(double frac, struct pg_tm *tm, fsec_t *fsec,
-							   int scale);
-static void AdjustFractDays(double frac, struct pg_tm *tm, fsec_t *fsec,
-							int scale);
+static bool int64_multiply_add(int64 val, int64 multiplier, int64 *sum);
+static bool AdjustFractMicroseconds(double frac, int64 scale,
+									struct pg_itm_in *itm_in);
+static bool AdjustFractDays(double frac, int scale,
+							struct pg_itm_in *itm_in);
+static bool AdjustFractYears(double frac, int scale,
+							 struct pg_itm_in *itm_in);
+static bool AdjustMicroseconds(int64 val, double fval, int64 scale,
+							   struct pg_itm_in *itm_in);
+static bool AdjustDays(int64 val, int scale,
+					   struct pg_itm_in *itm_in);
+static bool AdjustMonths(int64 val, struct pg_itm_in *itm_in);
+static bool AdjustYears(int64 val, int scale,
+						struct pg_itm_in *itm_in);
 static int	DetermineTimeZoneOffsetInternal(struct pg_tm *tm, pg_tz *tzp,
 											pg_time_t *tp);
 static bool DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t,
@@ -425,7 +440,7 @@ GetCurrentTimeUsec(struct pg_tm *tm, fsec_t *fsec, int *tzp)
  * Returns a pointer to the new end of string.  No NUL terminator is put
  * there; callers are responsible for NUL terminating str themselves.
  *
- * Note that any sign is stripped from the input seconds values.
+ * Note that any sign is stripped from the input sec and fsec values.
  */
 static char *
 AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros)
@@ -471,7 +486,7 @@ AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros)
 
 		/*
 		 * If we still have a non-zero value then precision must have not been
-		 * enough to print the number.  We punt the problem to pg_ltostr(),
+		 * enough to print the number.  We punt the problem to pg_ultostr(),
 		 * which will generate a correct answer in the minimum valid width.
 		 */
 		if (value)
@@ -496,39 +511,163 @@ AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec)
 	return AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true);
 }
 
+
+/*
+ * Add val * multiplier to *sum.
+ * Returns true if successful, false on overflow.
+ */
+static bool
+int64_multiply_add(int64 val, int64 multiplier, int64 *sum)
+{
+	int64		product;
+
+	if (pg_mul_s64_overflow(val, multiplier, &product) ||
+		pg_add_s64_overflow(*sum, product, sum))
+		return false;
+	return true;
+}
+
 /*
- * Multiply frac by scale (to produce seconds) and add to *tm & *fsec.
- * We assume the input frac is less than 1 so overflow is not an issue.
+ * Multiply frac by scale (to produce microseconds) and add to itm_in->tm_usec.
+ * Returns true if successful, false if itm_in overflows.
  */
-static void
-AdjustFractSeconds(double frac, struct pg_tm *tm, fsec_t *fsec, int scale)
+static bool
+AdjustFractMicroseconds(double frac, int64 scale,
+						struct pg_itm_in *itm_in)
 {
-	int			sec;
+	int64		usec;
 
+	/* Fast path for common case */
 	if (frac == 0)
-		return;
+		return true;
+
+	/*
+	 * We assume the input frac has abs value less than 1, so overflow of frac
+	 * or usec is not an issue for interesting values of scale.
+	 */
 	frac *= scale;
-	sec = (int) frac;
-	tm->tm_sec += sec;
-	frac -= sec;
-	*fsec += rint(frac * 1000000);
+	usec = (int64) frac;
+
+	/* Round off any fractional microsecond */
+	frac -= usec;
+	if (frac > 0.5)
+		usec++;
+	else if (frac < -0.5)
+		usec--;
+
+	return !pg_add_s64_overflow(itm_in->tm_usec, usec, &itm_in->tm_usec);
 }
 
-/* As above, but initial scale produces days */
-static void
-AdjustFractDays(double frac, struct pg_tm *tm, fsec_t *fsec, int scale)
+/*
+ * Multiply frac by scale (to produce days).  Add the integral part of the
+ * result to itm_in->tm_mday, the fractional part to itm_in->tm_usec.
+ * Returns true if successful, false if itm_in overflows.
+ */
+static bool
+AdjustFractDays(double frac, int scale,
+				struct pg_itm_in *itm_in)
 {
 	int			extra_days;
 
+	/* Fast path for common case */
 	if (frac == 0)
-		return;
+		return true;
+
+	/*
+	 * We assume the input frac has abs value less than 1, so overflow of frac
+	 * or extra_days is not an issue.
+	 */
 	frac *= scale;
 	extra_days = (int) frac;
-	tm->tm_mday += extra_days;
+
+	/* ... but this could overflow, if tm_mday is already nonzero */
+	if (pg_add_s32_overflow(itm_in->tm_mday, extra_days, &itm_in->tm_mday))
+		return false;
+
+	/* Handle any fractional day */
 	frac -= extra_days;
-	AdjustFractSeconds(frac, tm, fsec, SECS_PER_DAY);
+	return AdjustFractMicroseconds(frac, USECS_PER_DAY, itm_in);
+}
+
+/*
+ * Multiply frac by scale (to produce years), then further scale up to months.
+ * Add the integral part of the result to itm_in->tm_mon, discarding any
+ * fractional part.
+ * Returns true if successful, false if itm_in overflows.
+ */
+static bool
+AdjustFractYears(double frac, int scale,
+				 struct pg_itm_in *itm_in)
+{
+	/*
+	 * As above, we assume abs(frac) < 1, so this can't overflow for any
+	 * interesting value of scale.
+	 */
+	int			extra_months = (int) rint(frac * scale * MONTHS_PER_YEAR);
+
+	return !pg_add_s32_overflow(itm_in->tm_mon, extra_months, &itm_in->tm_mon);
+}
+
+/*
+ * Add (val + fval) * scale to itm_in->tm_usec.
+ * Returns true if successful, false if itm_in overflows.
+ */
+static bool
+AdjustMicroseconds(int64 val, double fval, int64 scale,
+				   struct pg_itm_in *itm_in)
+{
+	/* Handle the integer part */
+	if (!int64_multiply_add(val, scale, &itm_in->tm_usec))
+		return false;
+	/* Handle the float part */
+	return AdjustFractMicroseconds(fval, scale, itm_in);
+}
+
+/*
+ * Multiply val by scale (to produce days) and add to itm_in->tm_mday.
+ * Returns true if successful, false if itm_in overflows.
+ */
+static bool
+AdjustDays(int64 val, int scale, struct pg_itm_in *itm_in)
+{
+	int			days;
+
+	if (val < INT_MIN || val > INT_MAX)
+		return false;
+	return !pg_mul_s32_overflow((int32) val, scale, &days) &&
+		!pg_add_s32_overflow(itm_in->tm_mday, days, &itm_in->tm_mday);
+}
+
+/*
+ * Add val to itm_in->tm_mon (no need for scale here, as val is always
+ * in months already).
+ * Returns true if successful, false if itm_in overflows.
+ */
+static bool
+AdjustMonths(int64 val, struct pg_itm_in *itm_in)
+{
+	if (val < INT_MIN || val > INT_MAX)
+		return false;
+	return !pg_add_s32_overflow(itm_in->tm_mon, (int32) val, &itm_in->tm_mon);
 }
 
+/*
+ * Multiply val by scale (to produce years) and add to itm_in->tm_year.
+ * Returns true if successful, false if itm_in overflows.
+ */
+static bool
+AdjustYears(int64 val, int scale,
+			struct pg_itm_in *itm_in)
+{
+	int			years;
+
+	if (val < INT_MIN || val > INT_MAX)
+		return false;
+	return !pg_mul_s32_overflow((int32) val, scale, &years) &&
+		!pg_add_s32_overflow(itm_in->tm_year, years, &itm_in->tm_year);
+}
+
+
 /* Fetch a fractional-second value with suitable error checking */
 static int
 ParseFractionalSecond(char *cp, fsec_t *fsec)
@@ -2548,79 +2687,143 @@ ValidateDate(int fmask, bool isjulian, bool is2digits, bool bc,
 }
 
 
-/* DecodeTime()
+/* DecodeTimeCommon()
  * Decode time string which includes delimiters.
  * Return 0 if okay, a DTERR code if not.
+ * tmask and itm are output parameters.
  *
- * Only check the lower limit on hours, since this same code can be
- * used to represent time spans.
+ * This code is shared between the timestamp and interval cases.
+ * We return a struct pg_itm (of which only the tm_usec, tm_sec, tm_min,
+ * and tm_hour fields are used) and let the wrapper functions below
+ * convert and range-check as necessary.
  */
 static int
-DecodeTime(char *str, int fmask, int range,
-		   int *tmask, struct pg_tm *tm, fsec_t *fsec)
+DecodeTimeCommon(char *str, int fmask, int range,
+				 int *tmask, struct pg_itm *itm)
 {
 	char	   *cp;
 	int			dterr;
+	fsec_t		fsec = 0;
 
 	*tmask = DTK_TIME_M;
 
 	errno = 0;
-	tm->tm_hour = strtoint(str, &cp, 10);
+	itm->tm_hour = strtoi64(str, &cp, 10);
 	if (errno == ERANGE)
 		return DTERR_FIELD_OVERFLOW;
 	if (*cp != ':')
 		return DTERR_BAD_FORMAT;
 	errno = 0;
-	tm->tm_min = strtoint(cp + 1, &cp, 10);
+	itm->tm_min = strtoint(cp + 1, &cp, 10);
 	if (errno == ERANGE)
 		return DTERR_FIELD_OVERFLOW;
 	if (*cp == '\0')
 	{
-		tm->tm_sec = 0;
-		*fsec = 0;
+		itm->tm_sec = 0;
 		/* If it's a MINUTE TO SECOND interval, take 2 fields as being mm:ss */
 		if (range == (INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND)))
 		{
-			tm->tm_sec = tm->tm_min;
-			tm->tm_min = tm->tm_hour;
-			tm->tm_hour = 0;
+			if (itm->tm_hour > INT_MAX || itm->tm_hour < INT_MIN)
+				return DTERR_FIELD_OVERFLOW;
+			itm->tm_sec = itm->tm_min;
+			itm->tm_min = (int) itm->tm_hour;
+			itm->tm_hour = 0;
 		}
 	}
 	else if (*cp == '.')
 	{
 		/* always assume mm:ss.sss is MINUTE TO SECOND */
-		dterr = ParseFractionalSecond(cp, fsec);
+		dterr = ParseFractionalSecond(cp, &fsec);
 		if (dterr)
 			return dterr;
-		tm->tm_sec = tm->tm_min;
-		tm->tm_min = tm->tm_hour;
-		tm->tm_hour = 0;
+		if (itm->tm_hour > INT_MAX || itm->tm_hour < INT_MIN)
+			return DTERR_FIELD_OVERFLOW;
+		itm->tm_sec = itm->tm_min;
+		itm->tm_min = (int) itm->tm_hour;
+		itm->tm_hour = 0;
 	}
 	else if (*cp == ':')
 	{
 		errno = 0;
-		tm->tm_sec = strtoint(cp + 1, &cp, 10);
+		itm->tm_sec = strtoint(cp + 1, &cp, 10);
 		if (errno == ERANGE)
 			return DTERR_FIELD_OVERFLOW;
-		if (*cp == '\0')
-			*fsec = 0;
-		else if (*cp == '.')
+		if (*cp == '.')
 		{
-			dterr = ParseFractionalSecond(cp, fsec);
+			dterr = ParseFractionalSecond(cp, &fsec);
 			if (dterr)
 				return dterr;
 		}
-		else
+		else if (*cp != '\0')
 			return DTERR_BAD_FORMAT;
 	}
 	else
 		return DTERR_BAD_FORMAT;
 
-	/* do a sanity check */
-	if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_min > MINS_PER_HOUR - 1 ||
-		tm->tm_sec < 0 || tm->tm_sec > SECS_PER_MINUTE ||
-		*fsec < INT64CONST(0) ||
-		*fsec > USECS_PER_SEC)
+	/* do a sanity check; but caller must check the range of tm_hour */
+	if (itm->tm_hour < 0 ||
+		itm->tm_min < 0 || itm->tm_min > MINS_PER_HOUR - 1 ||
+		itm->tm_sec < 0 || itm->tm_sec > SECS_PER_MINUTE ||
+		fsec < 0 || fsec > USECS_PER_SEC)
+		return DTERR_FIELD_OVERFLOW;
+
+	itm->tm_usec = (int) fsec;
+
+	return 0;
+}
+
+/* DecodeTime()
+ * Decode time string which includes delimiters.
+ * Return 0 if okay, a DTERR code if not.
+ *
+ * This version is used for timestamps.  The results are returned into
+ * the tm_hour/tm_min/tm_sec fields of *tm, and microseconds into *fsec.
+ */
+static int
+DecodeTime(char *str, int fmask, int range,
+		   int *tmask, struct pg_tm *tm, fsec_t *fsec)
+{
+	struct pg_itm itm;
+	int			dterr;
+
+	dterr = DecodeTimeCommon(str, fmask, range,
+							 tmask, &itm);
+	if (dterr)
+		return dterr;
+
+	if (itm.tm_hour > INT_MAX)
+		return DTERR_FIELD_OVERFLOW;
+	tm->tm_hour = (int) itm.tm_hour;
+	tm->tm_min = itm.tm_min;
+	tm->tm_sec = itm.tm_sec;
+	*fsec = itm.tm_usec;
+
+	return 0;
+}
+
+/* DecodeTimeForInterval()
+ * Decode time string which includes delimiters.
+ * Return 0 if okay, a DTERR code if not.
+ *
+ * This version is used for intervals.  The results are returned into
+ * itm_in->tm_usec.
+ */
+static int
+DecodeTimeForInterval(char *str, int fmask, int range,
+					  int *tmask, struct pg_itm_in *itm_in)
+{
+	struct pg_itm itm;
+	int			dterr;
+
+	dterr = DecodeTimeCommon(str, fmask, range,
+							 tmask, &itm);
+	if (dterr)
+		return dterr;
+
+	itm_in->tm_usec = itm.tm_usec;
+	if (!int64_multiply_add(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
+		!int64_multiply_add(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
+		!int64_multiply_add(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
 		return DTERR_FIELD_OVERFLOW;
 
 	return 0;
@@ -3064,27 +3267,24 @@ DecodeSpecial(int field, char *lowtoken, int *val)
 }
 
 
-/* ClearPgTm
+/* ClearPgItmIn
  *
- * Zero out a pg_tm and associated fsec_t
+ * Zero out a pg_itm_in
  */
 static inline void
-ClearPgTm(struct pg_tm *tm, fsec_t *fsec)
+ClearPgItmIn(struct pg_itm_in *itm_in)
 {
-	tm->tm_year = 0;
-	tm->tm_mon = 0;
-	tm->tm_mday = 0;
-	tm->tm_hour = 0;
-	tm->tm_min = 0;
-	tm->tm_sec = 0;
-	*fsec = 0;
+	itm_in->tm_usec = 0;
+	itm_in->tm_mday = 0;
+	itm_in->tm_mon = 0;
+	itm_in->tm_year = 0;
 }
 
 
 /* DecodeInterval()
  * Interpret previously parsed fields for general time interval.
  * Returns 0 if successful, DTERR code if bogus input detected.
- * dtype, tm, fsec are output parameters.
+ * dtype and itm_in are output parameters.
  *
  * Allow "date" field DTK_DATE since this could be just
  *	an unsigned floating point number. - thomas 1997-11-16
@@ -3094,21 +3294,53 @@ ClearPgTm(struct pg_tm *tm, fsec_t *fsec)
  */
 int
 DecodeInterval(char **field, int *ftype, int nf, int range,
-			   int *dtype, struct pg_tm *tm, fsec_t *fsec)
+			   int *dtype, struct pg_itm_in *itm_in)
 {
+	bool		force_negative = false;
 	bool		is_before = false;
 	char	   *cp;
 	int			fmask = 0,
 				tmask,
-				type;
+				type,
+				uval;
 	int			i;
 	int			dterr;
-	int			val;
+	int64		val;
 	double		fval;
 
 	*dtype = DTK_DELTA;
 	type = IGNORE_DTF;
-	ClearPgTm(tm, fsec);
+	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] == '-')
+	{
+		force_negative = true;
+		/* Check for additional explicit signs */
+		for (i = 1; i < nf; i++)
+		{
+			if (*field[i] == '-' || *field[i] == '+')
+			{
+				force_negative = false;
+				break;
+			}
+		}
+	}
 
 	/* read through list backwards to pick up units before values */
 	for (i = nf - 1; i >= 0; i--)
@@ -3116,10 +3348,13 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 		switch (ftype[i])
 		{
 			case DTK_TIME:
-				dterr = DecodeTime(field[i], fmask, range,
-								   &tmask, tm, fsec);
+				dterr = DecodeTimeForInterval(field[i], fmask, range,
+											  &tmask, itm_in);
 				if (dterr)
 					return dterr;
+				if (force_negative &&
+					itm_in->tm_usec > 0)
+					itm_in->tm_usec = -itm_in->tm_usec;
 				type = DTK_DAY;
 				break;
 
@@ -3137,18 +3372,21 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 				 * like DTK_TIME case above, plus handling the sign.
 				 */
 				if (strchr(field[i] + 1, ':') != NULL &&
-					DecodeTime(field[i] + 1, fmask, range,
-							   &tmask, tm, fsec) == 0)
+					DecodeTimeForInterval(field[i] + 1, fmask, range,
+										  &tmask, itm_in) == 0)
 				{
 					if (*field[i] == '-')
 					{
-						/* flip the sign on all fields */
-						tm->tm_hour = -tm->tm_hour;
-						tm->tm_min = -tm->tm_min;
-						tm->tm_sec = -tm->tm_sec;
-						*fsec = -(*fsec);
+						/* flip the sign on time field */
+						if (itm_in->tm_usec == PG_INT64_MIN)
+							return DTERR_FIELD_OVERFLOW;
+						itm_in->tm_usec = -itm_in->tm_usec;
 					}
 
+					if (force_negative &&
+						itm_in->tm_usec > 0)
+						itm_in->tm_usec = -itm_in->tm_usec;
+
 					/*
 					 * Set the next type to be a day, if units are not
 					 * specified. This handles the case of '1 +02:03' since we
@@ -3204,7 +3442,7 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 				}
 
 				errno = 0;
-				val = strtoint(field[i], &cp, 10);
+				val = strtoi64(field[i], &cp, 10);
 				if (errno == ERANGE)
 					return DTERR_FIELD_OVERFLOW;
 
@@ -3221,10 +3459,10 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 					type = DTK_MONTH;
 					if (*field[i] == '-')
 						val2 = -val2;
-					if (((double) val * MONTHS_PER_YEAR + val2) > INT_MAX ||
-						((double) val * MONTHS_PER_YEAR + val2) < INT_MIN)
+					if (pg_mul_s64_overflow(val, MONTHS_PER_YEAR, &val))
+						return DTERR_FIELD_OVERFLOW;
+					if (pg_add_s64_overflow(val, val2, &val))
 						return DTERR_FIELD_OVERFLOW;
-					val = val * MONTHS_PER_YEAR + val2;
 					fval = 0;
 				}
 				else if (*cp == '.')
@@ -3244,24 +3482,32 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 
 				tmask = 0;		/* DTK_M(type); */
 
+				if (force_negative)
+				{
+					/* val and fval should be of same sign, but test anyway */
+					if (val > 0)
+						val = -val;
+					if (fval > 0)
+						fval = -fval;
+				}
+
 				switch (type)
 				{
 					case DTK_MICROSEC:
-						*fsec += rint(val + fval);
+						if (!AdjustMicroseconds(val, fval, 1, itm_in))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(MICROSECOND);
 						break;
 
 					case DTK_MILLISEC:
-						/* avoid overflowing the fsec field */
-						tm->tm_sec += val / 1000;
-						val -= (val / 1000) * 1000;
-						*fsec += rint((val + fval) * 1000);
+						if (!AdjustMicroseconds(val, fval, 1000, itm_in))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(MILLISECOND);
 						break;
 
 					case DTK_SECOND:
-						tm->tm_sec += val;
-						*fsec += rint(fval * 1000000);
+						if (!AdjustMicroseconds(val, fval, USECS_PER_SEC, itm_in))
+							return DTERR_FIELD_OVERFLOW;
 
 						/*
 						 * If any subseconds were specified, consider this
@@ -3274,57 +3520,64 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 						break;
 
 					case DTK_MINUTE:
-						tm->tm_min += val;
-						AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
+						if (!AdjustMicroseconds(val, fval, USECS_PER_MINUTE, itm_in))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(MINUTE);
 						break;
 
 					case DTK_HOUR:
-						tm->tm_hour += val;
-						AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
+						if (!AdjustMicroseconds(val, fval, USECS_PER_HOUR, itm_in))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(HOUR);
 						type = DTK_DAY; /* set for next field */
 						break;
 
 					case DTK_DAY:
-						tm->tm_mday += val;
-						AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+						if (!AdjustDays(val, 1, itm_in) ||
+							!AdjustFractMicroseconds(fval, USECS_PER_DAY, itm_in))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(DAY);
 						break;
 
 					case DTK_WEEK:
-						tm->tm_mday += val * 7;
-						AdjustFractDays(fval, tm, fsec, 7);
+						if (!AdjustDays(val, 7, itm_in) ||
+							!AdjustFractDays(fval, 7, itm_in))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(WEEK);
 						break;
 
 					case DTK_MONTH:
-						tm->tm_mon += val;
-						AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
+						if (!AdjustMonths(val, itm_in) ||
+							!AdjustFractDays(fval, DAYS_PER_MONTH, itm_in))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(MONTH);
 						break;
 
 					case DTK_YEAR:
-						tm->tm_year += val;
-						tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
+						if (!AdjustYears(val, 1, itm_in) ||
+							!AdjustFractYears(fval, 1, itm_in))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(YEAR);
 						break;
 
 					case DTK_DECADE:
-						tm->tm_year += val * 10;
-						tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 10);
+						if (!AdjustYears(val, 10, itm_in) ||
+							!AdjustFractYears(fval, 10, itm_in))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(DECADE);
 						break;
 
 					case DTK_CENTURY:
-						tm->tm_year += val * 100;
-						tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 100);
+						if (!AdjustYears(val, 100, itm_in) ||
+							!AdjustFractYears(fval, 100, itm_in))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(CENTURY);
 						break;
 
 					case DTK_MILLENNIUM:
-						tm->tm_year += val * 1000;
-						tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 1000);
+						if (!AdjustYears(val, 1000, itm_in) ||
+							!AdjustFractYears(fval, 1000, itm_in))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(MILLENNIUM);
 						break;
 
@@ -3335,7 +3588,7 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 
 			case DTK_STRING:
 			case DTK_SPECIAL:
-				type = DecodeUnits(i, field[i], &val);
+				type = DecodeUnits(i, field[i], &uval);
 				if (type == IGNORE_DTF)
 					continue;
 
@@ -3343,17 +3596,17 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 				switch (type)
 				{
 					case UNITS:
-						type = val;
+						type = uval;
 						break;
 
 					case AGO:
 						is_before = true;
-						type = val;
+						type = uval;
 						break;
 
 					case RESERV:
 						tmask = (DTK_DATE_M | DTK_TIME_M);
-						*dtype = val;
+						*dtype = uval;
 						break;
 
 					default:
@@ -3374,79 +3627,19 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 	if (fmask == 0)
 		return DTERR_BAD_FORMAT;
 
-	/* ensure fractional seconds are fractional */
-	if (*fsec != 0)
-	{
-		int			sec;
-
-		sec = *fsec / USECS_PER_SEC;
-		*fsec -= sec * USECS_PER_SEC;
-		tm->tm_sec += sec;
-	}
-
-	/*----------
-	 * 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 (*fsec > 0)
-				*fsec = -(*fsec);
-			if (tm->tm_sec > 0)
-				tm->tm_sec = -tm->tm_sec;
-			if (tm->tm_min > 0)
-				tm->tm_min = -tm->tm_min;
-			if (tm->tm_hour > 0)
-				tm->tm_hour = -tm->tm_hour;
-			if (tm->tm_mday > 0)
-				tm->tm_mday = -tm->tm_mday;
-			if (tm->tm_mon > 0)
-				tm->tm_mon = -tm->tm_mon;
-			if (tm->tm_year > 0)
-				tm->tm_year = -tm->tm_year;
-		}
-	}
-
 	/* finally, AGO negates everything */
 	if (is_before)
 	{
-		*fsec = -(*fsec);
-		tm->tm_sec = -tm->tm_sec;
-		tm->tm_min = -tm->tm_min;
-		tm->tm_hour = -tm->tm_hour;
-		tm->tm_mday = -tm->tm_mday;
-		tm->tm_mon = -tm->tm_mon;
-		tm->tm_year = -tm->tm_year;
+		if (itm_in->tm_usec == PG_INT64_MIN ||
+			itm_in->tm_mday == INT_MIN ||
+			itm_in->tm_mon == INT_MIN ||
+			itm_in->tm_year == INT_MIN)
+			return DTERR_FIELD_OVERFLOW;
+
+		itm_in->tm_usec = -itm_in->tm_usec;
+		itm_in->tm_mday = -itm_in->tm_mday;
+		itm_in->tm_mon = -itm_in->tm_mon;
+		itm_in->tm_year = -itm_in->tm_year;
 	}
 
 	return 0;
@@ -3460,26 +3653,35 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
  * Returns 0 or DTERR code.
  */
 static int
-ParseISO8601Number(char *str, char **endptr, int *ipart, double *fpart)
+ParseISO8601Number(char *str, char **endptr, int64 *ipart, double *fpart)
 {
-	double		val;
+	int			sign = 1;
 
-	if (!(isdigit((unsigned char) *str) || *str == '-' || *str == '.'))
-		return DTERR_BAD_FORMAT;
+	*ipart = 0;
+	*fpart = 0.0;
+
+	/* Parse sign if there is any */
+	if (*str == '-')
+	{
+		sign = -1;
+		str++;
+	}
+
+	*endptr = str;
 	errno = 0;
-	val = strtod(str, endptr);
-	/* did we not see anything that looks like a double? */
+
+	/* Parse int64 part if there is any */
+	if (isdigit((unsigned char) **endptr))
+		*ipart = strtoi64(*endptr, endptr, 10) * sign;
+
+	/* Parse fractional part if there is any */
+	if (**endptr == '.')
+		*fpart = strtod(*endptr, endptr) * sign;
+
+	/* did we not see anything that looks like a number? */
 	if (*endptr == str || errno != 0)
 		return DTERR_BAD_FORMAT;
-	/* watch out for overflow */
-	if (val < INT_MIN || val > INT_MAX)
-		return DTERR_FIELD_OVERFLOW;
-	/* be very sure we truncate towards zero (cf dtrunc()) */
-	if (val >= 0)
-		*ipart = (int) floor(val);
-	else
-		*ipart = (int) -floor(-val);
-	*fpart = val - *ipart;
+
 	return 0;
 }
 
@@ -3508,7 +3710,7 @@ ISO8601IntegerWidth(char *fieldstart)
  * Returns 0 if successful, DTERR code if bogus input detected.
  * Note: error code should be DTERR_BAD_FORMAT if input doesn't look like
  * ISO8601, otherwise this could cause unexpected error messages.
- * dtype, tm, fsec are output parameters.
+ * dtype and itm_in are output parameters.
  *
  *	A couple exceptions from the spec:
  *	 - a week field ('W') may coexist with other units
@@ -3516,13 +3718,13 @@ ISO8601IntegerWidth(char *fieldstart)
  */
 int
 DecodeISO8601Interval(char *str,
-					  int *dtype, struct pg_tm *tm, fsec_t *fsec)
+					  int *dtype, struct pg_itm_in *itm_in)
 {
 	bool		datepart = true;
 	bool		havefield = false;
 
 	*dtype = DTK_DELTA;
-	ClearPgTm(tm, fsec);
+	ClearPgItmIn(itm_in);
 
 	if (strlen(str) < 2 || str[0] != 'P')
 		return DTERR_BAD_FORMAT;
@@ -3531,7 +3733,7 @@ DecodeISO8601Interval(char *str,
 	while (*str)
 	{
 		char	   *fieldstart;
-		int			val;
+		int64		val;
 		double		fval;
 		char		unit;
 		int			dterr;
@@ -3560,29 +3762,34 @@ DecodeISO8601Interval(char *str,
 			switch (unit)		/* before T: Y M W D */
 			{
 				case 'Y':
-					tm->tm_year += val;
-					tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
+					if (!AdjustYears(val, 1, itm_in) ||
+						!AdjustFractYears(fval, 1, itm_in))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case 'M':
-					tm->tm_mon += val;
-					AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
+					if (!AdjustMonths(val, itm_in) ||
+						!AdjustFractDays(fval, DAYS_PER_MONTH, itm_in))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case 'W':
-					tm->tm_mday += val * 7;
-					AdjustFractDays(fval, tm, fsec, 7);
+					if (!AdjustDays(val, 7, itm_in) ||
+						!AdjustFractDays(fval, 7, itm_in))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case 'D':
-					tm->tm_mday += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+					if (!AdjustDays(val, 1, itm_in) ||
+						!AdjustFractMicroseconds(fval, USECS_PER_DAY, itm_in))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case 'T':		/* ISO 8601 4.4.3.3 Alternative Format / Basic */
 				case '\0':
 					if (ISO8601IntegerWidth(fieldstart) == 8 && !havefield)
 					{
-						tm->tm_year += val / 10000;
-						tm->tm_mon += (val / 100) % 100;
-						tm->tm_mday += val % 100;
-						AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+						if (!AdjustYears(val / 10000, 1, itm_in) ||
+							!AdjustMonths((val / 100) % 100, itm_in) ||
+							!AdjustDays(val % 100, 1, itm_in) ||
+							!AdjustFractMicroseconds(fval, USECS_PER_DAY, itm_in))
+							return DTERR_FIELD_OVERFLOW;
 						if (unit == '\0')
 							return 0;
 						datepart = false;
@@ -3596,8 +3803,9 @@ DecodeISO8601Interval(char *str,
 					if (havefield)
 						return DTERR_BAD_FORMAT;
 
-					tm->tm_year += val;
-					tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
+					if (!AdjustYears(val, 1, itm_in) ||
+						!AdjustFractYears(fval, 1, itm_in))
+						return DTERR_FIELD_OVERFLOW;
 					if (unit == '\0')
 						return 0;
 					if (unit == 'T')
@@ -3610,8 +3818,9 @@ DecodeISO8601Interval(char *str,
 					dterr = ParseISO8601Number(str, &str, &val, &fval);
 					if (dterr)
 						return dterr;
-					tm->tm_mon += val;
-					AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
+					if (!AdjustMonths(val, itm_in) ||
+						!AdjustFractDays(fval, DAYS_PER_MONTH, itm_in))
+						return DTERR_FIELD_OVERFLOW;
 					if (*str == '\0')
 						return 0;
 					if (*str == 'T')
@@ -3627,8 +3836,9 @@ DecodeISO8601Interval(char *str,
 					dterr = ParseISO8601Number(str, &str, &val, &fval);
 					if (dterr)
 						return dterr;
-					tm->tm_mday += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+					if (!AdjustDays(val, 1, itm_in) ||
+						!AdjustFractMicroseconds(fval, USECS_PER_DAY, itm_in))
+						return DTERR_FIELD_OVERFLOW;
 					if (*str == '\0')
 						return 0;
 					if (*str == 'T')
@@ -3648,24 +3858,25 @@ DecodeISO8601Interval(char *str,
 			switch (unit)		/* after T: H M S */
 			{
 				case 'H':
-					tm->tm_hour += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
+					if (!AdjustMicroseconds(val, fval, USECS_PER_HOUR, itm_in))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case 'M':
-					tm->tm_min += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
+					if (!AdjustMicroseconds(val, fval, USECS_PER_MINUTE, itm_in))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case 'S':
-					tm->tm_sec += val;
-					AdjustFractSeconds(fval, tm, fsec, 1);
+					if (!AdjustMicroseconds(val, fval, USECS_PER_SEC, itm_in))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case '\0':		/* ISO 8601 4.4.3.3 Alternative Format */
 					if (ISO8601IntegerWidth(fieldstart) == 6 && !havefield)
 					{
-						tm->tm_hour += val / 10000;
-						tm->tm_min += (val / 100) % 100;
-						tm->tm_sec += val % 100;
-						AdjustFractSeconds(fval, tm, fsec, 1);
+						if (!AdjustMicroseconds(val / 10000, 0, USECS_PER_HOUR, itm_in) ||
+							!AdjustMicroseconds((val / 100) % 100, 0, USECS_PER_MINUTE, itm_in) ||
+							!AdjustMicroseconds(val % 100, 0, USECS_PER_SEC, itm_in) ||
+							!AdjustFractMicroseconds(fval, 1, itm_in))
+							return DTERR_FIELD_OVERFLOW;
 						return 0;
 					}
 					/* Else fall through to extended alternative format */
@@ -3675,16 +3886,16 @@ DecodeISO8601Interval(char *str,
 					if (havefield)
 						return DTERR_BAD_FORMAT;
 
-					tm->tm_hour += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
+					if (!AdjustMicroseconds(val, fval, USECS_PER_HOUR, itm_in))
+						return DTERR_FIELD_OVERFLOW;
 					if (unit == '\0')
 						return 0;
 
 					dterr = ParseISO8601Number(str, &str, &val, &fval);
 					if (dterr)
 						return dterr;
-					tm->tm_min += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
+					if (!AdjustMicroseconds(val, fval, USECS_PER_MINUTE, itm_in))
+						return DTERR_FIELD_OVERFLOW;
 					if (*str == '\0')
 						return 0;
 					if (*str != ':')
@@ -3694,8 +3905,8 @@ DecodeISO8601Interval(char *str,
 					dterr = ParseISO8601Number(str, &str, &val, &fval);
 					if (dterr)
 						return dterr;
-					tm->tm_sec += val;
-					AdjustFractSeconds(fval, tm, fsec, 1);
+					if (!AdjustMicroseconds(val, fval, USECS_PER_SEC, itm_in))
+						return DTERR_FIELD_OVERFLOW;
 					if (*str == '\0')
 						return 0;
 					return DTERR_BAD_FORMAT;
@@ -4166,25 +4377,25 @@ EncodeDateTime(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, const char
 
 /* Append an ISO-8601-style interval field, but only if value isn't zero */
 static char *
-AddISO8601IntPart(char *cp, int value, char units)
+AddISO8601IntPart(char *cp, int64 value, char units)
 {
 	if (value == 0)
 		return cp;
-	sprintf(cp, "%d%c", value, units);
+	sprintf(cp, "%lld%c", (long long) value, units);
 	return cp + strlen(cp);
 }
 
 /* Append a postgres-style interval field, but only if value isn't zero */
 static char *
-AddPostgresIntPart(char *cp, int value, const char *units,
+AddPostgresIntPart(char *cp, int64 value, const char *units,
 				   bool *is_zero, bool *is_before)
 {
 	if (value == 0)
 		return cp;
-	sprintf(cp, "%s%s%d %s%s",
+	sprintf(cp, "%s%s%lld %s%s",
 			(!*is_zero) ? " " : "",
 			(*is_before && value > 0) ? "+" : "",
-			value,
+			(long long) value,
 			units,
 			(value != 1) ? "s" : "");
 
@@ -4199,7 +4410,7 @@ AddPostgresIntPart(char *cp, int value, const char *units,
 
 /* Append a verbose-style interval field, but only if value isn't zero */
 static char *
-AddVerboseIntPart(char *cp, int value, const char *units,
+AddVerboseIntPart(char *cp, int64 value, const char *units,
 				  bool *is_zero, bool *is_before)
 {
 	if (value == 0)
@@ -4208,11 +4419,11 @@ AddVerboseIntPart(char *cp, int value, const char *units,
 	if (*is_zero)
 	{
 		*is_before = (value < 0);
-		value = abs(value);
+		value = Abs(value);
 	}
 	else if (*is_before)
 		value = -value;
-	sprintf(cp, " %d %s%s", value, units, (value == 1) ? "" : "s");
+	sprintf(cp, " %lld %s%s", (long long) value, units, (value == 1) ? "" : "s");
 	*is_zero = false;
 	return cp + strlen(cp);
 }
@@ -4238,15 +4449,16 @@ AddVerboseIntPart(char *cp, int value, const char *units,
  * "day-time literal"s (that look like ('4 5:6:7')
  */
 void
-EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
+EncodeInterval(struct pg_itm *itm, int style, char *str)
 {
 	char	   *cp = str;
-	int			year = tm->tm_year;
-	int			mon = tm->tm_mon;
-	int			mday = tm->tm_mday;
-	int			hour = tm->tm_hour;
-	int			min = tm->tm_min;
-	int			sec = tm->tm_sec;
+	int			year = itm->tm_year;
+	int			mon = itm->tm_mon;
+	int64		mday = itm->tm_mday;	/* tm_mday could be INT_MIN */
+	int64		hour = itm->tm_hour;
+	int			min = itm->tm_min;
+	int			sec = itm->tm_sec;
+	int			fsec = itm->tm_usec;
 	bool		is_before = false;
 	bool		is_zero = true;
 
@@ -4306,10 +4518,10 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
 					char		sec_sign = (hour < 0 || min < 0 ||
 											sec < 0 || fsec < 0) ? '-' : '+';
 
-					sprintf(cp, "%c%d-%d %c%d %c%d:%02d:",
+					sprintf(cp, "%c%d-%d %c%lld %c%lld:%02d:",
 							year_sign, abs(year), abs(mon),
-							day_sign, abs(mday),
-							sec_sign, abs(hour), abs(min));
+							day_sign, (long long) Abs(mday),
+							sec_sign, (long long) Abs(hour), abs(min));
 					cp += strlen(cp);
 					cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
 					*cp = '\0';
@@ -4320,14 +4532,15 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
 				}
 				else if (has_day)
 				{
-					sprintf(cp, "%d %d:%02d:", mday, hour, min);
+					sprintf(cp, "%lld %lld:%02d:",
+							(long long) mday, (long long) hour, min);
 					cp += strlen(cp);
 					cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
 					*cp = '\0';
 				}
 				else
 				{
-					sprintf(cp, "%d:%02d:", hour, min);
+					sprintf(cp, "%lld:%02d:", (long long) hour, min);
 					cp += strlen(cp);
 					cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
 					*cp = '\0';
@@ -4377,10 +4590,10 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
 			{
 				bool		minus = (hour < 0 || min < 0 || sec < 0 || fsec < 0);
 
-				sprintf(cp, "%s%s%02d:%02d:",
+				sprintf(cp, "%s%s%02lld:%02d:",
 						is_zero ? "" : " ",
 						(minus ? "-" : (is_before ? "+" : "")),
-						abs(hour), abs(min));
+						(long long) Abs(hour), abs(min));
 				cp += strlen(cp);
 				cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
 				*cp = '\0';
@@ -4668,7 +4881,7 @@ pg_timezone_abbrevs(PG_FUNCTION_ARGS)
 	int			gmtoffset;
 	bool		is_dst;
 	unsigned char *p;
-	struct pg_tm tm;
+	struct pg_itm_in itm_in;
 	Interval   *resInterval;
 
 	/* stuff done only on the first call of the function */
@@ -4761,11 +4974,11 @@ pg_timezone_abbrevs(PG_FUNCTION_ARGS)
 
 	values[0] = CStringGetTextDatum(buffer);
 
-	/* Convert offset (in seconds) to an interval */
-	MemSet(&tm, 0, sizeof(struct pg_tm));
-	tm.tm_sec = gmtoffset;
+	/* Convert offset (in seconds) to an interval; can't overflow */
+	MemSet(&itm_in, 0, sizeof(struct pg_itm_in));
+	itm_in.tm_usec = (int64) gmtoffset * USECS_PER_SEC;
 	resInterval = (Interval *) palloc(sizeof(Interval));
-	tm2interval(&tm, 0, resInterval);
+	(void) itmin2interval(&itm_in, resInterval);
 	values[1] = IntervalPGetDatum(resInterval);
 
 	values[2] = BoolGetDatum(is_dst);
@@ -4795,7 +5008,7 @@ pg_timezone_names(PG_FUNCTION_ARGS)
 	fsec_t		fsec;
 	const char *tzn;
 	Interval   *resInterval;
-	struct pg_tm itm;
+	struct pg_itm_in itm_in;
 
 	SetSingleFuncCall(fcinfo, 0);
 
@@ -4831,10 +5044,11 @@ pg_timezone_names(PG_FUNCTION_ARGS)
 		values[0] = CStringGetTextDatum(pg_get_timezone_name(tz));
 		values[1] = CStringGetTextDatum(tzn ? tzn : "");
 
-		MemSet(&itm, 0, sizeof(struct pg_tm));
-		itm.tm_sec = -tzoff;
+		/* Convert tzoff to an interval; can't overflow */
+		MemSet(&itm_in, 0, sizeof(struct pg_itm_in));
+		itm_in.tm_usec = (int64) -tzoff * USECS_PER_SEC;
 		resInterval = (Interval *) palloc(sizeof(Interval));
-		tm2interval(&itm, 0, resInterval);
+		(void) itmin2interval(&itm_in, resInterval);
 		values[2] = IntervalPGetDatum(resInterval);
 
 		values[3] = BoolGetDatum(tm.tm_isdst > 0);
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index ac74333be5..843b07d7d2 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -491,11 +491,28 @@ typedef struct
 
 /* ----------
  * Datetime to char conversion
+ *
+ * To support intervals as well as timestamps, we use a custom "tm" struct
+ * that is almost like struct pg_tm, but has a 64-bit tm_hour field.
+ * We omit the tm_isdst and tm_zone fields, which are not used here.
  * ----------
  */
+struct fmt_tm
+{
+	int			tm_sec;
+	int			tm_min;
+	int64		tm_hour;
+	int			tm_mday;
+	int			tm_mon;
+	int			tm_year;
+	int			tm_wday;
+	int			tm_yday;
+	long int	tm_gmtoff;
+};
+
 typedef struct TmToChar
 {
-	struct pg_tm tm;			/* classic 'tm' struct */
+	struct fmt_tm tm;			/* almost the classic 'tm' struct */
 	fsec_t		fsec;			/* fractional seconds */
 	const char *tzn;			/* timezone */
 } TmToChar;
@@ -504,12 +521,25 @@ typedef struct TmToChar
 #define tmtcTzn(_X) ((_X)->tzn)
 #define tmtcFsec(_X)	((_X)->fsec)
 
+/* Note: this is used to copy pg_tm to fmt_tm, so not quite a bitwise copy */
+#define COPY_tm(_DST, _SRC) \
+do {	\
+	(_DST)->tm_sec = (_SRC)->tm_sec; \
+	(_DST)->tm_min = (_SRC)->tm_min; \
+	(_DST)->tm_hour = (_SRC)->tm_hour; \
+	(_DST)->tm_mday = (_SRC)->tm_mday; \
+	(_DST)->tm_mon = (_SRC)->tm_mon; \
+	(_DST)->tm_year = (_SRC)->tm_year; \
+	(_DST)->tm_wday = (_SRC)->tm_wday; \
+	(_DST)->tm_yday = (_SRC)->tm_yday; \
+	(_DST)->tm_gmtoff = (_SRC)->tm_gmtoff; \
+} while(0)
+
+/* Caution: this is used to zero both pg_tm and fmt_tm structs */
 #define ZERO_tm(_X) \
 do {	\
-	(_X)->tm_sec  = (_X)->tm_year = (_X)->tm_min = (_X)->tm_wday = \
-	(_X)->tm_hour = (_X)->tm_yday = (_X)->tm_isdst = 0; \
-	(_X)->tm_mday = (_X)->tm_mon  = 1; \
-	(_X)->tm_zone = NULL; \
+	memset(_X, 0, sizeof(*(_X))); \
+	(_X)->tm_mday = (_X)->tm_mon = 1; \
 } while(0)
 
 #define ZERO_tmtc(_X) \
@@ -2649,7 +2679,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 {
 	FormatNode *n;
 	char	   *s;
-	struct pg_tm *tm = &in->tm;
+	struct fmt_tm *tm = &in->tm;
 	int			i;
 
 	/* cache localized days and months */
@@ -2698,16 +2728,17 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 				 * display time as shown on a 12-hour clock, even for
 				 * intervals
 				 */
-				sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : (tm->tm_hour >= 0) ? 2 : 3,
-						tm->tm_hour % (HOURS_PER_DAY / 2) == 0 ? HOURS_PER_DAY / 2 :
-						tm->tm_hour % (HOURS_PER_DAY / 2));
+				sprintf(s, "%0*lld", S_FM(n->suffix) ? 0 : (tm->tm_hour >= 0) ? 2 : 3,
+						tm->tm_hour % (HOURS_PER_DAY / 2) == 0 ?
+						(long long) (HOURS_PER_DAY / 2) :
+						(long long) (tm->tm_hour % (HOURS_PER_DAY / 2)));
 				if (S_THth(n->suffix))
 					str_numth(s, s, S_TH_TYPE(n->suffix));
 				s += strlen(s);
 				break;
 			case DCH_HH24:
-				sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : (tm->tm_hour >= 0) ? 2 : 3,
-						tm->tm_hour);
+				sprintf(s, "%0*lld", S_FM(n->suffix) ? 0 : (tm->tm_hour >= 0) ? 2 : 3,
+						(long long) tm->tm_hour);
 				if (S_THth(n->suffix))
 					str_numth(s, s, S_TH_TYPE(n->suffix));
 				s += strlen(s);
@@ -2755,9 +2786,10 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 				break;
 #undef DCH_to_char_fsec
 			case DCH_SSSS:
-				sprintf(s, "%d", tm->tm_hour * SECS_PER_HOUR +
-						tm->tm_min * SECS_PER_MINUTE +
-						tm->tm_sec);
+				sprintf(s, "%lld",
+						(long long) (tm->tm_hour * SECS_PER_HOUR +
+									 tm->tm_min * SECS_PER_MINUTE +
+									 tm->tm_sec));
 				if (S_THth(n->suffix))
 					str_numth(s, s, S_TH_TYPE(n->suffix));
 				s += strlen(s);
@@ -4088,7 +4120,8 @@ timestamp_to_char(PG_FUNCTION_ARGS)
 	text	   *fmt = PG_GETARG_TEXT_PP(1),
 			   *res;
 	TmToChar	tmtc;
-	struct pg_tm *tm;
+	struct pg_tm tt;
+	struct fmt_tm *tm;
 	int			thisdate;
 
 	if (VARSIZE_ANY_EXHDR(fmt) <= 0 || TIMESTAMP_NOT_FINITE(dt))
@@ -4097,10 +4130,11 @@ timestamp_to_char(PG_FUNCTION_ARGS)
 	ZERO_tmtc(&tmtc);
 	tm = tmtcTm(&tmtc);
 
-	if (timestamp2tm(dt, NULL, tm, &tmtcFsec(&tmtc), NULL, NULL) != 0)
+	if (timestamp2tm(dt, NULL, &tt, &tmtcFsec(&tmtc), NULL, NULL) != 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 				 errmsg("timestamp out of range")));
+	COPY_tm(tm, &tt);
 
 	thisdate = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday);
 	tm->tm_wday = (thisdate + 1) % 7;
@@ -4120,7 +4154,8 @@ timestamptz_to_char(PG_FUNCTION_ARGS)
 			   *res;
 	TmToChar	tmtc;
 	int			tz;
-	struct pg_tm *tm;
+	struct pg_tm tt;
+	struct fmt_tm *tm;
 	int			thisdate;
 
 	if (VARSIZE_ANY_EXHDR(fmt) <= 0 || TIMESTAMP_NOT_FINITE(dt))
@@ -4129,10 +4164,11 @@ timestamptz_to_char(PG_FUNCTION_ARGS)
 	ZERO_tmtc(&tmtc);
 	tm = tmtcTm(&tmtc);
 
-	if (timestamp2tm(dt, &tz, tm, &tmtcFsec(&tmtc), &tmtcTzn(&tmtc), NULL) != 0)
+	if (timestamp2tm(dt, &tz, &tt, &tmtcFsec(&tmtc), &tmtcTzn(&tmtc), NULL) != 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 				 errmsg("timestamp out of range")));
+	COPY_tm(tm, &tt);
 
 	thisdate = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday);
 	tm->tm_wday = (thisdate + 1) % 7;
@@ -4156,7 +4192,9 @@ interval_to_char(PG_FUNCTION_ARGS)
 	text	   *fmt = PG_GETARG_TEXT_PP(1),
 			   *res;
 	TmToChar	tmtc;
-	struct pg_tm *tm;
+	struct fmt_tm *tm;
+	struct pg_itm tt,
+			   *itm = &tt;
 
 	if (VARSIZE_ANY_EXHDR(fmt) <= 0)
 		PG_RETURN_NULL();
@@ -4164,8 +4202,14 @@ interval_to_char(PG_FUNCTION_ARGS)
 	ZERO_tmtc(&tmtc);
 	tm = tmtcTm(&tmtc);
 
-	if (interval2tm(*it, tm, &tmtcFsec(&tmtc)) != 0)
-		PG_RETURN_NULL();
+	interval2itm(*it, itm);
+	tmtc.fsec = itm->tm_usec;
+	tm->tm_sec = itm->tm_sec;
+	tm->tm_min = itm->tm_min;
+	tm->tm_hour = itm->tm_hour;
+	tm->tm_mday = itm->tm_mday;
+	tm->tm_mon = itm->tm_mon;
+	tm->tm_year = itm->tm_year;
 
 	/* wday is meaningless, yday approximates the total span in days */
 	tm->tm_yday = (tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon) * DAYS_PER_MONTH + tm->tm_mday;
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 2ba8d41284..f9489144c7 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -889,9 +889,8 @@ interval_in(PG_FUNCTION_ARGS)
 #endif
 	int32		typmod = PG_GETARG_INT32(2);
 	Interval   *result;
-	fsec_t		fsec;
-	struct pg_tm tt,
-			   *tm = &tt;
+	struct pg_itm_in tt,
+			   *itm_in = &tt;
 	int			dtype;
 	int			nf;
 	int			range;
@@ -900,13 +899,10 @@ interval_in(PG_FUNCTION_ARGS)
 	int			ftype[MAXDATEFIELDS];
 	char		workbuf[256];
 
-	tm->tm_year = 0;
-	tm->tm_mon = 0;
-	tm->tm_mday = 0;
-	tm->tm_hour = 0;
-	tm->tm_min = 0;
-	tm->tm_sec = 0;
-	fsec = 0;
+	itm_in->tm_year = 0;
+	itm_in->tm_mon = 0;
+	itm_in->tm_mday = 0;
+	itm_in->tm_usec = 0;
 
 	if (typmod >= 0)
 		range = INTERVAL_RANGE(typmod);
@@ -917,12 +913,12 @@ interval_in(PG_FUNCTION_ARGS)
 						  ftype, MAXDATEFIELDS, &nf);
 	if (dterr == 0)
 		dterr = DecodeInterval(field, ftype, nf, range,
-							   &dtype, tm, &fsec);
+							   &dtype, itm_in);
 
 	/* if those functions think it's a bad format, try ISO8601 style */
 	if (dterr == DTERR_BAD_FORMAT)
 		dterr = DecodeISO8601Interval(str,
-									  &dtype, tm, &fsec);
+									  &dtype, itm_in);
 
 	if (dterr != 0)
 	{
@@ -936,7 +932,7 @@ interval_in(PG_FUNCTION_ARGS)
 	switch (dtype)
 	{
 		case DTK_DELTA:
-			if (tm2interval(tm, fsec, result) != 0)
+			if (itmin2interval(itm_in, result) != 0)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 						 errmsg("interval out of range")));
@@ -960,15 +956,12 @@ interval_out(PG_FUNCTION_ARGS)
 {
 	Interval   *span = PG_GETARG_INTERVAL_P(0);
 	char	   *result;
-	struct pg_tm tt,
-			   *tm = &tt;
-	fsec_t		fsec;
+	struct pg_itm tt,
+			   *itm = &tt;
 	char		buf[MAXDATELEN + 1];
 
-	if (interval2tm(*span, tm, &fsec) != 0)
-		elog(ERROR, "could not convert interval to tm");
-
-	EncodeInterval(tm, fsec, IntervalStyle, buf);
+	interval2itm(*span, itm);
+	EncodeInterval(itm, IntervalStyle, buf);
 
 	result = pstrdup(buf);
 	PG_RETURN_CSTRING(result);
@@ -1960,50 +1953,77 @@ tm2timestamp(struct pg_tm *tm, fsec_t fsec, int *tzp, Timestamp *result)
 }
 
 
-/* interval2tm()
- * Convert an interval data type to a tm structure.
+/* interval2itm()
+ * Convert an Interval to a pg_itm structure.
+ * Note: overflow is not possible, because the pg_itm fields are
+ * wide enough for all possible conversion results.
  */
-int
-interval2tm(Interval span, struct pg_tm *tm, fsec_t *fsec)
+void
+interval2itm(Interval span, struct pg_itm *itm)
 {
 	TimeOffset	time;
 	TimeOffset	tfrac;
 
-	tm->tm_year = span.month / MONTHS_PER_YEAR;
-	tm->tm_mon = span.month % MONTHS_PER_YEAR;
-	tm->tm_mday = span.day;
+	itm->tm_year = span.month / MONTHS_PER_YEAR;
+	itm->tm_mon = span.month % MONTHS_PER_YEAR;
+	itm->tm_mday = span.day;
 	time = span.time;
 
 	tfrac = time / USECS_PER_HOUR;
 	time -= tfrac * USECS_PER_HOUR;
-	tm->tm_hour = tfrac;
-	if (!SAMESIGN(tm->tm_hour, tfrac))
-		ereport(ERROR,
-				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-				 errmsg("interval out of range")));
+	itm->tm_hour = tfrac;
 	tfrac = time / USECS_PER_MINUTE;
 	time -= tfrac * USECS_PER_MINUTE;
-	tm->tm_min = tfrac;
+	itm->tm_min = (int) tfrac;
 	tfrac = time / USECS_PER_SEC;
-	*fsec = time - (tfrac * USECS_PER_SEC);
-	tm->tm_sec = tfrac;
+	time -= tfrac * USECS_PER_SEC;
+	itm->tm_sec = (int) tfrac;
+	itm->tm_usec = (int) time;
+}
 
+/* itm2interval()
+ * Convert a pg_itm structure to an Interval.
+ * Returns 0 if OK, -1 on overflow.
+ */
+int
+itm2interval(struct pg_itm *itm, Interval *span)
+{
+	int64		total_months = (int64) itm->tm_year * MONTHS_PER_YEAR + itm->tm_mon;
+
+	if (total_months > INT_MAX || total_months < INT_MIN)
+		return -1;
+	span->month = (int32) total_months;
+	span->day = itm->tm_mday;
+	if (pg_mul_s64_overflow(itm->tm_hour, USECS_PER_HOUR,
+							&span->time))
+		return -1;
+	/* tm_min, tm_sec are 32 bits, so intermediate products can't overflow */
+	if (pg_add_s64_overflow(span->time, itm->tm_min * USECS_PER_MINUTE,
+							&span->time))
+		return -1;
+	if (pg_add_s64_overflow(span->time, itm->tm_sec * USECS_PER_SEC,
+							&span->time))
+		return -1;
+	if (pg_add_s64_overflow(span->time, itm->tm_usec,
+							&span->time))
+		return -1;
 	return 0;
 }
 
+/* itmin2interval()
+ * Convert a pg_itm_in structure to an Interval.
+ * Returns 0 if OK, -1 on overflow.
+ */
 int
-tm2interval(struct pg_tm *tm, fsec_t fsec, Interval *span)
+itmin2interval(struct pg_itm_in *itm_in, Interval *span)
 {
-	double		total_months = (double) tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon;
+	int64		total_months = (int64) itm_in->tm_year * MONTHS_PER_YEAR + itm_in->tm_mon;
 
 	if (total_months > INT_MAX || total_months < INT_MIN)
 		return -1;
-	span->month = total_months;
-	span->day = tm->tm_mday;
-	span->time = (((((tm->tm_hour * INT64CONST(60)) +
-					 tm->tm_min) * INT64CONST(60)) +
-				   tm->tm_sec) * USECS_PER_SEC) + fsec;
-
+	span->month = (int32) total_months;
+	span->day = itm_in->tm_mday;
+	span->time = itm_in->tm_usec;
 	return 0;
 }
 
@@ -3612,10 +3632,9 @@ timestamp_age(PG_FUNCTION_ARGS)
 	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
 	Timestamp	dt2 = PG_GETARG_TIMESTAMP(1);
 	Interval   *result;
-	fsec_t		fsec,
-				fsec1,
+	fsec_t		fsec1,
 				fsec2;
-	struct pg_tm tt,
+	struct pg_itm tt,
 			   *tm = &tt;
 	struct pg_tm tt1,
 			   *tm1 = &tt1;
@@ -3628,7 +3647,7 @@ timestamp_age(PG_FUNCTION_ARGS)
 		timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
 	{
 		/* form the symbolic difference */
-		fsec = fsec1 - fsec2;
+		tm->tm_usec = fsec1 - fsec2;
 		tm->tm_sec = tm1->tm_sec - tm2->tm_sec;
 		tm->tm_min = tm1->tm_min - tm2->tm_min;
 		tm->tm_hour = tm1->tm_hour - tm2->tm_hour;
@@ -3639,7 +3658,7 @@ timestamp_age(PG_FUNCTION_ARGS)
 		/* flip sign if necessary... */
 		if (dt1 < dt2)
 		{
-			fsec = -fsec;
+			tm->tm_usec = -tm->tm_usec;
 			tm->tm_sec = -tm->tm_sec;
 			tm->tm_min = -tm->tm_min;
 			tm->tm_hour = -tm->tm_hour;
@@ -3649,9 +3668,9 @@ timestamp_age(PG_FUNCTION_ARGS)
 		}
 
 		/* propagate any negative fields into the next higher field */
-		while (fsec < 0)
+		while (tm->tm_usec < 0)
 		{
-			fsec += USECS_PER_SEC;
+			tm->tm_usec += USECS_PER_SEC;
 			tm->tm_sec--;
 		}
 
@@ -3696,7 +3715,7 @@ timestamp_age(PG_FUNCTION_ARGS)
 		/* recover sign if necessary... */
 		if (dt1 < dt2)
 		{
-			fsec = -fsec;
+			tm->tm_usec = -tm->tm_usec;
 			tm->tm_sec = -tm->tm_sec;
 			tm->tm_min = -tm->tm_min;
 			tm->tm_hour = -tm->tm_hour;
@@ -3705,7 +3724,7 @@ timestamp_age(PG_FUNCTION_ARGS)
 			tm->tm_year = -tm->tm_year;
 		}
 
-		if (tm2interval(tm, fsec, result) != 0)
+		if (itm2interval(tm, result) != 0)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 					 errmsg("interval out of range")));
@@ -3731,10 +3750,9 @@ timestamptz_age(PG_FUNCTION_ARGS)
 	TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0);
 	TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1);
 	Interval   *result;
-	fsec_t		fsec,
-				fsec1,
+	fsec_t		fsec1,
 				fsec2;
-	struct pg_tm tt,
+	struct pg_itm tt,
 			   *tm = &tt;
 	struct pg_tm tt1,
 			   *tm1 = &tt1;
@@ -3749,7 +3767,7 @@ timestamptz_age(PG_FUNCTION_ARGS)
 		timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
 	{
 		/* form the symbolic difference */
-		fsec = fsec1 - fsec2;
+		tm->tm_usec = fsec1 - fsec2;
 		tm->tm_sec = tm1->tm_sec - tm2->tm_sec;
 		tm->tm_min = tm1->tm_min - tm2->tm_min;
 		tm->tm_hour = tm1->tm_hour - tm2->tm_hour;
@@ -3760,7 +3778,7 @@ timestamptz_age(PG_FUNCTION_ARGS)
 		/* flip sign if necessary... */
 		if (dt1 < dt2)
 		{
-			fsec = -fsec;
+			tm->tm_usec = -tm->tm_usec;
 			tm->tm_sec = -tm->tm_sec;
 			tm->tm_min = -tm->tm_min;
 			tm->tm_hour = -tm->tm_hour;
@@ -3770,9 +3788,9 @@ timestamptz_age(PG_FUNCTION_ARGS)
 		}
 
 		/* propagate any negative fields into the next higher field */
-		while (fsec < 0)
+		while (tm->tm_usec < 0)
 		{
-			fsec += USECS_PER_SEC;
+			tm->tm_usec += USECS_PER_SEC;
 			tm->tm_sec--;
 		}
 
@@ -3821,7 +3839,7 @@ timestamptz_age(PG_FUNCTION_ARGS)
 		/* recover sign if necessary... */
 		if (dt1 < dt2)
 		{
-			fsec = -fsec;
+			tm->tm_usec = -tm->tm_usec;
 			tm->tm_sec = -tm->tm_sec;
 			tm->tm_min = -tm->tm_min;
 			tm->tm_hour = -tm->tm_hour;
@@ -3830,7 +3848,7 @@ timestamptz_age(PG_FUNCTION_ARGS)
 			tm->tm_year = -tm->tm_year;
 		}
 
-		if (tm2interval(tm, fsec, result) != 0)
+		if (itm2interval(tm, result) != 0)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 					 errmsg("interval out of range")));
@@ -4317,8 +4335,7 @@ interval_trunc(PG_FUNCTION_ARGS)
 	int			type,
 				val;
 	char	   *lowunits;
-	fsec_t		fsec;
-	struct pg_tm tt,
+	struct pg_itm tt,
 			   *tm = &tt;
 
 	result = (Interval *) palloc(sizeof(Interval));
@@ -4331,7 +4348,7 @@ interval_trunc(PG_FUNCTION_ARGS)
 
 	if (type == UNITS)
 	{
-		if (interval2tm(*interval, tm, &fsec) == 0)
+		interval2itm(*interval, tm);
 		{
 			switch (val)
 			{
@@ -4366,10 +4383,10 @@ interval_trunc(PG_FUNCTION_ARGS)
 					tm->tm_sec = 0;
 					/* FALL THRU */
 				case DTK_SECOND:
-					fsec = 0;
+					tm->tm_usec = 0;
 					break;
 				case DTK_MILLISEC:
-					fsec = (fsec / 1000) * 1000;
+					tm->tm_usec = (tm->tm_usec / 1000) * 1000;
 					break;
 				case DTK_MICROSEC:
 					break;
@@ -4382,13 +4399,11 @@ interval_trunc(PG_FUNCTION_ARGS)
 							 (val == DTK_WEEK) ? errdetail("Months usually have fractional weeks.") : 0));
 			}
 
-			if (tm2interval(tm, fsec, result) != 0)
+			if (itm2interval(tm, result) != 0)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 						 errmsg("interval out of range")));
 		}
-		else
-			elog(ERROR, "could not convert interval to tm");
 	}
 	else
 	{
@@ -5200,8 +5215,7 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
 	int			type,
 				val;
 	char	   *lowunits;
-	fsec_t		fsec;
-	struct pg_tm tt,
+	struct pg_itm tt,
 			   *tm = &tt;
 
 	lowunits = downcase_truncate_identifier(VARDATA_ANY(units),
@@ -5214,12 +5228,12 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
 
 	if (type == UNITS)
 	{
-		if (interval2tm(*interval, tm, &fsec) == 0)
+		interval2itm(*interval, tm);
 		{
 			switch (val)
 			{
 				case DTK_MICROSEC:
-					intresult = tm->tm_sec * INT64CONST(1000000) + fsec;
+					intresult = tm->tm_sec * INT64CONST(1000000) + tm->tm_usec;
 					break;
 
 				case DTK_MILLISEC:
@@ -5228,9 +5242,9 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
 						 * tm->tm_sec * 1000 + fsec / 1000
 						 * = (tm->tm_sec * 1'000'000 + fsec) / 1000
 						 */
-						PG_RETURN_NUMERIC(int64_div_fast_to_numeric(tm->tm_sec * INT64CONST(1000000) + fsec, 3));
+						PG_RETURN_NUMERIC(int64_div_fast_to_numeric(tm->tm_sec * INT64CONST(1000000) + tm->tm_usec, 3));
 					else
-						PG_RETURN_FLOAT8(tm->tm_sec * 1000.0 + fsec / 1000.0);
+						PG_RETURN_FLOAT8(tm->tm_sec * 1000.0 + tm->tm_usec / 1000.0);
 					break;
 
 				case DTK_SECOND:
@@ -5239,9 +5253,9 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
 						 * tm->tm_sec + fsec / 1'000'000
 						 * = (tm->tm_sec * 1'000'000 + fsec) / 1'000'000
 						 */
-						PG_RETURN_NUMERIC(int64_div_fast_to_numeric(tm->tm_sec * INT64CONST(1000000) + fsec, 6));
+						PG_RETURN_NUMERIC(int64_div_fast_to_numeric(tm->tm_sec * INT64CONST(1000000) + tm->tm_usec, 6));
 					else
-						PG_RETURN_FLOAT8(tm->tm_sec + fsec / 1000000.0);
+						PG_RETURN_FLOAT8(tm->tm_sec + tm->tm_usec / 1000000.0);
 					break;
 
 				case DTK_MINUTE:
@@ -5291,11 +5305,6 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
 					intresult = 0;
 			}
 		}
-		else
-		{
-			elog(ERROR, "could not convert interval to tm");
-			intresult = 0;
-		}
 	}
 	else if (type == RESERV && val == DTK_EPOCH)
 	{
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 5fa38d20d8..d155f1b03b 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -40,6 +40,10 @@ typedef int64 TimestampTz;
 typedef int64 TimeOffset;
 typedef int32 fsec_t;			/* fractional seconds (in microseconds) */
 
+
+/*
+ * Storage format for type interval.
+ */
 typedef struct
 {
 	TimeOffset	time;			/* all time units other than days, months and
@@ -48,6 +52,41 @@ typedef struct
 	int32		month;			/* months and years, after time for alignment */
 } Interval;
 
+/*
+ * Data structure representing a broken-down interval.
+ *
+ * For historical reasons, this is modeled on struct pg_tm for timestamps.
+ * Unlike the situation for timestamps, there's no magic interpretation
+ * needed for months or years: they're just zero or not.  Note that fields
+ * can be negative; however, because of the divisions done while converting
+ * from struct Interval, only tm_mday could be INT_MIN.  This is important
+ * because we may need to negate the values in some code paths.
+ */
+struct pg_itm
+{
+	int			tm_usec;
+	int			tm_sec;
+	int			tm_min;
+	int64		tm_hour;		/* needs to be wide */
+	int			tm_mday;
+	int			tm_mon;
+	int			tm_year;
+};
+
+/*
+ * Data structure for decoding intervals.  We could just use struct pg_itm,
+ * but then the requirement for tm_usec to be 64 bits would propagate to
+ * places where it's not really needed.  Also, omitting the fields that
+ * aren't used during decoding seems like a good error-prevention measure.
+ */
+struct pg_itm_in
+{
+	int64		tm_usec;		/* needs to be wide */
+	int			tm_mday;
+	int			tm_mon;
+	int			tm_year;
+};
+
 
 /* Limits on the "precision" option (typmod) for these data types */
 #define MAX_TIMESTAMP_PRECISION 6
diff --git a/src/include/pgtime.h b/src/include/pgtime.h
index 2977b13aab..441d7847c1 100644
--- a/src/include/pgtime.h
+++ b/src/include/pgtime.h
@@ -23,6 +23,8 @@
 typedef int64 pg_time_t;
 
 /*
+ * Data structure representing a broken-down timestamp.
+ *
  * CAUTION: the IANA timezone library (src/timezone/) follows the POSIX
  * convention that tm_mon counts from 0 and tm_year is relative to 1900.
  * However, Postgres' datetime functions generally treat tm_mon as counting
@@ -44,6 +46,7 @@ struct pg_tm
 	const char *tm_zone;
 };
 
+/* These structs are opaque outside the timezone library */
 typedef struct pg_tz pg_tz;
 typedef struct pg_tzenum pg_tzenum;
 
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index 0d158f3e4b..0801858d60 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -300,9 +300,9 @@ extern int	DecodeTimeOnly(char **field, int *ftype,
 						   int nf, int *dtype,
 						   struct pg_tm *tm, fsec_t *fsec, int *tzp);
 extern int	DecodeInterval(char **field, int *ftype, int nf, int range,
-						   int *dtype, struct pg_tm *tm, fsec_t *fsec);
+						   int *dtype, struct pg_itm_in *itm_in);
 extern int	DecodeISO8601Interval(char *str,
-								  int *dtype, struct pg_tm *tm, fsec_t *fsec);
+								  int *dtype, struct pg_itm_in *itm_in);
 
 extern void DateTimeParseError(int dterr, const char *str,
 							   const char *datatype) pg_attribute_noreturn();
@@ -315,7 +315,7 @@ extern int	DetermineTimeZoneAbbrevOffsetTS(TimestampTz ts, const char *abbr,
 extern void EncodeDateOnly(struct pg_tm *tm, int style, char *str);
 extern void EncodeTimeOnly(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, int style, char *str);
 extern void EncodeDateTime(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, const char *tzn, int style, char *str);
-extern void EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str);
+extern void EncodeInterval(struct pg_itm *itm, int style, char *str);
 extern void EncodeSpecialTimestamp(Timestamp dt, char *str);
 
 extern int	ValidateDate(int fmask, bool isjulian, bool is2digits, bool bc,
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index c1a74f8e2b..d33421d380 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -88,8 +88,9 @@ extern int	timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm,
 						 fsec_t *fsec, const char **tzn, pg_tz *attimezone);
 extern void dt2time(Timestamp dt, int *hour, int *min, int *sec, fsec_t *fsec);
 
-extern int	interval2tm(Interval span, struct pg_tm *tm, fsec_t *fsec);
-extern int	tm2interval(struct pg_tm *tm, fsec_t fsec, Interval *span);
+extern void interval2itm(Interval span, struct pg_itm *itm);
+extern int	itm2interval(struct pg_itm *itm, Interval *span);
+extern int	itmin2interval(struct pg_itm_in *itm_in, Interval *span);
 
 extern Timestamp SetEpochTimestamp(void);
 extern void GetEpochTime(struct pg_tm *tm);
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 9a7e2852f2..86c8d4bc99 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -928,6 +928,617 @@ select interval '0:0:0.7', interval '@ 0.70 secs', interval '0.7 seconds';
  @ 0.7 secs | @ 0.7 secs | @ 0.7 secs
 (1 row)
 
+-- test time fields using entire 64 bit microseconds range
+select interval '2562047788.01521550194 hours';
+             interval              
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+select interval '-2562047788.01521550222 hours';
+               interval                
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+select interval '153722867280.912930117 minutes';
+             interval              
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+select interval '-153722867280.912930133 minutes';
+               interval                
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+select interval '9223372036854.775807 seconds';
+             interval              
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+select interval '-9223372036854.775808 seconds';
+               interval                
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+select interval '9223372036854775.807 milliseconds';
+             interval              
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+select interval '-9223372036854775.808 milliseconds';
+               interval                
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+select interval '9223372036854775807 microseconds';
+             interval              
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+select interval '-9223372036854775808 microseconds';
+               interval                
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+select interval 'PT2562047788H54.775807S';
+             interval              
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+select interval 'PT-2562047788H-54.775808S';
+               interval                
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+select interval 'PT2562047788:00:54.775807';
+             interval              
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+select interval 'PT2562047788.0152155019444';
+             interval              
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+select interval 'PT-2562047788.0152155022222';
+               interval                
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+-- overflow each date/time field
+select interval '2147483648 years';
+ERROR:  interval field value out of range: "2147483648 years"
+LINE 1: select interval '2147483648 years';
+                        ^
+select interval '-2147483649 years';
+ERROR:  interval field value out of range: "-2147483649 years"
+LINE 1: select interval '-2147483649 years';
+                        ^
+select interval '2147483648 months';
+ERROR:  interval field value out of range: "2147483648 months"
+LINE 1: select interval '2147483648 months';
+                        ^
+select interval '-2147483649 months';
+ERROR:  interval field value out of range: "-2147483649 months"
+LINE 1: select interval '-2147483649 months';
+                        ^
+select interval '2147483648 days';
+ERROR:  interval field value out of range: "2147483648 days"
+LINE 1: select interval '2147483648 days';
+                        ^
+select interval '-2147483649 days';
+ERROR:  interval field value out of range: "-2147483649 days"
+LINE 1: select interval '-2147483649 days';
+                        ^
+select interval '2562047789 hours';
+ERROR:  interval field value out of range: "2562047789 hours"
+LINE 1: select interval '2562047789 hours';
+                        ^
+select interval '-2562047789 hours';
+ERROR:  interval field value out of range: "-2562047789 hours"
+LINE 1: select interval '-2562047789 hours';
+                        ^
+select interval '153722867281 minutes';
+ERROR:  interval field value out of range: "153722867281 minutes"
+LINE 1: select interval '153722867281 minutes';
+                        ^
+select interval '-153722867281 minutes';
+ERROR:  interval field value out of range: "-153722867281 minutes"
+LINE 1: select interval '-153722867281 minutes';
+                        ^
+select interval '9223372036855 seconds';
+ERROR:  interval field value out of range: "9223372036855 seconds"
+LINE 1: select interval '9223372036855 seconds';
+                        ^
+select interval '-9223372036855 seconds';
+ERROR:  interval field value out of range: "-9223372036855 seconds"
+LINE 1: select interval '-9223372036855 seconds';
+                        ^
+select interval '9223372036854777 millisecond';
+ERROR:  interval field value out of range: "9223372036854777 millisecond"
+LINE 1: select interval '9223372036854777 millisecond';
+                        ^
+select interval '-9223372036854777 millisecond';
+ERROR:  interval field value out of range: "-9223372036854777 millisecond"
+LINE 1: select interval '-9223372036854777 millisecond';
+                        ^
+select interval '9223372036854775808 microsecond';
+ERROR:  interval field value out of range: "9223372036854775808 microsecond"
+LINE 1: select interval '9223372036854775808 microsecond';
+                        ^
+select interval '-9223372036854775809 microsecond';
+ERROR:  interval field value out of range: "-9223372036854775809 microsecond"
+LINE 1: select interval '-9223372036854775809 microsecond';
+                        ^
+select interval 'P2147483648';
+ERROR:  interval field value out of range: "P2147483648"
+LINE 1: select interval 'P2147483648';
+                        ^
+select interval 'P-2147483649';
+ERROR:  interval field value out of range: "P-2147483649"
+LINE 1: select interval 'P-2147483649';
+                        ^
+select interval 'P1-2147483647-2147483647';
+ERROR:  interval out of range
+LINE 1: select interval 'P1-2147483647-2147483647';
+                        ^
+select interval 'PT2562047789';
+ERROR:  interval field value out of range: "PT2562047789"
+LINE 1: select interval 'PT2562047789';
+                        ^
+select interval 'PT-2562047789';
+ERROR:  interval field value out of range: "PT-2562047789"
+LINE 1: select interval 'PT-2562047789';
+                        ^
+-- overflow with date/time unit aliases
+select interval '2147483647 weeks';
+ERROR:  interval field value out of range: "2147483647 weeks"
+LINE 1: select interval '2147483647 weeks';
+                        ^
+select interval '-2147483648 weeks';
+ERROR:  interval field value out of range: "-2147483648 weeks"
+LINE 1: select interval '-2147483648 weeks';
+                        ^
+select interval '2147483647 decades';
+ERROR:  interval field value out of range: "2147483647 decades"
+LINE 1: select interval '2147483647 decades';
+                        ^
+select interval '-2147483648 decades';
+ERROR:  interval field value out of range: "-2147483648 decades"
+LINE 1: select interval '-2147483648 decades';
+                        ^
+select interval '2147483647 centuries';
+ERROR:  interval field value out of range: "2147483647 centuries"
+LINE 1: select interval '2147483647 centuries';
+                        ^
+select interval '-2147483648 centuries';
+ERROR:  interval field value out of range: "-2147483648 centuries"
+LINE 1: select interval '-2147483648 centuries';
+                        ^
+select interval '2147483647 millennium';
+ERROR:  interval field value out of range: "2147483647 millennium"
+LINE 1: select interval '2147483647 millennium';
+                        ^
+select interval '-2147483648 millennium';
+ERROR:  interval field value out of range: "-2147483648 millennium"
+LINE 1: select interval '-2147483648 millennium';
+                        ^
+select interval '1 week 2147483647 days';
+ERROR:  interval field value out of range: "1 week 2147483647 days"
+LINE 1: select interval '1 week 2147483647 days';
+                        ^
+select interval '-1 week -2147483648 days';
+ERROR:  interval field value out of range: "-1 week -2147483648 days"
+LINE 1: select interval '-1 week -2147483648 days';
+                        ^
+select interval '2147483647 days 1 week';
+ERROR:  interval field value out of range: "2147483647 days 1 week"
+LINE 1: select interval '2147483647 days 1 week';
+                        ^
+select interval '-2147483648 days -1 week';
+ERROR:  interval field value out of range: "-2147483648 days -1 week"
+LINE 1: select interval '-2147483648 days -1 week';
+                        ^
+select interval 'P1W2147483647D';
+ERROR:  interval field value out of range: "P1W2147483647D"
+LINE 1: select interval 'P1W2147483647D';
+                        ^
+select interval 'P-1W-2147483648D';
+ERROR:  interval field value out of range: "P-1W-2147483648D"
+LINE 1: select interval 'P-1W-2147483648D';
+                        ^
+select interval 'P2147483647D1W';
+ERROR:  interval field value out of range: "P2147483647D1W"
+LINE 1: select interval 'P2147483647D1W';
+                        ^
+select interval 'P-2147483648D-1W';
+ERROR:  interval field value out of range: "P-2147483648D-1W"
+LINE 1: select interval 'P-2147483648D-1W';
+                        ^
+select interval '1 decade 2147483647 years';
+ERROR:  interval field value out of range: "1 decade 2147483647 years"
+LINE 1: select interval '1 decade 2147483647 years';
+                        ^
+select interval '1 century 2147483647 years';
+ERROR:  interval field value out of range: "1 century 2147483647 years"
+LINE 1: select interval '1 century 2147483647 years';
+                        ^
+select interval '1 millennium 2147483647 years';
+ERROR:  interval field value out of range: "1 millennium 2147483647 years"
+LINE 1: select interval '1 millennium 2147483647 years';
+                        ^
+select interval '-1 decade -2147483648 years';
+ERROR:  interval field value out of range: "-1 decade -2147483648 years"
+LINE 1: select interval '-1 decade -2147483648 years';
+                        ^
+select interval '-1 century -2147483648 years';
+ERROR:  interval field value out of range: "-1 century -2147483648 years"
+LINE 1: select interval '-1 century -2147483648 years';
+                        ^
+select interval '-1 millennium -2147483648 years';
+ERROR:  interval field value out of range: "-1 millennium -2147483648 years"
+LINE 1: select interval '-1 millennium -2147483648 years';
+                        ^
+select interval '2147483647 years 1 decade';
+ERROR:  interval field value out of range: "2147483647 years 1 decade"
+LINE 1: select interval '2147483647 years 1 decade';
+                        ^
+select interval '2147483647 years 1 century';
+ERROR:  interval field value out of range: "2147483647 years 1 century"
+LINE 1: select interval '2147483647 years 1 century';
+                        ^
+select interval '2147483647 years 1 millennium';
+ERROR:  interval field value out of range: "2147483647 years 1 millennium"
+LINE 1: select interval '2147483647 years 1 millennium';
+                        ^
+select interval '-2147483648 years -1 decade';
+ERROR:  interval field value out of range: "-2147483648 years -1 decade"
+LINE 1: select interval '-2147483648 years -1 decade';
+                        ^
+select interval '-2147483648 years -1 century';
+ERROR:  interval field value out of range: "-2147483648 years -1 century"
+LINE 1: select interval '-2147483648 years -1 century';
+                        ^
+select interval '-2147483648 years -1 millennium';
+ERROR:  interval field value out of range: "-2147483648 years -1 millennium"
+LINE 1: select interval '-2147483648 years -1 millennium';
+                        ^
+-- overflowing with fractional fields - postgres format
+select interval '0.1 millennium 2147483647 months';
+ERROR:  interval field value out of range: "0.1 millennium 2147483647 months"
+LINE 1: select interval '0.1 millennium 2147483647 months';
+                        ^
+select interval '0.1 centuries 2147483647 months';
+ERROR:  interval field value out of range: "0.1 centuries 2147483647 months"
+LINE 1: select interval '0.1 centuries 2147483647 months';
+                        ^
+select interval '0.1 decades 2147483647 months';
+ERROR:  interval field value out of range: "0.1 decades 2147483647 months"
+LINE 1: select interval '0.1 decades 2147483647 months';
+                        ^
+select interval '0.1 yrs 2147483647 months';
+ERROR:  interval field value out of range: "0.1 yrs 2147483647 months"
+LINE 1: select interval '0.1 yrs 2147483647 months';
+                        ^
+select interval '-0.1 millennium -2147483648 months';
+ERROR:  interval field value out of range: "-0.1 millennium -2147483648 months"
+LINE 1: select interval '-0.1 millennium -2147483648 months';
+                        ^
+select interval '-0.1 centuries -2147483648 months';
+ERROR:  interval field value out of range: "-0.1 centuries -2147483648 months"
+LINE 1: select interval '-0.1 centuries -2147483648 months';
+                        ^
+select interval '-0.1 decades -2147483648 months';
+ERROR:  interval field value out of range: "-0.1 decades -2147483648 months"
+LINE 1: select interval '-0.1 decades -2147483648 months';
+                        ^
+select interval '-0.1 yrs -2147483648 months';
+ERROR:  interval field value out of range: "-0.1 yrs -2147483648 months"
+LINE 1: select interval '-0.1 yrs -2147483648 months';
+                        ^
+select interval '2147483647 months 0.1 millennium';
+ERROR:  interval field value out of range: "2147483647 months 0.1 millennium"
+LINE 1: select interval '2147483647 months 0.1 millennium';
+                        ^
+select interval '2147483647 months 0.1 centuries';
+ERROR:  interval field value out of range: "2147483647 months 0.1 centuries"
+LINE 1: select interval '2147483647 months 0.1 centuries';
+                        ^
+select interval '2147483647 months 0.1 decades';
+ERROR:  interval field value out of range: "2147483647 months 0.1 decades"
+LINE 1: select interval '2147483647 months 0.1 decades';
+                        ^
+select interval '2147483647 months 0.1 yrs';
+ERROR:  interval field value out of range: "2147483647 months 0.1 yrs"
+LINE 1: select interval '2147483647 months 0.1 yrs';
+                        ^
+select interval '-2147483648 months -0.1 millennium';
+ERROR:  interval field value out of range: "-2147483648 months -0.1 millennium"
+LINE 1: select interval '-2147483648 months -0.1 millennium';
+                        ^
+select interval '-2147483648 months -0.1 centuries';
+ERROR:  interval field value out of range: "-2147483648 months -0.1 centuries"
+LINE 1: select interval '-2147483648 months -0.1 centuries';
+                        ^
+select interval '-2147483648 months -0.1 decades';
+ERROR:  interval field value out of range: "-2147483648 months -0.1 decades"
+LINE 1: select interval '-2147483648 months -0.1 decades';
+                        ^
+select interval '-2147483648 months -0.1 yrs';
+ERROR:  interval field value out of range: "-2147483648 months -0.1 yrs"
+LINE 1: select interval '-2147483648 months -0.1 yrs';
+                        ^
+select interval '0.1 months 2147483647 days';
+ERROR:  interval field value out of range: "0.1 months 2147483647 days"
+LINE 1: select interval '0.1 months 2147483647 days';
+                        ^
+select interval '-0.1 months -2147483648 days';
+ERROR:  interval field value out of range: "-0.1 months -2147483648 days"
+LINE 1: select interval '-0.1 months -2147483648 days';
+                        ^
+select interval '2147483647 days 0.1 months';
+ERROR:  interval field value out of range: "2147483647 days 0.1 months"
+LINE 1: select interval '2147483647 days 0.1 months';
+                        ^
+select interval '-2147483648 days -0.1 months';
+ERROR:  interval field value out of range: "-2147483648 days -0.1 months"
+LINE 1: select interval '-2147483648 days -0.1 months';
+                        ^
+select interval '0.5 weeks 2147483647 days';
+ERROR:  interval field value out of range: "0.5 weeks 2147483647 days"
+LINE 1: select interval '0.5 weeks 2147483647 days';
+                        ^
+select interval '-0.5 weeks -2147483648 days';
+ERROR:  interval field value out of range: "-0.5 weeks -2147483648 days"
+LINE 1: select interval '-0.5 weeks -2147483648 days';
+                        ^
+select interval '2147483647 days 0.5 weeks';
+ERROR:  interval field value out of range: "2147483647 days 0.5 weeks"
+LINE 1: select interval '2147483647 days 0.5 weeks';
+                        ^
+select interval '-2147483648 days -0.5 weeks';
+ERROR:  interval field value out of range: "-2147483648 days -0.5 weeks"
+LINE 1: select interval '-2147483648 days -0.5 weeks';
+                        ^
+select interval '0.01 months 9223372036854775807 microseconds';
+ERROR:  interval field value out of range: "0.01 months 9223372036854775807 microseconds"
+LINE 1: select interval '0.01 months 9223372036854775807 microsecond...
+                        ^
+select interval '-0.01 months -9223372036854775808 microseconds';
+ERROR:  interval field value out of range: "-0.01 months -9223372036854775808 microseconds"
+LINE 1: select interval '-0.01 months -9223372036854775808 microseco...
+                        ^
+select interval '9223372036854775807 microseconds 0.01 months';
+ERROR:  interval field value out of range: "9223372036854775807 microseconds 0.01 months"
+LINE 1: select interval '9223372036854775807 microseconds 0.01 month...
+                        ^
+select interval '-9223372036854775808 microseconds -0.01 months';
+ERROR:  interval field value out of range: "-9223372036854775808 microseconds -0.01 months"
+LINE 1: select interval '-9223372036854775808 microseconds -0.01 mon...
+                        ^
+select interval '0.1 weeks 9223372036854775807 microseconds';
+ERROR:  interval field value out of range: "0.1 weeks 9223372036854775807 microseconds"
+LINE 1: select interval '0.1 weeks 9223372036854775807 microseconds'...
+                        ^
+select interval '-0.1 weeks -9223372036854775808 microseconds';
+ERROR:  interval field value out of range: "-0.1 weeks -9223372036854775808 microseconds"
+LINE 1: select interval '-0.1 weeks -9223372036854775808 microsecond...
+                        ^
+select interval '9223372036854775807 microseconds 0.1 weeks';
+ERROR:  interval field value out of range: "9223372036854775807 microseconds 0.1 weeks"
+LINE 1: select interval '9223372036854775807 microseconds 0.1 weeks'...
+                        ^
+select interval '-9223372036854775808 microseconds -0.1 weeks';
+ERROR:  interval field value out of range: "-9223372036854775808 microseconds -0.1 weeks"
+LINE 1: select interval '-9223372036854775808 microseconds -0.1 week...
+                        ^
+select interval '0.1 days 9223372036854775807 microseconds';
+ERROR:  interval field value out of range: "0.1 days 9223372036854775807 microseconds"
+LINE 1: select interval '0.1 days 9223372036854775807 microseconds';
+                        ^
+select interval '-0.1 days -9223372036854775808 microseconds';
+ERROR:  interval field value out of range: "-0.1 days -9223372036854775808 microseconds"
+LINE 1: select interval '-0.1 days -9223372036854775808 microseconds...
+                        ^
+select interval '9223372036854775807 microseconds 0.1 days';
+ERROR:  interval field value out of range: "9223372036854775807 microseconds 0.1 days"
+LINE 1: select interval '9223372036854775807 microseconds 0.1 days';
+                        ^
+select interval '-9223372036854775808 microseconds -0.1 days';
+ERROR:  interval field value out of range: "-9223372036854775808 microseconds -0.1 days"
+LINE 1: select interval '-9223372036854775808 microseconds -0.1 days...
+                        ^
+-- overflowing with fractional fields - ISO8601 format
+select interval 'P0.1Y2147483647M';
+ERROR:  interval field value out of range: "P0.1Y2147483647M"
+LINE 1: select interval 'P0.1Y2147483647M';
+                        ^
+select interval 'P-0.1Y-2147483648M';
+ERROR:  interval field value out of range: "P-0.1Y-2147483648M"
+LINE 1: select interval 'P-0.1Y-2147483648M';
+                        ^
+select interval 'P2147483647M0.1Y';
+ERROR:  interval field value out of range: "P2147483647M0.1Y"
+LINE 1: select interval 'P2147483647M0.1Y';
+                        ^
+select interval 'P-2147483648M-0.1Y';
+ERROR:  interval field value out of range: "P-2147483648M-0.1Y"
+LINE 1: select interval 'P-2147483648M-0.1Y';
+                        ^
+select interval 'P0.1M2147483647D';
+ERROR:  interval field value out of range: "P0.1M2147483647D"
+LINE 1: select interval 'P0.1M2147483647D';
+                        ^
+select interval 'P-0.1M-2147483648D';
+ERROR:  interval field value out of range: "P-0.1M-2147483648D"
+LINE 1: select interval 'P-0.1M-2147483648D';
+                        ^
+select interval 'P2147483647D0.1M';
+ERROR:  interval field value out of range: "P2147483647D0.1M"
+LINE 1: select interval 'P2147483647D0.1M';
+                        ^
+select interval 'P-2147483648D-0.1M';
+ERROR:  interval field value out of range: "P-2147483648D-0.1M"
+LINE 1: select interval 'P-2147483648D-0.1M';
+                        ^
+select interval 'P0.5W2147483647D';
+ERROR:  interval field value out of range: "P0.5W2147483647D"
+LINE 1: select interval 'P0.5W2147483647D';
+                        ^
+select interval 'P-0.5W-2147483648D';
+ERROR:  interval field value out of range: "P-0.5W-2147483648D"
+LINE 1: select interval 'P-0.5W-2147483648D';
+                        ^
+select interval 'P2147483647D0.5W';
+ERROR:  interval field value out of range: "P2147483647D0.5W"
+LINE 1: select interval 'P2147483647D0.5W';
+                        ^
+select interval 'P-2147483648D-0.5W';
+ERROR:  interval field value out of range: "P-2147483648D-0.5W"
+LINE 1: select interval 'P-2147483648D-0.5W';
+                        ^
+select interval 'P0.01MT2562047788H54.775807S';
+ERROR:  interval field value out of range: "P0.01MT2562047788H54.775807S"
+LINE 1: select interval 'P0.01MT2562047788H54.775807S';
+                        ^
+select interval 'P-0.01MT-2562047788H-54.775808S';
+ERROR:  interval field value out of range: "P-0.01MT-2562047788H-54.775808S"
+LINE 1: select interval 'P-0.01MT-2562047788H-54.775808S';
+                        ^
+select interval 'P0.1DT2562047788H54.775807S';
+ERROR:  interval field value out of range: "P0.1DT2562047788H54.775807S"
+LINE 1: select interval 'P0.1DT2562047788H54.775807S';
+                        ^
+select interval 'P-0.1DT-2562047788H-54.775808S';
+ERROR:  interval field value out of range: "P-0.1DT-2562047788H-54.775808S"
+LINE 1: select interval 'P-0.1DT-2562047788H-54.775808S';
+                        ^
+select interval 'PT2562047788.1H54.775807S';
+ERROR:  interval field value out of range: "PT2562047788.1H54.775807S"
+LINE 1: select interval 'PT2562047788.1H54.775807S';
+                        ^
+select interval 'PT-2562047788.1H-54.775808S';
+ERROR:  interval field value out of range: "PT-2562047788.1H-54.775808S"
+LINE 1: select interval 'PT-2562047788.1H-54.775808S';
+                        ^
+select interval 'PT2562047788H0.1M54.775807S';
+ERROR:  interval field value out of range: "PT2562047788H0.1M54.775807S"
+LINE 1: select interval 'PT2562047788H0.1M54.775807S';
+                        ^
+select interval 'PT-2562047788H-0.1M-54.775808S';
+ERROR:  interval field value out of range: "PT-2562047788H-0.1M-54.775808S"
+LINE 1: select interval 'PT-2562047788H-0.1M-54.775808S';
+                        ^
+-- overflowing with fractional fields - ISO8601 alternative format
+select interval 'P0.1-2147483647-00';
+ERROR:  interval field value out of range: "P0.1-2147483647-00"
+LINE 1: select interval 'P0.1-2147483647-00';
+                        ^
+select interval 'P00-0.1-2147483647';
+ERROR:  interval field value out of range: "P00-0.1-2147483647"
+LINE 1: select interval 'P00-0.1-2147483647';
+                        ^
+select interval 'P00-0.01-00T2562047788:00:54.775807';
+ERROR:  interval field value out of range: "P00-0.01-00T2562047788:00:54.775807"
+LINE 1: select interval 'P00-0.01-00T2562047788:00:54.775807';
+                        ^
+select interval 'P00-00-0.1T2562047788:00:54.775807';
+ERROR:  interval field value out of range: "P00-00-0.1T2562047788:00:54.775807"
+LINE 1: select interval 'P00-00-0.1T2562047788:00:54.775807';
+                        ^
+select interval 'PT2562047788.1:00:54.775807';
+ERROR:  interval field value out of range: "PT2562047788.1:00:54.775807"
+LINE 1: select interval 'PT2562047788.1:00:54.775807';
+                        ^
+select interval 'PT2562047788:01.:54.775807';
+ERROR:  interval field value out of range: "PT2562047788:01.:54.775807"
+LINE 1: select interval 'PT2562047788:01.:54.775807';
+                        ^
+-- overflowing with fractional fields - SQL standard format
+select interval '0.1 2562047788:0:54.775807';
+ERROR:  interval field value out of range: "0.1 2562047788:0:54.775807"
+LINE 1: select interval '0.1 2562047788:0:54.775807';
+                        ^
+select interval '0.1 2562047788:0:54.775808 ago';
+ERROR:  interval field value out of range: "0.1 2562047788:0:54.775808 ago"
+LINE 1: select interval '0.1 2562047788:0:54.775808 ago';
+                        ^
+select interval '2562047788.1:0:54.775807';
+ERROR:  interval field value out of range: "2562047788.1:0:54.775807"
+LINE 1: select interval '2562047788.1:0:54.775807';
+                        ^
+select interval '2562047788.1:0:54.775808 ago';
+ERROR:  interval field value out of range: "2562047788.1:0:54.775808 ago"
+LINE 1: select interval '2562047788.1:0:54.775808 ago';
+                        ^
+select interval '2562047788:0.1:54.775807';
+ERROR:  invalid input syntax for type interval: "2562047788:0.1:54.775807"
+LINE 1: select interval '2562047788:0.1:54.775807';
+                        ^
+select interval '2562047788:0.1:54.775808 ago';
+ERROR:  invalid input syntax for type interval: "2562047788:0.1:54.775808 ago"
+LINE 1: select interval '2562047788:0.1:54.775808 ago';
+                        ^
+-- overflowing using AGO with INT_MIN
+select interval '-2147483648 months ago';
+ERROR:  interval field value out of range: "-2147483648 months ago"
+LINE 1: select interval '-2147483648 months ago';
+                        ^
+select interval '-2147483648 days ago';
+ERROR:  interval field value out of range: "-2147483648 days ago"
+LINE 1: select interval '-2147483648 days ago';
+                        ^
+select interval '-9223372036854775808 microseconds ago';
+ERROR:  interval field value out of range: "-9223372036854775808 microseconds ago"
+LINE 1: select interval '-9223372036854775808 microseconds ago';
+                        ^
+select interval '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+ERROR:  interval field value out of range: "-2147483648 months -2147483648 days -9223372036854775808 microseconds ago"
+LINE 1: select interval '-2147483648 months -2147483648 days -922337...
+                        ^
+-- test that INT_MIN number is formatted properly
+SET IntervalStyle to postgres;
+select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+                              interval                              
+--------------------------------------------------------------------
+ -178956970 years -8 mons -2147483648 days -2562047788:00:54.775808
+(1 row)
+
+SET IntervalStyle to sql_standard;
+select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+                     interval                      
+---------------------------------------------------
+ -178956970-8 -2147483648 -2562047788:00:54.775808
+(1 row)
+
+SET IntervalStyle to iso_8601;
+select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+                      interval                       
+-----------------------------------------------------
+ P-178956970Y-8M-2147483648DT-2562047788H-54.775808S
+(1 row)
+
+SET IntervalStyle to postgres_verbose;
+select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+                                   interval                                   
+------------------------------------------------------------------------------
+ @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775808 secs ago
+(1 row)
+
 -- check that '30 days' equals '1 month' according to the hash function
 select '30 days'::interval = '1 month'::interval as t;
  t 
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 811b581e09..f05055e03a 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -318,6 +318,190 @@ select interval '-10 mons -3 days +03:55:06.70';
 select interval '1 year 2 mons 3 days 04:05:06.699999';
 select interval '0:0:0.7', interval '@ 0.70 secs', interval '0.7 seconds';
 
+-- test time fields using entire 64 bit microseconds range
+select interval '2562047788.01521550194 hours';
+select interval '-2562047788.01521550222 hours';
+select interval '153722867280.912930117 minutes';
+select interval '-153722867280.912930133 minutes';
+select interval '9223372036854.775807 seconds';
+select interval '-9223372036854.775808 seconds';
+select interval '9223372036854775.807 milliseconds';
+select interval '-9223372036854775.808 milliseconds';
+select interval '9223372036854775807 microseconds';
+select interval '-9223372036854775808 microseconds';
+
+select interval 'PT2562047788H54.775807S';
+select interval 'PT-2562047788H-54.775808S';
+
+select interval 'PT2562047788:00:54.775807';
+
+select interval 'PT2562047788.0152155019444';
+select interval 'PT-2562047788.0152155022222';
+
+-- overflow each date/time field
+select interval '2147483648 years';
+select interval '-2147483649 years';
+select interval '2147483648 months';
+select interval '-2147483649 months';
+select interval '2147483648 days';
+select interval '-2147483649 days';
+select interval '2562047789 hours';
+select interval '-2562047789 hours';
+select interval '153722867281 minutes';
+select interval '-153722867281 minutes';
+select interval '9223372036855 seconds';
+select interval '-9223372036855 seconds';
+select interval '9223372036854777 millisecond';
+select interval '-9223372036854777 millisecond';
+select interval '9223372036854775808 microsecond';
+select interval '-9223372036854775809 microsecond';
+
+select interval 'P2147483648';
+select interval 'P-2147483649';
+select interval 'P1-2147483647-2147483647';
+select interval 'PT2562047789';
+select interval 'PT-2562047789';
+
+-- overflow with date/time unit aliases
+select interval '2147483647 weeks';
+select interval '-2147483648 weeks';
+select interval '2147483647 decades';
+select interval '-2147483648 decades';
+select interval '2147483647 centuries';
+select interval '-2147483648 centuries';
+select interval '2147483647 millennium';
+select interval '-2147483648 millennium';
+
+select interval '1 week 2147483647 days';
+select interval '-1 week -2147483648 days';
+select interval '2147483647 days 1 week';
+select interval '-2147483648 days -1 week';
+
+select interval 'P1W2147483647D';
+select interval 'P-1W-2147483648D';
+select interval 'P2147483647D1W';
+select interval 'P-2147483648D-1W';
+
+select interval '1 decade 2147483647 years';
+select interval '1 century 2147483647 years';
+select interval '1 millennium 2147483647 years';
+select interval '-1 decade -2147483648 years';
+select interval '-1 century -2147483648 years';
+select interval '-1 millennium -2147483648 years';
+
+select interval '2147483647 years 1 decade';
+select interval '2147483647 years 1 century';
+select interval '2147483647 years 1 millennium';
+select interval '-2147483648 years -1 decade';
+select interval '-2147483648 years -1 century';
+select interval '-2147483648 years -1 millennium';
+
+-- overflowing with fractional fields - postgres format
+select interval '0.1 millennium 2147483647 months';
+select interval '0.1 centuries 2147483647 months';
+select interval '0.1 decades 2147483647 months';
+select interval '0.1 yrs 2147483647 months';
+select interval '-0.1 millennium -2147483648 months';
+select interval '-0.1 centuries -2147483648 months';
+select interval '-0.1 decades -2147483648 months';
+select interval '-0.1 yrs -2147483648 months';
+
+select interval '2147483647 months 0.1 millennium';
+select interval '2147483647 months 0.1 centuries';
+select interval '2147483647 months 0.1 decades';
+select interval '2147483647 months 0.1 yrs';
+select interval '-2147483648 months -0.1 millennium';
+select interval '-2147483648 months -0.1 centuries';
+select interval '-2147483648 months -0.1 decades';
+select interval '-2147483648 months -0.1 yrs';
+
+select interval '0.1 months 2147483647 days';
+select interval '-0.1 months -2147483648 days';
+select interval '2147483647 days 0.1 months';
+select interval '-2147483648 days -0.1 months';
+
+select interval '0.5 weeks 2147483647 days';
+select interval '-0.5 weeks -2147483648 days';
+select interval '2147483647 days 0.5 weeks';
+select interval '-2147483648 days -0.5 weeks';
+
+select interval '0.01 months 9223372036854775807 microseconds';
+select interval '-0.01 months -9223372036854775808 microseconds';
+select interval '9223372036854775807 microseconds 0.01 months';
+select interval '-9223372036854775808 microseconds -0.01 months';
+
+select interval '0.1 weeks 9223372036854775807 microseconds';
+select interval '-0.1 weeks -9223372036854775808 microseconds';
+select interval '9223372036854775807 microseconds 0.1 weeks';
+select interval '-9223372036854775808 microseconds -0.1 weeks';
+
+select interval '0.1 days 9223372036854775807 microseconds';
+select interval '-0.1 days -9223372036854775808 microseconds';
+select interval '9223372036854775807 microseconds 0.1 days';
+select interval '-9223372036854775808 microseconds -0.1 days';
+
+-- overflowing with fractional fields - ISO8601 format
+select interval 'P0.1Y2147483647M';
+select interval 'P-0.1Y-2147483648M';
+select interval 'P2147483647M0.1Y';
+select interval 'P-2147483648M-0.1Y';
+
+select interval 'P0.1M2147483647D';
+select interval 'P-0.1M-2147483648D';
+select interval 'P2147483647D0.1M';
+select interval 'P-2147483648D-0.1M';
+
+select interval 'P0.5W2147483647D';
+select interval 'P-0.5W-2147483648D';
+select interval 'P2147483647D0.5W';
+select interval 'P-2147483648D-0.5W';
+
+select interval 'P0.01MT2562047788H54.775807S';
+select interval 'P-0.01MT-2562047788H-54.775808S';
+
+select interval 'P0.1DT2562047788H54.775807S';
+select interval 'P-0.1DT-2562047788H-54.775808S';
+
+select interval 'PT2562047788.1H54.775807S';
+select interval 'PT-2562047788.1H-54.775808S';
+
+select interval 'PT2562047788H0.1M54.775807S';
+select interval 'PT-2562047788H-0.1M-54.775808S';
+
+-- overflowing with fractional fields - ISO8601 alternative format
+select interval 'P0.1-2147483647-00';
+select interval 'P00-0.1-2147483647';
+select interval 'P00-0.01-00T2562047788:00:54.775807';
+select interval 'P00-00-0.1T2562047788:00:54.775807';
+select interval 'PT2562047788.1:00:54.775807';
+select interval 'PT2562047788:01.:54.775807';
+
+-- overflowing with fractional fields - SQL standard format
+select interval '0.1 2562047788:0:54.775807';
+select interval '0.1 2562047788:0:54.775808 ago';
+
+select interval '2562047788.1:0:54.775807';
+select interval '2562047788.1:0:54.775808 ago';
+
+select interval '2562047788:0.1:54.775807';
+select interval '2562047788:0.1:54.775808 ago';
+
+-- overflowing using AGO with INT_MIN
+select interval '-2147483648 months ago';
+select interval '-2147483648 days ago';
+select interval '-9223372036854775808 microseconds ago';
+select interval '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+
+-- test that INT_MIN number is formatted properly
+SET IntervalStyle to postgres;
+select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+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_verbose;
+select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+
 -- check that '30 days' equals '1 month' according to the hash function
 select '30 days'::interval = '1 month'::interval as t;
 select interval_hash('30 days'::interval) = interval_hash('1 month'::interval) as t;


^ permalink  raw  reply  [nested|flat] 23+ messages in thread

* [PATCH v15 05/14] meson: Add windows resource files
@ 2022-09-21 18:03 Andres Freund <[email protected]>
  0 siblings, 0 replies; 23+ messages in thread

From: Andres Freund @ 2022-09-21 18:03 UTC (permalink / raw)

Author: Andres Freund <[email protected]>
Author: Nazir Bilal Yavuz <[email protected]>
---
 src/backend/jit/llvm/meson.build              |   6 +
 .../replication/libpqwalreceiver/meson.build  |   6 +
 src/backend/replication/pgoutput/meson.build  |   6 +
 src/backend/snowball/meson.build              |   6 +
 .../utils/mb/conversion_procs/meson.build     | 144 ++++++++++++++----
 src/bin/initdb/meson.build                    |   6 +
 src/bin/pg_amcheck/meson.build                |   8 +-
 src/bin/pg_archivecleanup/meson.build         |  12 +-
 src/bin/pg_basebackup/meson.build             |  38 ++++-
 src/bin/pg_checksums/meson.build              |  12 +-
 src/bin/pg_config/meson.build                 |  12 +-
 src/bin/pg_controldata/meson.build            |  12 +-
 src/bin/pg_ctl/meson.build                    |  12 +-
 src/bin/pg_dump/meson.build                   |  18 +++
 src/bin/pg_resetwal/meson.build               |  12 +-
 src/bin/pg_rewind/meson.build                 |   6 +
 src/bin/pg_test_fsync/meson.build             |  10 +-
 src/bin/pg_test_timing/meson.build            |  12 +-
 src/bin/pg_upgrade/meson.build                |   6 +
 src/bin/pg_verifybackup/meson.build           |   6 +
 src/bin/pg_waldump/meson.build                |   6 +
 src/bin/pgbench/meson.build                   |   6 +
 src/bin/pgevent/meson.build                   |   6 +
 src/bin/psql/meson.build                      |   6 +
 src/bin/scripts/meson.build                   |  10 +-
 src/interfaces/libpq/meson.build              |   5 +
 src/interfaces/libpq/test/meson.build         |  25 ++-
 src/pl/plperl/meson.build                     |   7 +
 src/pl/plpgsql/src/meson.build                |   6 +
 src/pl/plpython/meson.build                   |   6 +
 src/pl/tcl/meson.build                        |   6 +
 contrib/adminpack/meson.build                 |  12 +-
 contrib/amcheck/meson.build                   |  17 ++-
 contrib/auth_delay/meson.build                |  12 +-
 contrib/auto_explain/meson.build              |  12 +-
 contrib/basebackup_to_shell/meson.build       |   6 +
 contrib/basic_archive/meson.build             |   6 +
 contrib/bloom/meson.build                     |   6 +
 contrib/bool_plperl/meson.build               |   6 +
 contrib/btree_gin/meson.build                 |  12 +-
 contrib/btree_gist/meson.build                |   6 +
 contrib/citext/meson.build                    |   6 +
 contrib/cube/meson.build                      |   6 +
 contrib/dblink/meson.build                    |   6 +
 contrib/dict_int/meson.build                  |  12 +-
 contrib/dict_xsyn/meson.build                 |  12 +-
 contrib/earthdistance/meson.build             |  12 +-
 contrib/file_fdw/meson.build                  |  12 +-
 contrib/fuzzystrmatch/meson.build             |  16 +-
 contrib/hstore/meson.build                    |  24 ++-
 contrib/hstore_plperl/meson.build             |   6 +
 contrib/hstore_plpython/meson.build           |   6 +
 contrib/intarray/meson.build                  |   6 +
 contrib/isn/meson.build                       |   6 +
 contrib/jsonb_plperl/meson.build              |   6 +
 contrib/jsonb_plpython/meson.build            |   6 +
 contrib/lo/meson.build                        |   6 +
 contrib/ltree/meson.build                     |   6 +
 contrib/ltree_plpython/meson.build            |   6 +
 contrib/oid2name/meson.build                  |  12 +-
 contrib/old_snapshot/meson.build              |   6 +
 contrib/pageinspect/meson.build               |   6 +
 contrib/passwordcheck/meson.build             |   6 +
 contrib/pg_buffercache/meson.build            |  14 +-
 contrib/pg_freespacemap/meson.build           |  14 +-
 contrib/pg_prewarm/meson.build                |  16 +-
 contrib/pg_stat_statements/meson.build        |  12 +-
 contrib/pg_surgery/meson.build                |  14 +-
 contrib/pg_trgm/meson.build                   |  20 ++-
 contrib/pg_visibility/meson.build             |  14 +-
 contrib/pg_walinspect/meson.build             |   6 +
 contrib/pgcrypto/meson.build                  |   6 +
 contrib/pgrowlocks/meson.build                |  14 +-
 contrib/pgstattuple/meson.build               |  18 ++-
 contrib/postgres_fdw/meson.build              |   6 +
 contrib/seg/meson.build                       |   6 +
 contrib/sepgsql/meson.build                   |   6 +
 contrib/spi/meson.build                       |  48 +++++-
 contrib/sslinfo/meson.build                   |  14 +-
 contrib/tablefunc/meson.build                 |  14 +-
 contrib/tcn/meson.build                       |  14 +-
 contrib/test_decoding/meson.build             |   6 +
 contrib/tsm_system_rows/meson.build           |  14 +-
 contrib/tsm_system_time/meson.build           |  14 +-
 contrib/unaccent/meson.build                  |  14 +-
 contrib/uuid-ossp/meson.build                 |  14 +-
 contrib/vacuumlo/meson.build                  |  12 +-
 contrib/xml2/meson.build                      |  16 +-
 src/interfaces/ecpg/compatlib/meson.build     |   6 +-
 src/interfaces/ecpg/preproc/meson.build       |   6 +
 src/interfaces/ecpg/test/meson.build          |   6 +
 src/test/isolation/meson.build                |  13 ++
 src/test/modules/delay_execution/meson.build  |  13 +-
 src/test/modules/dummy_index_am/meson.build   |  13 +-
 src/test/modules/dummy_seclabel/meson.build   |  13 +-
 src/test/modules/libpq_pipeline/meson.build   |  14 +-
 src/test/modules/plsample/meson.build         |  13 +-
 src/test/modules/spgist_name_ops/meson.build  |  13 +-
 .../ssl_passphrase_callback/meson.build       |  13 +-
 src/test/modules/test_bloomfilter/meson.build |  13 +-
 src/test/modules/test_ddl_deparse/meson.build |  13 +-
 .../modules/test_ginpostinglist/meson.build   |  13 +-
 src/test/modules/test_integerset/meson.build  |  13 +-
 src/test/modules/test_lfind/meson.build       |  13 +-
 src/test/modules/test_oat_hooks/meson.build   |  13 +-
 src/test/modules/test_parser/meson.build      |  13 +-
 src/test/modules/test_predtest/meson.build    |  13 +-
 src/test/modules/test_rbtree/meson.build      |  13 +-
 src/test/modules/test_regex/meson.build       |  13 +-
 src/test/modules/test_rls_hooks/meson.build   |  13 +-
 src/test/modules/test_shm_mq/meson.build      |  19 ++-
 src/test/modules/worker_spi/meson.build       |  15 +-
 src/test/regress/meson.build                  |   6 +
 meson.build                                   |  61 ++++++++
 src/timezone/meson.build                      |   6 +
 src/tools/rcgen                               |  99 ++++++++++++
 116 files changed, 1383 insertions(+), 159 deletions(-)
 create mode 100755 src/tools/rcgen

diff --git a/src/backend/jit/llvm/meson.build b/src/backend/jit/llvm/meson.build
index de2e624ab58..5fb63768358 100644
--- a/src/backend/jit/llvm/meson.build
+++ b/src/backend/jit/llvm/meson.build
@@ -20,6 +20,12 @@ llvmjit_sources += files(
   'llvmjit_expr.c',
 )
 
+if host_system == 'windows'
+  llvmjit_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'llvmjit',
+    '--FILEDESC', 'llvmjit - JIT using LLVM',])
+endif
+
 llvmjit = shared_module('llvmjit',
   llvmjit_sources,
   kwargs: pg_mod_args + {
diff --git a/src/backend/replication/libpqwalreceiver/meson.build b/src/backend/replication/libpqwalreceiver/meson.build
index 3fc786c80a0..4c653a05d36 100644
--- a/src/backend/replication/libpqwalreceiver/meson.build
+++ b/src/backend/replication/libpqwalreceiver/meson.build
@@ -2,6 +2,12 @@ libpqwalreceiver_sources = files(
   'libpqwalreceiver.c',
 )
 
+if host_system == 'windows'
+  libpqwalreceiver_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pqwalreceiver',
+    '--FILEDESC', 'libpqwalreceiver - receive WAL during streaming replication',])
+endif
+
 libpqwalreceiver = shared_module('pqwalreceiver',
   libpqwalreceiver_sources,
   kwargs: pg_mod_args + {
diff --git a/src/backend/replication/pgoutput/meson.build b/src/backend/replication/pgoutput/meson.build
index ab956361a62..5df27d7b764 100644
--- a/src/backend/replication/pgoutput/meson.build
+++ b/src/backend/replication/pgoutput/meson.build
@@ -2,6 +2,12 @@ pgoutput_sources = files(
   'pgoutput.c',
 )
 
+if host_system == 'windows'
+  pgoutput_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pgoutput',
+    '--FILEDESC', 'pgoutput - standard logical replication output plugin',])
+endif
+
 pgoutput = shared_module('pgoutput',
   pgoutput_sources,
   kwargs: pg_mod_args,
diff --git a/src/backend/snowball/meson.build b/src/backend/snowball/meson.build
index 8c6f685cb32..974401d187e 100644
--- a/src/backend/snowball/meson.build
+++ b/src/backend/snowball/meson.build
@@ -58,6 +58,12 @@ dict_snowball_sources += files(
 # see comment in src/include/snowball/header.h
 stemmer_inc = include_directories('../../include/snowball')
 
+if host_system == 'windows'
+  dict_snowball_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'dict_snowball',
+    '--FILEDESC', 'snowball - natural language stemmers',])
+endif
+
 dict_snowball = shared_module('dict_snowball',
   dict_snowball_sources,
   kwargs: pg_mod_args + {
diff --git a/src/backend/utils/mb/conversion_procs/meson.build b/src/backend/utils/mb/conversion_procs/meson.build
index 1bc971d1945..00e721a2453 100644
--- a/src/backend/utils/mb/conversion_procs/meson.build
+++ b/src/backend/utils/mb/conversion_procs/meson.build
@@ -1,36 +1,122 @@
 encodings = {
-  'cyrillic_and_mic': ['cyrillic_and_mic/cyrillic_and_mic.c'],
-  'euc2004_sjis2004': ['euc2004_sjis2004/euc2004_sjis2004.c'],
-  'euc_cn_and_mic': ['euc_cn_and_mic/euc_cn_and_mic.c'],
-  'euc_jp_and_sjis': ['euc_jp_and_sjis/euc_jp_and_sjis.c'],
-  'euc_kr_and_mic': ['euc_kr_and_mic/euc_kr_and_mic.c'],
-  'euc_tw_and_big5': [
-    'euc_tw_and_big5/euc_tw_and_big5.c',
-    'euc_tw_and_big5/big5.c',
-  ],
-  'latin2_and_win1250': ['latin2_and_win1250/latin2_and_win1250.c'],
-  'latin_and_mic': ['latin_and_mic/latin_and_mic.c'],
-  'utf8_and_big5': ['utf8_and_big5/utf8_and_big5.c'],
-  'utf8_and_cyrillic': ['utf8_and_cyrillic/utf8_and_cyrillic.c'],
-  'utf8_and_euc2004': ['utf8_and_euc2004/utf8_and_euc2004.c'],
-  'utf8_and_euc_cn': ['utf8_and_euc_cn/utf8_and_euc_cn.c'],
-  'utf8_and_euc_jp': ['utf8_and_euc_jp/utf8_and_euc_jp.c'],
-  'utf8_and_euc_kr': ['utf8_and_euc_kr/utf8_and_euc_kr.c'],
-  'utf8_and_euc_tw': ['utf8_and_euc_tw/utf8_and_euc_tw.c'],
-  'utf8_and_gb18030': ['utf8_and_gb18030/utf8_and_gb18030.c'],
-  'utf8_and_gbk': ['utf8_and_gbk/utf8_and_gbk.c'],
-  'utf8_and_iso8859': ['utf8_and_iso8859/utf8_and_iso8859.c'],
-  'utf8_and_iso8859_1': ['utf8_and_iso8859_1/utf8_and_iso8859_1.c'],
-  'utf8_and_johab': ['utf8_and_johab/utf8_and_johab.c'],
-  'utf8_and_sjis': ['utf8_and_sjis/utf8_and_sjis.c'],
-  'utf8_and_sjis2004': ['utf8_and_sjis2004/utf8_and_sjis2004.c'],
-  'utf8_and_uhc': ['utf8_and_uhc/utf8_and_uhc.c'],
-  'utf8_and_win': ['utf8_and_win/utf8_and_win.c'],
+  'cyrillic_and_mic': {
+    'sources': ['cyrillic_and_mic/cyrillic_and_mic.c'],
+    'description': 'cyrillic <-> mic text conversions',
+  },
+  'euc2004_sjis2004': {
+    'sources': ['euc2004_sjis2004/euc2004_sjis2004.c'],
+    'description': 'euc2004 <-> sjis2004 text conversions',
+  },
+  'euc_cn_and_mic': {
+    'sources': ['euc_cn_and_mic/euc_cn_and_mic.c'],
+    'description': 'euc_cn <-> mic text conversions',
+  },
+  'euc_jp_and_sjis': {
+    'sources': ['euc_jp_and_sjis/euc_jp_and_sjis.c'],
+    'description': 'euc_jp <-> sjis text conversions',
+  },
+  'euc_kr_and_mic': {
+    'sources': ['euc_kr_and_mic/euc_kr_and_mic.c'],
+    'description': 'euc_kr <-> mic text conversions',
+  },
+  'euc_tw_and_big5': {
+    'sources': [
+      'euc_tw_and_big5/euc_tw_and_big5.c',
+      'euc_tw_and_big5/big5.c',
+    ],
+    'description': 'euc_tw <-> big5 text conversions',
+  },
+  'latin2_and_win1250': {
+    'sources': ['latin2_and_win1250/latin2_and_win1250.c'],
+    'description': 'latin2 <-> win1250 text conversions',
+  },
+  'latin_and_mic': {
+    'sources': ['latin_and_mic/latin_and_mic.c'],
+    'description': 'latin <-> mic text conversions',
+  },
+  'utf8_and_big5': {
+    'sources': ['utf8_and_big5/utf8_and_big5.c'],
+    'description': 'utf8 <-> big5 text conversions',
+  },
+  'utf8_and_cyrillic': {
+    'sources': ['utf8_and_cyrillic/utf8_and_cyrillic.c'],
+    'description': 'utf8 <-> cyrillic text conversions',
+  },
+  'utf8_and_euc2004': {
+    'sources': ['utf8_and_euc2004/utf8_and_euc2004.c'],
+    'description': 'utf8 <-> euc2004 text conversions',
+  },
+  'utf8_and_euc_cn': {
+    'sources': ['utf8_and_euc_cn/utf8_and_euc_cn.c'],
+    'description': 'utf8 <-> euc_cn text conversions',
+  },
+  'utf8_and_euc_jp': {
+    'sources': ['utf8_and_euc_jp/utf8_and_euc_jp.c'],
+    'description': 'utf8 <-> euc_jp text conversions',
+  },
+  'utf8_and_euc_kr': {
+    'sources': ['utf8_and_euc_kr/utf8_and_euc_kr.c'],
+    'description': 'utf8 <-> euc_kr text conversions',
+  },
+  'utf8_and_euc_tw': {
+    'sources': ['utf8_and_euc_tw/utf8_and_euc_tw.c'],
+    'description': 'utf8 <-> euc_tw text conversions',
+  },
+  'utf8_and_gb18030': {
+    'sources': ['utf8_and_gb18030/utf8_and_gb18030.c'],
+    'description': 'utf8 <-> gb18030 text conversions',
+  },
+  'utf8_and_gbk': {
+    'sources': ['utf8_and_gbk/utf8_and_gbk.c'],
+    'description': 'utf8 <-> gbk text conversions',
+  },
+  'utf8_and_iso8859': {
+    'sources': ['utf8_and_iso8859/utf8_and_iso8859.c'],
+    'description': 'utf8 <-> iso8859 text conversions',
+  },
+  'utf8_and_iso8859_1': {
+    'sources': ['utf8_and_iso8859_1/utf8_and_iso8859_1.c'],
+    'description': 'utf8 <-> iso8859_1 text conversions',
+  },
+  'utf8_and_johab': {
+    'sources': ['utf8_and_johab/utf8_and_johab.c'],
+    'description': 'utf8 <-> johab text conversions',
+  },
+  'utf8_and_sjis': {
+    'sources': ['utf8_and_sjis/utf8_and_sjis.c'],
+    'description': 'utf8 <-> sjis text conversions',
+  },
+  'utf8_and_sjis2004': {
+    'sources': ['utf8_and_sjis2004/utf8_and_sjis2004.c'],
+    'description': 'utf8 <-> sjis2004 text conversions',
+  },
+  'utf8_and_uhc': {
+    'sources': ['utf8_and_uhc/utf8_and_uhc.c'],
+    'description': 'utf8 <-> uhc text conversions',
+  },
+  'utf8_and_win': {
+    'sources': ['utf8_and_win/utf8_and_win.c'],
+    'description': 'utf8 <-> win text conversions',
+  },
 }
 
-foreach encoding, sources : encodings
+foreach encoding, values: encodings
+  sources = values['sources']
+  description = values['description']
+
+  source_files = files()
+  foreach source: sources
+    source_files += files(source)
+  endforeach
+
+  if host_system == 'windows'
+    source_files += rc_lib_gen.process(win32ver_rc, extra_args: [
+      '--NAME', encoding,
+      '--FILEDESC', description,])
+  endif
+
   backend_targets += shared_module(encoding,
-    sources,
+    source_files,
     kwargs: pg_mod_args,
   )
 endforeach
diff --git a/src/bin/initdb/meson.build b/src/bin/initdb/meson.build
index 9f213274d2f..6ced9a31b80 100644
--- a/src/bin/initdb/meson.build
+++ b/src/bin/initdb/meson.build
@@ -7,6 +7,12 @@ initdb_sources += timezone_localtime_source
 
 #fixme: reimplement libpq_pgport logic
 
+if host_system == 'windows'
+  initdb_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'initdb',
+    '--FILEDESC', 'initdb - initialize a new database cluster',])
+endif
+
 initdb = executable('initdb',
   initdb_sources,
   include_directories: [timezone_inc],
diff --git a/src/bin/pg_amcheck/meson.build b/src/bin/pg_amcheck/meson.build
index 8e197eba5f3..25f5e7a0948 100644
--- a/src/bin/pg_amcheck/meson.build
+++ b/src/bin/pg_amcheck/meson.build
@@ -1,7 +1,13 @@
 pg_amcheck_sources = files(
-  'pg_amcheck.c'
+  'pg_amcheck.c',
 )
 
+if host_system == 'windows'
+  pg_amcheck_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_amcheck',
+    '--FILEDESC', 'pg_amcheck - detect corruption within database relations',])
+endif
+
 pg_amcheck = executable('pg_amcheck',
   pg_amcheck_sources,
   dependencies: [frontend_code, libpq],
diff --git a/src/bin/pg_archivecleanup/meson.build b/src/bin/pg_archivecleanup/meson.build
index 87a0d980c4f..aaa2e76977f 100644
--- a/src/bin/pg_archivecleanup/meson.build
+++ b/src/bin/pg_archivecleanup/meson.build
@@ -1,5 +1,15 @@
+pg_archivecleanup_sources = files(
+  'pg_archivecleanup.c',
+)
+
+if host_system == 'windows'
+  pg_archivecleanup_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_archivecleanup',
+    '--FILEDESC', 'pg_archivecleanup - cleans archive when used with streaming replication',])
+endif
+
 pg_archivecleanup = executable('pg_archivecleanup',
-  ['pg_archivecleanup.c'],
+  pg_archivecleanup_sources,
   dependencies: [frontend_code],
   kwargs: default_bin_args,
 )
diff --git a/src/bin/pg_basebackup/meson.build b/src/bin/pg_basebackup/meson.build
index d26fed9cd8a..2c934e0c26e 100644
--- a/src/bin/pg_basebackup/meson.build
+++ b/src/bin/pg_basebackup/meson.build
@@ -17,24 +17,56 @@ pg_basebackup_common = static_library('libpg_basebackup_common',
   kwargs: internal_lib_args,
 )
 
-pg_basebackup = executable('pg_basebackup',
+pg_basebackup_sources = files(
   'pg_basebackup.c',
+)
+
+if host_system == 'windows'
+  pg_basebackup_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_basebackup',
+    '--FILEDESC', 'pg_basebackup - streaming WAL and backup receivers',])
+endif
+
+pg_basebackup = executable('pg_basebackup',
+  pg_basebackup_sources,
   link_with: [pg_basebackup_common],
   dependencies: pg_basebackup_deps,
   kwargs: default_bin_args,
 )
 bin_targets += pg_basebackup
 
-pg_receivewal = executable('pg_receivewal',
+
+pg_receivewal_sources = files(
   'pg_receivewal.c',
+)
+
+if host_system == 'windows'
+  pg_receivewal_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_receivewal',
+    '--FILEDESC', 'pg_receivewal - streaming WAL and backup receivers',])
+endif
+
+pg_receivewal = executable('pg_receivewal',
+  pg_receivewal_sources,
   link_with: [pg_basebackup_common],
   dependencies: pg_basebackup_deps,
   kwargs: default_bin_args,
 )
 bin_targets += pg_receivewal
 
-pg_recvlogical = executable('pg_recvlogical',
+
+pg_recvlogical_sources = files(
   'pg_recvlogical.c',
+)
+
+if host_system == 'windows'
+  pg_recvlogical_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_recvlogical',
+    '--FILEDESC', 'pg_recvlogical - streaming WAL and backup receivers',])
+endif
+
+pg_recvlogical = executable('pg_recvlogical',
+  pg_recvlogical_sources,
   link_with: [pg_basebackup_common],
   dependencies: pg_basebackup_deps,
   kwargs: default_bin_args,
diff --git a/src/bin/pg_checksums/meson.build b/src/bin/pg_checksums/meson.build
index ee1f367bac3..d07ebc999b3 100644
--- a/src/bin/pg_checksums/meson.build
+++ b/src/bin/pg_checksums/meson.build
@@ -1,5 +1,15 @@
+pg_checksums_sources = files(
+  'pg_checksums.c',
+)
+
+if host_system == 'windows'
+  pg_checksums_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_checksums',
+    '--FILEDESC', 'pg_checksums - verify data checksums in an offline cluster',])
+endif
+
 pg_checksums = executable('pg_checksums',
-  ['pg_checksums.c'],
+  pg_checksums_sources,
   include_directories: [timezone_inc],
   dependencies: [frontend_code],
   kwargs: default_bin_args,
diff --git a/src/bin/pg_config/meson.build b/src/bin/pg_config/meson.build
index 0ecbf2f9d28..4be2fdc84ae 100644
--- a/src/bin/pg_config/meson.build
+++ b/src/bin/pg_config/meson.build
@@ -1,5 +1,15 @@
+pg_config_sources = files(
+  'pg_config.c',
+)
+
+if host_system == 'windows'
+  pg_config_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_config',
+    '--FILEDESC', 'pg_config - report configuration information',])
+endif
+
 pg_config = executable('pg_config',
-  ['pg_config.c'],
+  pg_config_sources,
   dependencies: [frontend_code],
   kwargs: default_bin_args,
 )
diff --git a/src/bin/pg_controldata/meson.build b/src/bin/pg_controldata/meson.build
index 557e672beb7..7fc239dbe65 100644
--- a/src/bin/pg_controldata/meson.build
+++ b/src/bin/pg_controldata/meson.build
@@ -1,5 +1,15 @@
+pg_controldata_sources = files(
+  'pg_controldata.c',
+)
+
+if host_system == 'windows'
+  pg_controldata_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_controldata',
+    '--FILEDESC', 'pg_controldata - reads the data from pg_control',])
+endif
+
 pg_controldata = executable('pg_controldata',
-  ['pg_controldata.c'],
+  pg_controldata_sources,
   dependencies: [frontend_code],
   kwargs: default_bin_args,
 )
diff --git a/src/bin/pg_ctl/meson.build b/src/bin/pg_ctl/meson.build
index 6812e73e329..96f962fa762 100644
--- a/src/bin/pg_ctl/meson.build
+++ b/src/bin/pg_ctl/meson.build
@@ -1,5 +1,15 @@
+pg_ctl_sources = files(
+  'pg_ctl.c',
+)
+
+if host_system == 'windows'
+  pg_ctl_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_ctl',
+    '--FILEDESC', 'pg_ctl - starts/stops/restarts the PostgreSQL server',])
+endif
+
 pg_ctl = executable('pg_ctl',
-  ['pg_ctl.c'],
+  pg_ctl_sources,
   dependencies: [frontend_code, libpq],
   kwargs: default_bin_args,
 )
diff --git a/src/bin/pg_dump/meson.build b/src/bin/pg_dump/meson.build
index 785ec094dbd..3527a25c288 100644
--- a/src/bin/pg_dump/meson.build
+++ b/src/bin/pg_dump/meson.build
@@ -24,6 +24,12 @@ pg_dump_sources = files(
   'pg_dump_sort.c',
 )
 
+if host_system == 'windows'
+  pg_dump_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_dump',
+    '--FILEDESC', 'pg_dump - backup one PostgreSQL database',])
+endif
+
 pg_dump = executable('pg_dump',
   pg_dump_sources,
   link_with: [pg_dump_common],
@@ -37,6 +43,12 @@ pg_dumpall_sources = files(
   'pg_dumpall.c',
 )
 
+if host_system == 'windows'
+  pg_dumpall_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_dumpall',
+    '--FILEDESC', 'pg_dumpall - backup PostgreSQL databases'])
+endif
+
 pg_dumpall = executable('pg_dumpall',
   pg_dumpall_sources,
   link_with: [pg_dump_common],
@@ -50,6 +62,12 @@ pg_restore_sources = files(
   'pg_restore.c',
 )
 
+if host_system == 'windows'
+  pg_restore_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_restore',
+    '--FILEDESC', 'pg_restore - restore PostgreSQL databases'])
+endif
+
 pg_restore = executable('pg_restore',
   pg_restore_sources,
   link_with: [pg_dump_common],
diff --git a/src/bin/pg_resetwal/meson.build b/src/bin/pg_resetwal/meson.build
index 7c5de134ac0..d503db97b71 100644
--- a/src/bin/pg_resetwal/meson.build
+++ b/src/bin/pg_resetwal/meson.build
@@ -1,5 +1,15 @@
+pg_resetwal_sources = files(
+  'pg_resetwal.c',
+)
+
+if host_system == 'windows'
+  pg_resetwal_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_resetwal',
+    '--FILEDESC', 'pg_resetwal - reset PostgreSQL WAL log'])
+endif
+
 pg_resetwal = executable('pg_resetwal',
-  files('pg_resetwal.c'),
+  pg_resetwal_sources,
   dependencies: [frontend_code],
   kwargs: default_bin_args,
 )
diff --git a/src/bin/pg_rewind/meson.build b/src/bin/pg_rewind/meson.build
index d8ec9e482d5..6cd970909a2 100644
--- a/src/bin/pg_rewind/meson.build
+++ b/src/bin/pg_rewind/meson.build
@@ -11,6 +11,12 @@ pg_rewind_sources = files(
 
 pg_rewind_sources += xlogreader_sources
 
+if host_system == 'windows'
+  pg_rewind_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_rewind',
+    '--FILEDESC', 'pg_rewind - synchronize a data directory with another one forked from'])
+endif
+
 pg_rewind = executable('pg_rewind',
   pg_rewind_sources,
   dependencies: [frontend_code, libpq, lz4, zstd],
diff --git a/src/bin/pg_test_fsync/meson.build b/src/bin/pg_test_fsync/meson.build
index 2c01831e11f..31d288ba6da 100644
--- a/src/bin/pg_test_fsync/meson.build
+++ b/src/bin/pg_test_fsync/meson.build
@@ -1,4 +1,12 @@
-test_fsync_sources = files('pg_test_fsync.c')
+test_fsync_sources = files(
+  'pg_test_fsync.c',
+)
+
+if host_system == 'windows'
+  test_fsync_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_test_fsync',
+    '--FILEDESC', 'pg_test_fsync - test various disk sync methods'])
+endif
 
 pg_test_fsync = executable('pg_test_fsync',
   test_fsync_sources,
diff --git a/src/bin/pg_test_timing/meson.build b/src/bin/pg_test_timing/meson.build
index 0a3068f1657..0aed03ea32f 100644
--- a/src/bin/pg_test_timing/meson.build
+++ b/src/bin/pg_test_timing/meson.build
@@ -1,5 +1,15 @@
+pg_test_timing_sources = files(
+  'pg_test_timing.c'
+)
+
+if host_system == 'windows'
+  pg_test_timing_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_test_timing',
+    '--FILEDESC', 'pg_test_timing - test timing overhead'])
+endif
+
 pg_test_timing = executable('pg_test_timing',
-  ['pg_test_timing.c'],
+  pg_test_timing_sources,
   dependencies: [frontend_code],
   kwargs: default_bin_args,
 )
diff --git a/src/bin/pg_upgrade/meson.build b/src/bin/pg_upgrade/meson.build
index 02f030e0ccf..a7b927a45c7 100644
--- a/src/bin/pg_upgrade/meson.build
+++ b/src/bin/pg_upgrade/meson.build
@@ -16,6 +16,12 @@ pg_upgrade_sources = files(
   'version.c',
 )
 
+if host_system == 'windows'
+  pg_upgrade_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_upgrade',
+    '--FILEDESC', 'pg_upgrade - an in-place binary upgrade utility'])
+endif
+
 pg_upgrade = executable('pg_upgrade',
   pg_upgrade_sources,
   dependencies: [frontend_code, libpq],
diff --git a/src/bin/pg_verifybackup/meson.build b/src/bin/pg_verifybackup/meson.build
index 4c3b2bb5f97..b934a408443 100644
--- a/src/bin/pg_verifybackup/meson.build
+++ b/src/bin/pg_verifybackup/meson.build
@@ -3,6 +3,12 @@ pg_verifybackup_sources = files(
   'pg_verifybackup.c'
 )
 
+if host_system == 'windows'
+  pg_verifybackup_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_verifybackup',
+    '--FILEDESC', 'pg_verifybackup - verify a backup against using a backup manifest'])
+endif
+
 pg_verifybackup = executable('pg_verifybackup',
   pg_verifybackup_sources,
   dependencies: [frontend_code, libpq],
diff --git a/src/bin/pg_waldump/meson.build b/src/bin/pg_waldump/meson.build
index 95872652ffd..9605976870d 100644
--- a/src/bin/pg_waldump/meson.build
+++ b/src/bin/pg_waldump/meson.build
@@ -8,6 +8,12 @@ pg_waldump_sources += rmgr_desc_sources
 pg_waldump_sources += xlogreader_sources
 pg_waldump_sources += files('../../backend/access/transam/xlogstats.c')
 
+if host_system == 'windows'
+  pg_waldump_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_waldump',
+    '--FILEDESC', 'pg_waldump - decode and display WA'])
+endif
+
 pg_waldump = executable('pg_waldump',
   pg_waldump_sources,
   dependencies: [frontend_code, lz4, zstd],
diff --git a/src/bin/pgbench/meson.build b/src/bin/pgbench/meson.build
index 6564e54029c..a32eb51fe07 100644
--- a/src/bin/pgbench/meson.build
+++ b/src/bin/pgbench/meson.build
@@ -17,6 +17,12 @@ exprparse = custom_target('exprparse',
 generated_sources += exprparse.to_list()
 pgbench_sources += exprparse
 
+if host_system == 'windows'
+  pgbench_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pgbench',
+    '--FILEDESC', 'pgbench - a simple program for running benchmark tests'])
+endif
+
 pgbench = executable('pgbench',
   pgbench_sources,
   dependencies: [frontend_code, libpq, thread_dep],
diff --git a/src/bin/pgevent/meson.build b/src/bin/pgevent/meson.build
index 7a468879fd2..2e9aea4b0e1 100644
--- a/src/bin/pgevent/meson.build
+++ b/src/bin/pgevent/meson.build
@@ -6,6 +6,12 @@ pgevent_sources = files(
   'pgevent.c',
 )
 
+pgevent_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+  '--NAME', 'pgevent',
+  '--FILEDESC', 'Eventlog message formatter',])
+
+pgevent_sources += windows.compile_resources('pgmsgevent.rc')
+
 # FIXME: copied from Mkvcbuild.pm, but I don't think that's the right approach
 pgevent_link_args = []
 if cc.get_id() == 'msvc'
diff --git a/src/bin/psql/meson.build b/src/bin/psql/meson.build
index ea850c8fdf5..3474389d3b6 100644
--- a/src/bin/psql/meson.build
+++ b/src/bin/psql/meson.build
@@ -38,6 +38,12 @@ generated_sources += sql_help.to_list()
 psql_sources += sql_help
 bin_targets += sql_help
 
+if host_system == 'windows'
+  psql_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'psql',
+    '--FILEDESC', 'psql - the PostgreSQL interactive terminal',])
+endif
+
 psql = executable('psql',
   psql_sources,
   include_directories: include_directories('.'),
diff --git a/src/bin/scripts/meson.build b/src/bin/scripts/meson.build
index eaf250c7f73..837562c24e5 100644
--- a/src/bin/scripts/meson.build
+++ b/src/bin/scripts/meson.build
@@ -16,8 +16,16 @@ binaries = [
 ]
 
 foreach binary : binaries
+  binary_sources = files('@[email protected]'.format(binary))
+
+  if host_system == 'windows'
+    binary_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+      '--NAME', binary,
+      '--FILEDESC', '@0@ - PostgreSQL utility'.format(binary),])
+  endif
+
   binary = executable(binary,
-    files(binary + '.c'),
+    binary_sources,
     link_with: [scripts_common],
     dependencies: [frontend_code, libpq],
     kwargs: default_bin_args,
diff --git a/src/interfaces/libpq/meson.build b/src/interfaces/libpq/meson.build
index bc047e00d62..2c9edeaa088 100644
--- a/src/interfaces/libpq/meson.build
+++ b/src/interfaces/libpq/meson.build
@@ -16,9 +16,13 @@ libpq_sources = files(
   'libpq-events.c',
   'pqexpbuffer.c',
 )
+libpq_so_sources = [] # only for shared lib, in addition to above
 
 if host_system == 'windows'
   libpq_sources += files('pthread-win32.c', 'win32.c')
+  libpq_so_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'libpq',
+    '--FILEDESC', 'PostgreSQL Access Library',])
 endif
 
 if ssl.found()
@@ -52,6 +56,7 @@ libpq_st = static_library('libpq',
 # not using both_libraries here, causes problems with precompiled headers and
 # resource files with msbuild
 libpq_so = shared_library('libpq',
+  libpq_so_sources,
   dependencies: libpq_deps,
   include_directories: [libpq_inc, postgres_inc],
   c_args: ['-DSO_MAJOR_VERSION=5'],
diff --git a/src/interfaces/libpq/test/meson.build b/src/interfaces/libpq/test/meson.build
index 16f94c1ed8b..017f729d435 100644
--- a/src/interfaces/libpq/test/meson.build
+++ b/src/interfaces/libpq/test/meson.build
@@ -1,13 +1,34 @@
+libpq_uri_regress_sources = files(
+  'libpq_uri_regress.c',
+)
+
+if host_system == 'windows'
+  libpq_uri_regress_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'libpq_uri_regress',
+    '--FILEDESC', 'libpq test program',])
+endif
+
 executable('libpq_uri_regress',
-  files('libpq_uri_regress.c'),
+  libpq_uri_regress_sources,
   dependencies: [frontend_code, libpq],
   kwargs: default_bin_args + {
     'install': false,
   }
 )
 
+
+libpq_testclient_sources = files(
+  'libpq_testclient.c',
+)
+
+if host_system == 'windows'
+  libpq_testclient_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'libpq_testclient',
+    '--FILEDESC', 'libpq test program',])
+endif
+
 executable('libpq_testclient',
-  files('libpq_testclient.c'),
+  libpq_testclient_sources,
   dependencies: [frontend_code, libpq],
   kwargs: default_bin_args + {
     'install': false,
diff --git a/src/pl/plperl/meson.build b/src/pl/plperl/meson.build
index 73b733dd50b..535660085dd 100644
--- a/src/pl/plperl/meson.build
+++ b/src/pl/plperl/meson.build
@@ -36,6 +36,13 @@ foreach n : ['SPI', 'Util']
 endforeach
 
 plperl_inc = include_directories('.')
+
+if host_system == 'windows'
+  plperl_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'plperl',
+    '--FILEDESC', 'PL/Perl - procedural language',])
+endif
+
 plperl = shared_module('plperl',
   plperl_sources,
   include_directories: [plperl_inc, postgres_inc],
diff --git a/src/pl/plpgsql/src/meson.build b/src/pl/plpgsql/src/meson.build
index dd499fdd151..c46c0a1da2a 100644
--- a/src/pl/plpgsql/src/meson.build
+++ b/src/pl/plpgsql/src/meson.build
@@ -40,6 +40,12 @@ pl_unreserved = custom_target('pl_unreserved_kwlist',
 generated_sources += pl_unreserved
 plpgsql_sources += pl_unreserved
 
+if host_system == 'windows'
+  plpgsql_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'plpgsql',
+    '--FILEDESC', 'PL/pgSQL - procedural language',])
+endif
+
 plpgsql = shared_module('plpgsql',
   plpgsql_sources,
   include_directories: include_directories('.'),
diff --git a/src/pl/plpython/meson.build b/src/pl/plpython/meson.build
index 366b3b171ac..40888386b5f 100644
--- a/src/pl/plpython/meson.build
+++ b/src/pl/plpython/meson.build
@@ -28,6 +28,12 @@ plpython_sources += custom_target('spiexceptions.h',
 # FIXME: need to duplicate import library ugliness?
 plpython_inc = include_directories('.')
 
+if host_system == 'windows'
+  plpython_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'plpython3',
+    '--FILEDESC', 'PL/Python - procedural language',])
+endif
+
 plpython = shared_module('plpython3',
   plpython_sources,
   include_directories: [plpython_inc, postgres_inc],
diff --git a/src/pl/tcl/meson.build b/src/pl/tcl/meson.build
index 9b6addd7fd5..f09bb14c950 100644
--- a/src/pl/tcl/meson.build
+++ b/src/pl/tcl/meson.build
@@ -14,6 +14,12 @@ pltcl_sources += custom_target('pltclerrcodes.h',
   command: [perl, gen_pltclerrcodes, '@INPUT@']
 )
 
+if host_system == 'windows'
+  pltcl_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pltcl',
+    '--FILEDESC', 'PL/Tcl - procedural language',])
+endif
+
 pltcl = shared_module('pltcl',
   pltcl_sources,
   include_directories: [include_directories('.'), postgres_inc],
diff --git a/contrib/adminpack/meson.build b/contrib/adminpack/meson.build
index fc2368d02cf..7efec0efbc0 100644
--- a/contrib/adminpack/meson.build
+++ b/contrib/adminpack/meson.build
@@ -1,5 +1,15 @@
+adminpack_sources = files(
+  'adminpack.c',
+)
+
+if host_system == 'windows'
+  adminpack_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'adminpack',
+    '--FILEDESC', 'adminpack - support functions for pgAdmin',])
+endif
+
 adminpack = shared_module('adminpack',
-  ['adminpack.c'],
+  adminpack_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += adminpack
diff --git a/contrib/amcheck/meson.build b/contrib/amcheck/meson.build
index 1db3d20349e..fa807b72d98 100644
--- a/contrib/amcheck/meson.build
+++ b/contrib/amcheck/meson.build
@@ -1,7 +1,16 @@
-amcheck = shared_module('amcheck', [
-    'verify_heapam.c',
-    'verify_nbtree.c',
-  ],
+amcheck_sources = files(
+  'verify_heapam.c',
+  'verify_nbtree.c',
+)
+
+if host_system == 'windows'
+  amcheck_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'amcheck',
+    '--FILEDESC', 'amcheck - function for verifying relation integrity',])
+endif
+
+amcheck = shared_module('amcheck',
+  amcheck_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += amcheck
diff --git a/contrib/auth_delay/meson.build b/contrib/auth_delay/meson.build
index d2e01968f54..c4ffb0663bc 100644
--- a/contrib/auth_delay/meson.build
+++ b/contrib/auth_delay/meson.build
@@ -1,5 +1,15 @@
+auth_delay_sources = files(
+  'auth_delay.c',
+)
+
+if host_system == 'windows'
+  auth_delay_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'auth_delay',
+    '--FILEDESC', 'auth_delay - delay authentication failure reports',])
+endif
+
 autoinc = shared_module('auth_delay',
-  ['auth_delay.c'],
+  auth_delay_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += autoinc
diff --git a/contrib/auto_explain/meson.build b/contrib/auto_explain/meson.build
index 249a8376faa..76f86617850 100644
--- a/contrib/auto_explain/meson.build
+++ b/contrib/auto_explain/meson.build
@@ -1,5 +1,15 @@
+auto_explain_sources = files(
+  'auto_explain.c',
+)
+
+if host_system == 'windows'
+  auto_explain_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'auto_explain',
+    '--FILEDESC', 'auto_explain - logging facility for execution plans',])
+endif
+
 auto_explain = shared_module('auto_explain',
-  files('auto_explain.c'),
+  auto_explain_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += auto_explain
diff --git a/contrib/basebackup_to_shell/meson.build b/contrib/basebackup_to_shell/meson.build
index 9f0517f1701..3a389de9175 100644
--- a/contrib/basebackup_to_shell/meson.build
+++ b/contrib/basebackup_to_shell/meson.build
@@ -2,6 +2,12 @@ basebackup_to_shell_sources = files(
   'basebackup_to_shell.c',
 )
 
+if host_system == 'windows'
+  basebackup_to_shell_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'basebackup_to_shell',
+    '--FILEDESC', 'basebackup_to_shell - target basebackup to shell command',])
+endif
+
 basebackup_to_shell = shared_module('basebackup_to_shell',
   basebackup_to_shell_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/basic_archive/meson.build b/contrib/basic_archive/meson.build
index b67cbef60bd..c30dcfa5d41 100644
--- a/contrib/basic_archive/meson.build
+++ b/contrib/basic_archive/meson.build
@@ -2,6 +2,12 @@ basic_archive_sources = files(
   'basic_archive.c',
 )
 
+if host_system == 'windows'
+  basic_archive_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'basic_archive',
+    '--FILEDESC', 'basic_archive - basic archive module',])
+endif
+
 basic_archive = shared_module('basic_archive',
   basic_archive_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/bloom/meson.build b/contrib/bloom/meson.build
index 1fe7632bdbe..16f3b83e4d2 100644
--- a/contrib/bloom/meson.build
+++ b/contrib/bloom/meson.build
@@ -7,6 +7,12 @@ bloom_sources = files(
   'blvalidate.c',
 )
 
+if host_system == 'windows'
+  bloom_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'bloom',
+    '--FILEDESC', 'bloom access method - signature file based index',])
+endif
+
 bloom = shared_module('bloom',
   bloom_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/bool_plperl/meson.build b/contrib/bool_plperl/meson.build
index c20b667d75f..a68daab0dcd 100644
--- a/contrib/bool_plperl/meson.build
+++ b/contrib/bool_plperl/meson.build
@@ -6,6 +6,12 @@ bool_plperl_sources = files(
   'bool_plperl.c',
 )
 
+if host_system == 'windows'
+  bool_plperl_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'bool_plperl',
+    '--FILEDESC', 'bool_plperl - bool transform for plperl',])
+endif
+
 bool_plperl = shared_module('bool_plperl',
   bool_plperl_sources,
   include_directories: [plperl_inc, include_directories('.')],
diff --git a/contrib/btree_gin/meson.build b/contrib/btree_gin/meson.build
index 15d6d31a6ee..fd4c76767a7 100644
--- a/contrib/btree_gin/meson.build
+++ b/contrib/btree_gin/meson.build
@@ -1,5 +1,15 @@
+btree_gin_sources = files(
+  'btree_gin.c',
+)
+
+if host_system == 'windows'
+  btree_gin_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'btree_gin',
+    '--FILEDESC', 'btree_gin - B-tree equivalent GIN operator classes',])
+endif
+
 btree_gin = shared_module('btree_gin',
-  files('btree_gin.c'),
+  btree_gin_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += btree_gin
diff --git a/contrib/btree_gist/meson.build b/contrib/btree_gist/meson.build
index c0a8d238540..e98c91dacc8 100644
--- a/contrib/btree_gist/meson.build
+++ b/contrib/btree_gist/meson.build
@@ -25,6 +25,12 @@ btree_gist_sources = files(
   'btree_uuid.c',
 )
 
+if host_system == 'windows'
+  btree_gist_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'btree_gist',
+    '--FILEDESC', 'btree_gist - B-tree equivalent GiST operator classes',])
+endif
+
 btree_gist = shared_module('btree_gist',
   btree_gist_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/citext/meson.build b/contrib/citext/meson.build
index ca60eded80b..26a101a19bd 100644
--- a/contrib/citext/meson.build
+++ b/contrib/citext/meson.build
@@ -2,6 +2,12 @@ citext_sources = files(
   'citext.c',
 )
 
+if host_system == 'windows'
+  citext_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'citext',
+    '--FILEDESC', 'citext - case-insensitive character string data type',])
+endif
+
 citext = shared_module('citext',
   citext_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/cube/meson.build b/contrib/cube/meson.build
index 72342b0c82c..041acf95a90 100644
--- a/contrib/cube/meson.build
+++ b/contrib/cube/meson.build
@@ -17,6 +17,12 @@ cube_parse = custom_target('cubeparse',
 generated_sources += cube_parse.to_list()
 cube_sources += cube_parse
 
+if host_system == 'windows'
+  cube_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'cube',
+    '--FILEDESC', 'cube - multidimensional cube data type',])
+endif
+
 cube = shared_module('cube',
   cube_sources,
   include_directories: include_directories('.'),
diff --git a/contrib/dblink/meson.build b/contrib/dblink/meson.build
index d35f7b5d49e..66eeb03b736 100644
--- a/contrib/dblink/meson.build
+++ b/contrib/dblink/meson.build
@@ -2,6 +2,12 @@ dblink_sources = files(
   'dblink.c',
 )
 
+if host_system == 'windows'
+  dblink_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'dblink',
+    '--FILEDESC', 'dblink - connect to other PostgreSQL databases',])
+endif
+
 dblink = shared_module('dblink',
   dblink_sources,
   kwargs: contrib_mod_args + {
diff --git a/contrib/dict_int/meson.build b/contrib/dict_int/meson.build
index f00e8085619..6fff921adda 100644
--- a/contrib/dict_int/meson.build
+++ b/contrib/dict_int/meson.build
@@ -1,5 +1,15 @@
+dict_int_sources = files(
+  'dict_int.c',
+)
+
+if host_system == 'windows'
+  dict_int_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'dict_int',
+    '--FILEDESC', 'dict_int - add-on dictionary template for full-text search',])
+endif
+
 dict_int = shared_module('dict_int',
-  files('dict_int.c'),
+  dict_int_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += dict_int
diff --git a/contrib/dict_xsyn/meson.build b/contrib/dict_xsyn/meson.build
index be53f55bb79..fabd505a7df 100644
--- a/contrib/dict_xsyn/meson.build
+++ b/contrib/dict_xsyn/meson.build
@@ -1,5 +1,15 @@
+dict_xsyn_sources = files(
+  'dict_xsyn.c',
+)
+
+if host_system == 'windows'
+  dict_xsyn_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'dict_xsyn',
+    '--FILEDESC', 'dict_xsyn - add-on dictionary template for full-text search',])
+endif
+
 dict_xsyn = shared_module('dict_xsyn',
-  files('dict_xsyn.c'),
+  dict_xsyn_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += dict_xsyn
diff --git a/contrib/earthdistance/meson.build b/contrib/earthdistance/meson.build
index 807f5cb7de3..78dc29c3da3 100644
--- a/contrib/earthdistance/meson.build
+++ b/contrib/earthdistance/meson.build
@@ -1,5 +1,15 @@
+earthdistance_sources = files(
+  'earthdistance.c',
+)
+
+if host_system == 'windows'
+  earthdistance_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'earthdistance',
+    '--FILEDESC', 'earthdistance - calculate distances on the surface of the Earth',])
+endif
+
 earthdistance = shared_module('earthdistance',
-  files('earthdistance.c'),
+  earthdistance_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += earthdistance
diff --git a/contrib/file_fdw/meson.build b/contrib/file_fdw/meson.build
index f13efb6e38e..c4071faa669 100644
--- a/contrib/file_fdw/meson.build
+++ b/contrib/file_fdw/meson.build
@@ -1,5 +1,15 @@
+file_fdw_sources = files(
+  'file_fdw.c',
+)
+
+if host_system == 'windows'
+  file_fdw_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'file_fdw',
+    '--FILEDESC', 'file_fdw - foreign data wrapper for files',])
+endif
+
 file_fdw = shared_module('file_fdw',
-  files('file_fdw.c'),
+  file_fdw_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += file_fdw
diff --git a/contrib/fuzzystrmatch/meson.build b/contrib/fuzzystrmatch/meson.build
index ec278a6211e..e6d06149cec 100644
--- a/contrib/fuzzystrmatch/meson.build
+++ b/contrib/fuzzystrmatch/meson.build
@@ -1,8 +1,16 @@
+fuzzystrmatch_sources = files(
+  'fuzzystrmatch.c',
+  'dmetaphone.c',
+)
+
+if host_system == 'windows'
+  fuzzystrmatch_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'fuzzystrmatch',
+    '--FILEDESC', 'fuzzystrmatch - similarities and distance between strings',])
+endif
+
 fuzzystrmatch = shared_module('fuzzystrmatch',
-  files(
-    'fuzzystrmatch.c',
-    'dmetaphone.c'
-  ),
+  fuzzystrmatch_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += fuzzystrmatch
diff --git a/contrib/hstore/meson.build b/contrib/hstore/meson.build
index 07c59f40a97..2bb26bb772b 100644
--- a/contrib/hstore/meson.build
+++ b/contrib/hstore/meson.build
@@ -1,15 +1,23 @@
 # .. so that includes of hstore/hstore.h work
 hstore_inc = include_directories('.', '../')
 
+hstore_sources = files(
+  'hstore_compat.c',
+  'hstore_gin.c',
+  'hstore_gist.c',
+  'hstore_io.c',
+  'hstore_op.c',
+  'hstore_subs.c',
+)
+
+if host_system == 'windows'
+  hstore_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'hstore',
+    '--FILEDESC', 'hstore - key/value pair data type',])
+endif
+
 hstore = shared_module('hstore',
-  files(
-    'hstore_compat.c',
-    'hstore_gin.c',
-    'hstore_gist.c',
-    'hstore_io.c',
-    'hstore_op.c',
-    'hstore_subs.c',
-  ),
+  hstore_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += hstore
diff --git a/contrib/hstore_plperl/meson.build b/contrib/hstore_plperl/meson.build
index bbafa0221bd..a238fee6a26 100644
--- a/contrib/hstore_plperl/meson.build
+++ b/contrib/hstore_plperl/meson.build
@@ -6,6 +6,12 @@ hstore_plperl_sources = files(
   'hstore_plperl.c',
 )
 
+if host_system == 'windows'
+  hstore_plperl_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'hstore_plperl',
+    '--FILEDESC', 'hstore_plperl - hstore transform for plperl',])
+endif
+
 hstore_plperl = shared_module('hstore_plperl',
   hstore_plperl_sources,
   include_directories: [plperl_inc, hstore_inc],
diff --git a/contrib/hstore_plpython/meson.build b/contrib/hstore_plpython/meson.build
index 214b48519a9..6071aaeb4b3 100644
--- a/contrib/hstore_plpython/meson.build
+++ b/contrib/hstore_plpython/meson.build
@@ -6,6 +6,12 @@ hstore_plpython_sources = files(
   'hstore_plpython.c',
 )
 
+if host_system == 'windows'
+  hstore_plpython_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'hstore_plpython3',
+    '--FILEDESC', 'hstore_plpython - hstore transform for plpython',])
+endif
+
 hstore_plpython = shared_module('hstore_plpython3',
   hstore_plpython_sources,
   include_directories: [plpython_inc, hstore_inc, ],
diff --git a/contrib/intarray/meson.build b/contrib/intarray/meson.build
index 1655bcbb3fd..b7cf1ce0cad 100644
--- a/contrib/intarray/meson.build
+++ b/contrib/intarray/meson.build
@@ -8,6 +8,12 @@ intarray_sources = files(
   '_intbig_gist.c',
 )
 
+if host_system == 'windows'
+  intarray_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', '_int',
+    '--FILEDESC', 'intarray - functions and operators for arrays of integers',])
+endif
+
 intarray = shared_module('_int',
   intarray_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/isn/meson.build b/contrib/isn/meson.build
index cc30bbeb55c..db68a718313 100644
--- a/contrib/isn/meson.build
+++ b/contrib/isn/meson.build
@@ -2,6 +2,12 @@ isn_sources = files(
   'isn.c',
 )
 
+if host_system == 'windows'
+  isn_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'isn',
+    '--FILEDESC', 'isn - data types for international product numbering standards',])
+endif
+
 isn = shared_module('isn',
   isn_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/jsonb_plperl/meson.build b/contrib/jsonb_plperl/meson.build
index 5c915d8ed94..071a7a98d2c 100644
--- a/contrib/jsonb_plperl/meson.build
+++ b/contrib/jsonb_plperl/meson.build
@@ -6,6 +6,12 @@ jsonb_plperl_sources = files(
   'jsonb_plperl.c',
 )
 
+if host_system == 'windows'
+  jsonb_plperl_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'jsonb_plperl',
+    '--FILEDESC', 'jsonb_plperl - jsonb transform for plperl',])
+endif
+
 jsonb_plperl = shared_module('jsonb_plperl',
   jsonb_plperl_sources,
   include_directories: [plperl_inc],
diff --git a/contrib/jsonb_plpython/meson.build b/contrib/jsonb_plpython/meson.build
index de8e1105c6a..84dc1161e8b 100644
--- a/contrib/jsonb_plpython/meson.build
+++ b/contrib/jsonb_plpython/meson.build
@@ -6,6 +6,12 @@ jsonb_plpython_sources = files(
   'jsonb_plpython.c',
 )
 
+if host_system == 'windows'
+  jsonb_plpython_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'jsonb_plpython3',
+    '--FILEDESC', 'jsonb_plpython - jsonb transform for plpython',])
+endif
+
 jsonb_plpython = shared_module('jsonb_plpython3',
   jsonb_plpython_sources,
   include_directories: [plpython_inc],
diff --git a/contrib/lo/meson.build b/contrib/lo/meson.build
index 9082d5713c7..61ae131f1cc 100644
--- a/contrib/lo/meson.build
+++ b/contrib/lo/meson.build
@@ -2,6 +2,12 @@ lo_sources = files(
   'lo.c',
 )
 
+if host_system == 'windows'
+  lo_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'lo',
+    '--FILEDESC', 'lo - management for large objects',])
+endif
+
 lo = shared_module('lo',
   lo_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/ltree/meson.build b/contrib/ltree/meson.build
index 9463fc2c5e5..421292cea9d 100644
--- a/contrib/ltree/meson.build
+++ b/contrib/ltree/meson.build
@@ -13,6 +13,12 @@ ltree_sources = files(
 # .. so that includes of ltree/ltree.h work
 ltree_inc = include_directories('.', '../')
 
+if host_system == 'windows'
+  ltree_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'ltree',
+    '--FILEDESC', 'ltree - hierarchical label data type',])
+endif
+
 ltree = shared_module('ltree',
   ltree_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/ltree_plpython/meson.build b/contrib/ltree_plpython/meson.build
index 429d75006aa..acf5e4a6fc8 100644
--- a/contrib/ltree_plpython/meson.build
+++ b/contrib/ltree_plpython/meson.build
@@ -6,6 +6,12 @@ ltree_plpython_sources = files(
   'ltree_plpython.c',
 )
 
+if host_system == 'windows'
+  ltree_plpython_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'ltree_plpython3',
+    '--FILEDESC', 'ltree_plpython - ltree transform for plpython',])
+endif
+
 ltree_plpython = shared_module('ltree_plpython3',
   ltree_plpython_sources,
   include_directories: [plpython_inc, ltree_inc],
diff --git a/contrib/oid2name/meson.build b/contrib/oid2name/meson.build
index 1dad5d8f6e7..1a248f19260 100644
--- a/contrib/oid2name/meson.build
+++ b/contrib/oid2name/meson.build
@@ -1,5 +1,15 @@
+oid2name_sources = files(
+  'oid2name.c',
+)
+
+if host_system == 'windows'
+  oid2name_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'oid2name',
+    '--FILEDESC', 'oid2name - examine the file structure',])
+endif
+
 oid2name = executable('oid2name',
-  ['oid2name.c'],
+  oid2name_sources,
   dependencies: [frontend_code, libpq],
   kwargs: default_bin_args,
 )
diff --git a/contrib/old_snapshot/meson.build b/contrib/old_snapshot/meson.build
index 8e7ee09a43a..77276c3715a 100644
--- a/contrib/old_snapshot/meson.build
+++ b/contrib/old_snapshot/meson.build
@@ -2,6 +2,12 @@ old_snapshot_sources = files(
   'time_mapping.c',
 )
 
+if host_system == 'windows'
+  old_snapshot_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'old_snapshot',
+    '--FILEDESC', 'old_snapshot - utilities in support of old_snapshot_threshold',])
+endif
+
 old_snapshot = shared_module('old_snapshot',
   old_snapshot_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/pageinspect/meson.build b/contrib/pageinspect/meson.build
index 4af8153e4fd..3ec50b9445e 100644
--- a/contrib/pageinspect/meson.build
+++ b/contrib/pageinspect/meson.build
@@ -9,6 +9,12 @@ pageinspect_sources = files(
   'rawpage.c',
 )
 
+if host_system == 'windows'
+  pageinspect_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pageinspect',
+    '--FILEDESC', 'pageinspect - functions to inspect contents of database pages',])
+endif
+
 pageinspect = shared_module('pageinspect',
   pageinspect_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/passwordcheck/meson.build b/contrib/passwordcheck/meson.build
index 7da47d02f1d..383d7df372a 100644
--- a/contrib/passwordcheck/meson.build
+++ b/contrib/passwordcheck/meson.build
@@ -9,6 +9,12 @@ passwordcheck_deps = []
 # passwordcheck_c_args += ['-DUSE_CRACKLIB', '-DCRACKLIB_DICTPATH="/usr/lib/cracklib_dict"']
 # passwordcheck_deps += [cc.find_library('crack')]
 
+if host_system == 'windows'
+  passwordcheck_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'passwordcheck',
+    '--FILEDESC', 'passwordcheck - strengthen user password checks',])
+endif
+
 passwordcheck = shared_module('passwordcheck',
   passwordcheck_sources,
   c_args: passwordcheck_c_args,
diff --git a/contrib/pg_buffercache/meson.build b/contrib/pg_buffercache/meson.build
index 2c69eae3ea2..dd9948e5f0b 100644
--- a/contrib/pg_buffercache/meson.build
+++ b/contrib/pg_buffercache/meson.build
@@ -1,7 +1,15 @@
+pg_buffercache_sources = files(
+  'pg_buffercache_pages.c',
+)
+
+if host_system == 'windows'
+  pg_buffercache_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_buffercache',
+    '--FILEDESC', 'pg_buffercache - monitoring of shared buffer cache in real-time',])
+endif
+
 pg_buffercache = shared_module('pg_buffercache',
-  files(
-    'pg_buffercache_pages.c',
-  ),
+  pg_buffercache_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += pg_buffercache
diff --git a/contrib/pg_freespacemap/meson.build b/contrib/pg_freespacemap/meson.build
index f795014d7ca..904b37b6e9b 100644
--- a/contrib/pg_freespacemap/meson.build
+++ b/contrib/pg_freespacemap/meson.build
@@ -1,7 +1,15 @@
+pg_freespacemap_sources = files(
+  'pg_freespacemap.c',
+)
+
+if host_system == 'windows'
+  pg_freespacemap_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_freespacemap',
+    '--FILEDESC', 'pg_freespacemap - monitoring of free space map',])
+endif
+
 pg_freespacemap = shared_module('pg_freespacemap',
-  files(
-    'pg_freespacemap.c',
-  ),
+  pg_freespacemap_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += pg_freespacemap
diff --git a/contrib/pg_prewarm/meson.build b/contrib/pg_prewarm/meson.build
index bdca9af4f27..b7140cee34b 100644
--- a/contrib/pg_prewarm/meson.build
+++ b/contrib/pg_prewarm/meson.build
@@ -1,8 +1,16 @@
+pg_prewarm_sources = files(
+  'autoprewarm.c',
+  'pg_prewarm.c',
+)
+
+if host_system == 'windows'
+  pg_prewarm_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_prewarm',
+    '--FILEDESC', 'pg_prewarm - preload relation data into system buffer cache',])
+endif
+
 pg_prewarm = shared_module('pg_prewarm',
-  files(
-    'autoprewarm.c',
-    'pg_prewarm.c',
-  ),
+  pg_prewarm_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += pg_prewarm
diff --git a/contrib/pg_stat_statements/meson.build b/contrib/pg_stat_statements/meson.build
index ac117d2fc1d..854df138e76 100644
--- a/contrib/pg_stat_statements/meson.build
+++ b/contrib/pg_stat_statements/meson.build
@@ -1,5 +1,15 @@
+pg_stat_statements_sources = files(
+  'pg_stat_statements.c',
+)
+
+if host_system == 'windows'
+  pg_stat_statements_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_stat_statements',
+    '--FILEDESC', 'pg_stat_statements - execution statistics of SQL statements',])
+endif
+
 pg_stat_statements = shared_module('pg_stat_statements',
-  files('pg_stat_statements.c'),
+  pg_stat_statements_sources,
   kwargs: contrib_mod_args + {
     'dependencies': contrib_mod_args['dependencies'],
   },
diff --git a/contrib/pg_surgery/meson.build b/contrib/pg_surgery/meson.build
index ac71caa5276..7b5c5999f4b 100644
--- a/contrib/pg_surgery/meson.build
+++ b/contrib/pg_surgery/meson.build
@@ -1,7 +1,15 @@
+pg_surgery_sources = files(
+  'heap_surgery.c',
+)
+
+if host_system == 'windows'
+  pg_surgery_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_surgery',
+    '--FILEDESC', 'pg_surgery - perform surgery on a damaged relation',])
+endif
+
 pg_surgery = shared_module('pg_surgery',
-  files(
-    'heap_surgery.c',
-  ),
+  pg_surgery_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += pg_surgery
diff --git a/contrib/pg_trgm/meson.build b/contrib/pg_trgm/meson.build
index a90628d23c6..c8c7c07b308 100644
--- a/contrib/pg_trgm/meson.build
+++ b/contrib/pg_trgm/meson.build
@@ -1,10 +1,18 @@
+pg_trgm_sources = files(
+  'trgm_gin.c',
+  'trgm_gist.c',
+  'trgm_op.c',
+  'trgm_regexp.c',
+)
+
+if host_system == 'windows'
+  pg_trgm_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_trgm',
+    '--FILEDESC', 'pg_trgm - trigram matching',])
+endif
+
 pg_trgm = shared_module('pg_trgm',
-  files(
-    'trgm_gin.c',
-    'trgm_gist.c',
-    'trgm_op.c',
-    'trgm_regexp.c',
-  ),
+  pg_trgm_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += pg_trgm
diff --git a/contrib/pg_visibility/meson.build b/contrib/pg_visibility/meson.build
index 933dc99ac4d..263a0d08b82 100644
--- a/contrib/pg_visibility/meson.build
+++ b/contrib/pg_visibility/meson.build
@@ -1,7 +1,15 @@
+pg_visibility_sources = files(
+  'pg_visibility.c',
+)
+
+if host_system == 'windows'
+  pg_visibility_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_visibility',
+    '--FILEDESC', 'pg_visibility - page visibility information',])
+endif
+
 pg_visibility = shared_module('pg_visibility',
-  files(
-    'pg_visibility.c',
-  ),
+  pg_visibility_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += pg_visibility
diff --git a/contrib/pg_walinspect/meson.build b/contrib/pg_walinspect/meson.build
index d6b27877dd0..4314a3182a2 100644
--- a/contrib/pg_walinspect/meson.build
+++ b/contrib/pg_walinspect/meson.build
@@ -1,5 +1,11 @@
 pg_walinspect_sources = files('pg_walinspect.c')
 
+if host_system == 'windows'
+  pg_walinspect_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_walinspect',
+    '--FILEDESC', 'pg_walinspect - functions to inspect contents of PostgreSQL Write-Ahead Log',])
+endif
+
 pg_walinspect = shared_module('pg_walinspect',
   pg_walinspect_sources,
   kwargs: contrib_mod_args + {
diff --git a/contrib/pgcrypto/meson.build b/contrib/pgcrypto/meson.build
index fe0851bf8e8..7fc7bbc7ca1 100644
--- a/contrib/pgcrypto/meson.build
+++ b/contrib/pgcrypto/meson.build
@@ -69,6 +69,12 @@ else
   pgcrypto_regress += 'pgp-zlib-DISABLED'
 endif
 
+if host_system == 'windows'
+  pgcrypto_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pgcrypto',
+    '--FILEDESC', 'pgcrypto - cryptographic functions',])
+endif
+
 pgcrypto = shared_module('pgcrypto',
   pgcrypto_sources,
   link_with: pgcrypto_link_with,
diff --git a/contrib/pgrowlocks/meson.build b/contrib/pgrowlocks/meson.build
index 1b41691a2a3..8092f0d4a64 100644
--- a/contrib/pgrowlocks/meson.build
+++ b/contrib/pgrowlocks/meson.build
@@ -1,7 +1,15 @@
+pgrowlocks_sources = files(
+  'pgrowlocks.c',
+)
+
+if host_system == 'windows'
+  pgrowlocks_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pgrowlocks',
+    '--FILEDESC', 'pgrowlocks - display row locking information',])
+endif
+
 pgrowlocks = shared_module('pgrowlocks',
-  files(
-    'pgrowlocks.c',
-  ),
+  pgrowlocks_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += pgrowlocks
diff --git a/contrib/pgstattuple/meson.build b/contrib/pgstattuple/meson.build
index 8e828692d5c..05e4cd46a5c 100644
--- a/contrib/pgstattuple/meson.build
+++ b/contrib/pgstattuple/meson.build
@@ -1,9 +1,17 @@
+pgstattuple_sources = files(
+  'pgstatapprox.c',
+  'pgstatindex.c',
+  'pgstattuple.c',
+)
+
+if host_system == 'windows'
+  pgstattuple_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pgstattuple',
+    '--FILEDESC', 'pgstattuple - tuple-level statistics',])
+endif
+
 pgstattuple = shared_module('pgstattuple',
-  files(
-    'pgstatapprox.c',
-    'pgstatindex.c',
-    'pgstattuple.c',
-  ),
+  pgstattuple_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += pgstattuple
diff --git a/contrib/postgres_fdw/meson.build b/contrib/postgres_fdw/meson.build
index 378885ec93b..d3746ff135c 100644
--- a/contrib/postgres_fdw/meson.build
+++ b/contrib/postgres_fdw/meson.build
@@ -6,6 +6,12 @@ postgres_fdw_sources = files(
   'shippable.c',
 )
 
+if host_system == 'windows'
+  postgres_fdw_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'postgres_fdw',
+    '--FILEDESC', 'postgres_fdw - foreign data wrapper for PostgreSQL',])
+endif
+
 postgres_fdw = shared_module('postgres_fdw',
   postgres_fdw_sources,
   kwargs: contrib_mod_args + {
diff --git a/contrib/seg/meson.build b/contrib/seg/meson.build
index e476eab2a77..c6fbb22999b 100644
--- a/contrib/seg/meson.build
+++ b/contrib/seg/meson.build
@@ -17,6 +17,12 @@ seg_parse = custom_target('segparse',
 generated_sources += seg_parse.to_list()
 seg_sources += seg_parse
 
+if host_system == 'windows'
+  seg_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'seg',
+    '--FILEDESC', 'seg - line segment data type',])
+endif
+
 seg = shared_module('seg',
   seg_sources,
   include_directories: include_directories('.'),
diff --git a/contrib/sepgsql/meson.build b/contrib/sepgsql/meson.build
index 60a95e17c2f..8bef239e3c2 100644
--- a/contrib/sepgsql/meson.build
+++ b/contrib/sepgsql/meson.build
@@ -14,6 +14,12 @@ sepgsql_sources = files(
   'uavc.c',
 )
 
+if host_system == 'windows'
+  sepgsql_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'sepgsql',
+    '--FILEDESC', 'sepgsql - SELinux integration',])
+endif
+
 sepgsql = shared_module('sepgsql',
   sepgsql_sources,
   kwargs: contrib_mod_args + {
diff --git a/contrib/spi/meson.build b/contrib/spi/meson.build
index 98008980ec2..e7d78189ef5 100644
--- a/contrib/spi/meson.build
+++ b/contrib/spi/meson.build
@@ -1,5 +1,15 @@
+autoinc_sources = files(
+  'autoinc.c',
+)
+
+if host_system == 'windows'
+  autoinc_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'autoinc',
+    '--FILEDESC', 'spi - examples of using SPI and triggers',])
+endif
+
 autoinc = shared_module('autoinc',
-  ['autoinc.c'],
+  autoinc_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += autoinc
@@ -9,8 +19,18 @@ install_data('autoinc.control', 'autoinc--1.0.sql',
 )
 
 
+insert_username_sources = files(
+  'insert_username.c',
+)
+
+if host_system == 'windows'
+  insert_username_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'insert_username',
+    '--FILEDESC', 'spi - examples of using SPI and triggers',])
+endif
+
 insert_username = shared_module('insert_username',
-  ['insert_username.c'],
+  insert_username_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += insert_username
@@ -22,8 +42,18 @@ install_data(
 )
 
 
+moddatetime_sources = files(
+  'moddatetime.c',
+)
+
+if host_system == 'windows'
+  moddatetime_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'moddatetime',
+    '--FILEDESC', 'spi - examples of using SPI and triggers',])
+endif
+
 moddatetime = shared_module('moddatetime',
-  ['moddatetime.c'],
+  moddatetime_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += moddatetime
@@ -38,8 +68,18 @@ install_data(
 # comment out if you want a quieter refint package for other uses
 refint_cflags = ['-DREFINT_VERBOSE']
 
+refint_sources = files(
+  'refint.c',
+)
+
+if host_system == 'windows'
+  refint_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'refint',
+    '--FILEDESC', 'spi - examples of using SPI and triggers',])
+endif
+
 refint = shared_module('refint',
-  ['refint.c'],
+  refint_sources,
   c_args: refint_cflags,
   kwargs: contrib_mod_args,
 )
diff --git a/contrib/sslinfo/meson.build b/contrib/sslinfo/meson.build
index 53f752a08ac..136983e783d 100644
--- a/contrib/sslinfo/meson.build
+++ b/contrib/sslinfo/meson.build
@@ -2,10 +2,18 @@ if not ssl.found()
   subdir_done()
 endif
 
+sslinfo_sources = files(
+  'sslinfo.c',
+)
+
+if host_system == 'windows'
+  sslinfo_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'sslinfo',
+    '--FILEDESC', 'sslinfo - information about client SSL certificate',])
+endif
+
 sslinfo = shared_module('sslinfo',
-  files(
-    'sslinfo.c',
-  ),
+  sslinfo_sources,
   kwargs: contrib_mod_args + {
     'dependencies': [ssl, contrib_mod_args['dependencies']],
   }
diff --git a/contrib/tablefunc/meson.build b/contrib/tablefunc/meson.build
index f4230096c0c..d2ddc8d3b39 100644
--- a/contrib/tablefunc/meson.build
+++ b/contrib/tablefunc/meson.build
@@ -1,7 +1,15 @@
+tablefunc_sources = files(
+  'tablefunc.c',
+)
+
+if host_system == 'windows'
+  tablefunc_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'tablefunc',
+    '--FILEDESC', 'tablefunc - various functions that return tables',])
+endif
+
 tablefunc = shared_module('tablefunc',
-  files(
-    'tablefunc.c',
-  ),
+  tablefunc_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += tablefunc
diff --git a/contrib/tcn/meson.build b/contrib/tcn/meson.build
index c3a025247d4..71261c3b0a2 100644
--- a/contrib/tcn/meson.build
+++ b/contrib/tcn/meson.build
@@ -1,7 +1,15 @@
+tcn_sources = files(
+  'tcn.c',
+)
+
+if host_system == 'windows'
+  tcn_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'tcn',
+    '--FILEDESC', 'tcn - trigger function notifying listeners',])
+endif
+
 tcn = shared_module('tcn',
-  files(
-    'tcn.c',
-  ),
+  tcn_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += tcn
diff --git a/contrib/test_decoding/meson.build b/contrib/test_decoding/meson.build
index dd7cb0101ad..6376103c689 100644
--- a/contrib/test_decoding/meson.build
+++ b/contrib/test_decoding/meson.build
@@ -2,6 +2,12 @@ test_decoding_sources = files(
   'test_decoding.c',
 )
 
+if host_system == 'windows'
+  test_decoding_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_decoding',
+    '--FILEDESC', 'test_decoding - example of a logical decoding output plugin',])
+endif
+
 test_decoding = shared_module('test_decoding',
   test_decoding_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/tsm_system_rows/meson.build b/contrib/tsm_system_rows/meson.build
index b9cd42115a8..380abb49883 100644
--- a/contrib/tsm_system_rows/meson.build
+++ b/contrib/tsm_system_rows/meson.build
@@ -1,7 +1,15 @@
+tsm_system_rows_sources = files(
+  'tsm_system_rows.c',
+)
+
+if host_system == 'windows'
+  tsm_system_rows_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'tsm_system_rows',
+    '--FILEDESC', 'tsm_system_rows - TABLESAMPLE method which accepts number of rows as a limit',])
+endif
+
 tsm_system_rows = shared_module('tsm_system_rows',
-  files(
-    'tsm_system_rows.c',
-  ),
+  tsm_system_rows_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += tsm_system_rows
diff --git a/contrib/tsm_system_time/meson.build b/contrib/tsm_system_time/meson.build
index 18015912ffb..e57a2702c60 100644
--- a/contrib/tsm_system_time/meson.build
+++ b/contrib/tsm_system_time/meson.build
@@ -1,7 +1,15 @@
+tsm_system_time_sources = files(
+  'tsm_system_time.c',
+)
+
+if host_system == 'windows'
+  tsm_system_time_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'tsm_system_time',
+    '--FILEDESC', 'tsm_system_time - TABLESAMPLE method which accepts time in milliseconds as a limit',])
+endif
+
 tsm_system_time = shared_module('tsm_system_time',
-  files(
-    'tsm_system_time.c',
-  ),
+  tsm_system_time_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += tsm_system_time
diff --git a/contrib/unaccent/meson.build b/contrib/unaccent/meson.build
index 872b76e3223..438035132f8 100644
--- a/contrib/unaccent/meson.build
+++ b/contrib/unaccent/meson.build
@@ -1,7 +1,15 @@
+unaccent_sources = files(
+  'unaccent.c',
+)
+
+if host_system == 'windows'
+  unaccent_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'unaccent',
+    '--FILEDESC', 'unaccent - text search dictionary that removes accents',])
+endif
+
 unaccent = shared_module('unaccent',
-  files(
-    'unaccent.c',
-  ),
+  unaccent_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += unaccent
diff --git a/contrib/uuid-ossp/meson.build b/contrib/uuid-ossp/meson.build
index da6d1d75c12..28730f398f0 100644
--- a/contrib/uuid-ossp/meson.build
+++ b/contrib/uuid-ossp/meson.build
@@ -2,10 +2,18 @@ if not uuid.found()
   subdir_done()
 endif
 
+uuid_ossp_sources = files(
+  'uuid-ossp.c',
+)
+
+if host_system == 'windows'
+  uuid_ossp_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'uuid-ossp',
+    '--FILEDESC', 'uuid-ossp - UUID generation',])
+endif
+
 uuid_ossp = shared_module('uuid-ossp',
-  files(
-    'uuid-ossp.c',
-  ),
+  uuid_ossp_sources,
   kwargs: contrib_mod_args + {
     'dependencies': [uuid, contrib_mod_args['dependencies']],
   },
diff --git a/contrib/vacuumlo/meson.build b/contrib/vacuumlo/meson.build
index 7a632b87d1b..846de47dbd1 100644
--- a/contrib/vacuumlo/meson.build
+++ b/contrib/vacuumlo/meson.build
@@ -1,5 +1,15 @@
+vacuumlo_sources = files(
+  'vacuumlo.c',
+)
+
+if host_system == 'windows'
+  vacuumlo_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'vacuumlo',
+    '--FILEDESC', 'vacuumlo - removes orphaned large objects',])
+endif
+
 vacuumlo = executable('vacuumlo',
-  ['vacuumlo.c'],
+  vacuumlo_sources,
   dependencies: [frontend_code, libpq],
   kwargs: default_bin_args,
 )
diff --git a/contrib/xml2/meson.build b/contrib/xml2/meson.build
index 9c0b56f01f6..89b0d677516 100644
--- a/contrib/xml2/meson.build
+++ b/contrib/xml2/meson.build
@@ -2,11 +2,19 @@ if not libxml.found()
   subdir_done()
 endif
 
+xml2_sources = files(
+  'xpath.c',
+  'xslt_proc.c',
+)
+
+if host_system == 'windows'
+  xml2_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pgxml',
+    '--FILEDESC', 'xml2 - XPath querying and XSLT',])
+endif
+
 xml2 = shared_module('pgxml',
-  files(
-    'xpath.c',
-    'xslt_proc.c',
-  ),
+  xml2_sources,
   kwargs: contrib_mod_args + {
     'dependencies': [libxml, libxslt, contrib_mod_args['dependencies']],
   },
diff --git a/src/interfaces/ecpg/compatlib/meson.build b/src/interfaces/ecpg/compatlib/meson.build
index 5887cb92b52..e5dc0edc04b 100644
--- a/src/interfaces/ecpg/compatlib/meson.build
+++ b/src/interfaces/ecpg/compatlib/meson.build
@@ -1,7 +1,11 @@
 export_file = custom_target('libpq.exports', kwargs: gen_export_kwargs)
 
-ecpg_compat = both_libraries('libecpg_compat',
+ecpg_compat_sources = files(
   'informix.c',
+)
+
+ecpg_compat = both_libraries('libecpg_compat',
+  ecpg_compat_sources,
   include_directories: ['.', ecpg_inc, postgres_inc, libpq_inc],
   c_args: ['-DSO_MAJOR_VERSION=3'],
   dependencies: [frontend_code, thread_dep],
diff --git a/src/interfaces/ecpg/preproc/meson.build b/src/interfaces/ecpg/preproc/meson.build
index 1be49c8c27f..74876f039c9 100644
--- a/src/interfaces/ecpg/preproc/meson.build
+++ b/src/interfaces/ecpg/preproc/meson.build
@@ -93,6 +93,12 @@ ecpg_kwlist = custom_target('ecpg_kwlist_d.h',
 generated_sources += ecpg_kwlist
 ecpg_sources += ecpg_kwlist
 
+if host_system == 'windows'
+  ecpg_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'ecpg',
+    '--FILEDESC', 'ecpg - embedded SQL precompiler for C',])
+endif
+
 ecpg_exe = executable('ecpg',
   ecpg_sources,
   include_directories: ['.', ecpg_inc, postgres_inc, libpq_inc],
diff --git a/src/interfaces/ecpg/test/meson.build b/src/interfaces/ecpg/test/meson.build
index f0ace641f0c..f67f2dffb71 100644
--- a/src/interfaces/ecpg/test/meson.build
+++ b/src/interfaces/ecpg/test/meson.build
@@ -7,6 +7,12 @@ pg_regress_ecpg_sources = pg_regress_c + files(
   'pg_regress_ecpg.c',
 )
 
+if host_system == 'windows'
+  pg_regress_ecpg_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_regress_ecpg',
+    '--FILEDESC', 'ECPG Test - regression tests for ECPG',])
+endif
+
 pg_regress_ecpg = executable('pg_regress_ecpg',
   pg_regress_ecpg_sources,
   c_args: pg_regress_cflags,
diff --git a/src/test/isolation/meson.build b/src/test/isolation/meson.build
index c7656fd4609..ba27b8c1d44 100644
--- a/src/test/isolation/meson.build
+++ b/src/test/isolation/meson.build
@@ -23,6 +23,12 @@ spec_parser = custom_target('specparse',
 isolationtester_sources += spec_parser
 generated_sources += spec_parser.to_list()
 
+if host_system == 'windows'
+  isolation_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_isolation_regress',
+    '--FILEDESC', 'pg_isolation_regress - multi-client test driver',])
+endif
+
 pg_isolation_regress = executable('pg_isolation_regress',
   isolation_sources,
   c_args: pg_regress_cflags,
@@ -34,6 +40,13 @@ pg_isolation_regress = executable('pg_isolation_regress',
 )
 bin_targets += pg_isolation_regress
 
+
+if host_system == 'windows'
+  isolationtester_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'isolationtester',
+    '--FILEDESC', 'isolationtester - multi-client test driver',])
+endif
+
 isolationtester = executable('isolationtester',
   isolationtester_sources,
   include_directories: include_directories('.'),
diff --git a/src/test/modules/delay_execution/meson.build b/src/test/modules/delay_execution/meson.build
index cf4bdaba637..a0c3ab6afe7 100644
--- a/src/test/modules/delay_execution/meson.build
+++ b/src/test/modules/delay_execution/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+delay_execution_sources = files(
+  'delay_execution.c',
+)
+
+if host_system == 'windows'
+  delay_execution_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'delay_execution',
+    '--FILEDESC', 'delay_execution - allow delay between parsing and execution',])
+endif
+
 delay_execution = shared_module('delay_execution',
-  ['delay_execution.c'],
+  delay_execution_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += delay_execution
diff --git a/src/test/modules/dummy_index_am/meson.build b/src/test/modules/dummy_index_am/meson.build
index 56ff5f48001..4ce82491135 100644
--- a/src/test/modules/dummy_index_am/meson.build
+++ b/src/test/modules/dummy_index_am/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+dummy_index_am_sources = files(
+  'dummy_index_am.c',
+)
+
+if host_system == 'windows'
+  dummy_index_am_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'dummy_index_am',
+    '--FILEDESC', 'dummy_index_am - index access method template',])
+endif
+
 dummy_index_am = shared_module('dummy_index_am',
-  ['dummy_index_am.c'],
+  dummy_index_am_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += dummy_index_am
diff --git a/src/test/modules/dummy_seclabel/meson.build b/src/test/modules/dummy_seclabel/meson.build
index 21b7cf8f353..81b626e496c 100644
--- a/src/test/modules/dummy_seclabel/meson.build
+++ b/src/test/modules/dummy_seclabel/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+dummy_seclabel_sources = files(
+  'dummy_seclabel.c',
+)
+
+if host_system == 'windows'
+  dummy_seclabel_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'dummy_seclabel',
+    '--FILEDESC', 'dummy_seclabel - regression testing of the SECURITY LABEL statement',])
+endif
+
 dummy_seclabel = shared_module('dummy_seclabel',
-  ['dummy_seclabel.c'],
+  dummy_seclabel_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += dummy_seclabel
diff --git a/src/test/modules/libpq_pipeline/meson.build b/src/test/modules/libpq_pipeline/meson.build
index 8384b6e3b2a..de0e2d15626 100644
--- a/src/test/modules/libpq_pipeline/meson.build
+++ b/src/test/modules/libpq_pipeline/meson.build
@@ -1,7 +1,15 @@
+libpq_pipeline_sources = files(
+  'libpq_pipeline.c',
+)
+
+if host_system == 'windows'
+  libpq_pipeline_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'libpq_pipeline',
+    '--FILEDESC', 'libpq_pipeline - test program for pipeline execution',])
+endif
+
 libpq_pipeline = executable('libpq_pipeline',
-  files(
-    'libpq_pipeline.c',
-  ),
+  libpq_pipeline_sources,
   dependencies: [frontend_code, libpq],
   kwargs: default_bin_args + {
     'install': false,
diff --git a/src/test/modules/plsample/meson.build b/src/test/modules/plsample/meson.build
index 45de3f1990d..e1ea2c7a16f 100644
--- a/src/test/modules/plsample/meson.build
+++ b/src/test/modules/plsample/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+plsample_sources = files(
+  'plsample.c',
+)
+
+if host_system == 'windows'
+  plsample_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'plsample',
+    '--FILEDESC', 'PL/Sample - template for procedural language',])
+endif
+
 plsample = shared_module('plsample',
-  ['plsample.c'],
+  plsample_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += plsample
diff --git a/src/test/modules/spgist_name_ops/meson.build b/src/test/modules/spgist_name_ops/meson.build
index 857fc7e140e..445296fee0b 100644
--- a/src/test/modules/spgist_name_ops/meson.build
+++ b/src/test/modules/spgist_name_ops/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+spgist_name_ops_sources = files(
+  'spgist_name_ops.c',
+)
+
+if host_system == 'windows'
+  spgist_name_ops_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'spgist_name_ops',
+    '--FILEDESC', 'spgist_name_ops - test opclass for SP-GiST',])
+endif
+
 spgist_name_ops = shared_module('spgist_name_ops',
-  ['spgist_name_ops.c'],
+  spgist_name_ops_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += spgist_name_ops
diff --git a/src/test/modules/ssl_passphrase_callback/meson.build b/src/test/modules/ssl_passphrase_callback/meson.build
index a57bd0693a3..a9eb4c564da 100644
--- a/src/test/modules/ssl_passphrase_callback/meson.build
+++ b/src/test/modules/ssl_passphrase_callback/meson.build
@@ -3,8 +3,19 @@ if not ssl.found()
 endif
 
 # FIXME: prevent install during main install, but not during test :/
+
+ssl_passphrase_callback_sources = files(
+  'ssl_passphrase_func.c',
+)
+
+if host_system == 'windows'
+  ssl_passphrase_callback_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'ssl_passphrase_func',
+    '--FILEDESC', 'callback function to provide a passphrase',])
+endif
+
 ssl_passphrase_callback = shared_module('ssl_passphrase_func',
-  ['ssl_passphrase_func.c'],
+  ssl_passphrase_callback_sources,
   kwargs: pg_mod_args + {
     'dependencies': [ssl, pg_mod_args['dependencies']],
   },
diff --git a/src/test/modules/test_bloomfilter/meson.build b/src/test/modules/test_bloomfilter/meson.build
index 945eb5a70c4..3cf6b05754f 100644
--- a/src/test/modules/test_bloomfilter/meson.build
+++ b/src/test/modules/test_bloomfilter/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_bloomfilter_sources = files(
+  'test_bloomfilter.c',
+)
+
+if host_system == 'windows'
+  test_bloomfilter_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_bloomfilter',
+    '--FILEDESC', 'test_bloomfilter - test code for Bloom filter library',])
+endif
+
 test_bloomfilter = shared_module('test_bloomfilter',
-  ['test_bloomfilter.c'],
+  test_bloomfilter_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_bloomfilter
diff --git a/src/test/modules/test_ddl_deparse/meson.build b/src/test/modules/test_ddl_deparse/meson.build
index 81ad5adc526..54d44f9b2b4 100644
--- a/src/test/modules/test_ddl_deparse/meson.build
+++ b/src/test/modules/test_ddl_deparse/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_ddl_deparse_sources = files(
+  'test_ddl_deparse.c',
+)
+
+if host_system == 'windows'
+  test_ddl_deparse_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_ddl_deparse',
+    '--FILEDESC', 'test_ddl_deparse - regression testing for DDL deparsing',])
+endif
+
 test_ddl_deparse = shared_module('test_ddl_deparse',
-  ['test_ddl_deparse.c'],
+  test_ddl_deparse_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_ddl_deparse
diff --git a/src/test/modules/test_ginpostinglist/meson.build b/src/test/modules/test_ginpostinglist/meson.build
index abf0a3b0430..b3b49c56122 100644
--- a/src/test/modules/test_ginpostinglist/meson.build
+++ b/src/test/modules/test_ginpostinglist/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_ginpostinglist_sources = files(
+  'test_ginpostinglist.c',
+)
+
+if host_system == 'windows'
+  test_ginpostinglist_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_ginpostinglist',
+    '--FILEDESC', 'test_ginpostinglist - test code for src/backend/access/gin//ginpostinglist.c',])
+endif
+
 test_ginpostinglist = shared_module('test_ginpostinglist',
-  ['test_ginpostinglist.c'],
+  test_ginpostinglist_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_ginpostinglist
diff --git a/src/test/modules/test_integerset/meson.build b/src/test/modules/test_integerset/meson.build
index c32c469c69a..4bd75af4b5e 100644
--- a/src/test/modules/test_integerset/meson.build
+++ b/src/test/modules/test_integerset/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_integerset_sources = files(
+  'test_integerset.c',
+)
+
+if host_system == 'windows'
+  test_integerset_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_integerset',
+    '--FILEDESC', 'test_integerset - test code for src/backend/lib/integerset.c',])
+endif
+
 test_integerset = shared_module('test_integerset',
-  ['test_integerset.c'],
+  test_integerset_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_integerset
diff --git a/src/test/modules/test_lfind/meson.build b/src/test/modules/test_lfind/meson.build
index a388de1156a..c5405b8f878 100644
--- a/src/test/modules/test_lfind/meson.build
+++ b/src/test/modules/test_lfind/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_lfind_sources = files(
+  'test_lfind.c',
+)
+
+if host_system == 'windows'
+  test_lfind_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_lfind',
+    '--FILEDESC', 'test_lfind - test code for optimized linear search functions',])
+endif
+
 test_lfind = shared_module('test_lfind',
-  ['test_lfind.c'],
+  test_lfind_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_lfind
diff --git a/src/test/modules/test_oat_hooks/meson.build b/src/test/modules/test_oat_hooks/meson.build
index 5faf0459777..8802bbbac55 100644
--- a/src/test/modules/test_oat_hooks/meson.build
+++ b/src/test/modules/test_oat_hooks/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_oat_hooks_sources = files(
+  'test_oat_hooks.c',
+)
+
+if host_system == 'windows'
+  test_oat_hooks_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_oat_hooks',
+    '--FILEDESC', 'test_oat_hooks - example use of object access hooks',])
+endif
+
 test_oat_hooks = shared_module('test_oat_hooks',
-  ['test_oat_hooks.c'],
+  test_oat_hooks_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_oat_hooks
diff --git a/src/test/modules/test_parser/meson.build b/src/test/modules/test_parser/meson.build
index b59960f615e..1c17113347f 100644
--- a/src/test/modules/test_parser/meson.build
+++ b/src/test/modules/test_parser/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_parser_sources = files(
+  'test_parser.c',
+)
+
+if host_system == 'windows'
+  test_parser_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_parser',
+    '--FILEDESC', 'test_parser - example of a custom parser for full-text search',])
+endif
+
 test_parser = shared_module('test_parser',
-  ['test_parser.c'],
+  test_parser_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_parser
diff --git a/src/test/modules/test_predtest/meson.build b/src/test/modules/test_predtest/meson.build
index 1cfa84b3609..9a5be43c9c0 100644
--- a/src/test/modules/test_predtest/meson.build
+++ b/src/test/modules/test_predtest/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_predtest_sources = files(
+  'test_predtest.c',
+)
+
+if host_system == 'windows'
+  test_predtest_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_predtest',
+    '--FILEDESC', 'test_predtest - test code for optimizer/util/predtest.c',])
+endif
+
 test_predtest = shared_module('test_predtest',
-  ['test_predtest.c'],
+  test_predtest_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_predtest
diff --git a/src/test/modules/test_rbtree/meson.build b/src/test/modules/test_rbtree/meson.build
index 34cbc3e1624..f067e08d321 100644
--- a/src/test/modules/test_rbtree/meson.build
+++ b/src/test/modules/test_rbtree/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_rbtree_sources = files(
+  'test_rbtree.c',
+)
+
+if host_system == 'windows'
+  test_rbtree_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_rbtree',
+    '--FILEDESC', 'test_rbtree - test code for red-black tree library',])
+endif
+
 test_rbtree = shared_module('test_rbtree',
-  ['test_rbtree.c'],
+  test_rbtree_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_rbtree
diff --git a/src/test/modules/test_regex/meson.build b/src/test/modules/test_regex/meson.build
index 867a64e57c3..cfb938d9f1e 100644
--- a/src/test/modules/test_regex/meson.build
+++ b/src/test/modules/test_regex/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_regex_sources = files(
+  'test_regex.c',
+)
+
+if host_system == 'windows'
+  test_regex_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_regex',
+    '--FILEDESC', 'test_regex - test code for backend/regex/',])
+endif
+
 test_regex = shared_module('test_regex',
-  ['test_regex.c'],
+  test_regex_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_regex
diff --git a/src/test/modules/test_rls_hooks/meson.build b/src/test/modules/test_rls_hooks/meson.build
index 80d8adda332..3fb273b2934 100644
--- a/src/test/modules/test_rls_hooks/meson.build
+++ b/src/test/modules/test_rls_hooks/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_rls_hooks_sources = files(
+  'test_rls_hooks.c',
+)
+
+if host_system == 'windows'
+  test_rls_hooks_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_rls_hooks',
+    '--FILEDESC', 'test_rls_hooks - example use of RLS hooks',])
+endif
+
 test_rls_hooks = shared_module('test_rls_hooks',
-  ['test_rls_hooks.c'],
+  test_rls_hooks_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_rls_hooks
diff --git a/src/test/modules/test_shm_mq/meson.build b/src/test/modules/test_shm_mq/meson.build
index b663543d616..16c8fdb57f4 100644
--- a/src/test/modules/test_shm_mq/meson.build
+++ b/src/test/modules/test_shm_mq/meson.build
@@ -1,10 +1,19 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_shm_mq_sources = files(
+  'setup.c',
+  'test.c',
+  'worker.c',
+)
+
+if host_system == 'windows'
+  test_shm_mq_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_shm_mq',
+    '--FILEDESC', 'test_shm_mq - example use of shared memory message queue',])
+endif
+
 test_shm_mq = shared_module('test_shm_mq',
-  files(
-    'setup.c',
-    'test.c',
-    'worker.c',
-  ),
+  test_shm_mq_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_shm_mq
diff --git a/src/test/modules/worker_spi/meson.build b/src/test/modules/worker_spi/meson.build
index 32acad883b2..a4a158c75b9 100644
--- a/src/test/modules/worker_spi/meson.build
+++ b/src/test/modules/worker_spi/meson.build
@@ -1,8 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_worker_spi_sources = files(
+  'worker_spi.c',
+)
+
+if host_system == 'windows'
+  test_worker_spi_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'worker_spi',
+    '--FILEDESC', 'worker_spi - background worker example',])
+endif
+
 test_worker_spi = shared_module('worker_spi',
-  files(
-    'worker_spi.c',
-  ),
+  test_worker_spi_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_worker_spi
diff --git a/src/test/regress/meson.build b/src/test/regress/meson.build
index fd8ee995b79..963c0f64ed5 100644
--- a/src/test/regress/meson.build
+++ b/src/test/regress/meson.build
@@ -8,6 +8,12 @@ regress_sources = pg_regress_c + files(
 
 pg_regress_cflags = ['-DHOST_TUPLE="frak"', '-DSHELLPROG="/bin/sh"']
 
+if host_system == 'windows'
+  regress_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_regress',
+    '--FILEDESC', 'pg_regress - test driver',])
+endif
+
 pg_regress = executable('pg_regress',
   regress_sources,
   c_args: pg_regress_cflags,
diff --git a/meson.build b/meson.build
index 6ffae59ba03..4ad06b25213 100644
--- a/meson.build
+++ b/meson.build
@@ -2523,6 +2523,67 @@ gen_export_kwargs = {
 
 
 
+###
+### windows resources related stuff
+###
+
+if host_system == 'windows'
+  pg_ico = meson.source_root() / 'src' / 'port' / 'win32.ico'
+  win32ver_rc = files('src/port/win32ver.rc')
+  rcgen = find_program('src/tools/rcgen', native: true)
+
+  rcgen_base_args = [
+    '--srcdir', '@SOURCE_DIR@',
+    '--builddir', meson.build_root(),
+    '--rcout', '@OUTPUT0@',
+    '--out', '@OUTPUT1@',
+    '--input', '@INPUT@',
+    '@EXTRA_ARGS@',
+  ]
+
+  if cc.get_argument_syntax() == 'msvc'
+    rcgen_base_args += [
+      '--rc', find_program('rc', required: true).path(),
+    ]
+    rcgen_outputs = ['@[email protected]', '@[email protected]']
+  else
+    rcgen_base_args += [
+      '--windres', find_program('windres', required: true).path(),
+    ]
+    rcgen_outputs = ['@[email protected]', '@[email protected]']
+  endif
+
+  # msbuild backend doesn't support this atm
+  if meson.backend() == 'ninja'
+    rcgen_base_args += ['--depfile', '@DEPFILE@']
+  endif
+
+  rcgen_bin_args = rcgen_base_args + [
+    '--VFT_TYPE', 'VFT_APP',
+    '--FILEENDING', 'exe',
+    '--ICO', pg_ico
+  ]
+
+  rcgen_lib_args = rcgen_base_args + [
+    '--VFT_TYPE', 'VFT_DLL',
+    '--FILEENDING', 'dll',
+  ]
+
+  rc_bin_gen = generator(rcgen,
+    depfile: '@[email protected]',
+    arguments: rcgen_bin_args,
+    output: rcgen_outputs,
+  )
+
+  rc_lib_gen = generator(rcgen,
+    depfile: '@[email protected]',
+    arguments: rcgen_lib_args,
+    output: rcgen_outputs,
+  )
+endif
+
+
+
 # headers that the whole build tree depends on
 generated_headers = []
 # headers that the backend build depends on
diff --git a/src/timezone/meson.build b/src/timezone/meson.build
index 16f082ecfa8..9e0934c000b 100644
--- a/src/timezone/meson.build
+++ b/src/timezone/meson.build
@@ -28,6 +28,12 @@ if get_option('system_tzdata') == ''
   if meson.is_cross_build()
     zic = find_program(get_option('ZIC'), native: true, required: true)
   else
+    if host_system == 'windows'
+      zic_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+        '--NAME', 'zic',
+        '--FILEDESC', 'zic - time zone compiler',])
+    endif
+
     zic = executable('zic', zic_sources,
                      dependencies: [frontend_code],
                      kwargs: default_bin_args + {'install': false}
diff --git a/src/tools/rcgen b/src/tools/rcgen
new file mode 100755
index 00000000000..5b62bfe5410
--- /dev/null
+++ b/src/tools/rcgen
@@ -0,0 +1,99 @@
+#!/usr/bin/env python3
+
+import argparse
+import os
+import subprocess
+import sys
+
+parser = argparse.ArgumentParser(description='generate PostgreSQL rc file')
+
+parser.add_argument('--srcdir', type=os.path.abspath,
+                    required=True)
+parser.add_argument('--builddir', type=os.path.abspath,
+                    required=True)
+
+binaries = parser.add_argument_group('binaries')
+binaries.add_argument('--windres', type=os.path.abspath)
+binaries.add_argument('--rc', type=os.path.abspath)
+
+inout = parser.add_argument_group('inout')
+inout.add_argument('--depfile', type=argparse.FileType('w'))
+inout.add_argument('--input', type=argparse.FileType('r'),
+                   required=True)
+inout.add_argument('--rcout', type=argparse.FileType('w'),
+                   required=True)
+inout.add_argument('--out', type=str,
+                   required=True)
+
+replacements = parser.add_argument_group('replacements')
+replacements.add_argument('--FILEDESC', type=str)
+replacements.add_argument('--NAME', type=str, required=True)
+replacements.add_argument('--VFT_TYPE', type=str, required=True)
+replacements.add_argument('--FILEENDING', type=str, required=True)
+replacements.add_argument('--ICO', type=str)
+
+args = parser.parse_args()
+
+# determine replacement strings
+
+internal_name = '"{0}"'.format(args.NAME)
+original_name = '"{0}.{1}"'.format(args.NAME, args.FILEENDING)
+
+# if no description is passed in, generate one based on the name
+if args.FILEDESC:
+    filedesc = args.FILEDESC
+elif args.NAME:
+    if args.VFT_TYPE == 'VFT_DLL':
+        filedesc = 'PostgreSQL {0} library'.format(args.NAME)
+    else:
+        filedesc = 'PostgreSQL {0} binary'.format(args.NAME)
+filedesc = '"{0}"'.format(filedesc)
+
+
+if args.ICO:
+    ico = 'IDI_ICON ICON "{0}"'.format(args.ICO)
+    if args.depfile:
+        args.depfile.write("{0} : {1}\n".format(args.rcout.name, args.ICO))
+else:
+    ico = ''
+
+
+data = args.input.read()
+
+data = data.replace('VFT_APP', args.VFT_TYPE)
+data = data.replace('_INTERNAL_NAME_', internal_name)
+data = data.replace('_ORIGINAL_NAME_', original_name)
+data = data.replace('FILEDESC', filedesc)
+data = data.replace("_ICO_", ico)
+
+args.rcout.write(data)
+args.rcout.close()
+
+if args.windres:
+    cmd = [
+        args.windres,
+        '-I{0}/src/include/'.format(args.builddir),
+        '-I{0}/src/include/'.format(args.srcdir),
+        '-o', args.out, '-i', args.rcout.name,
+    ]
+elif args.rc:
+    cmd = [
+        args.rc, '/nologo',
+        '-I{0}/src/include/'.format(args.builddir),
+        '-I{0}/src/include/'.format(args.srcdir),
+        '/fo', args.out, args.rcout.name,
+    ]
+else:
+    sys.exit('either --windres or --rc needs to be specified')
+
+sp = subprocess.run(cmd)
+if sp.returncode != 0:
+    sys.exit(sp.returncode)
+
+# It'd be nicer if we could generate correct dependencies here, but 'rc'
+# doesn't support doing so. It's unlikely we'll ever need more, so...
+if args.depfile:
+    args.depfile.write("{0} : {1}\n".format(
+        args.rcout.name, args.input.name))
+    args.depfile.write("{0} : {1}/{2}\n".format(
+        args.out, args.builddir, 'src/include/pg_config.h'))
-- 
2.37.3.542.gdd3f6c4cae


--ayx3t42hstokjw3l
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
	filename="v15-0006-meson-Add-support-for-relative-rpaths-fixing-tes.patch"



^ permalink  raw  reply  [nested|flat] 23+ messages in thread

* [PATCH v16 04/16] meson: Add windows resource files
@ 2022-09-21 18:03 Andres Freund <[email protected]>
  0 siblings, 0 replies; 23+ messages in thread

From: Andres Freund @ 2022-09-21 18:03 UTC (permalink / raw)

Author: Andres Freund <[email protected]>
Author: Nazir Bilal Yavuz <[email protected]>
---
 src/backend/jit/llvm/meson.build              |   6 +
 .../replication/libpqwalreceiver/meson.build  |   6 +
 src/backend/replication/pgoutput/meson.build  |   6 +
 src/backend/snowball/meson.build              |   6 +
 .../utils/mb/conversion_procs/meson.build     | 144 ++++++++++++++----
 src/bin/initdb/meson.build                    |   6 +
 src/bin/pg_amcheck/meson.build                |   8 +-
 src/bin/pg_archivecleanup/meson.build         |  12 +-
 src/bin/pg_basebackup/meson.build             |  38 ++++-
 src/bin/pg_checksums/meson.build              |  12 +-
 src/bin/pg_config/meson.build                 |  12 +-
 src/bin/pg_controldata/meson.build            |  12 +-
 src/bin/pg_ctl/meson.build                    |  12 +-
 src/bin/pg_dump/meson.build                   |  18 +++
 src/bin/pg_resetwal/meson.build               |  12 +-
 src/bin/pg_rewind/meson.build                 |   6 +
 src/bin/pg_test_fsync/meson.build             |  10 +-
 src/bin/pg_test_timing/meson.build            |  12 +-
 src/bin/pg_upgrade/meson.build                |   6 +
 src/bin/pg_verifybackup/meson.build           |   6 +
 src/bin/pg_waldump/meson.build                |   6 +
 src/bin/pgbench/meson.build                   |   6 +
 src/bin/pgevent/meson.build                   |   6 +
 src/bin/psql/meson.build                      |   6 +
 src/bin/scripts/meson.build                   |  10 +-
 src/interfaces/libpq/meson.build              |   5 +
 src/interfaces/libpq/test/meson.build         |  25 ++-
 src/pl/plperl/meson.build                     |   7 +
 src/pl/plpgsql/src/meson.build                |   6 +
 src/pl/plpython/meson.build                   |   6 +
 src/pl/tcl/meson.build                        |   6 +
 contrib/adminpack/meson.build                 |  12 +-
 contrib/amcheck/meson.build                   |  17 ++-
 contrib/auth_delay/meson.build                |  12 +-
 contrib/auto_explain/meson.build              |  12 +-
 contrib/basebackup_to_shell/meson.build       |   6 +
 contrib/basic_archive/meson.build             |   6 +
 contrib/bloom/meson.build                     |   6 +
 contrib/bool_plperl/meson.build               |   6 +
 contrib/btree_gin/meson.build                 |  12 +-
 contrib/btree_gist/meson.build                |   6 +
 contrib/citext/meson.build                    |   6 +
 contrib/cube/meson.build                      |   6 +
 contrib/dblink/meson.build                    |   6 +
 contrib/dict_int/meson.build                  |  12 +-
 contrib/dict_xsyn/meson.build                 |  12 +-
 contrib/earthdistance/meson.build             |  12 +-
 contrib/file_fdw/meson.build                  |  12 +-
 contrib/fuzzystrmatch/meson.build             |  16 +-
 contrib/hstore/meson.build                    |  24 ++-
 contrib/hstore_plperl/meson.build             |   6 +
 contrib/hstore_plpython/meson.build           |   6 +
 contrib/intarray/meson.build                  |   6 +
 contrib/isn/meson.build                       |   6 +
 contrib/jsonb_plperl/meson.build              |   6 +
 contrib/jsonb_plpython/meson.build            |   6 +
 contrib/lo/meson.build                        |   6 +
 contrib/ltree/meson.build                     |   6 +
 contrib/ltree_plpython/meson.build            |   6 +
 contrib/oid2name/meson.build                  |  12 +-
 contrib/old_snapshot/meson.build              |   6 +
 contrib/pageinspect/meson.build               |   6 +
 contrib/passwordcheck/meson.build             |   6 +
 contrib/pg_buffercache/meson.build            |  14 +-
 contrib/pg_freespacemap/meson.build           |  14 +-
 contrib/pg_prewarm/meson.build                |  16 +-
 contrib/pg_stat_statements/meson.build        |  12 +-
 contrib/pg_surgery/meson.build                |  14 +-
 contrib/pg_trgm/meson.build                   |  20 ++-
 contrib/pg_visibility/meson.build             |  14 +-
 contrib/pg_walinspect/meson.build             |   6 +
 contrib/pgcrypto/meson.build                  |   6 +
 contrib/pgrowlocks/meson.build                |  14 +-
 contrib/pgstattuple/meson.build               |  18 ++-
 contrib/postgres_fdw/meson.build              |   6 +
 contrib/seg/meson.build                       |   6 +
 contrib/sepgsql/meson.build                   |   6 +
 contrib/spi/meson.build                       |  48 +++++-
 contrib/sslinfo/meson.build                   |  14 +-
 contrib/tablefunc/meson.build                 |  14 +-
 contrib/tcn/meson.build                       |  14 +-
 contrib/test_decoding/meson.build             |   6 +
 contrib/tsm_system_rows/meson.build           |  14 +-
 contrib/tsm_system_time/meson.build           |  14 +-
 contrib/unaccent/meson.build                  |  14 +-
 contrib/uuid-ossp/meson.build                 |  14 +-
 contrib/vacuumlo/meson.build                  |  12 +-
 contrib/xml2/meson.build                      |  16 +-
 src/interfaces/ecpg/compatlib/meson.build     |   6 +-
 src/interfaces/ecpg/preproc/meson.build       |   6 +
 src/interfaces/ecpg/test/meson.build          |   6 +
 src/test/isolation/meson.build                |  13 ++
 src/test/modules/delay_execution/meson.build  |  13 +-
 src/test/modules/dummy_index_am/meson.build   |  13 +-
 src/test/modules/dummy_seclabel/meson.build   |  13 +-
 src/test/modules/libpq_pipeline/meson.build   |  14 +-
 src/test/modules/plsample/meson.build         |  13 +-
 src/test/modules/spgist_name_ops/meson.build  |  13 +-
 .../ssl_passphrase_callback/meson.build       |  13 +-
 src/test/modules/test_bloomfilter/meson.build |  13 +-
 src/test/modules/test_ddl_deparse/meson.build |  13 +-
 .../modules/test_ginpostinglist/meson.build   |  13 +-
 src/test/modules/test_integerset/meson.build  |  13 +-
 src/test/modules/test_lfind/meson.build       |  13 +-
 src/test/modules/test_oat_hooks/meson.build   |  13 +-
 src/test/modules/test_parser/meson.build      |  13 +-
 src/test/modules/test_predtest/meson.build    |  13 +-
 src/test/modules/test_rbtree/meson.build      |  13 +-
 src/test/modules/test_regex/meson.build       |  13 +-
 src/test/modules/test_rls_hooks/meson.build   |  13 +-
 src/test/modules/test_shm_mq/meson.build      |  19 ++-
 src/test/modules/worker_spi/meson.build       |  15 +-
 src/test/regress/meson.build                  |   6 +
 meson.build                                   |  61 ++++++++
 src/timezone/meson.build                      |   6 +
 src/tools/rcgen                               |  99 ++++++++++++
 116 files changed, 1383 insertions(+), 159 deletions(-)
 create mode 100755 src/tools/rcgen

diff --git a/src/backend/jit/llvm/meson.build b/src/backend/jit/llvm/meson.build
index de2e624ab58..5fb63768358 100644
--- a/src/backend/jit/llvm/meson.build
+++ b/src/backend/jit/llvm/meson.build
@@ -20,6 +20,12 @@ llvmjit_sources += files(
   'llvmjit_expr.c',
 )
 
+if host_system == 'windows'
+  llvmjit_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'llvmjit',
+    '--FILEDESC', 'llvmjit - JIT using LLVM',])
+endif
+
 llvmjit = shared_module('llvmjit',
   llvmjit_sources,
   kwargs: pg_mod_args + {
diff --git a/src/backend/replication/libpqwalreceiver/meson.build b/src/backend/replication/libpqwalreceiver/meson.build
index 3fc786c80a0..4c653a05d36 100644
--- a/src/backend/replication/libpqwalreceiver/meson.build
+++ b/src/backend/replication/libpqwalreceiver/meson.build
@@ -2,6 +2,12 @@ libpqwalreceiver_sources = files(
   'libpqwalreceiver.c',
 )
 
+if host_system == 'windows'
+  libpqwalreceiver_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pqwalreceiver',
+    '--FILEDESC', 'libpqwalreceiver - receive WAL during streaming replication',])
+endif
+
 libpqwalreceiver = shared_module('pqwalreceiver',
   libpqwalreceiver_sources,
   kwargs: pg_mod_args + {
diff --git a/src/backend/replication/pgoutput/meson.build b/src/backend/replication/pgoutput/meson.build
index ab956361a62..5df27d7b764 100644
--- a/src/backend/replication/pgoutput/meson.build
+++ b/src/backend/replication/pgoutput/meson.build
@@ -2,6 +2,12 @@ pgoutput_sources = files(
   'pgoutput.c',
 )
 
+if host_system == 'windows'
+  pgoutput_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pgoutput',
+    '--FILEDESC', 'pgoutput - standard logical replication output plugin',])
+endif
+
 pgoutput = shared_module('pgoutput',
   pgoutput_sources,
   kwargs: pg_mod_args,
diff --git a/src/backend/snowball/meson.build b/src/backend/snowball/meson.build
index 8c6f685cb32..974401d187e 100644
--- a/src/backend/snowball/meson.build
+++ b/src/backend/snowball/meson.build
@@ -58,6 +58,12 @@ dict_snowball_sources += files(
 # see comment in src/include/snowball/header.h
 stemmer_inc = include_directories('../../include/snowball')
 
+if host_system == 'windows'
+  dict_snowball_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'dict_snowball',
+    '--FILEDESC', 'snowball - natural language stemmers',])
+endif
+
 dict_snowball = shared_module('dict_snowball',
   dict_snowball_sources,
   kwargs: pg_mod_args + {
diff --git a/src/backend/utils/mb/conversion_procs/meson.build b/src/backend/utils/mb/conversion_procs/meson.build
index 1bc971d1945..00e721a2453 100644
--- a/src/backend/utils/mb/conversion_procs/meson.build
+++ b/src/backend/utils/mb/conversion_procs/meson.build
@@ -1,36 +1,122 @@
 encodings = {
-  'cyrillic_and_mic': ['cyrillic_and_mic/cyrillic_and_mic.c'],
-  'euc2004_sjis2004': ['euc2004_sjis2004/euc2004_sjis2004.c'],
-  'euc_cn_and_mic': ['euc_cn_and_mic/euc_cn_and_mic.c'],
-  'euc_jp_and_sjis': ['euc_jp_and_sjis/euc_jp_and_sjis.c'],
-  'euc_kr_and_mic': ['euc_kr_and_mic/euc_kr_and_mic.c'],
-  'euc_tw_and_big5': [
-    'euc_tw_and_big5/euc_tw_and_big5.c',
-    'euc_tw_and_big5/big5.c',
-  ],
-  'latin2_and_win1250': ['latin2_and_win1250/latin2_and_win1250.c'],
-  'latin_and_mic': ['latin_and_mic/latin_and_mic.c'],
-  'utf8_and_big5': ['utf8_and_big5/utf8_and_big5.c'],
-  'utf8_and_cyrillic': ['utf8_and_cyrillic/utf8_and_cyrillic.c'],
-  'utf8_and_euc2004': ['utf8_and_euc2004/utf8_and_euc2004.c'],
-  'utf8_and_euc_cn': ['utf8_and_euc_cn/utf8_and_euc_cn.c'],
-  'utf8_and_euc_jp': ['utf8_and_euc_jp/utf8_and_euc_jp.c'],
-  'utf8_and_euc_kr': ['utf8_and_euc_kr/utf8_and_euc_kr.c'],
-  'utf8_and_euc_tw': ['utf8_and_euc_tw/utf8_and_euc_tw.c'],
-  'utf8_and_gb18030': ['utf8_and_gb18030/utf8_and_gb18030.c'],
-  'utf8_and_gbk': ['utf8_and_gbk/utf8_and_gbk.c'],
-  'utf8_and_iso8859': ['utf8_and_iso8859/utf8_and_iso8859.c'],
-  'utf8_and_iso8859_1': ['utf8_and_iso8859_1/utf8_and_iso8859_1.c'],
-  'utf8_and_johab': ['utf8_and_johab/utf8_and_johab.c'],
-  'utf8_and_sjis': ['utf8_and_sjis/utf8_and_sjis.c'],
-  'utf8_and_sjis2004': ['utf8_and_sjis2004/utf8_and_sjis2004.c'],
-  'utf8_and_uhc': ['utf8_and_uhc/utf8_and_uhc.c'],
-  'utf8_and_win': ['utf8_and_win/utf8_and_win.c'],
+  'cyrillic_and_mic': {
+    'sources': ['cyrillic_and_mic/cyrillic_and_mic.c'],
+    'description': 'cyrillic <-> mic text conversions',
+  },
+  'euc2004_sjis2004': {
+    'sources': ['euc2004_sjis2004/euc2004_sjis2004.c'],
+    'description': 'euc2004 <-> sjis2004 text conversions',
+  },
+  'euc_cn_and_mic': {
+    'sources': ['euc_cn_and_mic/euc_cn_and_mic.c'],
+    'description': 'euc_cn <-> mic text conversions',
+  },
+  'euc_jp_and_sjis': {
+    'sources': ['euc_jp_and_sjis/euc_jp_and_sjis.c'],
+    'description': 'euc_jp <-> sjis text conversions',
+  },
+  'euc_kr_and_mic': {
+    'sources': ['euc_kr_and_mic/euc_kr_and_mic.c'],
+    'description': 'euc_kr <-> mic text conversions',
+  },
+  'euc_tw_and_big5': {
+    'sources': [
+      'euc_tw_and_big5/euc_tw_and_big5.c',
+      'euc_tw_and_big5/big5.c',
+    ],
+    'description': 'euc_tw <-> big5 text conversions',
+  },
+  'latin2_and_win1250': {
+    'sources': ['latin2_and_win1250/latin2_and_win1250.c'],
+    'description': 'latin2 <-> win1250 text conversions',
+  },
+  'latin_and_mic': {
+    'sources': ['latin_and_mic/latin_and_mic.c'],
+    'description': 'latin <-> mic text conversions',
+  },
+  'utf8_and_big5': {
+    'sources': ['utf8_and_big5/utf8_and_big5.c'],
+    'description': 'utf8 <-> big5 text conversions',
+  },
+  'utf8_and_cyrillic': {
+    'sources': ['utf8_and_cyrillic/utf8_and_cyrillic.c'],
+    'description': 'utf8 <-> cyrillic text conversions',
+  },
+  'utf8_and_euc2004': {
+    'sources': ['utf8_and_euc2004/utf8_and_euc2004.c'],
+    'description': 'utf8 <-> euc2004 text conversions',
+  },
+  'utf8_and_euc_cn': {
+    'sources': ['utf8_and_euc_cn/utf8_and_euc_cn.c'],
+    'description': 'utf8 <-> euc_cn text conversions',
+  },
+  'utf8_and_euc_jp': {
+    'sources': ['utf8_and_euc_jp/utf8_and_euc_jp.c'],
+    'description': 'utf8 <-> euc_jp text conversions',
+  },
+  'utf8_and_euc_kr': {
+    'sources': ['utf8_and_euc_kr/utf8_and_euc_kr.c'],
+    'description': 'utf8 <-> euc_kr text conversions',
+  },
+  'utf8_and_euc_tw': {
+    'sources': ['utf8_and_euc_tw/utf8_and_euc_tw.c'],
+    'description': 'utf8 <-> euc_tw text conversions',
+  },
+  'utf8_and_gb18030': {
+    'sources': ['utf8_and_gb18030/utf8_and_gb18030.c'],
+    'description': 'utf8 <-> gb18030 text conversions',
+  },
+  'utf8_and_gbk': {
+    'sources': ['utf8_and_gbk/utf8_and_gbk.c'],
+    'description': 'utf8 <-> gbk text conversions',
+  },
+  'utf8_and_iso8859': {
+    'sources': ['utf8_and_iso8859/utf8_and_iso8859.c'],
+    'description': 'utf8 <-> iso8859 text conversions',
+  },
+  'utf8_and_iso8859_1': {
+    'sources': ['utf8_and_iso8859_1/utf8_and_iso8859_1.c'],
+    'description': 'utf8 <-> iso8859_1 text conversions',
+  },
+  'utf8_and_johab': {
+    'sources': ['utf8_and_johab/utf8_and_johab.c'],
+    'description': 'utf8 <-> johab text conversions',
+  },
+  'utf8_and_sjis': {
+    'sources': ['utf8_and_sjis/utf8_and_sjis.c'],
+    'description': 'utf8 <-> sjis text conversions',
+  },
+  'utf8_and_sjis2004': {
+    'sources': ['utf8_and_sjis2004/utf8_and_sjis2004.c'],
+    'description': 'utf8 <-> sjis2004 text conversions',
+  },
+  'utf8_and_uhc': {
+    'sources': ['utf8_and_uhc/utf8_and_uhc.c'],
+    'description': 'utf8 <-> uhc text conversions',
+  },
+  'utf8_and_win': {
+    'sources': ['utf8_and_win/utf8_and_win.c'],
+    'description': 'utf8 <-> win text conversions',
+  },
 }
 
-foreach encoding, sources : encodings
+foreach encoding, values: encodings
+  sources = values['sources']
+  description = values['description']
+
+  source_files = files()
+  foreach source: sources
+    source_files += files(source)
+  endforeach
+
+  if host_system == 'windows'
+    source_files += rc_lib_gen.process(win32ver_rc, extra_args: [
+      '--NAME', encoding,
+      '--FILEDESC', description,])
+  endif
+
   backend_targets += shared_module(encoding,
-    sources,
+    source_files,
     kwargs: pg_mod_args,
   )
 endforeach
diff --git a/src/bin/initdb/meson.build b/src/bin/initdb/meson.build
index 9f213274d2f..6ced9a31b80 100644
--- a/src/bin/initdb/meson.build
+++ b/src/bin/initdb/meson.build
@@ -7,6 +7,12 @@ initdb_sources += timezone_localtime_source
 
 #fixme: reimplement libpq_pgport logic
 
+if host_system == 'windows'
+  initdb_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'initdb',
+    '--FILEDESC', 'initdb - initialize a new database cluster',])
+endif
+
 initdb = executable('initdb',
   initdb_sources,
   include_directories: [timezone_inc],
diff --git a/src/bin/pg_amcheck/meson.build b/src/bin/pg_amcheck/meson.build
index 8e197eba5f3..25f5e7a0948 100644
--- a/src/bin/pg_amcheck/meson.build
+++ b/src/bin/pg_amcheck/meson.build
@@ -1,7 +1,13 @@
 pg_amcheck_sources = files(
-  'pg_amcheck.c'
+  'pg_amcheck.c',
 )
 
+if host_system == 'windows'
+  pg_amcheck_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_amcheck',
+    '--FILEDESC', 'pg_amcheck - detect corruption within database relations',])
+endif
+
 pg_amcheck = executable('pg_amcheck',
   pg_amcheck_sources,
   dependencies: [frontend_code, libpq],
diff --git a/src/bin/pg_archivecleanup/meson.build b/src/bin/pg_archivecleanup/meson.build
index 87a0d980c4f..aaa2e76977f 100644
--- a/src/bin/pg_archivecleanup/meson.build
+++ b/src/bin/pg_archivecleanup/meson.build
@@ -1,5 +1,15 @@
+pg_archivecleanup_sources = files(
+  'pg_archivecleanup.c',
+)
+
+if host_system == 'windows'
+  pg_archivecleanup_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_archivecleanup',
+    '--FILEDESC', 'pg_archivecleanup - cleans archive when used with streaming replication',])
+endif
+
 pg_archivecleanup = executable('pg_archivecleanup',
-  ['pg_archivecleanup.c'],
+  pg_archivecleanup_sources,
   dependencies: [frontend_code],
   kwargs: default_bin_args,
 )
diff --git a/src/bin/pg_basebackup/meson.build b/src/bin/pg_basebackup/meson.build
index d26fed9cd8a..2c934e0c26e 100644
--- a/src/bin/pg_basebackup/meson.build
+++ b/src/bin/pg_basebackup/meson.build
@@ -17,24 +17,56 @@ pg_basebackup_common = static_library('libpg_basebackup_common',
   kwargs: internal_lib_args,
 )
 
-pg_basebackup = executable('pg_basebackup',
+pg_basebackup_sources = files(
   'pg_basebackup.c',
+)
+
+if host_system == 'windows'
+  pg_basebackup_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_basebackup',
+    '--FILEDESC', 'pg_basebackup - streaming WAL and backup receivers',])
+endif
+
+pg_basebackup = executable('pg_basebackup',
+  pg_basebackup_sources,
   link_with: [pg_basebackup_common],
   dependencies: pg_basebackup_deps,
   kwargs: default_bin_args,
 )
 bin_targets += pg_basebackup
 
-pg_receivewal = executable('pg_receivewal',
+
+pg_receivewal_sources = files(
   'pg_receivewal.c',
+)
+
+if host_system == 'windows'
+  pg_receivewal_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_receivewal',
+    '--FILEDESC', 'pg_receivewal - streaming WAL and backup receivers',])
+endif
+
+pg_receivewal = executable('pg_receivewal',
+  pg_receivewal_sources,
   link_with: [pg_basebackup_common],
   dependencies: pg_basebackup_deps,
   kwargs: default_bin_args,
 )
 bin_targets += pg_receivewal
 
-pg_recvlogical = executable('pg_recvlogical',
+
+pg_recvlogical_sources = files(
   'pg_recvlogical.c',
+)
+
+if host_system == 'windows'
+  pg_recvlogical_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_recvlogical',
+    '--FILEDESC', 'pg_recvlogical - streaming WAL and backup receivers',])
+endif
+
+pg_recvlogical = executable('pg_recvlogical',
+  pg_recvlogical_sources,
   link_with: [pg_basebackup_common],
   dependencies: pg_basebackup_deps,
   kwargs: default_bin_args,
diff --git a/src/bin/pg_checksums/meson.build b/src/bin/pg_checksums/meson.build
index ee1f367bac3..d07ebc999b3 100644
--- a/src/bin/pg_checksums/meson.build
+++ b/src/bin/pg_checksums/meson.build
@@ -1,5 +1,15 @@
+pg_checksums_sources = files(
+  'pg_checksums.c',
+)
+
+if host_system == 'windows'
+  pg_checksums_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_checksums',
+    '--FILEDESC', 'pg_checksums - verify data checksums in an offline cluster',])
+endif
+
 pg_checksums = executable('pg_checksums',
-  ['pg_checksums.c'],
+  pg_checksums_sources,
   include_directories: [timezone_inc],
   dependencies: [frontend_code],
   kwargs: default_bin_args,
diff --git a/src/bin/pg_config/meson.build b/src/bin/pg_config/meson.build
index 0ecbf2f9d28..4be2fdc84ae 100644
--- a/src/bin/pg_config/meson.build
+++ b/src/bin/pg_config/meson.build
@@ -1,5 +1,15 @@
+pg_config_sources = files(
+  'pg_config.c',
+)
+
+if host_system == 'windows'
+  pg_config_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_config',
+    '--FILEDESC', 'pg_config - report configuration information',])
+endif
+
 pg_config = executable('pg_config',
-  ['pg_config.c'],
+  pg_config_sources,
   dependencies: [frontend_code],
   kwargs: default_bin_args,
 )
diff --git a/src/bin/pg_controldata/meson.build b/src/bin/pg_controldata/meson.build
index 557e672beb7..7fc239dbe65 100644
--- a/src/bin/pg_controldata/meson.build
+++ b/src/bin/pg_controldata/meson.build
@@ -1,5 +1,15 @@
+pg_controldata_sources = files(
+  'pg_controldata.c',
+)
+
+if host_system == 'windows'
+  pg_controldata_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_controldata',
+    '--FILEDESC', 'pg_controldata - reads the data from pg_control',])
+endif
+
 pg_controldata = executable('pg_controldata',
-  ['pg_controldata.c'],
+  pg_controldata_sources,
   dependencies: [frontend_code],
   kwargs: default_bin_args,
 )
diff --git a/src/bin/pg_ctl/meson.build b/src/bin/pg_ctl/meson.build
index 6812e73e329..96f962fa762 100644
--- a/src/bin/pg_ctl/meson.build
+++ b/src/bin/pg_ctl/meson.build
@@ -1,5 +1,15 @@
+pg_ctl_sources = files(
+  'pg_ctl.c',
+)
+
+if host_system == 'windows'
+  pg_ctl_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_ctl',
+    '--FILEDESC', 'pg_ctl - starts/stops/restarts the PostgreSQL server',])
+endif
+
 pg_ctl = executable('pg_ctl',
-  ['pg_ctl.c'],
+  pg_ctl_sources,
   dependencies: [frontend_code, libpq],
   kwargs: default_bin_args,
 )
diff --git a/src/bin/pg_dump/meson.build b/src/bin/pg_dump/meson.build
index 785ec094dbd..3527a25c288 100644
--- a/src/bin/pg_dump/meson.build
+++ b/src/bin/pg_dump/meson.build
@@ -24,6 +24,12 @@ pg_dump_sources = files(
   'pg_dump_sort.c',
 )
 
+if host_system == 'windows'
+  pg_dump_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_dump',
+    '--FILEDESC', 'pg_dump - backup one PostgreSQL database',])
+endif
+
 pg_dump = executable('pg_dump',
   pg_dump_sources,
   link_with: [pg_dump_common],
@@ -37,6 +43,12 @@ pg_dumpall_sources = files(
   'pg_dumpall.c',
 )
 
+if host_system == 'windows'
+  pg_dumpall_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_dumpall',
+    '--FILEDESC', 'pg_dumpall - backup PostgreSQL databases'])
+endif
+
 pg_dumpall = executable('pg_dumpall',
   pg_dumpall_sources,
   link_with: [pg_dump_common],
@@ -50,6 +62,12 @@ pg_restore_sources = files(
   'pg_restore.c',
 )
 
+if host_system == 'windows'
+  pg_restore_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_restore',
+    '--FILEDESC', 'pg_restore - restore PostgreSQL databases'])
+endif
+
 pg_restore = executable('pg_restore',
   pg_restore_sources,
   link_with: [pg_dump_common],
diff --git a/src/bin/pg_resetwal/meson.build b/src/bin/pg_resetwal/meson.build
index 7c5de134ac0..d503db97b71 100644
--- a/src/bin/pg_resetwal/meson.build
+++ b/src/bin/pg_resetwal/meson.build
@@ -1,5 +1,15 @@
+pg_resetwal_sources = files(
+  'pg_resetwal.c',
+)
+
+if host_system == 'windows'
+  pg_resetwal_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_resetwal',
+    '--FILEDESC', 'pg_resetwal - reset PostgreSQL WAL log'])
+endif
+
 pg_resetwal = executable('pg_resetwal',
-  files('pg_resetwal.c'),
+  pg_resetwal_sources,
   dependencies: [frontend_code],
   kwargs: default_bin_args,
 )
diff --git a/src/bin/pg_rewind/meson.build b/src/bin/pg_rewind/meson.build
index d8ec9e482d5..6cd970909a2 100644
--- a/src/bin/pg_rewind/meson.build
+++ b/src/bin/pg_rewind/meson.build
@@ -11,6 +11,12 @@ pg_rewind_sources = files(
 
 pg_rewind_sources += xlogreader_sources
 
+if host_system == 'windows'
+  pg_rewind_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_rewind',
+    '--FILEDESC', 'pg_rewind - synchronize a data directory with another one forked from'])
+endif
+
 pg_rewind = executable('pg_rewind',
   pg_rewind_sources,
   dependencies: [frontend_code, libpq, lz4, zstd],
diff --git a/src/bin/pg_test_fsync/meson.build b/src/bin/pg_test_fsync/meson.build
index 2c01831e11f..31d288ba6da 100644
--- a/src/bin/pg_test_fsync/meson.build
+++ b/src/bin/pg_test_fsync/meson.build
@@ -1,4 +1,12 @@
-test_fsync_sources = files('pg_test_fsync.c')
+test_fsync_sources = files(
+  'pg_test_fsync.c',
+)
+
+if host_system == 'windows'
+  test_fsync_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_test_fsync',
+    '--FILEDESC', 'pg_test_fsync - test various disk sync methods'])
+endif
 
 pg_test_fsync = executable('pg_test_fsync',
   test_fsync_sources,
diff --git a/src/bin/pg_test_timing/meson.build b/src/bin/pg_test_timing/meson.build
index 0a3068f1657..0aed03ea32f 100644
--- a/src/bin/pg_test_timing/meson.build
+++ b/src/bin/pg_test_timing/meson.build
@@ -1,5 +1,15 @@
+pg_test_timing_sources = files(
+  'pg_test_timing.c'
+)
+
+if host_system == 'windows'
+  pg_test_timing_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_test_timing',
+    '--FILEDESC', 'pg_test_timing - test timing overhead'])
+endif
+
 pg_test_timing = executable('pg_test_timing',
-  ['pg_test_timing.c'],
+  pg_test_timing_sources,
   dependencies: [frontend_code],
   kwargs: default_bin_args,
 )
diff --git a/src/bin/pg_upgrade/meson.build b/src/bin/pg_upgrade/meson.build
index 02f030e0ccf..a7b927a45c7 100644
--- a/src/bin/pg_upgrade/meson.build
+++ b/src/bin/pg_upgrade/meson.build
@@ -16,6 +16,12 @@ pg_upgrade_sources = files(
   'version.c',
 )
 
+if host_system == 'windows'
+  pg_upgrade_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_upgrade',
+    '--FILEDESC', 'pg_upgrade - an in-place binary upgrade utility'])
+endif
+
 pg_upgrade = executable('pg_upgrade',
   pg_upgrade_sources,
   dependencies: [frontend_code, libpq],
diff --git a/src/bin/pg_verifybackup/meson.build b/src/bin/pg_verifybackup/meson.build
index 4c3b2bb5f97..b934a408443 100644
--- a/src/bin/pg_verifybackup/meson.build
+++ b/src/bin/pg_verifybackup/meson.build
@@ -3,6 +3,12 @@ pg_verifybackup_sources = files(
   'pg_verifybackup.c'
 )
 
+if host_system == 'windows'
+  pg_verifybackup_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_verifybackup',
+    '--FILEDESC', 'pg_verifybackup - verify a backup against using a backup manifest'])
+endif
+
 pg_verifybackup = executable('pg_verifybackup',
   pg_verifybackup_sources,
   dependencies: [frontend_code, libpq],
diff --git a/src/bin/pg_waldump/meson.build b/src/bin/pg_waldump/meson.build
index 95872652ffd..9605976870d 100644
--- a/src/bin/pg_waldump/meson.build
+++ b/src/bin/pg_waldump/meson.build
@@ -8,6 +8,12 @@ pg_waldump_sources += rmgr_desc_sources
 pg_waldump_sources += xlogreader_sources
 pg_waldump_sources += files('../../backend/access/transam/xlogstats.c')
 
+if host_system == 'windows'
+  pg_waldump_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_waldump',
+    '--FILEDESC', 'pg_waldump - decode and display WA'])
+endif
+
 pg_waldump = executable('pg_waldump',
   pg_waldump_sources,
   dependencies: [frontend_code, lz4, zstd],
diff --git a/src/bin/pgbench/meson.build b/src/bin/pgbench/meson.build
index 6564e54029c..a32eb51fe07 100644
--- a/src/bin/pgbench/meson.build
+++ b/src/bin/pgbench/meson.build
@@ -17,6 +17,12 @@ exprparse = custom_target('exprparse',
 generated_sources += exprparse.to_list()
 pgbench_sources += exprparse
 
+if host_system == 'windows'
+  pgbench_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pgbench',
+    '--FILEDESC', 'pgbench - a simple program for running benchmark tests'])
+endif
+
 pgbench = executable('pgbench',
   pgbench_sources,
   dependencies: [frontend_code, libpq, thread_dep],
diff --git a/src/bin/pgevent/meson.build b/src/bin/pgevent/meson.build
index 7a468879fd2..2e9aea4b0e1 100644
--- a/src/bin/pgevent/meson.build
+++ b/src/bin/pgevent/meson.build
@@ -6,6 +6,12 @@ pgevent_sources = files(
   'pgevent.c',
 )
 
+pgevent_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+  '--NAME', 'pgevent',
+  '--FILEDESC', 'Eventlog message formatter',])
+
+pgevent_sources += windows.compile_resources('pgmsgevent.rc')
+
 # FIXME: copied from Mkvcbuild.pm, but I don't think that's the right approach
 pgevent_link_args = []
 if cc.get_id() == 'msvc'
diff --git a/src/bin/psql/meson.build b/src/bin/psql/meson.build
index 410788e4767..1264fc19fbd 100644
--- a/src/bin/psql/meson.build
+++ b/src/bin/psql/meson.build
@@ -36,6 +36,12 @@ sql_help = custom_target('psql_help',
 generated_sources += sql_help.to_list()
 psql_sources += sql_help
 
+if host_system == 'windows'
+  psql_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'psql',
+    '--FILEDESC', 'psql - the PostgreSQL interactive terminal',])
+endif
+
 psql = executable('psql',
   psql_sources,
   include_directories: include_directories('.'),
diff --git a/src/bin/scripts/meson.build b/src/bin/scripts/meson.build
index eaf250c7f73..837562c24e5 100644
--- a/src/bin/scripts/meson.build
+++ b/src/bin/scripts/meson.build
@@ -16,8 +16,16 @@ binaries = [
 ]
 
 foreach binary : binaries
+  binary_sources = files('@[email protected]'.format(binary))
+
+  if host_system == 'windows'
+    binary_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+      '--NAME', binary,
+      '--FILEDESC', '@0@ - PostgreSQL utility'.format(binary),])
+  endif
+
   binary = executable(binary,
-    files(binary + '.c'),
+    binary_sources,
     link_with: [scripts_common],
     dependencies: [frontend_code, libpq],
     kwargs: default_bin_args,
diff --git a/src/interfaces/libpq/meson.build b/src/interfaces/libpq/meson.build
index bc047e00d62..2c9edeaa088 100644
--- a/src/interfaces/libpq/meson.build
+++ b/src/interfaces/libpq/meson.build
@@ -16,9 +16,13 @@ libpq_sources = files(
   'libpq-events.c',
   'pqexpbuffer.c',
 )
+libpq_so_sources = [] # only for shared lib, in addition to above
 
 if host_system == 'windows'
   libpq_sources += files('pthread-win32.c', 'win32.c')
+  libpq_so_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'libpq',
+    '--FILEDESC', 'PostgreSQL Access Library',])
 endif
 
 if ssl.found()
@@ -52,6 +56,7 @@ libpq_st = static_library('libpq',
 # not using both_libraries here, causes problems with precompiled headers and
 # resource files with msbuild
 libpq_so = shared_library('libpq',
+  libpq_so_sources,
   dependencies: libpq_deps,
   include_directories: [libpq_inc, postgres_inc],
   c_args: ['-DSO_MAJOR_VERSION=5'],
diff --git a/src/interfaces/libpq/test/meson.build b/src/interfaces/libpq/test/meson.build
index 16f94c1ed8b..017f729d435 100644
--- a/src/interfaces/libpq/test/meson.build
+++ b/src/interfaces/libpq/test/meson.build
@@ -1,13 +1,34 @@
+libpq_uri_regress_sources = files(
+  'libpq_uri_regress.c',
+)
+
+if host_system == 'windows'
+  libpq_uri_regress_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'libpq_uri_regress',
+    '--FILEDESC', 'libpq test program',])
+endif
+
 executable('libpq_uri_regress',
-  files('libpq_uri_regress.c'),
+  libpq_uri_regress_sources,
   dependencies: [frontend_code, libpq],
   kwargs: default_bin_args + {
     'install': false,
   }
 )
 
+
+libpq_testclient_sources = files(
+  'libpq_testclient.c',
+)
+
+if host_system == 'windows'
+  libpq_testclient_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'libpq_testclient',
+    '--FILEDESC', 'libpq test program',])
+endif
+
 executable('libpq_testclient',
-  files('libpq_testclient.c'),
+  libpq_testclient_sources,
   dependencies: [frontend_code, libpq],
   kwargs: default_bin_args + {
     'install': false,
diff --git a/src/pl/plperl/meson.build b/src/pl/plperl/meson.build
index 73b733dd50b..535660085dd 100644
--- a/src/pl/plperl/meson.build
+++ b/src/pl/plperl/meson.build
@@ -36,6 +36,13 @@ foreach n : ['SPI', 'Util']
 endforeach
 
 plperl_inc = include_directories('.')
+
+if host_system == 'windows'
+  plperl_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'plperl',
+    '--FILEDESC', 'PL/Perl - procedural language',])
+endif
+
 plperl = shared_module('plperl',
   plperl_sources,
   include_directories: [plperl_inc, postgres_inc],
diff --git a/src/pl/plpgsql/src/meson.build b/src/pl/plpgsql/src/meson.build
index dd499fdd151..c46c0a1da2a 100644
--- a/src/pl/plpgsql/src/meson.build
+++ b/src/pl/plpgsql/src/meson.build
@@ -40,6 +40,12 @@ pl_unreserved = custom_target('pl_unreserved_kwlist',
 generated_sources += pl_unreserved
 plpgsql_sources += pl_unreserved
 
+if host_system == 'windows'
+  plpgsql_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'plpgsql',
+    '--FILEDESC', 'PL/pgSQL - procedural language',])
+endif
+
 plpgsql = shared_module('plpgsql',
   plpgsql_sources,
   include_directories: include_directories('.'),
diff --git a/src/pl/plpython/meson.build b/src/pl/plpython/meson.build
index 366b3b171ac..40888386b5f 100644
--- a/src/pl/plpython/meson.build
+++ b/src/pl/plpython/meson.build
@@ -28,6 +28,12 @@ plpython_sources += custom_target('spiexceptions.h',
 # FIXME: need to duplicate import library ugliness?
 plpython_inc = include_directories('.')
 
+if host_system == 'windows'
+  plpython_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'plpython3',
+    '--FILEDESC', 'PL/Python - procedural language',])
+endif
+
 plpython = shared_module('plpython3',
   plpython_sources,
   include_directories: [plpython_inc, postgres_inc],
diff --git a/src/pl/tcl/meson.build b/src/pl/tcl/meson.build
index 9b6addd7fd5..f09bb14c950 100644
--- a/src/pl/tcl/meson.build
+++ b/src/pl/tcl/meson.build
@@ -14,6 +14,12 @@ pltcl_sources += custom_target('pltclerrcodes.h',
   command: [perl, gen_pltclerrcodes, '@INPUT@']
 )
 
+if host_system == 'windows'
+  pltcl_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pltcl',
+    '--FILEDESC', 'PL/Tcl - procedural language',])
+endif
+
 pltcl = shared_module('pltcl',
   pltcl_sources,
   include_directories: [include_directories('.'), postgres_inc],
diff --git a/contrib/adminpack/meson.build b/contrib/adminpack/meson.build
index fc2368d02cf..7efec0efbc0 100644
--- a/contrib/adminpack/meson.build
+++ b/contrib/adminpack/meson.build
@@ -1,5 +1,15 @@
+adminpack_sources = files(
+  'adminpack.c',
+)
+
+if host_system == 'windows'
+  adminpack_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'adminpack',
+    '--FILEDESC', 'adminpack - support functions for pgAdmin',])
+endif
+
 adminpack = shared_module('adminpack',
-  ['adminpack.c'],
+  adminpack_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += adminpack
diff --git a/contrib/amcheck/meson.build b/contrib/amcheck/meson.build
index 1db3d20349e..fa807b72d98 100644
--- a/contrib/amcheck/meson.build
+++ b/contrib/amcheck/meson.build
@@ -1,7 +1,16 @@
-amcheck = shared_module('amcheck', [
-    'verify_heapam.c',
-    'verify_nbtree.c',
-  ],
+amcheck_sources = files(
+  'verify_heapam.c',
+  'verify_nbtree.c',
+)
+
+if host_system == 'windows'
+  amcheck_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'amcheck',
+    '--FILEDESC', 'amcheck - function for verifying relation integrity',])
+endif
+
+amcheck = shared_module('amcheck',
+  amcheck_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += amcheck
diff --git a/contrib/auth_delay/meson.build b/contrib/auth_delay/meson.build
index d2e01968f54..c4ffb0663bc 100644
--- a/contrib/auth_delay/meson.build
+++ b/contrib/auth_delay/meson.build
@@ -1,5 +1,15 @@
+auth_delay_sources = files(
+  'auth_delay.c',
+)
+
+if host_system == 'windows'
+  auth_delay_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'auth_delay',
+    '--FILEDESC', 'auth_delay - delay authentication failure reports',])
+endif
+
 autoinc = shared_module('auth_delay',
-  ['auth_delay.c'],
+  auth_delay_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += autoinc
diff --git a/contrib/auto_explain/meson.build b/contrib/auto_explain/meson.build
index 249a8376faa..76f86617850 100644
--- a/contrib/auto_explain/meson.build
+++ b/contrib/auto_explain/meson.build
@@ -1,5 +1,15 @@
+auto_explain_sources = files(
+  'auto_explain.c',
+)
+
+if host_system == 'windows'
+  auto_explain_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'auto_explain',
+    '--FILEDESC', 'auto_explain - logging facility for execution plans',])
+endif
+
 auto_explain = shared_module('auto_explain',
-  files('auto_explain.c'),
+  auto_explain_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += auto_explain
diff --git a/contrib/basebackup_to_shell/meson.build b/contrib/basebackup_to_shell/meson.build
index 9f0517f1701..3a389de9175 100644
--- a/contrib/basebackup_to_shell/meson.build
+++ b/contrib/basebackup_to_shell/meson.build
@@ -2,6 +2,12 @@ basebackup_to_shell_sources = files(
   'basebackup_to_shell.c',
 )
 
+if host_system == 'windows'
+  basebackup_to_shell_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'basebackup_to_shell',
+    '--FILEDESC', 'basebackup_to_shell - target basebackup to shell command',])
+endif
+
 basebackup_to_shell = shared_module('basebackup_to_shell',
   basebackup_to_shell_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/basic_archive/meson.build b/contrib/basic_archive/meson.build
index b67cbef60bd..c30dcfa5d41 100644
--- a/contrib/basic_archive/meson.build
+++ b/contrib/basic_archive/meson.build
@@ -2,6 +2,12 @@ basic_archive_sources = files(
   'basic_archive.c',
 )
 
+if host_system == 'windows'
+  basic_archive_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'basic_archive',
+    '--FILEDESC', 'basic_archive - basic archive module',])
+endif
+
 basic_archive = shared_module('basic_archive',
   basic_archive_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/bloom/meson.build b/contrib/bloom/meson.build
index 1fe7632bdbe..16f3b83e4d2 100644
--- a/contrib/bloom/meson.build
+++ b/contrib/bloom/meson.build
@@ -7,6 +7,12 @@ bloom_sources = files(
   'blvalidate.c',
 )
 
+if host_system == 'windows'
+  bloom_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'bloom',
+    '--FILEDESC', 'bloom access method - signature file based index',])
+endif
+
 bloom = shared_module('bloom',
   bloom_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/bool_plperl/meson.build b/contrib/bool_plperl/meson.build
index c20b667d75f..a68daab0dcd 100644
--- a/contrib/bool_plperl/meson.build
+++ b/contrib/bool_plperl/meson.build
@@ -6,6 +6,12 @@ bool_plperl_sources = files(
   'bool_plperl.c',
 )
 
+if host_system == 'windows'
+  bool_plperl_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'bool_plperl',
+    '--FILEDESC', 'bool_plperl - bool transform for plperl',])
+endif
+
 bool_plperl = shared_module('bool_plperl',
   bool_plperl_sources,
   include_directories: [plperl_inc, include_directories('.')],
diff --git a/contrib/btree_gin/meson.build b/contrib/btree_gin/meson.build
index 15d6d31a6ee..fd4c76767a7 100644
--- a/contrib/btree_gin/meson.build
+++ b/contrib/btree_gin/meson.build
@@ -1,5 +1,15 @@
+btree_gin_sources = files(
+  'btree_gin.c',
+)
+
+if host_system == 'windows'
+  btree_gin_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'btree_gin',
+    '--FILEDESC', 'btree_gin - B-tree equivalent GIN operator classes',])
+endif
+
 btree_gin = shared_module('btree_gin',
-  files('btree_gin.c'),
+  btree_gin_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += btree_gin
diff --git a/contrib/btree_gist/meson.build b/contrib/btree_gist/meson.build
index c0a8d238540..e98c91dacc8 100644
--- a/contrib/btree_gist/meson.build
+++ b/contrib/btree_gist/meson.build
@@ -25,6 +25,12 @@ btree_gist_sources = files(
   'btree_uuid.c',
 )
 
+if host_system == 'windows'
+  btree_gist_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'btree_gist',
+    '--FILEDESC', 'btree_gist - B-tree equivalent GiST operator classes',])
+endif
+
 btree_gist = shared_module('btree_gist',
   btree_gist_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/citext/meson.build b/contrib/citext/meson.build
index ca60eded80b..26a101a19bd 100644
--- a/contrib/citext/meson.build
+++ b/contrib/citext/meson.build
@@ -2,6 +2,12 @@ citext_sources = files(
   'citext.c',
 )
 
+if host_system == 'windows'
+  citext_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'citext',
+    '--FILEDESC', 'citext - case-insensitive character string data type',])
+endif
+
 citext = shared_module('citext',
   citext_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/cube/meson.build b/contrib/cube/meson.build
index 72342b0c82c..041acf95a90 100644
--- a/contrib/cube/meson.build
+++ b/contrib/cube/meson.build
@@ -17,6 +17,12 @@ cube_parse = custom_target('cubeparse',
 generated_sources += cube_parse.to_list()
 cube_sources += cube_parse
 
+if host_system == 'windows'
+  cube_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'cube',
+    '--FILEDESC', 'cube - multidimensional cube data type',])
+endif
+
 cube = shared_module('cube',
   cube_sources,
   include_directories: include_directories('.'),
diff --git a/contrib/dblink/meson.build b/contrib/dblink/meson.build
index d35f7b5d49e..66eeb03b736 100644
--- a/contrib/dblink/meson.build
+++ b/contrib/dblink/meson.build
@@ -2,6 +2,12 @@ dblink_sources = files(
   'dblink.c',
 )
 
+if host_system == 'windows'
+  dblink_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'dblink',
+    '--FILEDESC', 'dblink - connect to other PostgreSQL databases',])
+endif
+
 dblink = shared_module('dblink',
   dblink_sources,
   kwargs: contrib_mod_args + {
diff --git a/contrib/dict_int/meson.build b/contrib/dict_int/meson.build
index f00e8085619..6fff921adda 100644
--- a/contrib/dict_int/meson.build
+++ b/contrib/dict_int/meson.build
@@ -1,5 +1,15 @@
+dict_int_sources = files(
+  'dict_int.c',
+)
+
+if host_system == 'windows'
+  dict_int_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'dict_int',
+    '--FILEDESC', 'dict_int - add-on dictionary template for full-text search',])
+endif
+
 dict_int = shared_module('dict_int',
-  files('dict_int.c'),
+  dict_int_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += dict_int
diff --git a/contrib/dict_xsyn/meson.build b/contrib/dict_xsyn/meson.build
index be53f55bb79..fabd505a7df 100644
--- a/contrib/dict_xsyn/meson.build
+++ b/contrib/dict_xsyn/meson.build
@@ -1,5 +1,15 @@
+dict_xsyn_sources = files(
+  'dict_xsyn.c',
+)
+
+if host_system == 'windows'
+  dict_xsyn_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'dict_xsyn',
+    '--FILEDESC', 'dict_xsyn - add-on dictionary template for full-text search',])
+endif
+
 dict_xsyn = shared_module('dict_xsyn',
-  files('dict_xsyn.c'),
+  dict_xsyn_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += dict_xsyn
diff --git a/contrib/earthdistance/meson.build b/contrib/earthdistance/meson.build
index 807f5cb7de3..78dc29c3da3 100644
--- a/contrib/earthdistance/meson.build
+++ b/contrib/earthdistance/meson.build
@@ -1,5 +1,15 @@
+earthdistance_sources = files(
+  'earthdistance.c',
+)
+
+if host_system == 'windows'
+  earthdistance_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'earthdistance',
+    '--FILEDESC', 'earthdistance - calculate distances on the surface of the Earth',])
+endif
+
 earthdistance = shared_module('earthdistance',
-  files('earthdistance.c'),
+  earthdistance_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += earthdistance
diff --git a/contrib/file_fdw/meson.build b/contrib/file_fdw/meson.build
index f13efb6e38e..c4071faa669 100644
--- a/contrib/file_fdw/meson.build
+++ b/contrib/file_fdw/meson.build
@@ -1,5 +1,15 @@
+file_fdw_sources = files(
+  'file_fdw.c',
+)
+
+if host_system == 'windows'
+  file_fdw_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'file_fdw',
+    '--FILEDESC', 'file_fdw - foreign data wrapper for files',])
+endif
+
 file_fdw = shared_module('file_fdw',
-  files('file_fdw.c'),
+  file_fdw_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += file_fdw
diff --git a/contrib/fuzzystrmatch/meson.build b/contrib/fuzzystrmatch/meson.build
index ec278a6211e..e6d06149cec 100644
--- a/contrib/fuzzystrmatch/meson.build
+++ b/contrib/fuzzystrmatch/meson.build
@@ -1,8 +1,16 @@
+fuzzystrmatch_sources = files(
+  'fuzzystrmatch.c',
+  'dmetaphone.c',
+)
+
+if host_system == 'windows'
+  fuzzystrmatch_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'fuzzystrmatch',
+    '--FILEDESC', 'fuzzystrmatch - similarities and distance between strings',])
+endif
+
 fuzzystrmatch = shared_module('fuzzystrmatch',
-  files(
-    'fuzzystrmatch.c',
-    'dmetaphone.c'
-  ),
+  fuzzystrmatch_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += fuzzystrmatch
diff --git a/contrib/hstore/meson.build b/contrib/hstore/meson.build
index 07c59f40a97..2bb26bb772b 100644
--- a/contrib/hstore/meson.build
+++ b/contrib/hstore/meson.build
@@ -1,15 +1,23 @@
 # .. so that includes of hstore/hstore.h work
 hstore_inc = include_directories('.', '../')
 
+hstore_sources = files(
+  'hstore_compat.c',
+  'hstore_gin.c',
+  'hstore_gist.c',
+  'hstore_io.c',
+  'hstore_op.c',
+  'hstore_subs.c',
+)
+
+if host_system == 'windows'
+  hstore_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'hstore',
+    '--FILEDESC', 'hstore - key/value pair data type',])
+endif
+
 hstore = shared_module('hstore',
-  files(
-    'hstore_compat.c',
-    'hstore_gin.c',
-    'hstore_gist.c',
-    'hstore_io.c',
-    'hstore_op.c',
-    'hstore_subs.c',
-  ),
+  hstore_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += hstore
diff --git a/contrib/hstore_plperl/meson.build b/contrib/hstore_plperl/meson.build
index bbafa0221bd..a238fee6a26 100644
--- a/contrib/hstore_plperl/meson.build
+++ b/contrib/hstore_plperl/meson.build
@@ -6,6 +6,12 @@ hstore_plperl_sources = files(
   'hstore_plperl.c',
 )
 
+if host_system == 'windows'
+  hstore_plperl_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'hstore_plperl',
+    '--FILEDESC', 'hstore_plperl - hstore transform for plperl',])
+endif
+
 hstore_plperl = shared_module('hstore_plperl',
   hstore_plperl_sources,
   include_directories: [plperl_inc, hstore_inc],
diff --git a/contrib/hstore_plpython/meson.build b/contrib/hstore_plpython/meson.build
index 214b48519a9..6071aaeb4b3 100644
--- a/contrib/hstore_plpython/meson.build
+++ b/contrib/hstore_plpython/meson.build
@@ -6,6 +6,12 @@ hstore_plpython_sources = files(
   'hstore_plpython.c',
 )
 
+if host_system == 'windows'
+  hstore_plpython_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'hstore_plpython3',
+    '--FILEDESC', 'hstore_plpython - hstore transform for plpython',])
+endif
+
 hstore_plpython = shared_module('hstore_plpython3',
   hstore_plpython_sources,
   include_directories: [plpython_inc, hstore_inc, ],
diff --git a/contrib/intarray/meson.build b/contrib/intarray/meson.build
index 1655bcbb3fd..b7cf1ce0cad 100644
--- a/contrib/intarray/meson.build
+++ b/contrib/intarray/meson.build
@@ -8,6 +8,12 @@ intarray_sources = files(
   '_intbig_gist.c',
 )
 
+if host_system == 'windows'
+  intarray_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', '_int',
+    '--FILEDESC', 'intarray - functions and operators for arrays of integers',])
+endif
+
 intarray = shared_module('_int',
   intarray_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/isn/meson.build b/contrib/isn/meson.build
index cc30bbeb55c..db68a718313 100644
--- a/contrib/isn/meson.build
+++ b/contrib/isn/meson.build
@@ -2,6 +2,12 @@ isn_sources = files(
   'isn.c',
 )
 
+if host_system == 'windows'
+  isn_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'isn',
+    '--FILEDESC', 'isn - data types for international product numbering standards',])
+endif
+
 isn = shared_module('isn',
   isn_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/jsonb_plperl/meson.build b/contrib/jsonb_plperl/meson.build
index 5c915d8ed94..071a7a98d2c 100644
--- a/contrib/jsonb_plperl/meson.build
+++ b/contrib/jsonb_plperl/meson.build
@@ -6,6 +6,12 @@ jsonb_plperl_sources = files(
   'jsonb_plperl.c',
 )
 
+if host_system == 'windows'
+  jsonb_plperl_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'jsonb_plperl',
+    '--FILEDESC', 'jsonb_plperl - jsonb transform for plperl',])
+endif
+
 jsonb_plperl = shared_module('jsonb_plperl',
   jsonb_plperl_sources,
   include_directories: [plperl_inc],
diff --git a/contrib/jsonb_plpython/meson.build b/contrib/jsonb_plpython/meson.build
index de8e1105c6a..84dc1161e8b 100644
--- a/contrib/jsonb_plpython/meson.build
+++ b/contrib/jsonb_plpython/meson.build
@@ -6,6 +6,12 @@ jsonb_plpython_sources = files(
   'jsonb_plpython.c',
 )
 
+if host_system == 'windows'
+  jsonb_plpython_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'jsonb_plpython3',
+    '--FILEDESC', 'jsonb_plpython - jsonb transform for plpython',])
+endif
+
 jsonb_plpython = shared_module('jsonb_plpython3',
   jsonb_plpython_sources,
   include_directories: [plpython_inc],
diff --git a/contrib/lo/meson.build b/contrib/lo/meson.build
index 9082d5713c7..61ae131f1cc 100644
--- a/contrib/lo/meson.build
+++ b/contrib/lo/meson.build
@@ -2,6 +2,12 @@ lo_sources = files(
   'lo.c',
 )
 
+if host_system == 'windows'
+  lo_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'lo',
+    '--FILEDESC', 'lo - management for large objects',])
+endif
+
 lo = shared_module('lo',
   lo_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/ltree/meson.build b/contrib/ltree/meson.build
index 9463fc2c5e5..421292cea9d 100644
--- a/contrib/ltree/meson.build
+++ b/contrib/ltree/meson.build
@@ -13,6 +13,12 @@ ltree_sources = files(
 # .. so that includes of ltree/ltree.h work
 ltree_inc = include_directories('.', '../')
 
+if host_system == 'windows'
+  ltree_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'ltree',
+    '--FILEDESC', 'ltree - hierarchical label data type',])
+endif
+
 ltree = shared_module('ltree',
   ltree_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/ltree_plpython/meson.build b/contrib/ltree_plpython/meson.build
index 429d75006aa..acf5e4a6fc8 100644
--- a/contrib/ltree_plpython/meson.build
+++ b/contrib/ltree_plpython/meson.build
@@ -6,6 +6,12 @@ ltree_plpython_sources = files(
   'ltree_plpython.c',
 )
 
+if host_system == 'windows'
+  ltree_plpython_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'ltree_plpython3',
+    '--FILEDESC', 'ltree_plpython - ltree transform for plpython',])
+endif
+
 ltree_plpython = shared_module('ltree_plpython3',
   ltree_plpython_sources,
   include_directories: [plpython_inc, ltree_inc],
diff --git a/contrib/oid2name/meson.build b/contrib/oid2name/meson.build
index 1dad5d8f6e7..1a248f19260 100644
--- a/contrib/oid2name/meson.build
+++ b/contrib/oid2name/meson.build
@@ -1,5 +1,15 @@
+oid2name_sources = files(
+  'oid2name.c',
+)
+
+if host_system == 'windows'
+  oid2name_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'oid2name',
+    '--FILEDESC', 'oid2name - examine the file structure',])
+endif
+
 oid2name = executable('oid2name',
-  ['oid2name.c'],
+  oid2name_sources,
   dependencies: [frontend_code, libpq],
   kwargs: default_bin_args,
 )
diff --git a/contrib/old_snapshot/meson.build b/contrib/old_snapshot/meson.build
index 8e7ee09a43a..77276c3715a 100644
--- a/contrib/old_snapshot/meson.build
+++ b/contrib/old_snapshot/meson.build
@@ -2,6 +2,12 @@ old_snapshot_sources = files(
   'time_mapping.c',
 )
 
+if host_system == 'windows'
+  old_snapshot_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'old_snapshot',
+    '--FILEDESC', 'old_snapshot - utilities in support of old_snapshot_threshold',])
+endif
+
 old_snapshot = shared_module('old_snapshot',
   old_snapshot_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/pageinspect/meson.build b/contrib/pageinspect/meson.build
index 4af8153e4fd..3ec50b9445e 100644
--- a/contrib/pageinspect/meson.build
+++ b/contrib/pageinspect/meson.build
@@ -9,6 +9,12 @@ pageinspect_sources = files(
   'rawpage.c',
 )
 
+if host_system == 'windows'
+  pageinspect_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pageinspect',
+    '--FILEDESC', 'pageinspect - functions to inspect contents of database pages',])
+endif
+
 pageinspect = shared_module('pageinspect',
   pageinspect_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/passwordcheck/meson.build b/contrib/passwordcheck/meson.build
index 7da47d02f1d..383d7df372a 100644
--- a/contrib/passwordcheck/meson.build
+++ b/contrib/passwordcheck/meson.build
@@ -9,6 +9,12 @@ passwordcheck_deps = []
 # passwordcheck_c_args += ['-DUSE_CRACKLIB', '-DCRACKLIB_DICTPATH="/usr/lib/cracklib_dict"']
 # passwordcheck_deps += [cc.find_library('crack')]
 
+if host_system == 'windows'
+  passwordcheck_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'passwordcheck',
+    '--FILEDESC', 'passwordcheck - strengthen user password checks',])
+endif
+
 passwordcheck = shared_module('passwordcheck',
   passwordcheck_sources,
   c_args: passwordcheck_c_args,
diff --git a/contrib/pg_buffercache/meson.build b/contrib/pg_buffercache/meson.build
index 2c69eae3ea2..dd9948e5f0b 100644
--- a/contrib/pg_buffercache/meson.build
+++ b/contrib/pg_buffercache/meson.build
@@ -1,7 +1,15 @@
+pg_buffercache_sources = files(
+  'pg_buffercache_pages.c',
+)
+
+if host_system == 'windows'
+  pg_buffercache_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_buffercache',
+    '--FILEDESC', 'pg_buffercache - monitoring of shared buffer cache in real-time',])
+endif
+
 pg_buffercache = shared_module('pg_buffercache',
-  files(
-    'pg_buffercache_pages.c',
-  ),
+  pg_buffercache_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += pg_buffercache
diff --git a/contrib/pg_freespacemap/meson.build b/contrib/pg_freespacemap/meson.build
index f795014d7ca..904b37b6e9b 100644
--- a/contrib/pg_freespacemap/meson.build
+++ b/contrib/pg_freespacemap/meson.build
@@ -1,7 +1,15 @@
+pg_freespacemap_sources = files(
+  'pg_freespacemap.c',
+)
+
+if host_system == 'windows'
+  pg_freespacemap_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_freespacemap',
+    '--FILEDESC', 'pg_freespacemap - monitoring of free space map',])
+endif
+
 pg_freespacemap = shared_module('pg_freespacemap',
-  files(
-    'pg_freespacemap.c',
-  ),
+  pg_freespacemap_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += pg_freespacemap
diff --git a/contrib/pg_prewarm/meson.build b/contrib/pg_prewarm/meson.build
index bdca9af4f27..b7140cee34b 100644
--- a/contrib/pg_prewarm/meson.build
+++ b/contrib/pg_prewarm/meson.build
@@ -1,8 +1,16 @@
+pg_prewarm_sources = files(
+  'autoprewarm.c',
+  'pg_prewarm.c',
+)
+
+if host_system == 'windows'
+  pg_prewarm_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_prewarm',
+    '--FILEDESC', 'pg_prewarm - preload relation data into system buffer cache',])
+endif
+
 pg_prewarm = shared_module('pg_prewarm',
-  files(
-    'autoprewarm.c',
-    'pg_prewarm.c',
-  ),
+  pg_prewarm_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += pg_prewarm
diff --git a/contrib/pg_stat_statements/meson.build b/contrib/pg_stat_statements/meson.build
index ac117d2fc1d..854df138e76 100644
--- a/contrib/pg_stat_statements/meson.build
+++ b/contrib/pg_stat_statements/meson.build
@@ -1,5 +1,15 @@
+pg_stat_statements_sources = files(
+  'pg_stat_statements.c',
+)
+
+if host_system == 'windows'
+  pg_stat_statements_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_stat_statements',
+    '--FILEDESC', 'pg_stat_statements - execution statistics of SQL statements',])
+endif
+
 pg_stat_statements = shared_module('pg_stat_statements',
-  files('pg_stat_statements.c'),
+  pg_stat_statements_sources,
   kwargs: contrib_mod_args + {
     'dependencies': contrib_mod_args['dependencies'],
   },
diff --git a/contrib/pg_surgery/meson.build b/contrib/pg_surgery/meson.build
index ac71caa5276..7b5c5999f4b 100644
--- a/contrib/pg_surgery/meson.build
+++ b/contrib/pg_surgery/meson.build
@@ -1,7 +1,15 @@
+pg_surgery_sources = files(
+  'heap_surgery.c',
+)
+
+if host_system == 'windows'
+  pg_surgery_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_surgery',
+    '--FILEDESC', 'pg_surgery - perform surgery on a damaged relation',])
+endif
+
 pg_surgery = shared_module('pg_surgery',
-  files(
-    'heap_surgery.c',
-  ),
+  pg_surgery_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += pg_surgery
diff --git a/contrib/pg_trgm/meson.build b/contrib/pg_trgm/meson.build
index a90628d23c6..c8c7c07b308 100644
--- a/contrib/pg_trgm/meson.build
+++ b/contrib/pg_trgm/meson.build
@@ -1,10 +1,18 @@
+pg_trgm_sources = files(
+  'trgm_gin.c',
+  'trgm_gist.c',
+  'trgm_op.c',
+  'trgm_regexp.c',
+)
+
+if host_system == 'windows'
+  pg_trgm_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_trgm',
+    '--FILEDESC', 'pg_trgm - trigram matching',])
+endif
+
 pg_trgm = shared_module('pg_trgm',
-  files(
-    'trgm_gin.c',
-    'trgm_gist.c',
-    'trgm_op.c',
-    'trgm_regexp.c',
-  ),
+  pg_trgm_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += pg_trgm
diff --git a/contrib/pg_visibility/meson.build b/contrib/pg_visibility/meson.build
index 933dc99ac4d..263a0d08b82 100644
--- a/contrib/pg_visibility/meson.build
+++ b/contrib/pg_visibility/meson.build
@@ -1,7 +1,15 @@
+pg_visibility_sources = files(
+  'pg_visibility.c',
+)
+
+if host_system == 'windows'
+  pg_visibility_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_visibility',
+    '--FILEDESC', 'pg_visibility - page visibility information',])
+endif
+
 pg_visibility = shared_module('pg_visibility',
-  files(
-    'pg_visibility.c',
-  ),
+  pg_visibility_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += pg_visibility
diff --git a/contrib/pg_walinspect/meson.build b/contrib/pg_walinspect/meson.build
index d6b27877dd0..4314a3182a2 100644
--- a/contrib/pg_walinspect/meson.build
+++ b/contrib/pg_walinspect/meson.build
@@ -1,5 +1,11 @@
 pg_walinspect_sources = files('pg_walinspect.c')
 
+if host_system == 'windows'
+  pg_walinspect_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_walinspect',
+    '--FILEDESC', 'pg_walinspect - functions to inspect contents of PostgreSQL Write-Ahead Log',])
+endif
+
 pg_walinspect = shared_module('pg_walinspect',
   pg_walinspect_sources,
   kwargs: contrib_mod_args + {
diff --git a/contrib/pgcrypto/meson.build b/contrib/pgcrypto/meson.build
index fe0851bf8e8..7fc7bbc7ca1 100644
--- a/contrib/pgcrypto/meson.build
+++ b/contrib/pgcrypto/meson.build
@@ -69,6 +69,12 @@ else
   pgcrypto_regress += 'pgp-zlib-DISABLED'
 endif
 
+if host_system == 'windows'
+  pgcrypto_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pgcrypto',
+    '--FILEDESC', 'pgcrypto - cryptographic functions',])
+endif
+
 pgcrypto = shared_module('pgcrypto',
   pgcrypto_sources,
   link_with: pgcrypto_link_with,
diff --git a/contrib/pgrowlocks/meson.build b/contrib/pgrowlocks/meson.build
index 1b41691a2a3..8092f0d4a64 100644
--- a/contrib/pgrowlocks/meson.build
+++ b/contrib/pgrowlocks/meson.build
@@ -1,7 +1,15 @@
+pgrowlocks_sources = files(
+  'pgrowlocks.c',
+)
+
+if host_system == 'windows'
+  pgrowlocks_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pgrowlocks',
+    '--FILEDESC', 'pgrowlocks - display row locking information',])
+endif
+
 pgrowlocks = shared_module('pgrowlocks',
-  files(
-    'pgrowlocks.c',
-  ),
+  pgrowlocks_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += pgrowlocks
diff --git a/contrib/pgstattuple/meson.build b/contrib/pgstattuple/meson.build
index 8e828692d5c..05e4cd46a5c 100644
--- a/contrib/pgstattuple/meson.build
+++ b/contrib/pgstattuple/meson.build
@@ -1,9 +1,17 @@
+pgstattuple_sources = files(
+  'pgstatapprox.c',
+  'pgstatindex.c',
+  'pgstattuple.c',
+)
+
+if host_system == 'windows'
+  pgstattuple_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pgstattuple',
+    '--FILEDESC', 'pgstattuple - tuple-level statistics',])
+endif
+
 pgstattuple = shared_module('pgstattuple',
-  files(
-    'pgstatapprox.c',
-    'pgstatindex.c',
-    'pgstattuple.c',
-  ),
+  pgstattuple_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += pgstattuple
diff --git a/contrib/postgres_fdw/meson.build b/contrib/postgres_fdw/meson.build
index 378885ec93b..d3746ff135c 100644
--- a/contrib/postgres_fdw/meson.build
+++ b/contrib/postgres_fdw/meson.build
@@ -6,6 +6,12 @@ postgres_fdw_sources = files(
   'shippable.c',
 )
 
+if host_system == 'windows'
+  postgres_fdw_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'postgres_fdw',
+    '--FILEDESC', 'postgres_fdw - foreign data wrapper for PostgreSQL',])
+endif
+
 postgres_fdw = shared_module('postgres_fdw',
   postgres_fdw_sources,
   kwargs: contrib_mod_args + {
diff --git a/contrib/seg/meson.build b/contrib/seg/meson.build
index e476eab2a77..c6fbb22999b 100644
--- a/contrib/seg/meson.build
+++ b/contrib/seg/meson.build
@@ -17,6 +17,12 @@ seg_parse = custom_target('segparse',
 generated_sources += seg_parse.to_list()
 seg_sources += seg_parse
 
+if host_system == 'windows'
+  seg_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'seg',
+    '--FILEDESC', 'seg - line segment data type',])
+endif
+
 seg = shared_module('seg',
   seg_sources,
   include_directories: include_directories('.'),
diff --git a/contrib/sepgsql/meson.build b/contrib/sepgsql/meson.build
index 60a95e17c2f..8bef239e3c2 100644
--- a/contrib/sepgsql/meson.build
+++ b/contrib/sepgsql/meson.build
@@ -14,6 +14,12 @@ sepgsql_sources = files(
   'uavc.c',
 )
 
+if host_system == 'windows'
+  sepgsql_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'sepgsql',
+    '--FILEDESC', 'sepgsql - SELinux integration',])
+endif
+
 sepgsql = shared_module('sepgsql',
   sepgsql_sources,
   kwargs: contrib_mod_args + {
diff --git a/contrib/spi/meson.build b/contrib/spi/meson.build
index 98008980ec2..e7d78189ef5 100644
--- a/contrib/spi/meson.build
+++ b/contrib/spi/meson.build
@@ -1,5 +1,15 @@
+autoinc_sources = files(
+  'autoinc.c',
+)
+
+if host_system == 'windows'
+  autoinc_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'autoinc',
+    '--FILEDESC', 'spi - examples of using SPI and triggers',])
+endif
+
 autoinc = shared_module('autoinc',
-  ['autoinc.c'],
+  autoinc_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += autoinc
@@ -9,8 +19,18 @@ install_data('autoinc.control', 'autoinc--1.0.sql',
 )
 
 
+insert_username_sources = files(
+  'insert_username.c',
+)
+
+if host_system == 'windows'
+  insert_username_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'insert_username',
+    '--FILEDESC', 'spi - examples of using SPI and triggers',])
+endif
+
 insert_username = shared_module('insert_username',
-  ['insert_username.c'],
+  insert_username_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += insert_username
@@ -22,8 +42,18 @@ install_data(
 )
 
 
+moddatetime_sources = files(
+  'moddatetime.c',
+)
+
+if host_system == 'windows'
+  moddatetime_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'moddatetime',
+    '--FILEDESC', 'spi - examples of using SPI and triggers',])
+endif
+
 moddatetime = shared_module('moddatetime',
-  ['moddatetime.c'],
+  moddatetime_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += moddatetime
@@ -38,8 +68,18 @@ install_data(
 # comment out if you want a quieter refint package for other uses
 refint_cflags = ['-DREFINT_VERBOSE']
 
+refint_sources = files(
+  'refint.c',
+)
+
+if host_system == 'windows'
+  refint_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'refint',
+    '--FILEDESC', 'spi - examples of using SPI and triggers',])
+endif
+
 refint = shared_module('refint',
-  ['refint.c'],
+  refint_sources,
   c_args: refint_cflags,
   kwargs: contrib_mod_args,
 )
diff --git a/contrib/sslinfo/meson.build b/contrib/sslinfo/meson.build
index 53f752a08ac..136983e783d 100644
--- a/contrib/sslinfo/meson.build
+++ b/contrib/sslinfo/meson.build
@@ -2,10 +2,18 @@ if not ssl.found()
   subdir_done()
 endif
 
+sslinfo_sources = files(
+  'sslinfo.c',
+)
+
+if host_system == 'windows'
+  sslinfo_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'sslinfo',
+    '--FILEDESC', 'sslinfo - information about client SSL certificate',])
+endif
+
 sslinfo = shared_module('sslinfo',
-  files(
-    'sslinfo.c',
-  ),
+  sslinfo_sources,
   kwargs: contrib_mod_args + {
     'dependencies': [ssl, contrib_mod_args['dependencies']],
   }
diff --git a/contrib/tablefunc/meson.build b/contrib/tablefunc/meson.build
index f4230096c0c..d2ddc8d3b39 100644
--- a/contrib/tablefunc/meson.build
+++ b/contrib/tablefunc/meson.build
@@ -1,7 +1,15 @@
+tablefunc_sources = files(
+  'tablefunc.c',
+)
+
+if host_system == 'windows'
+  tablefunc_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'tablefunc',
+    '--FILEDESC', 'tablefunc - various functions that return tables',])
+endif
+
 tablefunc = shared_module('tablefunc',
-  files(
-    'tablefunc.c',
-  ),
+  tablefunc_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += tablefunc
diff --git a/contrib/tcn/meson.build b/contrib/tcn/meson.build
index c3a025247d4..71261c3b0a2 100644
--- a/contrib/tcn/meson.build
+++ b/contrib/tcn/meson.build
@@ -1,7 +1,15 @@
+tcn_sources = files(
+  'tcn.c',
+)
+
+if host_system == 'windows'
+  tcn_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'tcn',
+    '--FILEDESC', 'tcn - trigger function notifying listeners',])
+endif
+
 tcn = shared_module('tcn',
-  files(
-    'tcn.c',
-  ),
+  tcn_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += tcn
diff --git a/contrib/test_decoding/meson.build b/contrib/test_decoding/meson.build
index dd7cb0101ad..6376103c689 100644
--- a/contrib/test_decoding/meson.build
+++ b/contrib/test_decoding/meson.build
@@ -2,6 +2,12 @@ test_decoding_sources = files(
   'test_decoding.c',
 )
 
+if host_system == 'windows'
+  test_decoding_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_decoding',
+    '--FILEDESC', 'test_decoding - example of a logical decoding output plugin',])
+endif
+
 test_decoding = shared_module('test_decoding',
   test_decoding_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/tsm_system_rows/meson.build b/contrib/tsm_system_rows/meson.build
index b9cd42115a8..380abb49883 100644
--- a/contrib/tsm_system_rows/meson.build
+++ b/contrib/tsm_system_rows/meson.build
@@ -1,7 +1,15 @@
+tsm_system_rows_sources = files(
+  'tsm_system_rows.c',
+)
+
+if host_system == 'windows'
+  tsm_system_rows_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'tsm_system_rows',
+    '--FILEDESC', 'tsm_system_rows - TABLESAMPLE method which accepts number of rows as a limit',])
+endif
+
 tsm_system_rows = shared_module('tsm_system_rows',
-  files(
-    'tsm_system_rows.c',
-  ),
+  tsm_system_rows_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += tsm_system_rows
diff --git a/contrib/tsm_system_time/meson.build b/contrib/tsm_system_time/meson.build
index 18015912ffb..e57a2702c60 100644
--- a/contrib/tsm_system_time/meson.build
+++ b/contrib/tsm_system_time/meson.build
@@ -1,7 +1,15 @@
+tsm_system_time_sources = files(
+  'tsm_system_time.c',
+)
+
+if host_system == 'windows'
+  tsm_system_time_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'tsm_system_time',
+    '--FILEDESC', 'tsm_system_time - TABLESAMPLE method which accepts time in milliseconds as a limit',])
+endif
+
 tsm_system_time = shared_module('tsm_system_time',
-  files(
-    'tsm_system_time.c',
-  ),
+  tsm_system_time_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += tsm_system_time
diff --git a/contrib/unaccent/meson.build b/contrib/unaccent/meson.build
index 872b76e3223..438035132f8 100644
--- a/contrib/unaccent/meson.build
+++ b/contrib/unaccent/meson.build
@@ -1,7 +1,15 @@
+unaccent_sources = files(
+  'unaccent.c',
+)
+
+if host_system == 'windows'
+  unaccent_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'unaccent',
+    '--FILEDESC', 'unaccent - text search dictionary that removes accents',])
+endif
+
 unaccent = shared_module('unaccent',
-  files(
-    'unaccent.c',
-  ),
+  unaccent_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += unaccent
diff --git a/contrib/uuid-ossp/meson.build b/contrib/uuid-ossp/meson.build
index da6d1d75c12..28730f398f0 100644
--- a/contrib/uuid-ossp/meson.build
+++ b/contrib/uuid-ossp/meson.build
@@ -2,10 +2,18 @@ if not uuid.found()
   subdir_done()
 endif
 
+uuid_ossp_sources = files(
+  'uuid-ossp.c',
+)
+
+if host_system == 'windows'
+  uuid_ossp_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'uuid-ossp',
+    '--FILEDESC', 'uuid-ossp - UUID generation',])
+endif
+
 uuid_ossp = shared_module('uuid-ossp',
-  files(
-    'uuid-ossp.c',
-  ),
+  uuid_ossp_sources,
   kwargs: contrib_mod_args + {
     'dependencies': [uuid, contrib_mod_args['dependencies']],
   },
diff --git a/contrib/vacuumlo/meson.build b/contrib/vacuumlo/meson.build
index 7a632b87d1b..846de47dbd1 100644
--- a/contrib/vacuumlo/meson.build
+++ b/contrib/vacuumlo/meson.build
@@ -1,5 +1,15 @@
+vacuumlo_sources = files(
+  'vacuumlo.c',
+)
+
+if host_system == 'windows'
+  vacuumlo_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'vacuumlo',
+    '--FILEDESC', 'vacuumlo - removes orphaned large objects',])
+endif
+
 vacuumlo = executable('vacuumlo',
-  ['vacuumlo.c'],
+  vacuumlo_sources,
   dependencies: [frontend_code, libpq],
   kwargs: default_bin_args,
 )
diff --git a/contrib/xml2/meson.build b/contrib/xml2/meson.build
index 9c0b56f01f6..89b0d677516 100644
--- a/contrib/xml2/meson.build
+++ b/contrib/xml2/meson.build
@@ -2,11 +2,19 @@ if not libxml.found()
   subdir_done()
 endif
 
+xml2_sources = files(
+  'xpath.c',
+  'xslt_proc.c',
+)
+
+if host_system == 'windows'
+  xml2_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pgxml',
+    '--FILEDESC', 'xml2 - XPath querying and XSLT',])
+endif
+
 xml2 = shared_module('pgxml',
-  files(
-    'xpath.c',
-    'xslt_proc.c',
-  ),
+  xml2_sources,
   kwargs: contrib_mod_args + {
     'dependencies': [libxml, libxslt, contrib_mod_args['dependencies']],
   },
diff --git a/src/interfaces/ecpg/compatlib/meson.build b/src/interfaces/ecpg/compatlib/meson.build
index 5887cb92b52..e5dc0edc04b 100644
--- a/src/interfaces/ecpg/compatlib/meson.build
+++ b/src/interfaces/ecpg/compatlib/meson.build
@@ -1,7 +1,11 @@
 export_file = custom_target('libpq.exports', kwargs: gen_export_kwargs)
 
-ecpg_compat = both_libraries('libecpg_compat',
+ecpg_compat_sources = files(
   'informix.c',
+)
+
+ecpg_compat = both_libraries('libecpg_compat',
+  ecpg_compat_sources,
   include_directories: ['.', ecpg_inc, postgres_inc, libpq_inc],
   c_args: ['-DSO_MAJOR_VERSION=3'],
   dependencies: [frontend_code, thread_dep],
diff --git a/src/interfaces/ecpg/preproc/meson.build b/src/interfaces/ecpg/preproc/meson.build
index 1be49c8c27f..74876f039c9 100644
--- a/src/interfaces/ecpg/preproc/meson.build
+++ b/src/interfaces/ecpg/preproc/meson.build
@@ -93,6 +93,12 @@ ecpg_kwlist = custom_target('ecpg_kwlist_d.h',
 generated_sources += ecpg_kwlist
 ecpg_sources += ecpg_kwlist
 
+if host_system == 'windows'
+  ecpg_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'ecpg',
+    '--FILEDESC', 'ecpg - embedded SQL precompiler for C',])
+endif
+
 ecpg_exe = executable('ecpg',
   ecpg_sources,
   include_directories: ['.', ecpg_inc, postgres_inc, libpq_inc],
diff --git a/src/interfaces/ecpg/test/meson.build b/src/interfaces/ecpg/test/meson.build
index f0ace641f0c..f67f2dffb71 100644
--- a/src/interfaces/ecpg/test/meson.build
+++ b/src/interfaces/ecpg/test/meson.build
@@ -7,6 +7,12 @@ pg_regress_ecpg_sources = pg_regress_c + files(
   'pg_regress_ecpg.c',
 )
 
+if host_system == 'windows'
+  pg_regress_ecpg_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_regress_ecpg',
+    '--FILEDESC', 'ECPG Test - regression tests for ECPG',])
+endif
+
 pg_regress_ecpg = executable('pg_regress_ecpg',
   pg_regress_ecpg_sources,
   c_args: pg_regress_cflags,
diff --git a/src/test/isolation/meson.build b/src/test/isolation/meson.build
index c7656fd4609..ba27b8c1d44 100644
--- a/src/test/isolation/meson.build
+++ b/src/test/isolation/meson.build
@@ -23,6 +23,12 @@ spec_parser = custom_target('specparse',
 isolationtester_sources += spec_parser
 generated_sources += spec_parser.to_list()
 
+if host_system == 'windows'
+  isolation_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_isolation_regress',
+    '--FILEDESC', 'pg_isolation_regress - multi-client test driver',])
+endif
+
 pg_isolation_regress = executable('pg_isolation_regress',
   isolation_sources,
   c_args: pg_regress_cflags,
@@ -34,6 +40,13 @@ pg_isolation_regress = executable('pg_isolation_regress',
 )
 bin_targets += pg_isolation_regress
 
+
+if host_system == 'windows'
+  isolationtester_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'isolationtester',
+    '--FILEDESC', 'isolationtester - multi-client test driver',])
+endif
+
 isolationtester = executable('isolationtester',
   isolationtester_sources,
   include_directories: include_directories('.'),
diff --git a/src/test/modules/delay_execution/meson.build b/src/test/modules/delay_execution/meson.build
index cf4bdaba637..a0c3ab6afe7 100644
--- a/src/test/modules/delay_execution/meson.build
+++ b/src/test/modules/delay_execution/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+delay_execution_sources = files(
+  'delay_execution.c',
+)
+
+if host_system == 'windows'
+  delay_execution_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'delay_execution',
+    '--FILEDESC', 'delay_execution - allow delay between parsing and execution',])
+endif
+
 delay_execution = shared_module('delay_execution',
-  ['delay_execution.c'],
+  delay_execution_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += delay_execution
diff --git a/src/test/modules/dummy_index_am/meson.build b/src/test/modules/dummy_index_am/meson.build
index 56ff5f48001..4ce82491135 100644
--- a/src/test/modules/dummy_index_am/meson.build
+++ b/src/test/modules/dummy_index_am/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+dummy_index_am_sources = files(
+  'dummy_index_am.c',
+)
+
+if host_system == 'windows'
+  dummy_index_am_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'dummy_index_am',
+    '--FILEDESC', 'dummy_index_am - index access method template',])
+endif
+
 dummy_index_am = shared_module('dummy_index_am',
-  ['dummy_index_am.c'],
+  dummy_index_am_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += dummy_index_am
diff --git a/src/test/modules/dummy_seclabel/meson.build b/src/test/modules/dummy_seclabel/meson.build
index 21b7cf8f353..81b626e496c 100644
--- a/src/test/modules/dummy_seclabel/meson.build
+++ b/src/test/modules/dummy_seclabel/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+dummy_seclabel_sources = files(
+  'dummy_seclabel.c',
+)
+
+if host_system == 'windows'
+  dummy_seclabel_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'dummy_seclabel',
+    '--FILEDESC', 'dummy_seclabel - regression testing of the SECURITY LABEL statement',])
+endif
+
 dummy_seclabel = shared_module('dummy_seclabel',
-  ['dummy_seclabel.c'],
+  dummy_seclabel_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += dummy_seclabel
diff --git a/src/test/modules/libpq_pipeline/meson.build b/src/test/modules/libpq_pipeline/meson.build
index 8384b6e3b2a..de0e2d15626 100644
--- a/src/test/modules/libpq_pipeline/meson.build
+++ b/src/test/modules/libpq_pipeline/meson.build
@@ -1,7 +1,15 @@
+libpq_pipeline_sources = files(
+  'libpq_pipeline.c',
+)
+
+if host_system == 'windows'
+  libpq_pipeline_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'libpq_pipeline',
+    '--FILEDESC', 'libpq_pipeline - test program for pipeline execution',])
+endif
+
 libpq_pipeline = executable('libpq_pipeline',
-  files(
-    'libpq_pipeline.c',
-  ),
+  libpq_pipeline_sources,
   dependencies: [frontend_code, libpq],
   kwargs: default_bin_args + {
     'install': false,
diff --git a/src/test/modules/plsample/meson.build b/src/test/modules/plsample/meson.build
index 45de3f1990d..e1ea2c7a16f 100644
--- a/src/test/modules/plsample/meson.build
+++ b/src/test/modules/plsample/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+plsample_sources = files(
+  'plsample.c',
+)
+
+if host_system == 'windows'
+  plsample_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'plsample',
+    '--FILEDESC', 'PL/Sample - template for procedural language',])
+endif
+
 plsample = shared_module('plsample',
-  ['plsample.c'],
+  plsample_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += plsample
diff --git a/src/test/modules/spgist_name_ops/meson.build b/src/test/modules/spgist_name_ops/meson.build
index 857fc7e140e..445296fee0b 100644
--- a/src/test/modules/spgist_name_ops/meson.build
+++ b/src/test/modules/spgist_name_ops/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+spgist_name_ops_sources = files(
+  'spgist_name_ops.c',
+)
+
+if host_system == 'windows'
+  spgist_name_ops_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'spgist_name_ops',
+    '--FILEDESC', 'spgist_name_ops - test opclass for SP-GiST',])
+endif
+
 spgist_name_ops = shared_module('spgist_name_ops',
-  ['spgist_name_ops.c'],
+  spgist_name_ops_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += spgist_name_ops
diff --git a/src/test/modules/ssl_passphrase_callback/meson.build b/src/test/modules/ssl_passphrase_callback/meson.build
index a57bd0693a3..a9eb4c564da 100644
--- a/src/test/modules/ssl_passphrase_callback/meson.build
+++ b/src/test/modules/ssl_passphrase_callback/meson.build
@@ -3,8 +3,19 @@ if not ssl.found()
 endif
 
 # FIXME: prevent install during main install, but not during test :/
+
+ssl_passphrase_callback_sources = files(
+  'ssl_passphrase_func.c',
+)
+
+if host_system == 'windows'
+  ssl_passphrase_callback_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'ssl_passphrase_func',
+    '--FILEDESC', 'callback function to provide a passphrase',])
+endif
+
 ssl_passphrase_callback = shared_module('ssl_passphrase_func',
-  ['ssl_passphrase_func.c'],
+  ssl_passphrase_callback_sources,
   kwargs: pg_mod_args + {
     'dependencies': [ssl, pg_mod_args['dependencies']],
   },
diff --git a/src/test/modules/test_bloomfilter/meson.build b/src/test/modules/test_bloomfilter/meson.build
index 945eb5a70c4..3cf6b05754f 100644
--- a/src/test/modules/test_bloomfilter/meson.build
+++ b/src/test/modules/test_bloomfilter/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_bloomfilter_sources = files(
+  'test_bloomfilter.c',
+)
+
+if host_system == 'windows'
+  test_bloomfilter_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_bloomfilter',
+    '--FILEDESC', 'test_bloomfilter - test code for Bloom filter library',])
+endif
+
 test_bloomfilter = shared_module('test_bloomfilter',
-  ['test_bloomfilter.c'],
+  test_bloomfilter_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_bloomfilter
diff --git a/src/test/modules/test_ddl_deparse/meson.build b/src/test/modules/test_ddl_deparse/meson.build
index 81ad5adc526..54d44f9b2b4 100644
--- a/src/test/modules/test_ddl_deparse/meson.build
+++ b/src/test/modules/test_ddl_deparse/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_ddl_deparse_sources = files(
+  'test_ddl_deparse.c',
+)
+
+if host_system == 'windows'
+  test_ddl_deparse_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_ddl_deparse',
+    '--FILEDESC', 'test_ddl_deparse - regression testing for DDL deparsing',])
+endif
+
 test_ddl_deparse = shared_module('test_ddl_deparse',
-  ['test_ddl_deparse.c'],
+  test_ddl_deparse_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_ddl_deparse
diff --git a/src/test/modules/test_ginpostinglist/meson.build b/src/test/modules/test_ginpostinglist/meson.build
index abf0a3b0430..b3b49c56122 100644
--- a/src/test/modules/test_ginpostinglist/meson.build
+++ b/src/test/modules/test_ginpostinglist/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_ginpostinglist_sources = files(
+  'test_ginpostinglist.c',
+)
+
+if host_system == 'windows'
+  test_ginpostinglist_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_ginpostinglist',
+    '--FILEDESC', 'test_ginpostinglist - test code for src/backend/access/gin//ginpostinglist.c',])
+endif
+
 test_ginpostinglist = shared_module('test_ginpostinglist',
-  ['test_ginpostinglist.c'],
+  test_ginpostinglist_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_ginpostinglist
diff --git a/src/test/modules/test_integerset/meson.build b/src/test/modules/test_integerset/meson.build
index c32c469c69a..4bd75af4b5e 100644
--- a/src/test/modules/test_integerset/meson.build
+++ b/src/test/modules/test_integerset/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_integerset_sources = files(
+  'test_integerset.c',
+)
+
+if host_system == 'windows'
+  test_integerset_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_integerset',
+    '--FILEDESC', 'test_integerset - test code for src/backend/lib/integerset.c',])
+endif
+
 test_integerset = shared_module('test_integerset',
-  ['test_integerset.c'],
+  test_integerset_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_integerset
diff --git a/src/test/modules/test_lfind/meson.build b/src/test/modules/test_lfind/meson.build
index a388de1156a..c5405b8f878 100644
--- a/src/test/modules/test_lfind/meson.build
+++ b/src/test/modules/test_lfind/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_lfind_sources = files(
+  'test_lfind.c',
+)
+
+if host_system == 'windows'
+  test_lfind_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_lfind',
+    '--FILEDESC', 'test_lfind - test code for optimized linear search functions',])
+endif
+
 test_lfind = shared_module('test_lfind',
-  ['test_lfind.c'],
+  test_lfind_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_lfind
diff --git a/src/test/modules/test_oat_hooks/meson.build b/src/test/modules/test_oat_hooks/meson.build
index 5faf0459777..8802bbbac55 100644
--- a/src/test/modules/test_oat_hooks/meson.build
+++ b/src/test/modules/test_oat_hooks/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_oat_hooks_sources = files(
+  'test_oat_hooks.c',
+)
+
+if host_system == 'windows'
+  test_oat_hooks_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_oat_hooks',
+    '--FILEDESC', 'test_oat_hooks - example use of object access hooks',])
+endif
+
 test_oat_hooks = shared_module('test_oat_hooks',
-  ['test_oat_hooks.c'],
+  test_oat_hooks_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_oat_hooks
diff --git a/src/test/modules/test_parser/meson.build b/src/test/modules/test_parser/meson.build
index b59960f615e..1c17113347f 100644
--- a/src/test/modules/test_parser/meson.build
+++ b/src/test/modules/test_parser/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_parser_sources = files(
+  'test_parser.c',
+)
+
+if host_system == 'windows'
+  test_parser_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_parser',
+    '--FILEDESC', 'test_parser - example of a custom parser for full-text search',])
+endif
+
 test_parser = shared_module('test_parser',
-  ['test_parser.c'],
+  test_parser_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_parser
diff --git a/src/test/modules/test_predtest/meson.build b/src/test/modules/test_predtest/meson.build
index 1cfa84b3609..9a5be43c9c0 100644
--- a/src/test/modules/test_predtest/meson.build
+++ b/src/test/modules/test_predtest/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_predtest_sources = files(
+  'test_predtest.c',
+)
+
+if host_system == 'windows'
+  test_predtest_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_predtest',
+    '--FILEDESC', 'test_predtest - test code for optimizer/util/predtest.c',])
+endif
+
 test_predtest = shared_module('test_predtest',
-  ['test_predtest.c'],
+  test_predtest_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_predtest
diff --git a/src/test/modules/test_rbtree/meson.build b/src/test/modules/test_rbtree/meson.build
index 34cbc3e1624..f067e08d321 100644
--- a/src/test/modules/test_rbtree/meson.build
+++ b/src/test/modules/test_rbtree/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_rbtree_sources = files(
+  'test_rbtree.c',
+)
+
+if host_system == 'windows'
+  test_rbtree_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_rbtree',
+    '--FILEDESC', 'test_rbtree - test code for red-black tree library',])
+endif
+
 test_rbtree = shared_module('test_rbtree',
-  ['test_rbtree.c'],
+  test_rbtree_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_rbtree
diff --git a/src/test/modules/test_regex/meson.build b/src/test/modules/test_regex/meson.build
index 867a64e57c3..cfb938d9f1e 100644
--- a/src/test/modules/test_regex/meson.build
+++ b/src/test/modules/test_regex/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_regex_sources = files(
+  'test_regex.c',
+)
+
+if host_system == 'windows'
+  test_regex_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_regex',
+    '--FILEDESC', 'test_regex - test code for backend/regex/',])
+endif
+
 test_regex = shared_module('test_regex',
-  ['test_regex.c'],
+  test_regex_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_regex
diff --git a/src/test/modules/test_rls_hooks/meson.build b/src/test/modules/test_rls_hooks/meson.build
index 80d8adda332..3fb273b2934 100644
--- a/src/test/modules/test_rls_hooks/meson.build
+++ b/src/test/modules/test_rls_hooks/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_rls_hooks_sources = files(
+  'test_rls_hooks.c',
+)
+
+if host_system == 'windows'
+  test_rls_hooks_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_rls_hooks',
+    '--FILEDESC', 'test_rls_hooks - example use of RLS hooks',])
+endif
+
 test_rls_hooks = shared_module('test_rls_hooks',
-  ['test_rls_hooks.c'],
+  test_rls_hooks_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_rls_hooks
diff --git a/src/test/modules/test_shm_mq/meson.build b/src/test/modules/test_shm_mq/meson.build
index b663543d616..16c8fdb57f4 100644
--- a/src/test/modules/test_shm_mq/meson.build
+++ b/src/test/modules/test_shm_mq/meson.build
@@ -1,10 +1,19 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_shm_mq_sources = files(
+  'setup.c',
+  'test.c',
+  'worker.c',
+)
+
+if host_system == 'windows'
+  test_shm_mq_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_shm_mq',
+    '--FILEDESC', 'test_shm_mq - example use of shared memory message queue',])
+endif
+
 test_shm_mq = shared_module('test_shm_mq',
-  files(
-    'setup.c',
-    'test.c',
-    'worker.c',
-  ),
+  test_shm_mq_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_shm_mq
diff --git a/src/test/modules/worker_spi/meson.build b/src/test/modules/worker_spi/meson.build
index 32acad883b2..a4a158c75b9 100644
--- a/src/test/modules/worker_spi/meson.build
+++ b/src/test/modules/worker_spi/meson.build
@@ -1,8 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_worker_spi_sources = files(
+  'worker_spi.c',
+)
+
+if host_system == 'windows'
+  test_worker_spi_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'worker_spi',
+    '--FILEDESC', 'worker_spi - background worker example',])
+endif
+
 test_worker_spi = shared_module('worker_spi',
-  files(
-    'worker_spi.c',
-  ),
+  test_worker_spi_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_worker_spi
diff --git a/src/test/regress/meson.build b/src/test/regress/meson.build
index fd8ee995b79..963c0f64ed5 100644
--- a/src/test/regress/meson.build
+++ b/src/test/regress/meson.build
@@ -8,6 +8,12 @@ regress_sources = pg_regress_c + files(
 
 pg_regress_cflags = ['-DHOST_TUPLE="frak"', '-DSHELLPROG="/bin/sh"']
 
+if host_system == 'windows'
+  regress_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_regress',
+    '--FILEDESC', 'pg_regress - test driver',])
+endif
+
 pg_regress = executable('pg_regress',
   regress_sources,
   c_args: pg_regress_cflags,
diff --git a/meson.build b/meson.build
index 6ffae59ba03..4ad06b25213 100644
--- a/meson.build
+++ b/meson.build
@@ -2523,6 +2523,67 @@ gen_export_kwargs = {
 
 
 
+###
+### windows resources related stuff
+###
+
+if host_system == 'windows'
+  pg_ico = meson.source_root() / 'src' / 'port' / 'win32.ico'
+  win32ver_rc = files('src/port/win32ver.rc')
+  rcgen = find_program('src/tools/rcgen', native: true)
+
+  rcgen_base_args = [
+    '--srcdir', '@SOURCE_DIR@',
+    '--builddir', meson.build_root(),
+    '--rcout', '@OUTPUT0@',
+    '--out', '@OUTPUT1@',
+    '--input', '@INPUT@',
+    '@EXTRA_ARGS@',
+  ]
+
+  if cc.get_argument_syntax() == 'msvc'
+    rcgen_base_args += [
+      '--rc', find_program('rc', required: true).path(),
+    ]
+    rcgen_outputs = ['@[email protected]', '@[email protected]']
+  else
+    rcgen_base_args += [
+      '--windres', find_program('windres', required: true).path(),
+    ]
+    rcgen_outputs = ['@[email protected]', '@[email protected]']
+  endif
+
+  # msbuild backend doesn't support this atm
+  if meson.backend() == 'ninja'
+    rcgen_base_args += ['--depfile', '@DEPFILE@']
+  endif
+
+  rcgen_bin_args = rcgen_base_args + [
+    '--VFT_TYPE', 'VFT_APP',
+    '--FILEENDING', 'exe',
+    '--ICO', pg_ico
+  ]
+
+  rcgen_lib_args = rcgen_base_args + [
+    '--VFT_TYPE', 'VFT_DLL',
+    '--FILEENDING', 'dll',
+  ]
+
+  rc_bin_gen = generator(rcgen,
+    depfile: '@[email protected]',
+    arguments: rcgen_bin_args,
+    output: rcgen_outputs,
+  )
+
+  rc_lib_gen = generator(rcgen,
+    depfile: '@[email protected]',
+    arguments: rcgen_lib_args,
+    output: rcgen_outputs,
+  )
+endif
+
+
+
 # headers that the whole build tree depends on
 generated_headers = []
 # headers that the backend build depends on
diff --git a/src/timezone/meson.build b/src/timezone/meson.build
index 16f082ecfa8..9e0934c000b 100644
--- a/src/timezone/meson.build
+++ b/src/timezone/meson.build
@@ -28,6 +28,12 @@ if get_option('system_tzdata') == ''
   if meson.is_cross_build()
     zic = find_program(get_option('ZIC'), native: true, required: true)
   else
+    if host_system == 'windows'
+      zic_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+        '--NAME', 'zic',
+        '--FILEDESC', 'zic - time zone compiler',])
+    endif
+
     zic = executable('zic', zic_sources,
                      dependencies: [frontend_code],
                      kwargs: default_bin_args + {'install': false}
diff --git a/src/tools/rcgen b/src/tools/rcgen
new file mode 100755
index 00000000000..5b62bfe5410
--- /dev/null
+++ b/src/tools/rcgen
@@ -0,0 +1,99 @@
+#!/usr/bin/env python3
+
+import argparse
+import os
+import subprocess
+import sys
+
+parser = argparse.ArgumentParser(description='generate PostgreSQL rc file')
+
+parser.add_argument('--srcdir', type=os.path.abspath,
+                    required=True)
+parser.add_argument('--builddir', type=os.path.abspath,
+                    required=True)
+
+binaries = parser.add_argument_group('binaries')
+binaries.add_argument('--windres', type=os.path.abspath)
+binaries.add_argument('--rc', type=os.path.abspath)
+
+inout = parser.add_argument_group('inout')
+inout.add_argument('--depfile', type=argparse.FileType('w'))
+inout.add_argument('--input', type=argparse.FileType('r'),
+                   required=True)
+inout.add_argument('--rcout', type=argparse.FileType('w'),
+                   required=True)
+inout.add_argument('--out', type=str,
+                   required=True)
+
+replacements = parser.add_argument_group('replacements')
+replacements.add_argument('--FILEDESC', type=str)
+replacements.add_argument('--NAME', type=str, required=True)
+replacements.add_argument('--VFT_TYPE', type=str, required=True)
+replacements.add_argument('--FILEENDING', type=str, required=True)
+replacements.add_argument('--ICO', type=str)
+
+args = parser.parse_args()
+
+# determine replacement strings
+
+internal_name = '"{0}"'.format(args.NAME)
+original_name = '"{0}.{1}"'.format(args.NAME, args.FILEENDING)
+
+# if no description is passed in, generate one based on the name
+if args.FILEDESC:
+    filedesc = args.FILEDESC
+elif args.NAME:
+    if args.VFT_TYPE == 'VFT_DLL':
+        filedesc = 'PostgreSQL {0} library'.format(args.NAME)
+    else:
+        filedesc = 'PostgreSQL {0} binary'.format(args.NAME)
+filedesc = '"{0}"'.format(filedesc)
+
+
+if args.ICO:
+    ico = 'IDI_ICON ICON "{0}"'.format(args.ICO)
+    if args.depfile:
+        args.depfile.write("{0} : {1}\n".format(args.rcout.name, args.ICO))
+else:
+    ico = ''
+
+
+data = args.input.read()
+
+data = data.replace('VFT_APP', args.VFT_TYPE)
+data = data.replace('_INTERNAL_NAME_', internal_name)
+data = data.replace('_ORIGINAL_NAME_', original_name)
+data = data.replace('FILEDESC', filedesc)
+data = data.replace("_ICO_", ico)
+
+args.rcout.write(data)
+args.rcout.close()
+
+if args.windres:
+    cmd = [
+        args.windres,
+        '-I{0}/src/include/'.format(args.builddir),
+        '-I{0}/src/include/'.format(args.srcdir),
+        '-o', args.out, '-i', args.rcout.name,
+    ]
+elif args.rc:
+    cmd = [
+        args.rc, '/nologo',
+        '-I{0}/src/include/'.format(args.builddir),
+        '-I{0}/src/include/'.format(args.srcdir),
+        '/fo', args.out, args.rcout.name,
+    ]
+else:
+    sys.exit('either --windres or --rc needs to be specified')
+
+sp = subprocess.run(cmd)
+if sp.returncode != 0:
+    sys.exit(sp.returncode)
+
+# It'd be nicer if we could generate correct dependencies here, but 'rc'
+# doesn't support doing so. It's unlikely we'll ever need more, so...
+if args.depfile:
+    args.depfile.write("{0} : {1}\n".format(
+        args.rcout.name, args.input.name))
+    args.depfile.write("{0} : {1}/{2}\n".format(
+        args.out, args.builddir, 'src/include/pg_config.h'))
-- 
2.37.3.542.gdd3f6c4cae


--ngschxobz5ssor62
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
	filename="v16-0005-meson-Add-support-for-relative-rpaths-fixing-tes.patch"



^ permalink  raw  reply  [nested|flat] 23+ messages in thread

* [PATCH v17 06/23] meson: Add windows resource files
@ 2022-09-26 17:32 Andres Freund <[email protected]>
  0 siblings, 0 replies; 23+ messages in thread

From: Andres Freund @ 2022-09-26 17:32 UTC (permalink / raw)

The generated resource files aren't exactly the same ones as the old
buildsystems generate. Previously "InternalName" and "OriginalFileName" were
mostly wrong / not set (despite being required), but that was hard to fix in
at least the make build. Additionally, the meson build falls back to a
"auto-generated" description when not set, and doesn't set it in a few cases -
unlikely that anybody looks at these descriptions in detail.

Author: Andres Freund <[email protected]>
Author: Nazir Bilal Yavuz <[email protected]>
---
 src/backend/jit/llvm/meson.build              |  6 ++
 .../replication/libpqwalreceiver/meson.build  |  6 ++
 src/backend/replication/pgoutput/meson.build  |  6 ++
 src/backend/snowball/meson.build              |  6 ++
 .../utils/mb/conversion_procs/meson.build     |  9 +-
 src/bin/initdb/meson.build                    |  6 ++
 src/bin/pg_amcheck/meson.build                |  8 +-
 src/bin/pg_archivecleanup/meson.build         | 12 ++-
 src/bin/pg_basebackup/meson.build             | 38 ++++++-
 src/bin/pg_checksums/meson.build              | 12 ++-
 src/bin/pg_config/meson.build                 | 12 ++-
 src/bin/pg_controldata/meson.build            | 12 ++-
 src/bin/pg_ctl/meson.build                    | 12 ++-
 src/bin/pg_dump/meson.build                   | 18 ++++
 src/bin/pg_resetwal/meson.build               | 12 ++-
 src/bin/pg_rewind/meson.build                 |  6 ++
 src/bin/pg_test_fsync/meson.build             | 10 +-
 src/bin/pg_test_timing/meson.build            | 12 ++-
 src/bin/pg_upgrade/meson.build                |  6 ++
 src/bin/pg_verifybackup/meson.build           |  6 ++
 src/bin/pg_waldump/meson.build                |  6 ++
 src/bin/pgbench/meson.build                   |  6 ++
 src/bin/pgevent/meson.build                   |  6 ++
 src/bin/psql/meson.build                      |  6 ++
 src/bin/scripts/meson.build                   | 10 +-
 src/interfaces/libpq/meson.build              |  5 +
 src/interfaces/libpq/test/meson.build         | 25 ++++-
 src/pl/plperl/meson.build                     |  7 ++
 src/pl/plpgsql/src/meson.build                |  6 ++
 src/pl/plpython/meson.build                   |  6 ++
 src/pl/tcl/meson.build                        |  6 ++
 contrib/adminpack/meson.build                 | 12 ++-
 contrib/amcheck/meson.build                   | 17 +++-
 contrib/auth_delay/meson.build                | 12 ++-
 contrib/auto_explain/meson.build              | 12 ++-
 contrib/basebackup_to_shell/meson.build       |  6 ++
 contrib/basic_archive/meson.build             |  6 ++
 contrib/bloom/meson.build                     |  6 ++
 contrib/bool_plperl/meson.build               |  6 ++
 contrib/btree_gin/meson.build                 | 12 ++-
 contrib/btree_gist/meson.build                |  6 ++
 contrib/citext/meson.build                    |  6 ++
 contrib/cube/meson.build                      |  6 ++
 contrib/dblink/meson.build                    |  6 ++
 contrib/dict_int/meson.build                  | 12 ++-
 contrib/dict_xsyn/meson.build                 | 12 ++-
 contrib/earthdistance/meson.build             | 12 ++-
 contrib/file_fdw/meson.build                  | 12 ++-
 contrib/fuzzystrmatch/meson.build             | 16 ++-
 contrib/hstore/meson.build                    | 24 +++--
 contrib/hstore_plperl/meson.build             |  6 ++
 contrib/hstore_plpython/meson.build           |  6 ++
 contrib/intarray/meson.build                  |  6 ++
 contrib/isn/meson.build                       |  6 ++
 contrib/jsonb_plperl/meson.build              |  6 ++
 contrib/jsonb_plpython/meson.build            |  6 ++
 contrib/lo/meson.build                        |  6 ++
 contrib/ltree/meson.build                     |  6 ++
 contrib/ltree_plpython/meson.build            |  6 ++
 contrib/oid2name/meson.build                  | 12 ++-
 contrib/old_snapshot/meson.build              |  6 ++
 contrib/pageinspect/meson.build               |  6 ++
 contrib/passwordcheck/meson.build             |  6 ++
 contrib/pg_buffercache/meson.build            | 14 ++-
 contrib/pg_freespacemap/meson.build           | 14 ++-
 contrib/pg_prewarm/meson.build                | 16 ++-
 contrib/pg_stat_statements/meson.build        | 12 ++-
 contrib/pg_surgery/meson.build                | 14 ++-
 contrib/pg_trgm/meson.build                   | 20 ++--
 contrib/pg_visibility/meson.build             | 14 ++-
 contrib/pg_walinspect/meson.build             |  6 ++
 contrib/pgcrypto/meson.build                  |  6 ++
 contrib/pgrowlocks/meson.build                | 14 ++-
 contrib/pgstattuple/meson.build               | 18 +++-
 contrib/postgres_fdw/meson.build              |  6 ++
 contrib/seg/meson.build                       |  6 ++
 contrib/sepgsql/meson.build                   |  6 ++
 contrib/spi/meson.build                       | 48 ++++++++-
 contrib/sslinfo/meson.build                   | 14 ++-
 contrib/tablefunc/meson.build                 | 14 ++-
 contrib/tcn/meson.build                       | 14 ++-
 contrib/test_decoding/meson.build             |  6 ++
 contrib/tsm_system_rows/meson.build           | 14 ++-
 contrib/tsm_system_time/meson.build           | 14 ++-
 contrib/unaccent/meson.build                  | 14 ++-
 contrib/uuid-ossp/meson.build                 | 14 ++-
 contrib/vacuumlo/meson.build                  | 12 ++-
 contrib/xml2/meson.build                      | 16 ++-
 src/interfaces/ecpg/compatlib/meson.build     |  6 ++
 src/interfaces/ecpg/ecpglib/meson.build       |  6 ++
 src/interfaces/ecpg/pgtypeslib/meson.build    |  6 ++
 src/interfaces/ecpg/preproc/meson.build       |  6 ++
 src/interfaces/ecpg/test/meson.build          |  5 +
 src/test/isolation/meson.build                | 13 +++
 src/test/modules/delay_execution/meson.build  | 13 ++-
 src/test/modules/dummy_index_am/meson.build   | 13 ++-
 src/test/modules/dummy_seclabel/meson.build   | 13 ++-
 src/test/modules/libpq_pipeline/meson.build   | 14 ++-
 src/test/modules/plsample/meson.build         | 13 ++-
 src/test/modules/spgist_name_ops/meson.build  | 13 ++-
 .../ssl_passphrase_callback/meson.build       | 13 ++-
 src/test/modules/test_bloomfilter/meson.build | 13 ++-
 src/test/modules/test_ddl_deparse/meson.build | 13 ++-
 .../modules/test_ginpostinglist/meson.build   | 13 ++-
 src/test/modules/test_integerset/meson.build  | 13 ++-
 src/test/modules/test_lfind/meson.build       | 13 ++-
 src/test/modules/test_oat_hooks/meson.build   | 13 ++-
 src/test/modules/test_parser/meson.build      | 13 ++-
 src/test/modules/test_predtest/meson.build    | 13 ++-
 src/test/modules/test_rbtree/meson.build      | 13 ++-
 src/test/modules/test_regex/meson.build       | 13 ++-
 src/test/modules/test_rls_hooks/meson.build   | 13 ++-
 src/test/modules/test_shm_mq/meson.build      | 19 +++-
 src/test/modules/worker_spi/meson.build       | 15 ++-
 src/test/regress/meson.build                  |  6 ++
 meson.build                                   | 59 +++++++++++
 src/timezone/meson.build                      |  6 ++
 src/tools/rcgen                               | 99 +++++++++++++++++++
 118 files changed, 1286 insertions(+), 130 deletions(-)
 create mode 100755 src/tools/rcgen

diff --git a/src/backend/jit/llvm/meson.build b/src/backend/jit/llvm/meson.build
index de2e624ab58..5fb63768358 100644
--- a/src/backend/jit/llvm/meson.build
+++ b/src/backend/jit/llvm/meson.build
@@ -20,6 +20,12 @@ llvmjit_sources += files(
   'llvmjit_expr.c',
 )
 
+if host_system == 'windows'
+  llvmjit_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'llvmjit',
+    '--FILEDESC', 'llvmjit - JIT using LLVM',])
+endif
+
 llvmjit = shared_module('llvmjit',
   llvmjit_sources,
   kwargs: pg_mod_args + {
diff --git a/src/backend/replication/libpqwalreceiver/meson.build b/src/backend/replication/libpqwalreceiver/meson.build
index 3fc786c80a0..4c653a05d36 100644
--- a/src/backend/replication/libpqwalreceiver/meson.build
+++ b/src/backend/replication/libpqwalreceiver/meson.build
@@ -2,6 +2,12 @@ libpqwalreceiver_sources = files(
   'libpqwalreceiver.c',
 )
 
+if host_system == 'windows'
+  libpqwalreceiver_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pqwalreceiver',
+    '--FILEDESC', 'libpqwalreceiver - receive WAL during streaming replication',])
+endif
+
 libpqwalreceiver = shared_module('pqwalreceiver',
   libpqwalreceiver_sources,
   kwargs: pg_mod_args + {
diff --git a/src/backend/replication/pgoutput/meson.build b/src/backend/replication/pgoutput/meson.build
index ab956361a62..5df27d7b764 100644
--- a/src/backend/replication/pgoutput/meson.build
+++ b/src/backend/replication/pgoutput/meson.build
@@ -2,6 +2,12 @@ pgoutput_sources = files(
   'pgoutput.c',
 )
 
+if host_system == 'windows'
+  pgoutput_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pgoutput',
+    '--FILEDESC', 'pgoutput - standard logical replication output plugin',])
+endif
+
 pgoutput = shared_module('pgoutput',
   pgoutput_sources,
   kwargs: pg_mod_args,
diff --git a/src/backend/snowball/meson.build b/src/backend/snowball/meson.build
index 8c6f685cb32..974401d187e 100644
--- a/src/backend/snowball/meson.build
+++ b/src/backend/snowball/meson.build
@@ -58,6 +58,12 @@ dict_snowball_sources += files(
 # see comment in src/include/snowball/header.h
 stemmer_inc = include_directories('../../include/snowball')
 
+if host_system == 'windows'
+  dict_snowball_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'dict_snowball',
+    '--FILEDESC', 'snowball - natural language stemmers',])
+endif
+
 dict_snowball = shared_module('dict_snowball',
   dict_snowball_sources,
   kwargs: pg_mod_args + {
diff --git a/src/backend/utils/mb/conversion_procs/meson.build b/src/backend/utils/mb/conversion_procs/meson.build
index 1bc971d1945..1c18f2ac85a 100644
--- a/src/backend/utils/mb/conversion_procs/meson.build
+++ b/src/backend/utils/mb/conversion_procs/meson.build
@@ -29,8 +29,15 @@ encodings = {
 }
 
 foreach encoding, sources : encodings
+  source_files = files(sources)
+
+  if host_system == 'windows'
+    source_files += rc_lib_gen.process(win32ver_rc, extra_args: [
+      '--NAME', encoding])
+  endif
+
   backend_targets += shared_module(encoding,
-    sources,
+    source_files,
     kwargs: pg_mod_args,
   )
 endforeach
diff --git a/src/bin/initdb/meson.build b/src/bin/initdb/meson.build
index 9f213274d2f..6ced9a31b80 100644
--- a/src/bin/initdb/meson.build
+++ b/src/bin/initdb/meson.build
@@ -7,6 +7,12 @@ initdb_sources += timezone_localtime_source
 
 #fixme: reimplement libpq_pgport logic
 
+if host_system == 'windows'
+  initdb_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'initdb',
+    '--FILEDESC', 'initdb - initialize a new database cluster',])
+endif
+
 initdb = executable('initdb',
   initdb_sources,
   include_directories: [timezone_inc],
diff --git a/src/bin/pg_amcheck/meson.build b/src/bin/pg_amcheck/meson.build
index 8e197eba5f3..25f5e7a0948 100644
--- a/src/bin/pg_amcheck/meson.build
+++ b/src/bin/pg_amcheck/meson.build
@@ -1,7 +1,13 @@
 pg_amcheck_sources = files(
-  'pg_amcheck.c'
+  'pg_amcheck.c',
 )
 
+if host_system == 'windows'
+  pg_amcheck_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_amcheck',
+    '--FILEDESC', 'pg_amcheck - detect corruption within database relations',])
+endif
+
 pg_amcheck = executable('pg_amcheck',
   pg_amcheck_sources,
   dependencies: [frontend_code, libpq],
diff --git a/src/bin/pg_archivecleanup/meson.build b/src/bin/pg_archivecleanup/meson.build
index 87a0d980c4f..aaa2e76977f 100644
--- a/src/bin/pg_archivecleanup/meson.build
+++ b/src/bin/pg_archivecleanup/meson.build
@@ -1,5 +1,15 @@
+pg_archivecleanup_sources = files(
+  'pg_archivecleanup.c',
+)
+
+if host_system == 'windows'
+  pg_archivecleanup_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_archivecleanup',
+    '--FILEDESC', 'pg_archivecleanup - cleans archive when used with streaming replication',])
+endif
+
 pg_archivecleanup = executable('pg_archivecleanup',
-  ['pg_archivecleanup.c'],
+  pg_archivecleanup_sources,
   dependencies: [frontend_code],
   kwargs: default_bin_args,
 )
diff --git a/src/bin/pg_basebackup/meson.build b/src/bin/pg_basebackup/meson.build
index d26fed9cd8a..2c934e0c26e 100644
--- a/src/bin/pg_basebackup/meson.build
+++ b/src/bin/pg_basebackup/meson.build
@@ -17,24 +17,56 @@ pg_basebackup_common = static_library('libpg_basebackup_common',
   kwargs: internal_lib_args,
 )
 
-pg_basebackup = executable('pg_basebackup',
+pg_basebackup_sources = files(
   'pg_basebackup.c',
+)
+
+if host_system == 'windows'
+  pg_basebackup_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_basebackup',
+    '--FILEDESC', 'pg_basebackup - streaming WAL and backup receivers',])
+endif
+
+pg_basebackup = executable('pg_basebackup',
+  pg_basebackup_sources,
   link_with: [pg_basebackup_common],
   dependencies: pg_basebackup_deps,
   kwargs: default_bin_args,
 )
 bin_targets += pg_basebackup
 
-pg_receivewal = executable('pg_receivewal',
+
+pg_receivewal_sources = files(
   'pg_receivewal.c',
+)
+
+if host_system == 'windows'
+  pg_receivewal_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_receivewal',
+    '--FILEDESC', 'pg_receivewal - streaming WAL and backup receivers',])
+endif
+
+pg_receivewal = executable('pg_receivewal',
+  pg_receivewal_sources,
   link_with: [pg_basebackup_common],
   dependencies: pg_basebackup_deps,
   kwargs: default_bin_args,
 )
 bin_targets += pg_receivewal
 
-pg_recvlogical = executable('pg_recvlogical',
+
+pg_recvlogical_sources = files(
   'pg_recvlogical.c',
+)
+
+if host_system == 'windows'
+  pg_recvlogical_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_recvlogical',
+    '--FILEDESC', 'pg_recvlogical - streaming WAL and backup receivers',])
+endif
+
+pg_recvlogical = executable('pg_recvlogical',
+  pg_recvlogical_sources,
   link_with: [pg_basebackup_common],
   dependencies: pg_basebackup_deps,
   kwargs: default_bin_args,
diff --git a/src/bin/pg_checksums/meson.build b/src/bin/pg_checksums/meson.build
index ee1f367bac3..d07ebc999b3 100644
--- a/src/bin/pg_checksums/meson.build
+++ b/src/bin/pg_checksums/meson.build
@@ -1,5 +1,15 @@
+pg_checksums_sources = files(
+  'pg_checksums.c',
+)
+
+if host_system == 'windows'
+  pg_checksums_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_checksums',
+    '--FILEDESC', 'pg_checksums - verify data checksums in an offline cluster',])
+endif
+
 pg_checksums = executable('pg_checksums',
-  ['pg_checksums.c'],
+  pg_checksums_sources,
   include_directories: [timezone_inc],
   dependencies: [frontend_code],
   kwargs: default_bin_args,
diff --git a/src/bin/pg_config/meson.build b/src/bin/pg_config/meson.build
index 0ecbf2f9d28..4be2fdc84ae 100644
--- a/src/bin/pg_config/meson.build
+++ b/src/bin/pg_config/meson.build
@@ -1,5 +1,15 @@
+pg_config_sources = files(
+  'pg_config.c',
+)
+
+if host_system == 'windows'
+  pg_config_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_config',
+    '--FILEDESC', 'pg_config - report configuration information',])
+endif
+
 pg_config = executable('pg_config',
-  ['pg_config.c'],
+  pg_config_sources,
   dependencies: [frontend_code],
   kwargs: default_bin_args,
 )
diff --git a/src/bin/pg_controldata/meson.build b/src/bin/pg_controldata/meson.build
index 557e672beb7..7fc239dbe65 100644
--- a/src/bin/pg_controldata/meson.build
+++ b/src/bin/pg_controldata/meson.build
@@ -1,5 +1,15 @@
+pg_controldata_sources = files(
+  'pg_controldata.c',
+)
+
+if host_system == 'windows'
+  pg_controldata_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_controldata',
+    '--FILEDESC', 'pg_controldata - reads the data from pg_control',])
+endif
+
 pg_controldata = executable('pg_controldata',
-  ['pg_controldata.c'],
+  pg_controldata_sources,
   dependencies: [frontend_code],
   kwargs: default_bin_args,
 )
diff --git a/src/bin/pg_ctl/meson.build b/src/bin/pg_ctl/meson.build
index 6812e73e329..96f962fa762 100644
--- a/src/bin/pg_ctl/meson.build
+++ b/src/bin/pg_ctl/meson.build
@@ -1,5 +1,15 @@
+pg_ctl_sources = files(
+  'pg_ctl.c',
+)
+
+if host_system == 'windows'
+  pg_ctl_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_ctl',
+    '--FILEDESC', 'pg_ctl - starts/stops/restarts the PostgreSQL server',])
+endif
+
 pg_ctl = executable('pg_ctl',
-  ['pg_ctl.c'],
+  pg_ctl_sources,
   dependencies: [frontend_code, libpq],
   kwargs: default_bin_args,
 )
diff --git a/src/bin/pg_dump/meson.build b/src/bin/pg_dump/meson.build
index 785ec094dbd..3527a25c288 100644
--- a/src/bin/pg_dump/meson.build
+++ b/src/bin/pg_dump/meson.build
@@ -24,6 +24,12 @@ pg_dump_sources = files(
   'pg_dump_sort.c',
 )
 
+if host_system == 'windows'
+  pg_dump_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_dump',
+    '--FILEDESC', 'pg_dump - backup one PostgreSQL database',])
+endif
+
 pg_dump = executable('pg_dump',
   pg_dump_sources,
   link_with: [pg_dump_common],
@@ -37,6 +43,12 @@ pg_dumpall_sources = files(
   'pg_dumpall.c',
 )
 
+if host_system == 'windows'
+  pg_dumpall_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_dumpall',
+    '--FILEDESC', 'pg_dumpall - backup PostgreSQL databases'])
+endif
+
 pg_dumpall = executable('pg_dumpall',
   pg_dumpall_sources,
   link_with: [pg_dump_common],
@@ -50,6 +62,12 @@ pg_restore_sources = files(
   'pg_restore.c',
 )
 
+if host_system == 'windows'
+  pg_restore_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_restore',
+    '--FILEDESC', 'pg_restore - restore PostgreSQL databases'])
+endif
+
 pg_restore = executable('pg_restore',
   pg_restore_sources,
   link_with: [pg_dump_common],
diff --git a/src/bin/pg_resetwal/meson.build b/src/bin/pg_resetwal/meson.build
index 7c5de134ac0..d503db97b71 100644
--- a/src/bin/pg_resetwal/meson.build
+++ b/src/bin/pg_resetwal/meson.build
@@ -1,5 +1,15 @@
+pg_resetwal_sources = files(
+  'pg_resetwal.c',
+)
+
+if host_system == 'windows'
+  pg_resetwal_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_resetwal',
+    '--FILEDESC', 'pg_resetwal - reset PostgreSQL WAL log'])
+endif
+
 pg_resetwal = executable('pg_resetwal',
-  files('pg_resetwal.c'),
+  pg_resetwal_sources,
   dependencies: [frontend_code],
   kwargs: default_bin_args,
 )
diff --git a/src/bin/pg_rewind/meson.build b/src/bin/pg_rewind/meson.build
index d8ec9e482d5..6cd970909a2 100644
--- a/src/bin/pg_rewind/meson.build
+++ b/src/bin/pg_rewind/meson.build
@@ -11,6 +11,12 @@ pg_rewind_sources = files(
 
 pg_rewind_sources += xlogreader_sources
 
+if host_system == 'windows'
+  pg_rewind_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_rewind',
+    '--FILEDESC', 'pg_rewind - synchronize a data directory with another one forked from'])
+endif
+
 pg_rewind = executable('pg_rewind',
   pg_rewind_sources,
   dependencies: [frontend_code, libpq, lz4, zstd],
diff --git a/src/bin/pg_test_fsync/meson.build b/src/bin/pg_test_fsync/meson.build
index 2c01831e11f..31d288ba6da 100644
--- a/src/bin/pg_test_fsync/meson.build
+++ b/src/bin/pg_test_fsync/meson.build
@@ -1,4 +1,12 @@
-test_fsync_sources = files('pg_test_fsync.c')
+test_fsync_sources = files(
+  'pg_test_fsync.c',
+)
+
+if host_system == 'windows'
+  test_fsync_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_test_fsync',
+    '--FILEDESC', 'pg_test_fsync - test various disk sync methods'])
+endif
 
 pg_test_fsync = executable('pg_test_fsync',
   test_fsync_sources,
diff --git a/src/bin/pg_test_timing/meson.build b/src/bin/pg_test_timing/meson.build
index 0a3068f1657..0aed03ea32f 100644
--- a/src/bin/pg_test_timing/meson.build
+++ b/src/bin/pg_test_timing/meson.build
@@ -1,5 +1,15 @@
+pg_test_timing_sources = files(
+  'pg_test_timing.c'
+)
+
+if host_system == 'windows'
+  pg_test_timing_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_test_timing',
+    '--FILEDESC', 'pg_test_timing - test timing overhead'])
+endif
+
 pg_test_timing = executable('pg_test_timing',
-  ['pg_test_timing.c'],
+  pg_test_timing_sources,
   dependencies: [frontend_code],
   kwargs: default_bin_args,
 )
diff --git a/src/bin/pg_upgrade/meson.build b/src/bin/pg_upgrade/meson.build
index 02f030e0ccf..a7b927a45c7 100644
--- a/src/bin/pg_upgrade/meson.build
+++ b/src/bin/pg_upgrade/meson.build
@@ -16,6 +16,12 @@ pg_upgrade_sources = files(
   'version.c',
 )
 
+if host_system == 'windows'
+  pg_upgrade_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_upgrade',
+    '--FILEDESC', 'pg_upgrade - an in-place binary upgrade utility'])
+endif
+
 pg_upgrade = executable('pg_upgrade',
   pg_upgrade_sources,
   dependencies: [frontend_code, libpq],
diff --git a/src/bin/pg_verifybackup/meson.build b/src/bin/pg_verifybackup/meson.build
index 4c3b2bb5f97..b934a408443 100644
--- a/src/bin/pg_verifybackup/meson.build
+++ b/src/bin/pg_verifybackup/meson.build
@@ -3,6 +3,12 @@ pg_verifybackup_sources = files(
   'pg_verifybackup.c'
 )
 
+if host_system == 'windows'
+  pg_verifybackup_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_verifybackup',
+    '--FILEDESC', 'pg_verifybackup - verify a backup against using a backup manifest'])
+endif
+
 pg_verifybackup = executable('pg_verifybackup',
   pg_verifybackup_sources,
   dependencies: [frontend_code, libpq],
diff --git a/src/bin/pg_waldump/meson.build b/src/bin/pg_waldump/meson.build
index 95872652ffd..9605976870d 100644
--- a/src/bin/pg_waldump/meson.build
+++ b/src/bin/pg_waldump/meson.build
@@ -8,6 +8,12 @@ pg_waldump_sources += rmgr_desc_sources
 pg_waldump_sources += xlogreader_sources
 pg_waldump_sources += files('../../backend/access/transam/xlogstats.c')
 
+if host_system == 'windows'
+  pg_waldump_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_waldump',
+    '--FILEDESC', 'pg_waldump - decode and display WA'])
+endif
+
 pg_waldump = executable('pg_waldump',
   pg_waldump_sources,
   dependencies: [frontend_code, lz4, zstd],
diff --git a/src/bin/pgbench/meson.build b/src/bin/pgbench/meson.build
index 6564e54029c..a32eb51fe07 100644
--- a/src/bin/pgbench/meson.build
+++ b/src/bin/pgbench/meson.build
@@ -17,6 +17,12 @@ exprparse = custom_target('exprparse',
 generated_sources += exprparse.to_list()
 pgbench_sources += exprparse
 
+if host_system == 'windows'
+  pgbench_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pgbench',
+    '--FILEDESC', 'pgbench - a simple program for running benchmark tests'])
+endif
+
 pgbench = executable('pgbench',
   pgbench_sources,
   dependencies: [frontend_code, libpq, thread_dep],
diff --git a/src/bin/pgevent/meson.build b/src/bin/pgevent/meson.build
index 7a468879fd2..2e9aea4b0e1 100644
--- a/src/bin/pgevent/meson.build
+++ b/src/bin/pgevent/meson.build
@@ -6,6 +6,12 @@ pgevent_sources = files(
   'pgevent.c',
 )
 
+pgevent_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+  '--NAME', 'pgevent',
+  '--FILEDESC', 'Eventlog message formatter',])
+
+pgevent_sources += windows.compile_resources('pgmsgevent.rc')
+
 # FIXME: copied from Mkvcbuild.pm, but I don't think that's the right approach
 pgevent_link_args = []
 if cc.get_id() == 'msvc'
diff --git a/src/bin/psql/meson.build b/src/bin/psql/meson.build
index 410788e4767..1264fc19fbd 100644
--- a/src/bin/psql/meson.build
+++ b/src/bin/psql/meson.build
@@ -36,6 +36,12 @@ sql_help = custom_target('psql_help',
 generated_sources += sql_help.to_list()
 psql_sources += sql_help
 
+if host_system == 'windows'
+  psql_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'psql',
+    '--FILEDESC', 'psql - the PostgreSQL interactive terminal',])
+endif
+
 psql = executable('psql',
   psql_sources,
   include_directories: include_directories('.'),
diff --git a/src/bin/scripts/meson.build b/src/bin/scripts/meson.build
index eaf250c7f73..837562c24e5 100644
--- a/src/bin/scripts/meson.build
+++ b/src/bin/scripts/meson.build
@@ -16,8 +16,16 @@ binaries = [
 ]
 
 foreach binary : binaries
+  binary_sources = files('@[email protected]'.format(binary))
+
+  if host_system == 'windows'
+    binary_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+      '--NAME', binary,
+      '--FILEDESC', '@0@ - PostgreSQL utility'.format(binary),])
+  endif
+
   binary = executable(binary,
-    files(binary + '.c'),
+    binary_sources,
     link_with: [scripts_common],
     dependencies: [frontend_code, libpq],
     kwargs: default_bin_args,
diff --git a/src/interfaces/libpq/meson.build b/src/interfaces/libpq/meson.build
index c203372a35b..63ab2fbacc2 100644
--- a/src/interfaces/libpq/meson.build
+++ b/src/interfaces/libpq/meson.build
@@ -16,9 +16,13 @@ libpq_sources = files(
   'libpq-events.c',
   'pqexpbuffer.c',
 )
+libpq_so_sources = [] # only for shared lib, in addition to above
 
 if host_system == 'windows'
   libpq_sources += files('pthread-win32.c', 'win32.c')
+  libpq_so_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'libpq',
+    '--FILEDESC', 'PostgreSQL Access Library',])
 endif
 
 if ssl.found()
@@ -52,6 +56,7 @@ libpq_st = static_library('libpq',
 # not using both_libraries() here as the resource file should only be in the
 # shared library
 libpq_so = shared_library('libpq',
+  libpq_so_sources,
   dependencies: libpq_deps,
   include_directories: [libpq_inc, postgres_inc],
   c_args: ['-DSO_MAJOR_VERSION=5'],
diff --git a/src/interfaces/libpq/test/meson.build b/src/interfaces/libpq/test/meson.build
index 16f94c1ed8b..017f729d435 100644
--- a/src/interfaces/libpq/test/meson.build
+++ b/src/interfaces/libpq/test/meson.build
@@ -1,13 +1,34 @@
+libpq_uri_regress_sources = files(
+  'libpq_uri_regress.c',
+)
+
+if host_system == 'windows'
+  libpq_uri_regress_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'libpq_uri_regress',
+    '--FILEDESC', 'libpq test program',])
+endif
+
 executable('libpq_uri_regress',
-  files('libpq_uri_regress.c'),
+  libpq_uri_regress_sources,
   dependencies: [frontend_code, libpq],
   kwargs: default_bin_args + {
     'install': false,
   }
 )
 
+
+libpq_testclient_sources = files(
+  'libpq_testclient.c',
+)
+
+if host_system == 'windows'
+  libpq_testclient_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'libpq_testclient',
+    '--FILEDESC', 'libpq test program',])
+endif
+
 executable('libpq_testclient',
-  files('libpq_testclient.c'),
+  libpq_testclient_sources,
   dependencies: [frontend_code, libpq],
   kwargs: default_bin_args + {
     'install': false,
diff --git a/src/pl/plperl/meson.build b/src/pl/plperl/meson.build
index 73b733dd50b..535660085dd 100644
--- a/src/pl/plperl/meson.build
+++ b/src/pl/plperl/meson.build
@@ -36,6 +36,13 @@ foreach n : ['SPI', 'Util']
 endforeach
 
 plperl_inc = include_directories('.')
+
+if host_system == 'windows'
+  plperl_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'plperl',
+    '--FILEDESC', 'PL/Perl - procedural language',])
+endif
+
 plperl = shared_module('plperl',
   plperl_sources,
   include_directories: [plperl_inc, postgres_inc],
diff --git a/src/pl/plpgsql/src/meson.build b/src/pl/plpgsql/src/meson.build
index dd499fdd151..c46c0a1da2a 100644
--- a/src/pl/plpgsql/src/meson.build
+++ b/src/pl/plpgsql/src/meson.build
@@ -40,6 +40,12 @@ pl_unreserved = custom_target('pl_unreserved_kwlist',
 generated_sources += pl_unreserved
 plpgsql_sources += pl_unreserved
 
+if host_system == 'windows'
+  plpgsql_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'plpgsql',
+    '--FILEDESC', 'PL/pgSQL - procedural language',])
+endif
+
 plpgsql = shared_module('plpgsql',
   plpgsql_sources,
   include_directories: include_directories('.'),
diff --git a/src/pl/plpython/meson.build b/src/pl/plpython/meson.build
index 366b3b171ac..40888386b5f 100644
--- a/src/pl/plpython/meson.build
+++ b/src/pl/plpython/meson.build
@@ -28,6 +28,12 @@ plpython_sources += custom_target('spiexceptions.h',
 # FIXME: need to duplicate import library ugliness?
 plpython_inc = include_directories('.')
 
+if host_system == 'windows'
+  plpython_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'plpython3',
+    '--FILEDESC', 'PL/Python - procedural language',])
+endif
+
 plpython = shared_module('plpython3',
   plpython_sources,
   include_directories: [plpython_inc, postgres_inc],
diff --git a/src/pl/tcl/meson.build b/src/pl/tcl/meson.build
index 9b6addd7fd5..f09bb14c950 100644
--- a/src/pl/tcl/meson.build
+++ b/src/pl/tcl/meson.build
@@ -14,6 +14,12 @@ pltcl_sources += custom_target('pltclerrcodes.h',
   command: [perl, gen_pltclerrcodes, '@INPUT@']
 )
 
+if host_system == 'windows'
+  pltcl_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pltcl',
+    '--FILEDESC', 'PL/Tcl - procedural language',])
+endif
+
 pltcl = shared_module('pltcl',
   pltcl_sources,
   include_directories: [include_directories('.'), postgres_inc],
diff --git a/contrib/adminpack/meson.build b/contrib/adminpack/meson.build
index fc2368d02cf..7efec0efbc0 100644
--- a/contrib/adminpack/meson.build
+++ b/contrib/adminpack/meson.build
@@ -1,5 +1,15 @@
+adminpack_sources = files(
+  'adminpack.c',
+)
+
+if host_system == 'windows'
+  adminpack_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'adminpack',
+    '--FILEDESC', 'adminpack - support functions for pgAdmin',])
+endif
+
 adminpack = shared_module('adminpack',
-  ['adminpack.c'],
+  adminpack_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += adminpack
diff --git a/contrib/amcheck/meson.build b/contrib/amcheck/meson.build
index 1db3d20349e..fa807b72d98 100644
--- a/contrib/amcheck/meson.build
+++ b/contrib/amcheck/meson.build
@@ -1,7 +1,16 @@
-amcheck = shared_module('amcheck', [
-    'verify_heapam.c',
-    'verify_nbtree.c',
-  ],
+amcheck_sources = files(
+  'verify_heapam.c',
+  'verify_nbtree.c',
+)
+
+if host_system == 'windows'
+  amcheck_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'amcheck',
+    '--FILEDESC', 'amcheck - function for verifying relation integrity',])
+endif
+
+amcheck = shared_module('amcheck',
+  amcheck_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += amcheck
diff --git a/contrib/auth_delay/meson.build b/contrib/auth_delay/meson.build
index d2e01968f54..c4ffb0663bc 100644
--- a/contrib/auth_delay/meson.build
+++ b/contrib/auth_delay/meson.build
@@ -1,5 +1,15 @@
+auth_delay_sources = files(
+  'auth_delay.c',
+)
+
+if host_system == 'windows'
+  auth_delay_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'auth_delay',
+    '--FILEDESC', 'auth_delay - delay authentication failure reports',])
+endif
+
 autoinc = shared_module('auth_delay',
-  ['auth_delay.c'],
+  auth_delay_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += autoinc
diff --git a/contrib/auto_explain/meson.build b/contrib/auto_explain/meson.build
index 249a8376faa..76f86617850 100644
--- a/contrib/auto_explain/meson.build
+++ b/contrib/auto_explain/meson.build
@@ -1,5 +1,15 @@
+auto_explain_sources = files(
+  'auto_explain.c',
+)
+
+if host_system == 'windows'
+  auto_explain_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'auto_explain',
+    '--FILEDESC', 'auto_explain - logging facility for execution plans',])
+endif
+
 auto_explain = shared_module('auto_explain',
-  files('auto_explain.c'),
+  auto_explain_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += auto_explain
diff --git a/contrib/basebackup_to_shell/meson.build b/contrib/basebackup_to_shell/meson.build
index 9f0517f1701..3a389de9175 100644
--- a/contrib/basebackup_to_shell/meson.build
+++ b/contrib/basebackup_to_shell/meson.build
@@ -2,6 +2,12 @@ basebackup_to_shell_sources = files(
   'basebackup_to_shell.c',
 )
 
+if host_system == 'windows'
+  basebackup_to_shell_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'basebackup_to_shell',
+    '--FILEDESC', 'basebackup_to_shell - target basebackup to shell command',])
+endif
+
 basebackup_to_shell = shared_module('basebackup_to_shell',
   basebackup_to_shell_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/basic_archive/meson.build b/contrib/basic_archive/meson.build
index b67cbef60bd..c30dcfa5d41 100644
--- a/contrib/basic_archive/meson.build
+++ b/contrib/basic_archive/meson.build
@@ -2,6 +2,12 @@ basic_archive_sources = files(
   'basic_archive.c',
 )
 
+if host_system == 'windows'
+  basic_archive_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'basic_archive',
+    '--FILEDESC', 'basic_archive - basic archive module',])
+endif
+
 basic_archive = shared_module('basic_archive',
   basic_archive_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/bloom/meson.build b/contrib/bloom/meson.build
index 1fe7632bdbe..16f3b83e4d2 100644
--- a/contrib/bloom/meson.build
+++ b/contrib/bloom/meson.build
@@ -7,6 +7,12 @@ bloom_sources = files(
   'blvalidate.c',
 )
 
+if host_system == 'windows'
+  bloom_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'bloom',
+    '--FILEDESC', 'bloom access method - signature file based index',])
+endif
+
 bloom = shared_module('bloom',
   bloom_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/bool_plperl/meson.build b/contrib/bool_plperl/meson.build
index c20b667d75f..a68daab0dcd 100644
--- a/contrib/bool_plperl/meson.build
+++ b/contrib/bool_plperl/meson.build
@@ -6,6 +6,12 @@ bool_plperl_sources = files(
   'bool_plperl.c',
 )
 
+if host_system == 'windows'
+  bool_plperl_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'bool_plperl',
+    '--FILEDESC', 'bool_plperl - bool transform for plperl',])
+endif
+
 bool_plperl = shared_module('bool_plperl',
   bool_plperl_sources,
   include_directories: [plperl_inc, include_directories('.')],
diff --git a/contrib/btree_gin/meson.build b/contrib/btree_gin/meson.build
index 15d6d31a6ee..fd4c76767a7 100644
--- a/contrib/btree_gin/meson.build
+++ b/contrib/btree_gin/meson.build
@@ -1,5 +1,15 @@
+btree_gin_sources = files(
+  'btree_gin.c',
+)
+
+if host_system == 'windows'
+  btree_gin_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'btree_gin',
+    '--FILEDESC', 'btree_gin - B-tree equivalent GIN operator classes',])
+endif
+
 btree_gin = shared_module('btree_gin',
-  files('btree_gin.c'),
+  btree_gin_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += btree_gin
diff --git a/contrib/btree_gist/meson.build b/contrib/btree_gist/meson.build
index c0a8d238540..e98c91dacc8 100644
--- a/contrib/btree_gist/meson.build
+++ b/contrib/btree_gist/meson.build
@@ -25,6 +25,12 @@ btree_gist_sources = files(
   'btree_uuid.c',
 )
 
+if host_system == 'windows'
+  btree_gist_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'btree_gist',
+    '--FILEDESC', 'btree_gist - B-tree equivalent GiST operator classes',])
+endif
+
 btree_gist = shared_module('btree_gist',
   btree_gist_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/citext/meson.build b/contrib/citext/meson.build
index ca60eded80b..26a101a19bd 100644
--- a/contrib/citext/meson.build
+++ b/contrib/citext/meson.build
@@ -2,6 +2,12 @@ citext_sources = files(
   'citext.c',
 )
 
+if host_system == 'windows'
+  citext_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'citext',
+    '--FILEDESC', 'citext - case-insensitive character string data type',])
+endif
+
 citext = shared_module('citext',
   citext_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/cube/meson.build b/contrib/cube/meson.build
index 72342b0c82c..041acf95a90 100644
--- a/contrib/cube/meson.build
+++ b/contrib/cube/meson.build
@@ -17,6 +17,12 @@ cube_parse = custom_target('cubeparse',
 generated_sources += cube_parse.to_list()
 cube_sources += cube_parse
 
+if host_system == 'windows'
+  cube_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'cube',
+    '--FILEDESC', 'cube - multidimensional cube data type',])
+endif
+
 cube = shared_module('cube',
   cube_sources,
   include_directories: include_directories('.'),
diff --git a/contrib/dblink/meson.build b/contrib/dblink/meson.build
index d35f7b5d49e..66eeb03b736 100644
--- a/contrib/dblink/meson.build
+++ b/contrib/dblink/meson.build
@@ -2,6 +2,12 @@ dblink_sources = files(
   'dblink.c',
 )
 
+if host_system == 'windows'
+  dblink_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'dblink',
+    '--FILEDESC', 'dblink - connect to other PostgreSQL databases',])
+endif
+
 dblink = shared_module('dblink',
   dblink_sources,
   kwargs: contrib_mod_args + {
diff --git a/contrib/dict_int/meson.build b/contrib/dict_int/meson.build
index f00e8085619..6fff921adda 100644
--- a/contrib/dict_int/meson.build
+++ b/contrib/dict_int/meson.build
@@ -1,5 +1,15 @@
+dict_int_sources = files(
+  'dict_int.c',
+)
+
+if host_system == 'windows'
+  dict_int_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'dict_int',
+    '--FILEDESC', 'dict_int - add-on dictionary template for full-text search',])
+endif
+
 dict_int = shared_module('dict_int',
-  files('dict_int.c'),
+  dict_int_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += dict_int
diff --git a/contrib/dict_xsyn/meson.build b/contrib/dict_xsyn/meson.build
index be53f55bb79..fabd505a7df 100644
--- a/contrib/dict_xsyn/meson.build
+++ b/contrib/dict_xsyn/meson.build
@@ -1,5 +1,15 @@
+dict_xsyn_sources = files(
+  'dict_xsyn.c',
+)
+
+if host_system == 'windows'
+  dict_xsyn_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'dict_xsyn',
+    '--FILEDESC', 'dict_xsyn - add-on dictionary template for full-text search',])
+endif
+
 dict_xsyn = shared_module('dict_xsyn',
-  files('dict_xsyn.c'),
+  dict_xsyn_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += dict_xsyn
diff --git a/contrib/earthdistance/meson.build b/contrib/earthdistance/meson.build
index 807f5cb7de3..78dc29c3da3 100644
--- a/contrib/earthdistance/meson.build
+++ b/contrib/earthdistance/meson.build
@@ -1,5 +1,15 @@
+earthdistance_sources = files(
+  'earthdistance.c',
+)
+
+if host_system == 'windows'
+  earthdistance_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'earthdistance',
+    '--FILEDESC', 'earthdistance - calculate distances on the surface of the Earth',])
+endif
+
 earthdistance = shared_module('earthdistance',
-  files('earthdistance.c'),
+  earthdistance_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += earthdistance
diff --git a/contrib/file_fdw/meson.build b/contrib/file_fdw/meson.build
index f13efb6e38e..c4071faa669 100644
--- a/contrib/file_fdw/meson.build
+++ b/contrib/file_fdw/meson.build
@@ -1,5 +1,15 @@
+file_fdw_sources = files(
+  'file_fdw.c',
+)
+
+if host_system == 'windows'
+  file_fdw_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'file_fdw',
+    '--FILEDESC', 'file_fdw - foreign data wrapper for files',])
+endif
+
 file_fdw = shared_module('file_fdw',
-  files('file_fdw.c'),
+  file_fdw_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += file_fdw
diff --git a/contrib/fuzzystrmatch/meson.build b/contrib/fuzzystrmatch/meson.build
index ec278a6211e..e6d06149cec 100644
--- a/contrib/fuzzystrmatch/meson.build
+++ b/contrib/fuzzystrmatch/meson.build
@@ -1,8 +1,16 @@
+fuzzystrmatch_sources = files(
+  'fuzzystrmatch.c',
+  'dmetaphone.c',
+)
+
+if host_system == 'windows'
+  fuzzystrmatch_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'fuzzystrmatch',
+    '--FILEDESC', 'fuzzystrmatch - similarities and distance between strings',])
+endif
+
 fuzzystrmatch = shared_module('fuzzystrmatch',
-  files(
-    'fuzzystrmatch.c',
-    'dmetaphone.c'
-  ),
+  fuzzystrmatch_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += fuzzystrmatch
diff --git a/contrib/hstore/meson.build b/contrib/hstore/meson.build
index 07c59f40a97..2bb26bb772b 100644
--- a/contrib/hstore/meson.build
+++ b/contrib/hstore/meson.build
@@ -1,15 +1,23 @@
 # .. so that includes of hstore/hstore.h work
 hstore_inc = include_directories('.', '../')
 
+hstore_sources = files(
+  'hstore_compat.c',
+  'hstore_gin.c',
+  'hstore_gist.c',
+  'hstore_io.c',
+  'hstore_op.c',
+  'hstore_subs.c',
+)
+
+if host_system == 'windows'
+  hstore_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'hstore',
+    '--FILEDESC', 'hstore - key/value pair data type',])
+endif
+
 hstore = shared_module('hstore',
-  files(
-    'hstore_compat.c',
-    'hstore_gin.c',
-    'hstore_gist.c',
-    'hstore_io.c',
-    'hstore_op.c',
-    'hstore_subs.c',
-  ),
+  hstore_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += hstore
diff --git a/contrib/hstore_plperl/meson.build b/contrib/hstore_plperl/meson.build
index bbafa0221bd..a238fee6a26 100644
--- a/contrib/hstore_plperl/meson.build
+++ b/contrib/hstore_plperl/meson.build
@@ -6,6 +6,12 @@ hstore_plperl_sources = files(
   'hstore_plperl.c',
 )
 
+if host_system == 'windows'
+  hstore_plperl_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'hstore_plperl',
+    '--FILEDESC', 'hstore_plperl - hstore transform for plperl',])
+endif
+
 hstore_plperl = shared_module('hstore_plperl',
   hstore_plperl_sources,
   include_directories: [plperl_inc, hstore_inc],
diff --git a/contrib/hstore_plpython/meson.build b/contrib/hstore_plpython/meson.build
index 214b48519a9..6071aaeb4b3 100644
--- a/contrib/hstore_plpython/meson.build
+++ b/contrib/hstore_plpython/meson.build
@@ -6,6 +6,12 @@ hstore_plpython_sources = files(
   'hstore_plpython.c',
 )
 
+if host_system == 'windows'
+  hstore_plpython_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'hstore_plpython3',
+    '--FILEDESC', 'hstore_plpython - hstore transform for plpython',])
+endif
+
 hstore_plpython = shared_module('hstore_plpython3',
   hstore_plpython_sources,
   include_directories: [plpython_inc, hstore_inc, ],
diff --git a/contrib/intarray/meson.build b/contrib/intarray/meson.build
index 1655bcbb3fd..b7cf1ce0cad 100644
--- a/contrib/intarray/meson.build
+++ b/contrib/intarray/meson.build
@@ -8,6 +8,12 @@ intarray_sources = files(
   '_intbig_gist.c',
 )
 
+if host_system == 'windows'
+  intarray_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', '_int',
+    '--FILEDESC', 'intarray - functions and operators for arrays of integers',])
+endif
+
 intarray = shared_module('_int',
   intarray_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/isn/meson.build b/contrib/isn/meson.build
index cc30bbeb55c..db68a718313 100644
--- a/contrib/isn/meson.build
+++ b/contrib/isn/meson.build
@@ -2,6 +2,12 @@ isn_sources = files(
   'isn.c',
 )
 
+if host_system == 'windows'
+  isn_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'isn',
+    '--FILEDESC', 'isn - data types for international product numbering standards',])
+endif
+
 isn = shared_module('isn',
   isn_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/jsonb_plperl/meson.build b/contrib/jsonb_plperl/meson.build
index 5c915d8ed94..071a7a98d2c 100644
--- a/contrib/jsonb_plperl/meson.build
+++ b/contrib/jsonb_plperl/meson.build
@@ -6,6 +6,12 @@ jsonb_plperl_sources = files(
   'jsonb_plperl.c',
 )
 
+if host_system == 'windows'
+  jsonb_plperl_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'jsonb_plperl',
+    '--FILEDESC', 'jsonb_plperl - jsonb transform for plperl',])
+endif
+
 jsonb_plperl = shared_module('jsonb_plperl',
   jsonb_plperl_sources,
   include_directories: [plperl_inc],
diff --git a/contrib/jsonb_plpython/meson.build b/contrib/jsonb_plpython/meson.build
index de8e1105c6a..84dc1161e8b 100644
--- a/contrib/jsonb_plpython/meson.build
+++ b/contrib/jsonb_plpython/meson.build
@@ -6,6 +6,12 @@ jsonb_plpython_sources = files(
   'jsonb_plpython.c',
 )
 
+if host_system == 'windows'
+  jsonb_plpython_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'jsonb_plpython3',
+    '--FILEDESC', 'jsonb_plpython - jsonb transform for plpython',])
+endif
+
 jsonb_plpython = shared_module('jsonb_plpython3',
   jsonb_plpython_sources,
   include_directories: [plpython_inc],
diff --git a/contrib/lo/meson.build b/contrib/lo/meson.build
index 9082d5713c7..61ae131f1cc 100644
--- a/contrib/lo/meson.build
+++ b/contrib/lo/meson.build
@@ -2,6 +2,12 @@ lo_sources = files(
   'lo.c',
 )
 
+if host_system == 'windows'
+  lo_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'lo',
+    '--FILEDESC', 'lo - management for large objects',])
+endif
+
 lo = shared_module('lo',
   lo_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/ltree/meson.build b/contrib/ltree/meson.build
index 9463fc2c5e5..421292cea9d 100644
--- a/contrib/ltree/meson.build
+++ b/contrib/ltree/meson.build
@@ -13,6 +13,12 @@ ltree_sources = files(
 # .. so that includes of ltree/ltree.h work
 ltree_inc = include_directories('.', '../')
 
+if host_system == 'windows'
+  ltree_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'ltree',
+    '--FILEDESC', 'ltree - hierarchical label data type',])
+endif
+
 ltree = shared_module('ltree',
   ltree_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/ltree_plpython/meson.build b/contrib/ltree_plpython/meson.build
index 429d75006aa..acf5e4a6fc8 100644
--- a/contrib/ltree_plpython/meson.build
+++ b/contrib/ltree_plpython/meson.build
@@ -6,6 +6,12 @@ ltree_plpython_sources = files(
   'ltree_plpython.c',
 )
 
+if host_system == 'windows'
+  ltree_plpython_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'ltree_plpython3',
+    '--FILEDESC', 'ltree_plpython - ltree transform for plpython',])
+endif
+
 ltree_plpython = shared_module('ltree_plpython3',
   ltree_plpython_sources,
   include_directories: [plpython_inc, ltree_inc],
diff --git a/contrib/oid2name/meson.build b/contrib/oid2name/meson.build
index 1dad5d8f6e7..1a248f19260 100644
--- a/contrib/oid2name/meson.build
+++ b/contrib/oid2name/meson.build
@@ -1,5 +1,15 @@
+oid2name_sources = files(
+  'oid2name.c',
+)
+
+if host_system == 'windows'
+  oid2name_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'oid2name',
+    '--FILEDESC', 'oid2name - examine the file structure',])
+endif
+
 oid2name = executable('oid2name',
-  ['oid2name.c'],
+  oid2name_sources,
   dependencies: [frontend_code, libpq],
   kwargs: default_bin_args,
 )
diff --git a/contrib/old_snapshot/meson.build b/contrib/old_snapshot/meson.build
index 8e7ee09a43a..77276c3715a 100644
--- a/contrib/old_snapshot/meson.build
+++ b/contrib/old_snapshot/meson.build
@@ -2,6 +2,12 @@ old_snapshot_sources = files(
   'time_mapping.c',
 )
 
+if host_system == 'windows'
+  old_snapshot_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'old_snapshot',
+    '--FILEDESC', 'old_snapshot - utilities in support of old_snapshot_threshold',])
+endif
+
 old_snapshot = shared_module('old_snapshot',
   old_snapshot_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/pageinspect/meson.build b/contrib/pageinspect/meson.build
index 4af8153e4fd..3ec50b9445e 100644
--- a/contrib/pageinspect/meson.build
+++ b/contrib/pageinspect/meson.build
@@ -9,6 +9,12 @@ pageinspect_sources = files(
   'rawpage.c',
 )
 
+if host_system == 'windows'
+  pageinspect_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pageinspect',
+    '--FILEDESC', 'pageinspect - functions to inspect contents of database pages',])
+endif
+
 pageinspect = shared_module('pageinspect',
   pageinspect_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/passwordcheck/meson.build b/contrib/passwordcheck/meson.build
index 7da47d02f1d..383d7df372a 100644
--- a/contrib/passwordcheck/meson.build
+++ b/contrib/passwordcheck/meson.build
@@ -9,6 +9,12 @@ passwordcheck_deps = []
 # passwordcheck_c_args += ['-DUSE_CRACKLIB', '-DCRACKLIB_DICTPATH="/usr/lib/cracklib_dict"']
 # passwordcheck_deps += [cc.find_library('crack')]
 
+if host_system == 'windows'
+  passwordcheck_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'passwordcheck',
+    '--FILEDESC', 'passwordcheck - strengthen user password checks',])
+endif
+
 passwordcheck = shared_module('passwordcheck',
   passwordcheck_sources,
   c_args: passwordcheck_c_args,
diff --git a/contrib/pg_buffercache/meson.build b/contrib/pg_buffercache/meson.build
index 2c69eae3ea2..dd9948e5f0b 100644
--- a/contrib/pg_buffercache/meson.build
+++ b/contrib/pg_buffercache/meson.build
@@ -1,7 +1,15 @@
+pg_buffercache_sources = files(
+  'pg_buffercache_pages.c',
+)
+
+if host_system == 'windows'
+  pg_buffercache_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_buffercache',
+    '--FILEDESC', 'pg_buffercache - monitoring of shared buffer cache in real-time',])
+endif
+
 pg_buffercache = shared_module('pg_buffercache',
-  files(
-    'pg_buffercache_pages.c',
-  ),
+  pg_buffercache_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += pg_buffercache
diff --git a/contrib/pg_freespacemap/meson.build b/contrib/pg_freespacemap/meson.build
index f795014d7ca..904b37b6e9b 100644
--- a/contrib/pg_freespacemap/meson.build
+++ b/contrib/pg_freespacemap/meson.build
@@ -1,7 +1,15 @@
+pg_freespacemap_sources = files(
+  'pg_freespacemap.c',
+)
+
+if host_system == 'windows'
+  pg_freespacemap_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_freespacemap',
+    '--FILEDESC', 'pg_freespacemap - monitoring of free space map',])
+endif
+
 pg_freespacemap = shared_module('pg_freespacemap',
-  files(
-    'pg_freespacemap.c',
-  ),
+  pg_freespacemap_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += pg_freespacemap
diff --git a/contrib/pg_prewarm/meson.build b/contrib/pg_prewarm/meson.build
index bdca9af4f27..b7140cee34b 100644
--- a/contrib/pg_prewarm/meson.build
+++ b/contrib/pg_prewarm/meson.build
@@ -1,8 +1,16 @@
+pg_prewarm_sources = files(
+  'autoprewarm.c',
+  'pg_prewarm.c',
+)
+
+if host_system == 'windows'
+  pg_prewarm_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_prewarm',
+    '--FILEDESC', 'pg_prewarm - preload relation data into system buffer cache',])
+endif
+
 pg_prewarm = shared_module('pg_prewarm',
-  files(
-    'autoprewarm.c',
-    'pg_prewarm.c',
-  ),
+  pg_prewarm_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += pg_prewarm
diff --git a/contrib/pg_stat_statements/meson.build b/contrib/pg_stat_statements/meson.build
index ac117d2fc1d..854df138e76 100644
--- a/contrib/pg_stat_statements/meson.build
+++ b/contrib/pg_stat_statements/meson.build
@@ -1,5 +1,15 @@
+pg_stat_statements_sources = files(
+  'pg_stat_statements.c',
+)
+
+if host_system == 'windows'
+  pg_stat_statements_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_stat_statements',
+    '--FILEDESC', 'pg_stat_statements - execution statistics of SQL statements',])
+endif
+
 pg_stat_statements = shared_module('pg_stat_statements',
-  files('pg_stat_statements.c'),
+  pg_stat_statements_sources,
   kwargs: contrib_mod_args + {
     'dependencies': contrib_mod_args['dependencies'],
   },
diff --git a/contrib/pg_surgery/meson.build b/contrib/pg_surgery/meson.build
index ac71caa5276..7b5c5999f4b 100644
--- a/contrib/pg_surgery/meson.build
+++ b/contrib/pg_surgery/meson.build
@@ -1,7 +1,15 @@
+pg_surgery_sources = files(
+  'heap_surgery.c',
+)
+
+if host_system == 'windows'
+  pg_surgery_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_surgery',
+    '--FILEDESC', 'pg_surgery - perform surgery on a damaged relation',])
+endif
+
 pg_surgery = shared_module('pg_surgery',
-  files(
-    'heap_surgery.c',
-  ),
+  pg_surgery_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += pg_surgery
diff --git a/contrib/pg_trgm/meson.build b/contrib/pg_trgm/meson.build
index a90628d23c6..c8c7c07b308 100644
--- a/contrib/pg_trgm/meson.build
+++ b/contrib/pg_trgm/meson.build
@@ -1,10 +1,18 @@
+pg_trgm_sources = files(
+  'trgm_gin.c',
+  'trgm_gist.c',
+  'trgm_op.c',
+  'trgm_regexp.c',
+)
+
+if host_system == 'windows'
+  pg_trgm_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_trgm',
+    '--FILEDESC', 'pg_trgm - trigram matching',])
+endif
+
 pg_trgm = shared_module('pg_trgm',
-  files(
-    'trgm_gin.c',
-    'trgm_gist.c',
-    'trgm_op.c',
-    'trgm_regexp.c',
-  ),
+  pg_trgm_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += pg_trgm
diff --git a/contrib/pg_visibility/meson.build b/contrib/pg_visibility/meson.build
index 933dc99ac4d..263a0d08b82 100644
--- a/contrib/pg_visibility/meson.build
+++ b/contrib/pg_visibility/meson.build
@@ -1,7 +1,15 @@
+pg_visibility_sources = files(
+  'pg_visibility.c',
+)
+
+if host_system == 'windows'
+  pg_visibility_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_visibility',
+    '--FILEDESC', 'pg_visibility - page visibility information',])
+endif
+
 pg_visibility = shared_module('pg_visibility',
-  files(
-    'pg_visibility.c',
-  ),
+  pg_visibility_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += pg_visibility
diff --git a/contrib/pg_walinspect/meson.build b/contrib/pg_walinspect/meson.build
index d6b27877dd0..4314a3182a2 100644
--- a/contrib/pg_walinspect/meson.build
+++ b/contrib/pg_walinspect/meson.build
@@ -1,5 +1,11 @@
 pg_walinspect_sources = files('pg_walinspect.c')
 
+if host_system == 'windows'
+  pg_walinspect_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_walinspect',
+    '--FILEDESC', 'pg_walinspect - functions to inspect contents of PostgreSQL Write-Ahead Log',])
+endif
+
 pg_walinspect = shared_module('pg_walinspect',
   pg_walinspect_sources,
   kwargs: contrib_mod_args + {
diff --git a/contrib/pgcrypto/meson.build b/contrib/pgcrypto/meson.build
index fe0851bf8e8..7fc7bbc7ca1 100644
--- a/contrib/pgcrypto/meson.build
+++ b/contrib/pgcrypto/meson.build
@@ -69,6 +69,12 @@ else
   pgcrypto_regress += 'pgp-zlib-DISABLED'
 endif
 
+if host_system == 'windows'
+  pgcrypto_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pgcrypto',
+    '--FILEDESC', 'pgcrypto - cryptographic functions',])
+endif
+
 pgcrypto = shared_module('pgcrypto',
   pgcrypto_sources,
   link_with: pgcrypto_link_with,
diff --git a/contrib/pgrowlocks/meson.build b/contrib/pgrowlocks/meson.build
index 1b41691a2a3..8092f0d4a64 100644
--- a/contrib/pgrowlocks/meson.build
+++ b/contrib/pgrowlocks/meson.build
@@ -1,7 +1,15 @@
+pgrowlocks_sources = files(
+  'pgrowlocks.c',
+)
+
+if host_system == 'windows'
+  pgrowlocks_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pgrowlocks',
+    '--FILEDESC', 'pgrowlocks - display row locking information',])
+endif
+
 pgrowlocks = shared_module('pgrowlocks',
-  files(
-    'pgrowlocks.c',
-  ),
+  pgrowlocks_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += pgrowlocks
diff --git a/contrib/pgstattuple/meson.build b/contrib/pgstattuple/meson.build
index 8e828692d5c..05e4cd46a5c 100644
--- a/contrib/pgstattuple/meson.build
+++ b/contrib/pgstattuple/meson.build
@@ -1,9 +1,17 @@
+pgstattuple_sources = files(
+  'pgstatapprox.c',
+  'pgstatindex.c',
+  'pgstattuple.c',
+)
+
+if host_system == 'windows'
+  pgstattuple_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pgstattuple',
+    '--FILEDESC', 'pgstattuple - tuple-level statistics',])
+endif
+
 pgstattuple = shared_module('pgstattuple',
-  files(
-    'pgstatapprox.c',
-    'pgstatindex.c',
-    'pgstattuple.c',
-  ),
+  pgstattuple_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += pgstattuple
diff --git a/contrib/postgres_fdw/meson.build b/contrib/postgres_fdw/meson.build
index 378885ec93b..d3746ff135c 100644
--- a/contrib/postgres_fdw/meson.build
+++ b/contrib/postgres_fdw/meson.build
@@ -6,6 +6,12 @@ postgres_fdw_sources = files(
   'shippable.c',
 )
 
+if host_system == 'windows'
+  postgres_fdw_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'postgres_fdw',
+    '--FILEDESC', 'postgres_fdw - foreign data wrapper for PostgreSQL',])
+endif
+
 postgres_fdw = shared_module('postgres_fdw',
   postgres_fdw_sources,
   kwargs: contrib_mod_args + {
diff --git a/contrib/seg/meson.build b/contrib/seg/meson.build
index e476eab2a77..c6fbb22999b 100644
--- a/contrib/seg/meson.build
+++ b/contrib/seg/meson.build
@@ -17,6 +17,12 @@ seg_parse = custom_target('segparse',
 generated_sources += seg_parse.to_list()
 seg_sources += seg_parse
 
+if host_system == 'windows'
+  seg_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'seg',
+    '--FILEDESC', 'seg - line segment data type',])
+endif
+
 seg = shared_module('seg',
   seg_sources,
   include_directories: include_directories('.'),
diff --git a/contrib/sepgsql/meson.build b/contrib/sepgsql/meson.build
index 60a95e17c2f..8bef239e3c2 100644
--- a/contrib/sepgsql/meson.build
+++ b/contrib/sepgsql/meson.build
@@ -14,6 +14,12 @@ sepgsql_sources = files(
   'uavc.c',
 )
 
+if host_system == 'windows'
+  sepgsql_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'sepgsql',
+    '--FILEDESC', 'sepgsql - SELinux integration',])
+endif
+
 sepgsql = shared_module('sepgsql',
   sepgsql_sources,
   kwargs: contrib_mod_args + {
diff --git a/contrib/spi/meson.build b/contrib/spi/meson.build
index 98008980ec2..e7d78189ef5 100644
--- a/contrib/spi/meson.build
+++ b/contrib/spi/meson.build
@@ -1,5 +1,15 @@
+autoinc_sources = files(
+  'autoinc.c',
+)
+
+if host_system == 'windows'
+  autoinc_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'autoinc',
+    '--FILEDESC', 'spi - examples of using SPI and triggers',])
+endif
+
 autoinc = shared_module('autoinc',
-  ['autoinc.c'],
+  autoinc_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += autoinc
@@ -9,8 +19,18 @@ install_data('autoinc.control', 'autoinc--1.0.sql',
 )
 
 
+insert_username_sources = files(
+  'insert_username.c',
+)
+
+if host_system == 'windows'
+  insert_username_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'insert_username',
+    '--FILEDESC', 'spi - examples of using SPI and triggers',])
+endif
+
 insert_username = shared_module('insert_username',
-  ['insert_username.c'],
+  insert_username_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += insert_username
@@ -22,8 +42,18 @@ install_data(
 )
 
 
+moddatetime_sources = files(
+  'moddatetime.c',
+)
+
+if host_system == 'windows'
+  moddatetime_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'moddatetime',
+    '--FILEDESC', 'spi - examples of using SPI and triggers',])
+endif
+
 moddatetime = shared_module('moddatetime',
-  ['moddatetime.c'],
+  moddatetime_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += moddatetime
@@ -38,8 +68,18 @@ install_data(
 # comment out if you want a quieter refint package for other uses
 refint_cflags = ['-DREFINT_VERBOSE']
 
+refint_sources = files(
+  'refint.c',
+)
+
+if host_system == 'windows'
+  refint_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'refint',
+    '--FILEDESC', 'spi - examples of using SPI and triggers',])
+endif
+
 refint = shared_module('refint',
-  ['refint.c'],
+  refint_sources,
   c_args: refint_cflags,
   kwargs: contrib_mod_args,
 )
diff --git a/contrib/sslinfo/meson.build b/contrib/sslinfo/meson.build
index 53f752a08ac..136983e783d 100644
--- a/contrib/sslinfo/meson.build
+++ b/contrib/sslinfo/meson.build
@@ -2,10 +2,18 @@ if not ssl.found()
   subdir_done()
 endif
 
+sslinfo_sources = files(
+  'sslinfo.c',
+)
+
+if host_system == 'windows'
+  sslinfo_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'sslinfo',
+    '--FILEDESC', 'sslinfo - information about client SSL certificate',])
+endif
+
 sslinfo = shared_module('sslinfo',
-  files(
-    'sslinfo.c',
-  ),
+  sslinfo_sources,
   kwargs: contrib_mod_args + {
     'dependencies': [ssl, contrib_mod_args['dependencies']],
   }
diff --git a/contrib/tablefunc/meson.build b/contrib/tablefunc/meson.build
index f4230096c0c..d2ddc8d3b39 100644
--- a/contrib/tablefunc/meson.build
+++ b/contrib/tablefunc/meson.build
@@ -1,7 +1,15 @@
+tablefunc_sources = files(
+  'tablefunc.c',
+)
+
+if host_system == 'windows'
+  tablefunc_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'tablefunc',
+    '--FILEDESC', 'tablefunc - various functions that return tables',])
+endif
+
 tablefunc = shared_module('tablefunc',
-  files(
-    'tablefunc.c',
-  ),
+  tablefunc_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += tablefunc
diff --git a/contrib/tcn/meson.build b/contrib/tcn/meson.build
index c3a025247d4..71261c3b0a2 100644
--- a/contrib/tcn/meson.build
+++ b/contrib/tcn/meson.build
@@ -1,7 +1,15 @@
+tcn_sources = files(
+  'tcn.c',
+)
+
+if host_system == 'windows'
+  tcn_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'tcn',
+    '--FILEDESC', 'tcn - trigger function notifying listeners',])
+endif
+
 tcn = shared_module('tcn',
-  files(
-    'tcn.c',
-  ),
+  tcn_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += tcn
diff --git a/contrib/test_decoding/meson.build b/contrib/test_decoding/meson.build
index dd7cb0101ad..6376103c689 100644
--- a/contrib/test_decoding/meson.build
+++ b/contrib/test_decoding/meson.build
@@ -2,6 +2,12 @@ test_decoding_sources = files(
   'test_decoding.c',
 )
 
+if host_system == 'windows'
+  test_decoding_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_decoding',
+    '--FILEDESC', 'test_decoding - example of a logical decoding output plugin',])
+endif
+
 test_decoding = shared_module('test_decoding',
   test_decoding_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/tsm_system_rows/meson.build b/contrib/tsm_system_rows/meson.build
index b9cd42115a8..380abb49883 100644
--- a/contrib/tsm_system_rows/meson.build
+++ b/contrib/tsm_system_rows/meson.build
@@ -1,7 +1,15 @@
+tsm_system_rows_sources = files(
+  'tsm_system_rows.c',
+)
+
+if host_system == 'windows'
+  tsm_system_rows_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'tsm_system_rows',
+    '--FILEDESC', 'tsm_system_rows - TABLESAMPLE method which accepts number of rows as a limit',])
+endif
+
 tsm_system_rows = shared_module('tsm_system_rows',
-  files(
-    'tsm_system_rows.c',
-  ),
+  tsm_system_rows_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += tsm_system_rows
diff --git a/contrib/tsm_system_time/meson.build b/contrib/tsm_system_time/meson.build
index 18015912ffb..e57a2702c60 100644
--- a/contrib/tsm_system_time/meson.build
+++ b/contrib/tsm_system_time/meson.build
@@ -1,7 +1,15 @@
+tsm_system_time_sources = files(
+  'tsm_system_time.c',
+)
+
+if host_system == 'windows'
+  tsm_system_time_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'tsm_system_time',
+    '--FILEDESC', 'tsm_system_time - TABLESAMPLE method which accepts time in milliseconds as a limit',])
+endif
+
 tsm_system_time = shared_module('tsm_system_time',
-  files(
-    'tsm_system_time.c',
-  ),
+  tsm_system_time_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += tsm_system_time
diff --git a/contrib/unaccent/meson.build b/contrib/unaccent/meson.build
index 872b76e3223..438035132f8 100644
--- a/contrib/unaccent/meson.build
+++ b/contrib/unaccent/meson.build
@@ -1,7 +1,15 @@
+unaccent_sources = files(
+  'unaccent.c',
+)
+
+if host_system == 'windows'
+  unaccent_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'unaccent',
+    '--FILEDESC', 'unaccent - text search dictionary that removes accents',])
+endif
+
 unaccent = shared_module('unaccent',
-  files(
-    'unaccent.c',
-  ),
+  unaccent_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += unaccent
diff --git a/contrib/uuid-ossp/meson.build b/contrib/uuid-ossp/meson.build
index da6d1d75c12..28730f398f0 100644
--- a/contrib/uuid-ossp/meson.build
+++ b/contrib/uuid-ossp/meson.build
@@ -2,10 +2,18 @@ if not uuid.found()
   subdir_done()
 endif
 
+uuid_ossp_sources = files(
+  'uuid-ossp.c',
+)
+
+if host_system == 'windows'
+  uuid_ossp_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'uuid-ossp',
+    '--FILEDESC', 'uuid-ossp - UUID generation',])
+endif
+
 uuid_ossp = shared_module('uuid-ossp',
-  files(
-    'uuid-ossp.c',
-  ),
+  uuid_ossp_sources,
   kwargs: contrib_mod_args + {
     'dependencies': [uuid, contrib_mod_args['dependencies']],
   },
diff --git a/contrib/vacuumlo/meson.build b/contrib/vacuumlo/meson.build
index 7a632b87d1b..846de47dbd1 100644
--- a/contrib/vacuumlo/meson.build
+++ b/contrib/vacuumlo/meson.build
@@ -1,5 +1,15 @@
+vacuumlo_sources = files(
+  'vacuumlo.c',
+)
+
+if host_system == 'windows'
+  vacuumlo_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'vacuumlo',
+    '--FILEDESC', 'vacuumlo - removes orphaned large objects',])
+endif
+
 vacuumlo = executable('vacuumlo',
-  ['vacuumlo.c'],
+  vacuumlo_sources,
   dependencies: [frontend_code, libpq],
   kwargs: default_bin_args,
 )
diff --git a/contrib/xml2/meson.build b/contrib/xml2/meson.build
index 9c0b56f01f6..89b0d677516 100644
--- a/contrib/xml2/meson.build
+++ b/contrib/xml2/meson.build
@@ -2,11 +2,19 @@ if not libxml.found()
   subdir_done()
 endif
 
+xml2_sources = files(
+  'xpath.c',
+  'xslt_proc.c',
+)
+
+if host_system == 'windows'
+  xml2_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pgxml',
+    '--FILEDESC', 'xml2 - XPath querying and XSLT',])
+endif
+
 xml2 = shared_module('pgxml',
-  files(
-    'xpath.c',
-    'xslt_proc.c',
-  ),
+  xml2_sources,
   kwargs: contrib_mod_args + {
     'dependencies': [libxml, libxslt, contrib_mod_args['dependencies']],
   },
diff --git a/src/interfaces/ecpg/compatlib/meson.build b/src/interfaces/ecpg/compatlib/meson.build
index 7aeb627ee86..aa9ab497832 100644
--- a/src/interfaces/ecpg/compatlib/meson.build
+++ b/src/interfaces/ecpg/compatlib/meson.build
@@ -7,6 +7,12 @@ ecpg_compat_inc = ['.', ecpg_inc, postgres_inc, libpq_inc]
 ecpg_compat_c_args = ['-DSO_MAJOR_VERSION=3']
 export_file = custom_target('libecpg_compat.exports', kwargs: gen_export_kwargs)
 
+if host_system == 'windows'
+  ecpg_compat_so_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'libecpg_compat',
+    '--FILEDESC', 'ECPG compat - compatibility library for ECPG',])
+endif
+
 # not using both_libraries(), as the resource file should only be in the
 # shared library
 ecpg_compat_st = static_library('libecpg_compat',
diff --git a/src/interfaces/ecpg/ecpglib/meson.build b/src/interfaces/ecpg/ecpglib/meson.build
index eb8dd616571..fd40ee86610 100644
--- a/src/interfaces/ecpg/ecpglib/meson.build
+++ b/src/interfaces/ecpg/ecpglib/meson.build
@@ -16,6 +16,12 @@ ecpglib_inc = ['.', ecpg_inc, postgres_inc]
 ecpglib_c_args = ['-DSO_MAJOR_VERSION=6']
 export_file = custom_target('libecpg.exports', kwargs: gen_export_kwargs)
 
+if host_system == 'windows'
+  ecpglib_so_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'libecpg',
+    '--FILEDESC', 'ECPG - embedded SQL in C',])
+endif
+
 # not using both_libraries(), as the resource file should only be in the
 # shared library
 ecpglib_st = static_library('libecpg',
diff --git a/src/interfaces/ecpg/pgtypeslib/meson.build b/src/interfaces/ecpg/pgtypeslib/meson.build
index 5254843f84d..53cdad36422 100644
--- a/src/interfaces/ecpg/pgtypeslib/meson.build
+++ b/src/interfaces/ecpg/pgtypeslib/meson.build
@@ -12,6 +12,12 @@ export_file = custom_target('libpgtypes.exports', kwargs: gen_export_kwargs)
 ecpg_pgtypes_inc = ['.', ecpg_inc, postgres_inc]
 ecpg_pgtypes_c_args = ['-DSO_MAJOR_VERSION=3']
 
+if host_system == 'windows'
+  ecpg_pgtypes_so_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pgtypes',
+    '--FILEDESC', 'pgtypes - library for data type mapping',])
+endif
+
 # not using both_libraries(), as the resource file should only be in the
 # shared library
 ecpg_pgtypes_st = static_library('libpgtypes',
diff --git a/src/interfaces/ecpg/preproc/meson.build b/src/interfaces/ecpg/preproc/meson.build
index 1be49c8c27f..74876f039c9 100644
--- a/src/interfaces/ecpg/preproc/meson.build
+++ b/src/interfaces/ecpg/preproc/meson.build
@@ -93,6 +93,12 @@ ecpg_kwlist = custom_target('ecpg_kwlist_d.h',
 generated_sources += ecpg_kwlist
 ecpg_sources += ecpg_kwlist
 
+if host_system == 'windows'
+  ecpg_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'ecpg',
+    '--FILEDESC', 'ecpg - embedded SQL precompiler for C',])
+endif
+
 ecpg_exe = executable('ecpg',
   ecpg_sources,
   include_directories: ['.', ecpg_inc, postgres_inc, libpq_inc],
diff --git a/src/interfaces/ecpg/test/meson.build b/src/interfaces/ecpg/test/meson.build
index 8904aa7fd90..94b26d10314 100644
--- a/src/interfaces/ecpg/test/meson.build
+++ b/src/interfaces/ecpg/test/meson.build
@@ -7,6 +7,11 @@ pg_regress_ecpg_sources = pg_regress_c + files(
   'pg_regress_ecpg.c',
 )
 
+if host_system == 'windows'
+  pg_regress_ecpg_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_regress_ecpg',])
+endif
+
 pg_regress_ecpg = executable('pg_regress_ecpg',
   pg_regress_ecpg_sources,
   c_args: pg_regress_cflags,
diff --git a/src/test/isolation/meson.build b/src/test/isolation/meson.build
index c7656fd4609..ba27b8c1d44 100644
--- a/src/test/isolation/meson.build
+++ b/src/test/isolation/meson.build
@@ -23,6 +23,12 @@ spec_parser = custom_target('specparse',
 isolationtester_sources += spec_parser
 generated_sources += spec_parser.to_list()
 
+if host_system == 'windows'
+  isolation_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_isolation_regress',
+    '--FILEDESC', 'pg_isolation_regress - multi-client test driver',])
+endif
+
 pg_isolation_regress = executable('pg_isolation_regress',
   isolation_sources,
   c_args: pg_regress_cflags,
@@ -34,6 +40,13 @@ pg_isolation_regress = executable('pg_isolation_regress',
 )
 bin_targets += pg_isolation_regress
 
+
+if host_system == 'windows'
+  isolationtester_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'isolationtester',
+    '--FILEDESC', 'isolationtester - multi-client test driver',])
+endif
+
 isolationtester = executable('isolationtester',
   isolationtester_sources,
   include_directories: include_directories('.'),
diff --git a/src/test/modules/delay_execution/meson.build b/src/test/modules/delay_execution/meson.build
index cf4bdaba637..a0c3ab6afe7 100644
--- a/src/test/modules/delay_execution/meson.build
+++ b/src/test/modules/delay_execution/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+delay_execution_sources = files(
+  'delay_execution.c',
+)
+
+if host_system == 'windows'
+  delay_execution_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'delay_execution',
+    '--FILEDESC', 'delay_execution - allow delay between parsing and execution',])
+endif
+
 delay_execution = shared_module('delay_execution',
-  ['delay_execution.c'],
+  delay_execution_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += delay_execution
diff --git a/src/test/modules/dummy_index_am/meson.build b/src/test/modules/dummy_index_am/meson.build
index 56ff5f48001..4ce82491135 100644
--- a/src/test/modules/dummy_index_am/meson.build
+++ b/src/test/modules/dummy_index_am/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+dummy_index_am_sources = files(
+  'dummy_index_am.c',
+)
+
+if host_system == 'windows'
+  dummy_index_am_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'dummy_index_am',
+    '--FILEDESC', 'dummy_index_am - index access method template',])
+endif
+
 dummy_index_am = shared_module('dummy_index_am',
-  ['dummy_index_am.c'],
+  dummy_index_am_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += dummy_index_am
diff --git a/src/test/modules/dummy_seclabel/meson.build b/src/test/modules/dummy_seclabel/meson.build
index 21b7cf8f353..81b626e496c 100644
--- a/src/test/modules/dummy_seclabel/meson.build
+++ b/src/test/modules/dummy_seclabel/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+dummy_seclabel_sources = files(
+  'dummy_seclabel.c',
+)
+
+if host_system == 'windows'
+  dummy_seclabel_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'dummy_seclabel',
+    '--FILEDESC', 'dummy_seclabel - regression testing of the SECURITY LABEL statement',])
+endif
+
 dummy_seclabel = shared_module('dummy_seclabel',
-  ['dummy_seclabel.c'],
+  dummy_seclabel_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += dummy_seclabel
diff --git a/src/test/modules/libpq_pipeline/meson.build b/src/test/modules/libpq_pipeline/meson.build
index 8384b6e3b2a..de0e2d15626 100644
--- a/src/test/modules/libpq_pipeline/meson.build
+++ b/src/test/modules/libpq_pipeline/meson.build
@@ -1,7 +1,15 @@
+libpq_pipeline_sources = files(
+  'libpq_pipeline.c',
+)
+
+if host_system == 'windows'
+  libpq_pipeline_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'libpq_pipeline',
+    '--FILEDESC', 'libpq_pipeline - test program for pipeline execution',])
+endif
+
 libpq_pipeline = executable('libpq_pipeline',
-  files(
-    'libpq_pipeline.c',
-  ),
+  libpq_pipeline_sources,
   dependencies: [frontend_code, libpq],
   kwargs: default_bin_args + {
     'install': false,
diff --git a/src/test/modules/plsample/meson.build b/src/test/modules/plsample/meson.build
index 45de3f1990d..e1ea2c7a16f 100644
--- a/src/test/modules/plsample/meson.build
+++ b/src/test/modules/plsample/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+plsample_sources = files(
+  'plsample.c',
+)
+
+if host_system == 'windows'
+  plsample_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'plsample',
+    '--FILEDESC', 'PL/Sample - template for procedural language',])
+endif
+
 plsample = shared_module('plsample',
-  ['plsample.c'],
+  plsample_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += plsample
diff --git a/src/test/modules/spgist_name_ops/meson.build b/src/test/modules/spgist_name_ops/meson.build
index 857fc7e140e..445296fee0b 100644
--- a/src/test/modules/spgist_name_ops/meson.build
+++ b/src/test/modules/spgist_name_ops/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+spgist_name_ops_sources = files(
+  'spgist_name_ops.c',
+)
+
+if host_system == 'windows'
+  spgist_name_ops_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'spgist_name_ops',
+    '--FILEDESC', 'spgist_name_ops - test opclass for SP-GiST',])
+endif
+
 spgist_name_ops = shared_module('spgist_name_ops',
-  ['spgist_name_ops.c'],
+  spgist_name_ops_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += spgist_name_ops
diff --git a/src/test/modules/ssl_passphrase_callback/meson.build b/src/test/modules/ssl_passphrase_callback/meson.build
index a57bd0693a3..a9eb4c564da 100644
--- a/src/test/modules/ssl_passphrase_callback/meson.build
+++ b/src/test/modules/ssl_passphrase_callback/meson.build
@@ -3,8 +3,19 @@ if not ssl.found()
 endif
 
 # FIXME: prevent install during main install, but not during test :/
+
+ssl_passphrase_callback_sources = files(
+  'ssl_passphrase_func.c',
+)
+
+if host_system == 'windows'
+  ssl_passphrase_callback_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'ssl_passphrase_func',
+    '--FILEDESC', 'callback function to provide a passphrase',])
+endif
+
 ssl_passphrase_callback = shared_module('ssl_passphrase_func',
-  ['ssl_passphrase_func.c'],
+  ssl_passphrase_callback_sources,
   kwargs: pg_mod_args + {
     'dependencies': [ssl, pg_mod_args['dependencies']],
   },
diff --git a/src/test/modules/test_bloomfilter/meson.build b/src/test/modules/test_bloomfilter/meson.build
index 945eb5a70c4..3cf6b05754f 100644
--- a/src/test/modules/test_bloomfilter/meson.build
+++ b/src/test/modules/test_bloomfilter/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_bloomfilter_sources = files(
+  'test_bloomfilter.c',
+)
+
+if host_system == 'windows'
+  test_bloomfilter_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_bloomfilter',
+    '--FILEDESC', 'test_bloomfilter - test code for Bloom filter library',])
+endif
+
 test_bloomfilter = shared_module('test_bloomfilter',
-  ['test_bloomfilter.c'],
+  test_bloomfilter_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_bloomfilter
diff --git a/src/test/modules/test_ddl_deparse/meson.build b/src/test/modules/test_ddl_deparse/meson.build
index 81ad5adc526..54d44f9b2b4 100644
--- a/src/test/modules/test_ddl_deparse/meson.build
+++ b/src/test/modules/test_ddl_deparse/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_ddl_deparse_sources = files(
+  'test_ddl_deparse.c',
+)
+
+if host_system == 'windows'
+  test_ddl_deparse_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_ddl_deparse',
+    '--FILEDESC', 'test_ddl_deparse - regression testing for DDL deparsing',])
+endif
+
 test_ddl_deparse = shared_module('test_ddl_deparse',
-  ['test_ddl_deparse.c'],
+  test_ddl_deparse_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_ddl_deparse
diff --git a/src/test/modules/test_ginpostinglist/meson.build b/src/test/modules/test_ginpostinglist/meson.build
index abf0a3b0430..b3b49c56122 100644
--- a/src/test/modules/test_ginpostinglist/meson.build
+++ b/src/test/modules/test_ginpostinglist/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_ginpostinglist_sources = files(
+  'test_ginpostinglist.c',
+)
+
+if host_system == 'windows'
+  test_ginpostinglist_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_ginpostinglist',
+    '--FILEDESC', 'test_ginpostinglist - test code for src/backend/access/gin//ginpostinglist.c',])
+endif
+
 test_ginpostinglist = shared_module('test_ginpostinglist',
-  ['test_ginpostinglist.c'],
+  test_ginpostinglist_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_ginpostinglist
diff --git a/src/test/modules/test_integerset/meson.build b/src/test/modules/test_integerset/meson.build
index c32c469c69a..4bd75af4b5e 100644
--- a/src/test/modules/test_integerset/meson.build
+++ b/src/test/modules/test_integerset/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_integerset_sources = files(
+  'test_integerset.c',
+)
+
+if host_system == 'windows'
+  test_integerset_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_integerset',
+    '--FILEDESC', 'test_integerset - test code for src/backend/lib/integerset.c',])
+endif
+
 test_integerset = shared_module('test_integerset',
-  ['test_integerset.c'],
+  test_integerset_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_integerset
diff --git a/src/test/modules/test_lfind/meson.build b/src/test/modules/test_lfind/meson.build
index a388de1156a..c5405b8f878 100644
--- a/src/test/modules/test_lfind/meson.build
+++ b/src/test/modules/test_lfind/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_lfind_sources = files(
+  'test_lfind.c',
+)
+
+if host_system == 'windows'
+  test_lfind_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_lfind',
+    '--FILEDESC', 'test_lfind - test code for optimized linear search functions',])
+endif
+
 test_lfind = shared_module('test_lfind',
-  ['test_lfind.c'],
+  test_lfind_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_lfind
diff --git a/src/test/modules/test_oat_hooks/meson.build b/src/test/modules/test_oat_hooks/meson.build
index 5faf0459777..8802bbbac55 100644
--- a/src/test/modules/test_oat_hooks/meson.build
+++ b/src/test/modules/test_oat_hooks/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_oat_hooks_sources = files(
+  'test_oat_hooks.c',
+)
+
+if host_system == 'windows'
+  test_oat_hooks_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_oat_hooks',
+    '--FILEDESC', 'test_oat_hooks - example use of object access hooks',])
+endif
+
 test_oat_hooks = shared_module('test_oat_hooks',
-  ['test_oat_hooks.c'],
+  test_oat_hooks_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_oat_hooks
diff --git a/src/test/modules/test_parser/meson.build b/src/test/modules/test_parser/meson.build
index b59960f615e..1c17113347f 100644
--- a/src/test/modules/test_parser/meson.build
+++ b/src/test/modules/test_parser/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_parser_sources = files(
+  'test_parser.c',
+)
+
+if host_system == 'windows'
+  test_parser_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_parser',
+    '--FILEDESC', 'test_parser - example of a custom parser for full-text search',])
+endif
+
 test_parser = shared_module('test_parser',
-  ['test_parser.c'],
+  test_parser_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_parser
diff --git a/src/test/modules/test_predtest/meson.build b/src/test/modules/test_predtest/meson.build
index 1cfa84b3609..9a5be43c9c0 100644
--- a/src/test/modules/test_predtest/meson.build
+++ b/src/test/modules/test_predtest/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_predtest_sources = files(
+  'test_predtest.c',
+)
+
+if host_system == 'windows'
+  test_predtest_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_predtest',
+    '--FILEDESC', 'test_predtest - test code for optimizer/util/predtest.c',])
+endif
+
 test_predtest = shared_module('test_predtest',
-  ['test_predtest.c'],
+  test_predtest_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_predtest
diff --git a/src/test/modules/test_rbtree/meson.build b/src/test/modules/test_rbtree/meson.build
index 34cbc3e1624..f067e08d321 100644
--- a/src/test/modules/test_rbtree/meson.build
+++ b/src/test/modules/test_rbtree/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_rbtree_sources = files(
+  'test_rbtree.c',
+)
+
+if host_system == 'windows'
+  test_rbtree_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_rbtree',
+    '--FILEDESC', 'test_rbtree - test code for red-black tree library',])
+endif
+
 test_rbtree = shared_module('test_rbtree',
-  ['test_rbtree.c'],
+  test_rbtree_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_rbtree
diff --git a/src/test/modules/test_regex/meson.build b/src/test/modules/test_regex/meson.build
index 867a64e57c3..cfb938d9f1e 100644
--- a/src/test/modules/test_regex/meson.build
+++ b/src/test/modules/test_regex/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_regex_sources = files(
+  'test_regex.c',
+)
+
+if host_system == 'windows'
+  test_regex_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_regex',
+    '--FILEDESC', 'test_regex - test code for backend/regex/',])
+endif
+
 test_regex = shared_module('test_regex',
-  ['test_regex.c'],
+  test_regex_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_regex
diff --git a/src/test/modules/test_rls_hooks/meson.build b/src/test/modules/test_rls_hooks/meson.build
index 80d8adda332..3fb273b2934 100644
--- a/src/test/modules/test_rls_hooks/meson.build
+++ b/src/test/modules/test_rls_hooks/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_rls_hooks_sources = files(
+  'test_rls_hooks.c',
+)
+
+if host_system == 'windows'
+  test_rls_hooks_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_rls_hooks',
+    '--FILEDESC', 'test_rls_hooks - example use of RLS hooks',])
+endif
+
 test_rls_hooks = shared_module('test_rls_hooks',
-  ['test_rls_hooks.c'],
+  test_rls_hooks_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_rls_hooks
diff --git a/src/test/modules/test_shm_mq/meson.build b/src/test/modules/test_shm_mq/meson.build
index b663543d616..16c8fdb57f4 100644
--- a/src/test/modules/test_shm_mq/meson.build
+++ b/src/test/modules/test_shm_mq/meson.build
@@ -1,10 +1,19 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_shm_mq_sources = files(
+  'setup.c',
+  'test.c',
+  'worker.c',
+)
+
+if host_system == 'windows'
+  test_shm_mq_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_shm_mq',
+    '--FILEDESC', 'test_shm_mq - example use of shared memory message queue',])
+endif
+
 test_shm_mq = shared_module('test_shm_mq',
-  files(
-    'setup.c',
-    'test.c',
-    'worker.c',
-  ),
+  test_shm_mq_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_shm_mq
diff --git a/src/test/modules/worker_spi/meson.build b/src/test/modules/worker_spi/meson.build
index 32acad883b2..a4a158c75b9 100644
--- a/src/test/modules/worker_spi/meson.build
+++ b/src/test/modules/worker_spi/meson.build
@@ -1,8 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_worker_spi_sources = files(
+  'worker_spi.c',
+)
+
+if host_system == 'windows'
+  test_worker_spi_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'worker_spi',
+    '--FILEDESC', 'worker_spi - background worker example',])
+endif
+
 test_worker_spi = shared_module('worker_spi',
-  files(
-    'worker_spi.c',
-  ),
+  test_worker_spi_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_worker_spi
diff --git a/src/test/regress/meson.build b/src/test/regress/meson.build
index fd8ee995b79..963c0f64ed5 100644
--- a/src/test/regress/meson.build
+++ b/src/test/regress/meson.build
@@ -8,6 +8,12 @@ regress_sources = pg_regress_c + files(
 
 pg_regress_cflags = ['-DHOST_TUPLE="frak"', '-DSHELLPROG="/bin/sh"']
 
+if host_system == 'windows'
+  regress_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_regress',
+    '--FILEDESC', 'pg_regress - test driver',])
+endif
+
 pg_regress = executable('pg_regress',
   regress_sources,
   c_args: pg_regress_cflags,
diff --git a/meson.build b/meson.build
index 38b2c3aae2e..774a8c2e05f 100644
--- a/meson.build
+++ b/meson.build
@@ -2536,6 +2536,65 @@ gen_export_kwargs = {
 
 
 
+###
+### windows resources related stuff
+###
+
+if host_system == 'windows'
+  pg_ico = meson.source_root() / 'src' / 'port' / 'win32.ico'
+  win32ver_rc = files('src/port/win32ver.rc')
+  rcgen = find_program('src/tools/rcgen', native: true)
+
+  rcgen_base_args = [
+    '--srcdir', '@SOURCE_DIR@',
+    '--builddir', meson.build_root(),
+    '--rcout', '@OUTPUT0@',
+    '--out', '@OUTPUT1@',
+    '--input', '@INPUT@',
+    '@EXTRA_ARGS@',
+  ]
+
+  if cc.get_argument_syntax() == 'msvc'
+    rc = find_program('rc', required: true)
+    rcgen_base_args += ['--rc', rc.path()]
+    rcgen_outputs = ['@[email protected]', '@[email protected]']
+  else
+    windres = find_program('windres', required: true)
+    rcgen_base_args += ['--windres', windres.path()]
+    rcgen_outputs = ['@[email protected]', '@[email protected]']
+  endif
+
+  # msbuild backend doesn't support this atm
+  if meson.backend() == 'ninja'
+    rcgen_base_args += ['--depfile', '@DEPFILE@']
+  endif
+
+  rcgen_bin_args = rcgen_base_args + [
+    '--VFT_TYPE', 'VFT_APP',
+    '--FILEENDING', 'exe',
+    '--ICO', pg_ico
+  ]
+
+  rcgen_lib_args = rcgen_base_args + [
+    '--VFT_TYPE', 'VFT_DLL',
+    '--FILEENDING', 'dll',
+  ]
+
+  rc_bin_gen = generator(rcgen,
+    depfile: '@[email protected]',
+    arguments: rcgen_bin_args,
+    output: rcgen_outputs,
+  )
+
+  rc_lib_gen = generator(rcgen,
+    depfile: '@[email protected]',
+    arguments: rcgen_lib_args,
+    output: rcgen_outputs,
+  )
+endif
+
+
+
 # headers that the whole build tree depends on
 generated_headers = []
 # headers that the backend build depends on
diff --git a/src/timezone/meson.build b/src/timezone/meson.build
index 16f082ecfa8..9e0934c000b 100644
--- a/src/timezone/meson.build
+++ b/src/timezone/meson.build
@@ -28,6 +28,12 @@ if get_option('system_tzdata') == ''
   if meson.is_cross_build()
     zic = find_program(get_option('ZIC'), native: true, required: true)
   else
+    if host_system == 'windows'
+      zic_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+        '--NAME', 'zic',
+        '--FILEDESC', 'zic - time zone compiler',])
+    endif
+
     zic = executable('zic', zic_sources,
                      dependencies: [frontend_code],
                      kwargs: default_bin_args + {'install': false}
diff --git a/src/tools/rcgen b/src/tools/rcgen
new file mode 100755
index 00000000000..5b62bfe5410
--- /dev/null
+++ b/src/tools/rcgen
@@ -0,0 +1,99 @@
+#!/usr/bin/env python3
+
+import argparse
+import os
+import subprocess
+import sys
+
+parser = argparse.ArgumentParser(description='generate PostgreSQL rc file')
+
+parser.add_argument('--srcdir', type=os.path.abspath,
+                    required=True)
+parser.add_argument('--builddir', type=os.path.abspath,
+                    required=True)
+
+binaries = parser.add_argument_group('binaries')
+binaries.add_argument('--windres', type=os.path.abspath)
+binaries.add_argument('--rc', type=os.path.abspath)
+
+inout = parser.add_argument_group('inout')
+inout.add_argument('--depfile', type=argparse.FileType('w'))
+inout.add_argument('--input', type=argparse.FileType('r'),
+                   required=True)
+inout.add_argument('--rcout', type=argparse.FileType('w'),
+                   required=True)
+inout.add_argument('--out', type=str,
+                   required=True)
+
+replacements = parser.add_argument_group('replacements')
+replacements.add_argument('--FILEDESC', type=str)
+replacements.add_argument('--NAME', type=str, required=True)
+replacements.add_argument('--VFT_TYPE', type=str, required=True)
+replacements.add_argument('--FILEENDING', type=str, required=True)
+replacements.add_argument('--ICO', type=str)
+
+args = parser.parse_args()
+
+# determine replacement strings
+
+internal_name = '"{0}"'.format(args.NAME)
+original_name = '"{0}.{1}"'.format(args.NAME, args.FILEENDING)
+
+# if no description is passed in, generate one based on the name
+if args.FILEDESC:
+    filedesc = args.FILEDESC
+elif args.NAME:
+    if args.VFT_TYPE == 'VFT_DLL':
+        filedesc = 'PostgreSQL {0} library'.format(args.NAME)
+    else:
+        filedesc = 'PostgreSQL {0} binary'.format(args.NAME)
+filedesc = '"{0}"'.format(filedesc)
+
+
+if args.ICO:
+    ico = 'IDI_ICON ICON "{0}"'.format(args.ICO)
+    if args.depfile:
+        args.depfile.write("{0} : {1}\n".format(args.rcout.name, args.ICO))
+else:
+    ico = ''
+
+
+data = args.input.read()
+
+data = data.replace('VFT_APP', args.VFT_TYPE)
+data = data.replace('_INTERNAL_NAME_', internal_name)
+data = data.replace('_ORIGINAL_NAME_', original_name)
+data = data.replace('FILEDESC', filedesc)
+data = data.replace("_ICO_", ico)
+
+args.rcout.write(data)
+args.rcout.close()
+
+if args.windres:
+    cmd = [
+        args.windres,
+        '-I{0}/src/include/'.format(args.builddir),
+        '-I{0}/src/include/'.format(args.srcdir),
+        '-o', args.out, '-i', args.rcout.name,
+    ]
+elif args.rc:
+    cmd = [
+        args.rc, '/nologo',
+        '-I{0}/src/include/'.format(args.builddir),
+        '-I{0}/src/include/'.format(args.srcdir),
+        '/fo', args.out, args.rcout.name,
+    ]
+else:
+    sys.exit('either --windres or --rc needs to be specified')
+
+sp = subprocess.run(cmd)
+if sp.returncode != 0:
+    sys.exit(sp.returncode)
+
+# It'd be nicer if we could generate correct dependencies here, but 'rc'
+# doesn't support doing so. It's unlikely we'll ever need more, so...
+if args.depfile:
+    args.depfile.write("{0} : {1}\n".format(
+        args.rcout.name, args.input.name))
+    args.depfile.write("{0} : {1}/{2}\n".format(
+        args.out, args.builddir, 'src/include/pg_config.h'))
-- 
2.37.3.542.gdd3f6c4cae


--losgcde4yzp6hbli
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
	filename="v17-0007-meson-Add-docs-for-building-with-meson.patch"



^ permalink  raw  reply  [nested|flat] 23+ messages in thread

* [PATCH v18 04/22] meson: Add windows resource files
@ 2022-10-01 18:54 Andres Freund <[email protected]>
  0 siblings, 0 replies; 23+ messages in thread

From: Andres Freund @ 2022-10-01 18:54 UTC (permalink / raw)

The generated resource files aren't exactly the same ones as the old
buildsystems generate. Previously "InternalName" and "OriginalFileName" were
mostly wrong / not set (despite being required), but that was hard to fix in
at least the make build. Additionally, the meson build falls back to a
"auto-generated" description when not set, and doesn't set it in a few cases -
unlikely that anybody looks at these descriptions in detail.

Author: Andres Freund <[email protected]>
Author: Nazir Bilal Yavuz <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
---
 src/backend/jit/llvm/meson.build              |   6 +
 .../replication/libpqwalreceiver/meson.build  |   6 +
 src/backend/replication/pgoutput/meson.build  |   6 +
 src/backend/snowball/meson.build              |   6 +
 .../utils/mb/conversion_procs/meson.build     |   9 +-
 src/bin/initdb/meson.build                    |   6 +
 src/bin/pg_amcheck/meson.build                |   8 +-
 src/bin/pg_archivecleanup/meson.build         |  12 +-
 src/bin/pg_basebackup/meson.build             |  38 ++++++-
 src/bin/pg_checksums/meson.build              |  12 +-
 src/bin/pg_config/meson.build                 |  12 +-
 src/bin/pg_controldata/meson.build            |  12 +-
 src/bin/pg_ctl/meson.build                    |  12 +-
 src/bin/pg_dump/meson.build                   |  18 +++
 src/bin/pg_resetwal/meson.build               |  12 +-
 src/bin/pg_rewind/meson.build                 |   6 +
 src/bin/pg_test_fsync/meson.build             |  10 +-
 src/bin/pg_test_timing/meson.build            |  12 +-
 src/bin/pg_upgrade/meson.build                |   6 +
 src/bin/pg_verifybackup/meson.build           |   6 +
 src/bin/pg_waldump/meson.build                |   6 +
 src/bin/pgbench/meson.build                   |   6 +
 src/bin/pgevent/meson.build                   |   6 +
 src/bin/psql/meson.build                      |   6 +
 src/bin/scripts/meson.build                   |  10 +-
 src/interfaces/libpq/meson.build              |   6 +-
 src/interfaces/libpq/test/meson.build         |  25 ++++-
 src/pl/plperl/meson.build                     |   7 ++
 src/pl/plpgsql/src/meson.build                |   6 +
 src/pl/plpython/meson.build                   |   6 +
 src/pl/tcl/meson.build                        |   6 +
 contrib/adminpack/meson.build                 |  12 +-
 contrib/amcheck/meson.build                   |  17 ++-
 contrib/auth_delay/meson.build                |  12 +-
 contrib/auto_explain/meson.build              |  12 +-
 contrib/basebackup_to_shell/meson.build       |   6 +
 contrib/basic_archive/meson.build             |   6 +
 contrib/bloom/meson.build                     |   6 +
 contrib/bool_plperl/meson.build               |   6 +
 contrib/btree_gin/meson.build                 |  12 +-
 contrib/btree_gist/meson.build                |   6 +
 contrib/citext/meson.build                    |   6 +
 contrib/cube/meson.build                      |   6 +
 contrib/dblink/meson.build                    |   6 +
 contrib/dict_int/meson.build                  |  12 +-
 contrib/dict_xsyn/meson.build                 |  12 +-
 contrib/earthdistance/meson.build             |  12 +-
 contrib/file_fdw/meson.build                  |  12 +-
 contrib/fuzzystrmatch/meson.build             |  16 ++-
 contrib/hstore/meson.build                    |  24 ++--
 contrib/hstore_plperl/meson.build             |   6 +
 contrib/hstore_plpython/meson.build           |   6 +
 contrib/intarray/meson.build                  |   6 +
 contrib/isn/meson.build                       |   6 +
 contrib/jsonb_plperl/meson.build              |   6 +
 contrib/jsonb_plpython/meson.build            |   6 +
 contrib/lo/meson.build                        |   6 +
 contrib/ltree/meson.build                     |   6 +
 contrib/ltree_plpython/meson.build            |   6 +
 contrib/oid2name/meson.build                  |  12 +-
 contrib/old_snapshot/meson.build              |   6 +
 contrib/pageinspect/meson.build               |   6 +
 contrib/passwordcheck/meson.build             |   6 +
 contrib/pg_buffercache/meson.build            |  14 ++-
 contrib/pg_freespacemap/meson.build           |  14 ++-
 contrib/pg_prewarm/meson.build                |  16 ++-
 contrib/pg_stat_statements/meson.build        |  12 +-
 contrib/pg_surgery/meson.build                |  14 ++-
 contrib/pg_trgm/meson.build                   |  20 +++-
 contrib/pg_visibility/meson.build             |  14 ++-
 contrib/pg_walinspect/meson.build             |   6 +
 contrib/pgcrypto/meson.build                  |   6 +
 contrib/pgrowlocks/meson.build                |  14 ++-
 contrib/pgstattuple/meson.build               |  18 ++-
 contrib/postgres_fdw/meson.build              |   6 +
 contrib/seg/meson.build                       |   6 +
 contrib/sepgsql/meson.build                   |   6 +
 contrib/spi/meson.build                       |  48 +++++++-
 contrib/sslinfo/meson.build                   |  14 ++-
 contrib/tablefunc/meson.build                 |  14 ++-
 contrib/tcn/meson.build                       |  14 ++-
 contrib/test_decoding/meson.build             |   6 +
 contrib/tsm_system_rows/meson.build           |  14 ++-
 contrib/tsm_system_time/meson.build           |  14 ++-
 contrib/unaccent/meson.build                  |  14 ++-
 contrib/uuid-ossp/meson.build                 |  14 ++-
 contrib/vacuumlo/meson.build                  |  12 +-
 contrib/xml2/meson.build                      |  16 ++-
 src/interfaces/ecpg/compatlib/meson.build     |   6 +
 src/interfaces/ecpg/ecpglib/meson.build       |   6 +
 src/interfaces/ecpg/pgtypeslib/meson.build    |   6 +
 src/interfaces/ecpg/preproc/meson.build       |   6 +
 src/interfaces/ecpg/test/meson.build          |   5 +
 src/test/isolation/meson.build                |  13 +++
 src/test/modules/delay_execution/meson.build  |  13 ++-
 src/test/modules/dummy_index_am/meson.build   |  13 ++-
 src/test/modules/dummy_seclabel/meson.build   |  13 ++-
 src/test/modules/libpq_pipeline/meson.build   |  14 ++-
 src/test/modules/plsample/meson.build         |  13 ++-
 src/test/modules/spgist_name_ops/meson.build  |  13 ++-
 .../ssl_passphrase_callback/meson.build       |  13 ++-
 src/test/modules/test_bloomfilter/meson.build |  13 ++-
 src/test/modules/test_ddl_deparse/meson.build |  13 ++-
 .../modules/test_ginpostinglist/meson.build   |  13 ++-
 src/test/modules/test_integerset/meson.build  |  13 ++-
 src/test/modules/test_lfind/meson.build       |  13 ++-
 src/test/modules/test_oat_hooks/meson.build   |  13 ++-
 src/test/modules/test_parser/meson.build      |  13 ++-
 src/test/modules/test_predtest/meson.build    |  13 ++-
 src/test/modules/test_rbtree/meson.build      |  13 ++-
 src/test/modules/test_regex/meson.build       |  13 ++-
 src/test/modules/test_rls_hooks/meson.build   |  13 ++-
 src/test/modules/test_shm_mq/meson.build      |  19 +++-
 src/test/modules/worker_spi/meson.build       |  15 ++-
 src/test/regress/meson.build                  |   6 +
 meson.build                                   |  59 ++++++++++
 src/timezone/meson.build                      |   6 +
 src/tools/rcgen                               | 105 ++++++++++++++++++
 118 files changed, 1292 insertions(+), 131 deletions(-)
 create mode 100755 src/tools/rcgen

diff --git a/src/backend/jit/llvm/meson.build b/src/backend/jit/llvm/meson.build
index 6ae7aaad015..25c5618e8a3 100644
--- a/src/backend/jit/llvm/meson.build
+++ b/src/backend/jit/llvm/meson.build
@@ -20,6 +20,12 @@ llvmjit_sources += files(
   'llvmjit_expr.c',
 )
 
+if host_system == 'windows'
+  llvmjit_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'llvmjit',
+    '--FILEDESC', 'llvmjit - JIT using LLVM',])
+endif
+
 llvmjit = shared_module('llvmjit',
   llvmjit_sources,
   kwargs: pg_mod_args + {
diff --git a/src/backend/replication/libpqwalreceiver/meson.build b/src/backend/replication/libpqwalreceiver/meson.build
index 3fc786c80a0..4c653a05d36 100644
--- a/src/backend/replication/libpqwalreceiver/meson.build
+++ b/src/backend/replication/libpqwalreceiver/meson.build
@@ -2,6 +2,12 @@ libpqwalreceiver_sources = files(
   'libpqwalreceiver.c',
 )
 
+if host_system == 'windows'
+  libpqwalreceiver_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pqwalreceiver',
+    '--FILEDESC', 'libpqwalreceiver - receive WAL during streaming replication',])
+endif
+
 libpqwalreceiver = shared_module('pqwalreceiver',
   libpqwalreceiver_sources,
   kwargs: pg_mod_args + {
diff --git a/src/backend/replication/pgoutput/meson.build b/src/backend/replication/pgoutput/meson.build
index ab956361a62..5df27d7b764 100644
--- a/src/backend/replication/pgoutput/meson.build
+++ b/src/backend/replication/pgoutput/meson.build
@@ -2,6 +2,12 @@ pgoutput_sources = files(
   'pgoutput.c',
 )
 
+if host_system == 'windows'
+  pgoutput_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pgoutput',
+    '--FILEDESC', 'pgoutput - standard logical replication output plugin',])
+endif
+
 pgoutput = shared_module('pgoutput',
   pgoutput_sources,
   kwargs: pg_mod_args,
diff --git a/src/backend/snowball/meson.build b/src/backend/snowball/meson.build
index 8c6f685cb32..974401d187e 100644
--- a/src/backend/snowball/meson.build
+++ b/src/backend/snowball/meson.build
@@ -58,6 +58,12 @@ dict_snowball_sources += files(
 # see comment in src/include/snowball/header.h
 stemmer_inc = include_directories('../../include/snowball')
 
+if host_system == 'windows'
+  dict_snowball_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'dict_snowball',
+    '--FILEDESC', 'snowball - natural language stemmers',])
+endif
+
 dict_snowball = shared_module('dict_snowball',
   dict_snowball_sources,
   kwargs: pg_mod_args + {
diff --git a/src/backend/utils/mb/conversion_procs/meson.build b/src/backend/utils/mb/conversion_procs/meson.build
index 1bc971d1945..1c18f2ac85a 100644
--- a/src/backend/utils/mb/conversion_procs/meson.build
+++ b/src/backend/utils/mb/conversion_procs/meson.build
@@ -29,8 +29,15 @@ encodings = {
 }
 
 foreach encoding, sources : encodings
+  source_files = files(sources)
+
+  if host_system == 'windows'
+    source_files += rc_lib_gen.process(win32ver_rc, extra_args: [
+      '--NAME', encoding])
+  endif
+
   backend_targets += shared_module(encoding,
-    sources,
+    source_files,
     kwargs: pg_mod_args,
   )
 endforeach
diff --git a/src/bin/initdb/meson.build b/src/bin/initdb/meson.build
index 9f213274d2f..6ced9a31b80 100644
--- a/src/bin/initdb/meson.build
+++ b/src/bin/initdb/meson.build
@@ -7,6 +7,12 @@ initdb_sources += timezone_localtime_source
 
 #fixme: reimplement libpq_pgport logic
 
+if host_system == 'windows'
+  initdb_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'initdb',
+    '--FILEDESC', 'initdb - initialize a new database cluster',])
+endif
+
 initdb = executable('initdb',
   initdb_sources,
   include_directories: [timezone_inc],
diff --git a/src/bin/pg_amcheck/meson.build b/src/bin/pg_amcheck/meson.build
index 8e197eba5f3..25f5e7a0948 100644
--- a/src/bin/pg_amcheck/meson.build
+++ b/src/bin/pg_amcheck/meson.build
@@ -1,7 +1,13 @@
 pg_amcheck_sources = files(
-  'pg_amcheck.c'
+  'pg_amcheck.c',
 )
 
+if host_system == 'windows'
+  pg_amcheck_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_amcheck',
+    '--FILEDESC', 'pg_amcheck - detect corruption within database relations',])
+endif
+
 pg_amcheck = executable('pg_amcheck',
   pg_amcheck_sources,
   dependencies: [frontend_code, libpq],
diff --git a/src/bin/pg_archivecleanup/meson.build b/src/bin/pg_archivecleanup/meson.build
index 87a0d980c4f..aaa2e76977f 100644
--- a/src/bin/pg_archivecleanup/meson.build
+++ b/src/bin/pg_archivecleanup/meson.build
@@ -1,5 +1,15 @@
+pg_archivecleanup_sources = files(
+  'pg_archivecleanup.c',
+)
+
+if host_system == 'windows'
+  pg_archivecleanup_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_archivecleanup',
+    '--FILEDESC', 'pg_archivecleanup - cleans archive when used with streaming replication',])
+endif
+
 pg_archivecleanup = executable('pg_archivecleanup',
-  ['pg_archivecleanup.c'],
+  pg_archivecleanup_sources,
   dependencies: [frontend_code],
   kwargs: default_bin_args,
 )
diff --git a/src/bin/pg_basebackup/meson.build b/src/bin/pg_basebackup/meson.build
index d26fed9cd8a..2c934e0c26e 100644
--- a/src/bin/pg_basebackup/meson.build
+++ b/src/bin/pg_basebackup/meson.build
@@ -17,24 +17,56 @@ pg_basebackup_common = static_library('libpg_basebackup_common',
   kwargs: internal_lib_args,
 )
 
-pg_basebackup = executable('pg_basebackup',
+pg_basebackup_sources = files(
   'pg_basebackup.c',
+)
+
+if host_system == 'windows'
+  pg_basebackup_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_basebackup',
+    '--FILEDESC', 'pg_basebackup - streaming WAL and backup receivers',])
+endif
+
+pg_basebackup = executable('pg_basebackup',
+  pg_basebackup_sources,
   link_with: [pg_basebackup_common],
   dependencies: pg_basebackup_deps,
   kwargs: default_bin_args,
 )
 bin_targets += pg_basebackup
 
-pg_receivewal = executable('pg_receivewal',
+
+pg_receivewal_sources = files(
   'pg_receivewal.c',
+)
+
+if host_system == 'windows'
+  pg_receivewal_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_receivewal',
+    '--FILEDESC', 'pg_receivewal - streaming WAL and backup receivers',])
+endif
+
+pg_receivewal = executable('pg_receivewal',
+  pg_receivewal_sources,
   link_with: [pg_basebackup_common],
   dependencies: pg_basebackup_deps,
   kwargs: default_bin_args,
 )
 bin_targets += pg_receivewal
 
-pg_recvlogical = executable('pg_recvlogical',
+
+pg_recvlogical_sources = files(
   'pg_recvlogical.c',
+)
+
+if host_system == 'windows'
+  pg_recvlogical_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_recvlogical',
+    '--FILEDESC', 'pg_recvlogical - streaming WAL and backup receivers',])
+endif
+
+pg_recvlogical = executable('pg_recvlogical',
+  pg_recvlogical_sources,
   link_with: [pg_basebackup_common],
   dependencies: pg_basebackup_deps,
   kwargs: default_bin_args,
diff --git a/src/bin/pg_checksums/meson.build b/src/bin/pg_checksums/meson.build
index ee1f367bac3..d07ebc999b3 100644
--- a/src/bin/pg_checksums/meson.build
+++ b/src/bin/pg_checksums/meson.build
@@ -1,5 +1,15 @@
+pg_checksums_sources = files(
+  'pg_checksums.c',
+)
+
+if host_system == 'windows'
+  pg_checksums_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_checksums',
+    '--FILEDESC', 'pg_checksums - verify data checksums in an offline cluster',])
+endif
+
 pg_checksums = executable('pg_checksums',
-  ['pg_checksums.c'],
+  pg_checksums_sources,
   include_directories: [timezone_inc],
   dependencies: [frontend_code],
   kwargs: default_bin_args,
diff --git a/src/bin/pg_config/meson.build b/src/bin/pg_config/meson.build
index 0ecbf2f9d28..4be2fdc84ae 100644
--- a/src/bin/pg_config/meson.build
+++ b/src/bin/pg_config/meson.build
@@ -1,5 +1,15 @@
+pg_config_sources = files(
+  'pg_config.c',
+)
+
+if host_system == 'windows'
+  pg_config_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_config',
+    '--FILEDESC', 'pg_config - report configuration information',])
+endif
+
 pg_config = executable('pg_config',
-  ['pg_config.c'],
+  pg_config_sources,
   dependencies: [frontend_code],
   kwargs: default_bin_args,
 )
diff --git a/src/bin/pg_controldata/meson.build b/src/bin/pg_controldata/meson.build
index 557e672beb7..7fc239dbe65 100644
--- a/src/bin/pg_controldata/meson.build
+++ b/src/bin/pg_controldata/meson.build
@@ -1,5 +1,15 @@
+pg_controldata_sources = files(
+  'pg_controldata.c',
+)
+
+if host_system == 'windows'
+  pg_controldata_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_controldata',
+    '--FILEDESC', 'pg_controldata - reads the data from pg_control',])
+endif
+
 pg_controldata = executable('pg_controldata',
-  ['pg_controldata.c'],
+  pg_controldata_sources,
   dependencies: [frontend_code],
   kwargs: default_bin_args,
 )
diff --git a/src/bin/pg_ctl/meson.build b/src/bin/pg_ctl/meson.build
index 6812e73e329..96f962fa762 100644
--- a/src/bin/pg_ctl/meson.build
+++ b/src/bin/pg_ctl/meson.build
@@ -1,5 +1,15 @@
+pg_ctl_sources = files(
+  'pg_ctl.c',
+)
+
+if host_system == 'windows'
+  pg_ctl_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_ctl',
+    '--FILEDESC', 'pg_ctl - starts/stops/restarts the PostgreSQL server',])
+endif
+
 pg_ctl = executable('pg_ctl',
-  ['pg_ctl.c'],
+  pg_ctl_sources,
   dependencies: [frontend_code, libpq],
   kwargs: default_bin_args,
 )
diff --git a/src/bin/pg_dump/meson.build b/src/bin/pg_dump/meson.build
index 785ec094dbd..3527a25c288 100644
--- a/src/bin/pg_dump/meson.build
+++ b/src/bin/pg_dump/meson.build
@@ -24,6 +24,12 @@ pg_dump_sources = files(
   'pg_dump_sort.c',
 )
 
+if host_system == 'windows'
+  pg_dump_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_dump',
+    '--FILEDESC', 'pg_dump - backup one PostgreSQL database',])
+endif
+
 pg_dump = executable('pg_dump',
   pg_dump_sources,
   link_with: [pg_dump_common],
@@ -37,6 +43,12 @@ pg_dumpall_sources = files(
   'pg_dumpall.c',
 )
 
+if host_system == 'windows'
+  pg_dumpall_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_dumpall',
+    '--FILEDESC', 'pg_dumpall - backup PostgreSQL databases'])
+endif
+
 pg_dumpall = executable('pg_dumpall',
   pg_dumpall_sources,
   link_with: [pg_dump_common],
@@ -50,6 +62,12 @@ pg_restore_sources = files(
   'pg_restore.c',
 )
 
+if host_system == 'windows'
+  pg_restore_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_restore',
+    '--FILEDESC', 'pg_restore - restore PostgreSQL databases'])
+endif
+
 pg_restore = executable('pg_restore',
   pg_restore_sources,
   link_with: [pg_dump_common],
diff --git a/src/bin/pg_resetwal/meson.build b/src/bin/pg_resetwal/meson.build
index 7c5de134ac0..d503db97b71 100644
--- a/src/bin/pg_resetwal/meson.build
+++ b/src/bin/pg_resetwal/meson.build
@@ -1,5 +1,15 @@
+pg_resetwal_sources = files(
+  'pg_resetwal.c',
+)
+
+if host_system == 'windows'
+  pg_resetwal_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_resetwal',
+    '--FILEDESC', 'pg_resetwal - reset PostgreSQL WAL log'])
+endif
+
 pg_resetwal = executable('pg_resetwal',
-  files('pg_resetwal.c'),
+  pg_resetwal_sources,
   dependencies: [frontend_code],
   kwargs: default_bin_args,
 )
diff --git a/src/bin/pg_rewind/meson.build b/src/bin/pg_rewind/meson.build
index d8ec9e482d5..6cd970909a2 100644
--- a/src/bin/pg_rewind/meson.build
+++ b/src/bin/pg_rewind/meson.build
@@ -11,6 +11,12 @@ pg_rewind_sources = files(
 
 pg_rewind_sources += xlogreader_sources
 
+if host_system == 'windows'
+  pg_rewind_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_rewind',
+    '--FILEDESC', 'pg_rewind - synchronize a data directory with another one forked from'])
+endif
+
 pg_rewind = executable('pg_rewind',
   pg_rewind_sources,
   dependencies: [frontend_code, libpq, lz4, zstd],
diff --git a/src/bin/pg_test_fsync/meson.build b/src/bin/pg_test_fsync/meson.build
index 2c01831e11f..31d288ba6da 100644
--- a/src/bin/pg_test_fsync/meson.build
+++ b/src/bin/pg_test_fsync/meson.build
@@ -1,4 +1,12 @@
-test_fsync_sources = files('pg_test_fsync.c')
+test_fsync_sources = files(
+  'pg_test_fsync.c',
+)
+
+if host_system == 'windows'
+  test_fsync_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_test_fsync',
+    '--FILEDESC', 'pg_test_fsync - test various disk sync methods'])
+endif
 
 pg_test_fsync = executable('pg_test_fsync',
   test_fsync_sources,
diff --git a/src/bin/pg_test_timing/meson.build b/src/bin/pg_test_timing/meson.build
index 0a3068f1657..0aed03ea32f 100644
--- a/src/bin/pg_test_timing/meson.build
+++ b/src/bin/pg_test_timing/meson.build
@@ -1,5 +1,15 @@
+pg_test_timing_sources = files(
+  'pg_test_timing.c'
+)
+
+if host_system == 'windows'
+  pg_test_timing_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_test_timing',
+    '--FILEDESC', 'pg_test_timing - test timing overhead'])
+endif
+
 pg_test_timing = executable('pg_test_timing',
-  ['pg_test_timing.c'],
+  pg_test_timing_sources,
   dependencies: [frontend_code],
   kwargs: default_bin_args,
 )
diff --git a/src/bin/pg_upgrade/meson.build b/src/bin/pg_upgrade/meson.build
index 02f030e0ccf..a7b927a45c7 100644
--- a/src/bin/pg_upgrade/meson.build
+++ b/src/bin/pg_upgrade/meson.build
@@ -16,6 +16,12 @@ pg_upgrade_sources = files(
   'version.c',
 )
 
+if host_system == 'windows'
+  pg_upgrade_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_upgrade',
+    '--FILEDESC', 'pg_upgrade - an in-place binary upgrade utility'])
+endif
+
 pg_upgrade = executable('pg_upgrade',
   pg_upgrade_sources,
   dependencies: [frontend_code, libpq],
diff --git a/src/bin/pg_verifybackup/meson.build b/src/bin/pg_verifybackup/meson.build
index 4c3b2bb5f97..b934a408443 100644
--- a/src/bin/pg_verifybackup/meson.build
+++ b/src/bin/pg_verifybackup/meson.build
@@ -3,6 +3,12 @@ pg_verifybackup_sources = files(
   'pg_verifybackup.c'
 )
 
+if host_system == 'windows'
+  pg_verifybackup_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_verifybackup',
+    '--FILEDESC', 'pg_verifybackup - verify a backup against using a backup manifest'])
+endif
+
 pg_verifybackup = executable('pg_verifybackup',
   pg_verifybackup_sources,
   dependencies: [frontend_code, libpq],
diff --git a/src/bin/pg_waldump/meson.build b/src/bin/pg_waldump/meson.build
index 95872652ffd..9605976870d 100644
--- a/src/bin/pg_waldump/meson.build
+++ b/src/bin/pg_waldump/meson.build
@@ -8,6 +8,12 @@ pg_waldump_sources += rmgr_desc_sources
 pg_waldump_sources += xlogreader_sources
 pg_waldump_sources += files('../../backend/access/transam/xlogstats.c')
 
+if host_system == 'windows'
+  pg_waldump_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_waldump',
+    '--FILEDESC', 'pg_waldump - decode and display WA'])
+endif
+
 pg_waldump = executable('pg_waldump',
   pg_waldump_sources,
   dependencies: [frontend_code, lz4, zstd],
diff --git a/src/bin/pgbench/meson.build b/src/bin/pgbench/meson.build
index 6564e54029c..a32eb51fe07 100644
--- a/src/bin/pgbench/meson.build
+++ b/src/bin/pgbench/meson.build
@@ -17,6 +17,12 @@ exprparse = custom_target('exprparse',
 generated_sources += exprparse.to_list()
 pgbench_sources += exprparse
 
+if host_system == 'windows'
+  pgbench_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pgbench',
+    '--FILEDESC', 'pgbench - a simple program for running benchmark tests'])
+endif
+
 pgbench = executable('pgbench',
   pgbench_sources,
   dependencies: [frontend_code, libpq, thread_dep],
diff --git a/src/bin/pgevent/meson.build b/src/bin/pgevent/meson.build
index 7a468879fd2..2e9aea4b0e1 100644
--- a/src/bin/pgevent/meson.build
+++ b/src/bin/pgevent/meson.build
@@ -6,6 +6,12 @@ pgevent_sources = files(
   'pgevent.c',
 )
 
+pgevent_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+  '--NAME', 'pgevent',
+  '--FILEDESC', 'Eventlog message formatter',])
+
+pgevent_sources += windows.compile_resources('pgmsgevent.rc')
+
 # FIXME: copied from Mkvcbuild.pm, but I don't think that's the right approach
 pgevent_link_args = []
 if cc.get_id() == 'msvc'
diff --git a/src/bin/psql/meson.build b/src/bin/psql/meson.build
index 410788e4767..1264fc19fbd 100644
--- a/src/bin/psql/meson.build
+++ b/src/bin/psql/meson.build
@@ -36,6 +36,12 @@ sql_help = custom_target('psql_help',
 generated_sources += sql_help.to_list()
 psql_sources += sql_help
 
+if host_system == 'windows'
+  psql_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'psql',
+    '--FILEDESC', 'psql - the PostgreSQL interactive terminal',])
+endif
+
 psql = executable('psql',
   psql_sources,
   include_directories: include_directories('.'),
diff --git a/src/bin/scripts/meson.build b/src/bin/scripts/meson.build
index eaf250c7f73..837562c24e5 100644
--- a/src/bin/scripts/meson.build
+++ b/src/bin/scripts/meson.build
@@ -16,8 +16,16 @@ binaries = [
 ]
 
 foreach binary : binaries
+  binary_sources = files('@[email protected]'.format(binary))
+
+  if host_system == 'windows'
+    binary_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+      '--NAME', binary,
+      '--FILEDESC', '@0@ - PostgreSQL utility'.format(binary),])
+  endif
+
   binary = executable(binary,
-    files(binary + '.c'),
+    binary_sources,
     link_with: [scripts_common],
     dependencies: [frontend_code, libpq],
     kwargs: default_bin_args,
diff --git a/src/interfaces/libpq/meson.build b/src/interfaces/libpq/meson.build
index 34cb58c2617..533b2e6f773 100644
--- a/src/interfaces/libpq/meson.build
+++ b/src/interfaces/libpq/meson.build
@@ -16,9 +16,13 @@ libpq_sources = files(
   'libpq-events.c',
   'pqexpbuffer.c',
 )
+libpq_so_sources = [] # for shared lib, in addition to the above
 
 if host_system == 'windows'
   libpq_sources += files('pthread-win32.c', 'win32.c')
+  libpq_so_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'libpq',
+    '--FILEDESC', 'PostgreSQL Access Library',])
 endif
 
 if ssl.found()
@@ -59,7 +63,7 @@ libpq_st = static_library('libpq',
 )
 
 libpq_so = shared_library('libpq',
-  libpq_sources,
+  libpq_sources + libpq_so_sources,
   include_directories: [libpq_inc, postgres_inc],
   c_args: libpq_c_args,
   version: '5.' + pg_version_major.to_string(),
diff --git a/src/interfaces/libpq/test/meson.build b/src/interfaces/libpq/test/meson.build
index 16f94c1ed8b..017f729d435 100644
--- a/src/interfaces/libpq/test/meson.build
+++ b/src/interfaces/libpq/test/meson.build
@@ -1,13 +1,34 @@
+libpq_uri_regress_sources = files(
+  'libpq_uri_regress.c',
+)
+
+if host_system == 'windows'
+  libpq_uri_regress_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'libpq_uri_regress',
+    '--FILEDESC', 'libpq test program',])
+endif
+
 executable('libpq_uri_regress',
-  files('libpq_uri_regress.c'),
+  libpq_uri_regress_sources,
   dependencies: [frontend_code, libpq],
   kwargs: default_bin_args + {
     'install': false,
   }
 )
 
+
+libpq_testclient_sources = files(
+  'libpq_testclient.c',
+)
+
+if host_system == 'windows'
+  libpq_testclient_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'libpq_testclient',
+    '--FILEDESC', 'libpq test program',])
+endif
+
 executable('libpq_testclient',
-  files('libpq_testclient.c'),
+  libpq_testclient_sources,
   dependencies: [frontend_code, libpq],
   kwargs: default_bin_args + {
     'install': false,
diff --git a/src/pl/plperl/meson.build b/src/pl/plperl/meson.build
index 73b733dd50b..535660085dd 100644
--- a/src/pl/plperl/meson.build
+++ b/src/pl/plperl/meson.build
@@ -36,6 +36,13 @@ foreach n : ['SPI', 'Util']
 endforeach
 
 plperl_inc = include_directories('.')
+
+if host_system == 'windows'
+  plperl_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'plperl',
+    '--FILEDESC', 'PL/Perl - procedural language',])
+endif
+
 plperl = shared_module('plperl',
   plperl_sources,
   include_directories: [plperl_inc, postgres_inc],
diff --git a/src/pl/plpgsql/src/meson.build b/src/pl/plpgsql/src/meson.build
index dd499fdd151..c46c0a1da2a 100644
--- a/src/pl/plpgsql/src/meson.build
+++ b/src/pl/plpgsql/src/meson.build
@@ -40,6 +40,12 @@ pl_unreserved = custom_target('pl_unreserved_kwlist',
 generated_sources += pl_unreserved
 plpgsql_sources += pl_unreserved
 
+if host_system == 'windows'
+  plpgsql_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'plpgsql',
+    '--FILEDESC', 'PL/pgSQL - procedural language',])
+endif
+
 plpgsql = shared_module('plpgsql',
   plpgsql_sources,
   include_directories: include_directories('.'),
diff --git a/src/pl/plpython/meson.build b/src/pl/plpython/meson.build
index 366b3b171ac..40888386b5f 100644
--- a/src/pl/plpython/meson.build
+++ b/src/pl/plpython/meson.build
@@ -28,6 +28,12 @@ plpython_sources += custom_target('spiexceptions.h',
 # FIXME: need to duplicate import library ugliness?
 plpython_inc = include_directories('.')
 
+if host_system == 'windows'
+  plpython_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'plpython3',
+    '--FILEDESC', 'PL/Python - procedural language',])
+endif
+
 plpython = shared_module('plpython3',
   plpython_sources,
   include_directories: [plpython_inc, postgres_inc],
diff --git a/src/pl/tcl/meson.build b/src/pl/tcl/meson.build
index 9b6addd7fd5..f09bb14c950 100644
--- a/src/pl/tcl/meson.build
+++ b/src/pl/tcl/meson.build
@@ -14,6 +14,12 @@ pltcl_sources += custom_target('pltclerrcodes.h',
   command: [perl, gen_pltclerrcodes, '@INPUT@']
 )
 
+if host_system == 'windows'
+  pltcl_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pltcl',
+    '--FILEDESC', 'PL/Tcl - procedural language',])
+endif
+
 pltcl = shared_module('pltcl',
   pltcl_sources,
   include_directories: [include_directories('.'), postgres_inc],
diff --git a/contrib/adminpack/meson.build b/contrib/adminpack/meson.build
index fc2368d02cf..7efec0efbc0 100644
--- a/contrib/adminpack/meson.build
+++ b/contrib/adminpack/meson.build
@@ -1,5 +1,15 @@
+adminpack_sources = files(
+  'adminpack.c',
+)
+
+if host_system == 'windows'
+  adminpack_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'adminpack',
+    '--FILEDESC', 'adminpack - support functions for pgAdmin',])
+endif
+
 adminpack = shared_module('adminpack',
-  ['adminpack.c'],
+  adminpack_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += adminpack
diff --git a/contrib/amcheck/meson.build b/contrib/amcheck/meson.build
index 1db3d20349e..fa807b72d98 100644
--- a/contrib/amcheck/meson.build
+++ b/contrib/amcheck/meson.build
@@ -1,7 +1,16 @@
-amcheck = shared_module('amcheck', [
-    'verify_heapam.c',
-    'verify_nbtree.c',
-  ],
+amcheck_sources = files(
+  'verify_heapam.c',
+  'verify_nbtree.c',
+)
+
+if host_system == 'windows'
+  amcheck_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'amcheck',
+    '--FILEDESC', 'amcheck - function for verifying relation integrity',])
+endif
+
+amcheck = shared_module('amcheck',
+  amcheck_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += amcheck
diff --git a/contrib/auth_delay/meson.build b/contrib/auth_delay/meson.build
index d2e01968f54..c4ffb0663bc 100644
--- a/contrib/auth_delay/meson.build
+++ b/contrib/auth_delay/meson.build
@@ -1,5 +1,15 @@
+auth_delay_sources = files(
+  'auth_delay.c',
+)
+
+if host_system == 'windows'
+  auth_delay_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'auth_delay',
+    '--FILEDESC', 'auth_delay - delay authentication failure reports',])
+endif
+
 autoinc = shared_module('auth_delay',
-  ['auth_delay.c'],
+  auth_delay_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += autoinc
diff --git a/contrib/auto_explain/meson.build b/contrib/auto_explain/meson.build
index 249a8376faa..76f86617850 100644
--- a/contrib/auto_explain/meson.build
+++ b/contrib/auto_explain/meson.build
@@ -1,5 +1,15 @@
+auto_explain_sources = files(
+  'auto_explain.c',
+)
+
+if host_system == 'windows'
+  auto_explain_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'auto_explain',
+    '--FILEDESC', 'auto_explain - logging facility for execution plans',])
+endif
+
 auto_explain = shared_module('auto_explain',
-  files('auto_explain.c'),
+  auto_explain_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += auto_explain
diff --git a/contrib/basebackup_to_shell/meson.build b/contrib/basebackup_to_shell/meson.build
index 9f0517f1701..3a389de9175 100644
--- a/contrib/basebackup_to_shell/meson.build
+++ b/contrib/basebackup_to_shell/meson.build
@@ -2,6 +2,12 @@ basebackup_to_shell_sources = files(
   'basebackup_to_shell.c',
 )
 
+if host_system == 'windows'
+  basebackup_to_shell_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'basebackup_to_shell',
+    '--FILEDESC', 'basebackup_to_shell - target basebackup to shell command',])
+endif
+
 basebackup_to_shell = shared_module('basebackup_to_shell',
   basebackup_to_shell_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/basic_archive/meson.build b/contrib/basic_archive/meson.build
index b67cbef60bd..c30dcfa5d41 100644
--- a/contrib/basic_archive/meson.build
+++ b/contrib/basic_archive/meson.build
@@ -2,6 +2,12 @@ basic_archive_sources = files(
   'basic_archive.c',
 )
 
+if host_system == 'windows'
+  basic_archive_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'basic_archive',
+    '--FILEDESC', 'basic_archive - basic archive module',])
+endif
+
 basic_archive = shared_module('basic_archive',
   basic_archive_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/bloom/meson.build b/contrib/bloom/meson.build
index 1fe7632bdbe..16f3b83e4d2 100644
--- a/contrib/bloom/meson.build
+++ b/contrib/bloom/meson.build
@@ -7,6 +7,12 @@ bloom_sources = files(
   'blvalidate.c',
 )
 
+if host_system == 'windows'
+  bloom_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'bloom',
+    '--FILEDESC', 'bloom access method - signature file based index',])
+endif
+
 bloom = shared_module('bloom',
   bloom_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/bool_plperl/meson.build b/contrib/bool_plperl/meson.build
index c20b667d75f..a68daab0dcd 100644
--- a/contrib/bool_plperl/meson.build
+++ b/contrib/bool_plperl/meson.build
@@ -6,6 +6,12 @@ bool_plperl_sources = files(
   'bool_plperl.c',
 )
 
+if host_system == 'windows'
+  bool_plperl_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'bool_plperl',
+    '--FILEDESC', 'bool_plperl - bool transform for plperl',])
+endif
+
 bool_plperl = shared_module('bool_plperl',
   bool_plperl_sources,
   include_directories: [plperl_inc, include_directories('.')],
diff --git a/contrib/btree_gin/meson.build b/contrib/btree_gin/meson.build
index 15d6d31a6ee..fd4c76767a7 100644
--- a/contrib/btree_gin/meson.build
+++ b/contrib/btree_gin/meson.build
@@ -1,5 +1,15 @@
+btree_gin_sources = files(
+  'btree_gin.c',
+)
+
+if host_system == 'windows'
+  btree_gin_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'btree_gin',
+    '--FILEDESC', 'btree_gin - B-tree equivalent GIN operator classes',])
+endif
+
 btree_gin = shared_module('btree_gin',
-  files('btree_gin.c'),
+  btree_gin_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += btree_gin
diff --git a/contrib/btree_gist/meson.build b/contrib/btree_gist/meson.build
index c0a8d238540..e98c91dacc8 100644
--- a/contrib/btree_gist/meson.build
+++ b/contrib/btree_gist/meson.build
@@ -25,6 +25,12 @@ btree_gist_sources = files(
   'btree_uuid.c',
 )
 
+if host_system == 'windows'
+  btree_gist_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'btree_gist',
+    '--FILEDESC', 'btree_gist - B-tree equivalent GiST operator classes',])
+endif
+
 btree_gist = shared_module('btree_gist',
   btree_gist_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/citext/meson.build b/contrib/citext/meson.build
index ca60eded80b..26a101a19bd 100644
--- a/contrib/citext/meson.build
+++ b/contrib/citext/meson.build
@@ -2,6 +2,12 @@ citext_sources = files(
   'citext.c',
 )
 
+if host_system == 'windows'
+  citext_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'citext',
+    '--FILEDESC', 'citext - case-insensitive character string data type',])
+endif
+
 citext = shared_module('citext',
   citext_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/cube/meson.build b/contrib/cube/meson.build
index 72342b0c82c..041acf95a90 100644
--- a/contrib/cube/meson.build
+++ b/contrib/cube/meson.build
@@ -17,6 +17,12 @@ cube_parse = custom_target('cubeparse',
 generated_sources += cube_parse.to_list()
 cube_sources += cube_parse
 
+if host_system == 'windows'
+  cube_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'cube',
+    '--FILEDESC', 'cube - multidimensional cube data type',])
+endif
+
 cube = shared_module('cube',
   cube_sources,
   include_directories: include_directories('.'),
diff --git a/contrib/dblink/meson.build b/contrib/dblink/meson.build
index d35f7b5d49e..66eeb03b736 100644
--- a/contrib/dblink/meson.build
+++ b/contrib/dblink/meson.build
@@ -2,6 +2,12 @@ dblink_sources = files(
   'dblink.c',
 )
 
+if host_system == 'windows'
+  dblink_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'dblink',
+    '--FILEDESC', 'dblink - connect to other PostgreSQL databases',])
+endif
+
 dblink = shared_module('dblink',
   dblink_sources,
   kwargs: contrib_mod_args + {
diff --git a/contrib/dict_int/meson.build b/contrib/dict_int/meson.build
index f00e8085619..6fff921adda 100644
--- a/contrib/dict_int/meson.build
+++ b/contrib/dict_int/meson.build
@@ -1,5 +1,15 @@
+dict_int_sources = files(
+  'dict_int.c',
+)
+
+if host_system == 'windows'
+  dict_int_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'dict_int',
+    '--FILEDESC', 'dict_int - add-on dictionary template for full-text search',])
+endif
+
 dict_int = shared_module('dict_int',
-  files('dict_int.c'),
+  dict_int_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += dict_int
diff --git a/contrib/dict_xsyn/meson.build b/contrib/dict_xsyn/meson.build
index be53f55bb79..fabd505a7df 100644
--- a/contrib/dict_xsyn/meson.build
+++ b/contrib/dict_xsyn/meson.build
@@ -1,5 +1,15 @@
+dict_xsyn_sources = files(
+  'dict_xsyn.c',
+)
+
+if host_system == 'windows'
+  dict_xsyn_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'dict_xsyn',
+    '--FILEDESC', 'dict_xsyn - add-on dictionary template for full-text search',])
+endif
+
 dict_xsyn = shared_module('dict_xsyn',
-  files('dict_xsyn.c'),
+  dict_xsyn_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += dict_xsyn
diff --git a/contrib/earthdistance/meson.build b/contrib/earthdistance/meson.build
index 807f5cb7de3..78dc29c3da3 100644
--- a/contrib/earthdistance/meson.build
+++ b/contrib/earthdistance/meson.build
@@ -1,5 +1,15 @@
+earthdistance_sources = files(
+  'earthdistance.c',
+)
+
+if host_system == 'windows'
+  earthdistance_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'earthdistance',
+    '--FILEDESC', 'earthdistance - calculate distances on the surface of the Earth',])
+endif
+
 earthdistance = shared_module('earthdistance',
-  files('earthdistance.c'),
+  earthdistance_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += earthdistance
diff --git a/contrib/file_fdw/meson.build b/contrib/file_fdw/meson.build
index f13efb6e38e..c4071faa669 100644
--- a/contrib/file_fdw/meson.build
+++ b/contrib/file_fdw/meson.build
@@ -1,5 +1,15 @@
+file_fdw_sources = files(
+  'file_fdw.c',
+)
+
+if host_system == 'windows'
+  file_fdw_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'file_fdw',
+    '--FILEDESC', 'file_fdw - foreign data wrapper for files',])
+endif
+
 file_fdw = shared_module('file_fdw',
-  files('file_fdw.c'),
+  file_fdw_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += file_fdw
diff --git a/contrib/fuzzystrmatch/meson.build b/contrib/fuzzystrmatch/meson.build
index ec278a6211e..e6d06149cec 100644
--- a/contrib/fuzzystrmatch/meson.build
+++ b/contrib/fuzzystrmatch/meson.build
@@ -1,8 +1,16 @@
+fuzzystrmatch_sources = files(
+  'fuzzystrmatch.c',
+  'dmetaphone.c',
+)
+
+if host_system == 'windows'
+  fuzzystrmatch_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'fuzzystrmatch',
+    '--FILEDESC', 'fuzzystrmatch - similarities and distance between strings',])
+endif
+
 fuzzystrmatch = shared_module('fuzzystrmatch',
-  files(
-    'fuzzystrmatch.c',
-    'dmetaphone.c'
-  ),
+  fuzzystrmatch_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += fuzzystrmatch
diff --git a/contrib/hstore/meson.build b/contrib/hstore/meson.build
index 07c59f40a97..2bb26bb772b 100644
--- a/contrib/hstore/meson.build
+++ b/contrib/hstore/meson.build
@@ -1,15 +1,23 @@
 # .. so that includes of hstore/hstore.h work
 hstore_inc = include_directories('.', '../')
 
+hstore_sources = files(
+  'hstore_compat.c',
+  'hstore_gin.c',
+  'hstore_gist.c',
+  'hstore_io.c',
+  'hstore_op.c',
+  'hstore_subs.c',
+)
+
+if host_system == 'windows'
+  hstore_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'hstore',
+    '--FILEDESC', 'hstore - key/value pair data type',])
+endif
+
 hstore = shared_module('hstore',
-  files(
-    'hstore_compat.c',
-    'hstore_gin.c',
-    'hstore_gist.c',
-    'hstore_io.c',
-    'hstore_op.c',
-    'hstore_subs.c',
-  ),
+  hstore_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += hstore
diff --git a/contrib/hstore_plperl/meson.build b/contrib/hstore_plperl/meson.build
index bbafa0221bd..a238fee6a26 100644
--- a/contrib/hstore_plperl/meson.build
+++ b/contrib/hstore_plperl/meson.build
@@ -6,6 +6,12 @@ hstore_plperl_sources = files(
   'hstore_plperl.c',
 )
 
+if host_system == 'windows'
+  hstore_plperl_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'hstore_plperl',
+    '--FILEDESC', 'hstore_plperl - hstore transform for plperl',])
+endif
+
 hstore_plperl = shared_module('hstore_plperl',
   hstore_plperl_sources,
   include_directories: [plperl_inc, hstore_inc],
diff --git a/contrib/hstore_plpython/meson.build b/contrib/hstore_plpython/meson.build
index 214b48519a9..6071aaeb4b3 100644
--- a/contrib/hstore_plpython/meson.build
+++ b/contrib/hstore_plpython/meson.build
@@ -6,6 +6,12 @@ hstore_plpython_sources = files(
   'hstore_plpython.c',
 )
 
+if host_system == 'windows'
+  hstore_plpython_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'hstore_plpython3',
+    '--FILEDESC', 'hstore_plpython - hstore transform for plpython',])
+endif
+
 hstore_plpython = shared_module('hstore_plpython3',
   hstore_plpython_sources,
   include_directories: [plpython_inc, hstore_inc, ],
diff --git a/contrib/intarray/meson.build b/contrib/intarray/meson.build
index 1655bcbb3fd..b7cf1ce0cad 100644
--- a/contrib/intarray/meson.build
+++ b/contrib/intarray/meson.build
@@ -8,6 +8,12 @@ intarray_sources = files(
   '_intbig_gist.c',
 )
 
+if host_system == 'windows'
+  intarray_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', '_int',
+    '--FILEDESC', 'intarray - functions and operators for arrays of integers',])
+endif
+
 intarray = shared_module('_int',
   intarray_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/isn/meson.build b/contrib/isn/meson.build
index cc30bbeb55c..db68a718313 100644
--- a/contrib/isn/meson.build
+++ b/contrib/isn/meson.build
@@ -2,6 +2,12 @@ isn_sources = files(
   'isn.c',
 )
 
+if host_system == 'windows'
+  isn_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'isn',
+    '--FILEDESC', 'isn - data types for international product numbering standards',])
+endif
+
 isn = shared_module('isn',
   isn_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/jsonb_plperl/meson.build b/contrib/jsonb_plperl/meson.build
index 5c915d8ed94..071a7a98d2c 100644
--- a/contrib/jsonb_plperl/meson.build
+++ b/contrib/jsonb_plperl/meson.build
@@ -6,6 +6,12 @@ jsonb_plperl_sources = files(
   'jsonb_plperl.c',
 )
 
+if host_system == 'windows'
+  jsonb_plperl_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'jsonb_plperl',
+    '--FILEDESC', 'jsonb_plperl - jsonb transform for plperl',])
+endif
+
 jsonb_plperl = shared_module('jsonb_plperl',
   jsonb_plperl_sources,
   include_directories: [plperl_inc],
diff --git a/contrib/jsonb_plpython/meson.build b/contrib/jsonb_plpython/meson.build
index de8e1105c6a..84dc1161e8b 100644
--- a/contrib/jsonb_plpython/meson.build
+++ b/contrib/jsonb_plpython/meson.build
@@ -6,6 +6,12 @@ jsonb_plpython_sources = files(
   'jsonb_plpython.c',
 )
 
+if host_system == 'windows'
+  jsonb_plpython_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'jsonb_plpython3',
+    '--FILEDESC', 'jsonb_plpython - jsonb transform for plpython',])
+endif
+
 jsonb_plpython = shared_module('jsonb_plpython3',
   jsonb_plpython_sources,
   include_directories: [plpython_inc],
diff --git a/contrib/lo/meson.build b/contrib/lo/meson.build
index 9082d5713c7..61ae131f1cc 100644
--- a/contrib/lo/meson.build
+++ b/contrib/lo/meson.build
@@ -2,6 +2,12 @@ lo_sources = files(
   'lo.c',
 )
 
+if host_system == 'windows'
+  lo_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'lo',
+    '--FILEDESC', 'lo - management for large objects',])
+endif
+
 lo = shared_module('lo',
   lo_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/ltree/meson.build b/contrib/ltree/meson.build
index 9463fc2c5e5..421292cea9d 100644
--- a/contrib/ltree/meson.build
+++ b/contrib/ltree/meson.build
@@ -13,6 +13,12 @@ ltree_sources = files(
 # .. so that includes of ltree/ltree.h work
 ltree_inc = include_directories('.', '../')
 
+if host_system == 'windows'
+  ltree_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'ltree',
+    '--FILEDESC', 'ltree - hierarchical label data type',])
+endif
+
 ltree = shared_module('ltree',
   ltree_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/ltree_plpython/meson.build b/contrib/ltree_plpython/meson.build
index 429d75006aa..acf5e4a6fc8 100644
--- a/contrib/ltree_plpython/meson.build
+++ b/contrib/ltree_plpython/meson.build
@@ -6,6 +6,12 @@ ltree_plpython_sources = files(
   'ltree_plpython.c',
 )
 
+if host_system == 'windows'
+  ltree_plpython_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'ltree_plpython3',
+    '--FILEDESC', 'ltree_plpython - ltree transform for plpython',])
+endif
+
 ltree_plpython = shared_module('ltree_plpython3',
   ltree_plpython_sources,
   include_directories: [plpython_inc, ltree_inc],
diff --git a/contrib/oid2name/meson.build b/contrib/oid2name/meson.build
index 1dad5d8f6e7..1a248f19260 100644
--- a/contrib/oid2name/meson.build
+++ b/contrib/oid2name/meson.build
@@ -1,5 +1,15 @@
+oid2name_sources = files(
+  'oid2name.c',
+)
+
+if host_system == 'windows'
+  oid2name_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'oid2name',
+    '--FILEDESC', 'oid2name - examine the file structure',])
+endif
+
 oid2name = executable('oid2name',
-  ['oid2name.c'],
+  oid2name_sources,
   dependencies: [frontend_code, libpq],
   kwargs: default_bin_args,
 )
diff --git a/contrib/old_snapshot/meson.build b/contrib/old_snapshot/meson.build
index 8e7ee09a43a..77276c3715a 100644
--- a/contrib/old_snapshot/meson.build
+++ b/contrib/old_snapshot/meson.build
@@ -2,6 +2,12 @@ old_snapshot_sources = files(
   'time_mapping.c',
 )
 
+if host_system == 'windows'
+  old_snapshot_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'old_snapshot',
+    '--FILEDESC', 'old_snapshot - utilities in support of old_snapshot_threshold',])
+endif
+
 old_snapshot = shared_module('old_snapshot',
   old_snapshot_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/pageinspect/meson.build b/contrib/pageinspect/meson.build
index 4af8153e4fd..3ec50b9445e 100644
--- a/contrib/pageinspect/meson.build
+++ b/contrib/pageinspect/meson.build
@@ -9,6 +9,12 @@ pageinspect_sources = files(
   'rawpage.c',
 )
 
+if host_system == 'windows'
+  pageinspect_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pageinspect',
+    '--FILEDESC', 'pageinspect - functions to inspect contents of database pages',])
+endif
+
 pageinspect = shared_module('pageinspect',
   pageinspect_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/passwordcheck/meson.build b/contrib/passwordcheck/meson.build
index 7da47d02f1d..383d7df372a 100644
--- a/contrib/passwordcheck/meson.build
+++ b/contrib/passwordcheck/meson.build
@@ -9,6 +9,12 @@ passwordcheck_deps = []
 # passwordcheck_c_args += ['-DUSE_CRACKLIB', '-DCRACKLIB_DICTPATH="/usr/lib/cracklib_dict"']
 # passwordcheck_deps += [cc.find_library('crack')]
 
+if host_system == 'windows'
+  passwordcheck_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'passwordcheck',
+    '--FILEDESC', 'passwordcheck - strengthen user password checks',])
+endif
+
 passwordcheck = shared_module('passwordcheck',
   passwordcheck_sources,
   c_args: passwordcheck_c_args,
diff --git a/contrib/pg_buffercache/meson.build b/contrib/pg_buffercache/meson.build
index 2c69eae3ea2..dd9948e5f0b 100644
--- a/contrib/pg_buffercache/meson.build
+++ b/contrib/pg_buffercache/meson.build
@@ -1,7 +1,15 @@
+pg_buffercache_sources = files(
+  'pg_buffercache_pages.c',
+)
+
+if host_system == 'windows'
+  pg_buffercache_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_buffercache',
+    '--FILEDESC', 'pg_buffercache - monitoring of shared buffer cache in real-time',])
+endif
+
 pg_buffercache = shared_module('pg_buffercache',
-  files(
-    'pg_buffercache_pages.c',
-  ),
+  pg_buffercache_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += pg_buffercache
diff --git a/contrib/pg_freespacemap/meson.build b/contrib/pg_freespacemap/meson.build
index f795014d7ca..904b37b6e9b 100644
--- a/contrib/pg_freespacemap/meson.build
+++ b/contrib/pg_freespacemap/meson.build
@@ -1,7 +1,15 @@
+pg_freespacemap_sources = files(
+  'pg_freespacemap.c',
+)
+
+if host_system == 'windows'
+  pg_freespacemap_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_freespacemap',
+    '--FILEDESC', 'pg_freespacemap - monitoring of free space map',])
+endif
+
 pg_freespacemap = shared_module('pg_freespacemap',
-  files(
-    'pg_freespacemap.c',
-  ),
+  pg_freespacemap_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += pg_freespacemap
diff --git a/contrib/pg_prewarm/meson.build b/contrib/pg_prewarm/meson.build
index bdca9af4f27..b7140cee34b 100644
--- a/contrib/pg_prewarm/meson.build
+++ b/contrib/pg_prewarm/meson.build
@@ -1,8 +1,16 @@
+pg_prewarm_sources = files(
+  'autoprewarm.c',
+  'pg_prewarm.c',
+)
+
+if host_system == 'windows'
+  pg_prewarm_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_prewarm',
+    '--FILEDESC', 'pg_prewarm - preload relation data into system buffer cache',])
+endif
+
 pg_prewarm = shared_module('pg_prewarm',
-  files(
-    'autoprewarm.c',
-    'pg_prewarm.c',
-  ),
+  pg_prewarm_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += pg_prewarm
diff --git a/contrib/pg_stat_statements/meson.build b/contrib/pg_stat_statements/meson.build
index ac117d2fc1d..854df138e76 100644
--- a/contrib/pg_stat_statements/meson.build
+++ b/contrib/pg_stat_statements/meson.build
@@ -1,5 +1,15 @@
+pg_stat_statements_sources = files(
+  'pg_stat_statements.c',
+)
+
+if host_system == 'windows'
+  pg_stat_statements_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_stat_statements',
+    '--FILEDESC', 'pg_stat_statements - execution statistics of SQL statements',])
+endif
+
 pg_stat_statements = shared_module('pg_stat_statements',
-  files('pg_stat_statements.c'),
+  pg_stat_statements_sources,
   kwargs: contrib_mod_args + {
     'dependencies': contrib_mod_args['dependencies'],
   },
diff --git a/contrib/pg_surgery/meson.build b/contrib/pg_surgery/meson.build
index ac71caa5276..7b5c5999f4b 100644
--- a/contrib/pg_surgery/meson.build
+++ b/contrib/pg_surgery/meson.build
@@ -1,7 +1,15 @@
+pg_surgery_sources = files(
+  'heap_surgery.c',
+)
+
+if host_system == 'windows'
+  pg_surgery_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_surgery',
+    '--FILEDESC', 'pg_surgery - perform surgery on a damaged relation',])
+endif
+
 pg_surgery = shared_module('pg_surgery',
-  files(
-    'heap_surgery.c',
-  ),
+  pg_surgery_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += pg_surgery
diff --git a/contrib/pg_trgm/meson.build b/contrib/pg_trgm/meson.build
index a90628d23c6..c8c7c07b308 100644
--- a/contrib/pg_trgm/meson.build
+++ b/contrib/pg_trgm/meson.build
@@ -1,10 +1,18 @@
+pg_trgm_sources = files(
+  'trgm_gin.c',
+  'trgm_gist.c',
+  'trgm_op.c',
+  'trgm_regexp.c',
+)
+
+if host_system == 'windows'
+  pg_trgm_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_trgm',
+    '--FILEDESC', 'pg_trgm - trigram matching',])
+endif
+
 pg_trgm = shared_module('pg_trgm',
-  files(
-    'trgm_gin.c',
-    'trgm_gist.c',
-    'trgm_op.c',
-    'trgm_regexp.c',
-  ),
+  pg_trgm_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += pg_trgm
diff --git a/contrib/pg_visibility/meson.build b/contrib/pg_visibility/meson.build
index 933dc99ac4d..263a0d08b82 100644
--- a/contrib/pg_visibility/meson.build
+++ b/contrib/pg_visibility/meson.build
@@ -1,7 +1,15 @@
+pg_visibility_sources = files(
+  'pg_visibility.c',
+)
+
+if host_system == 'windows'
+  pg_visibility_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_visibility',
+    '--FILEDESC', 'pg_visibility - page visibility information',])
+endif
+
 pg_visibility = shared_module('pg_visibility',
-  files(
-    'pg_visibility.c',
-  ),
+  pg_visibility_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += pg_visibility
diff --git a/contrib/pg_walinspect/meson.build b/contrib/pg_walinspect/meson.build
index d6b27877dd0..4314a3182a2 100644
--- a/contrib/pg_walinspect/meson.build
+++ b/contrib/pg_walinspect/meson.build
@@ -1,5 +1,11 @@
 pg_walinspect_sources = files('pg_walinspect.c')
 
+if host_system == 'windows'
+  pg_walinspect_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_walinspect',
+    '--FILEDESC', 'pg_walinspect - functions to inspect contents of PostgreSQL Write-Ahead Log',])
+endif
+
 pg_walinspect = shared_module('pg_walinspect',
   pg_walinspect_sources,
   kwargs: contrib_mod_args + {
diff --git a/contrib/pgcrypto/meson.build b/contrib/pgcrypto/meson.build
index fe0851bf8e8..7fc7bbc7ca1 100644
--- a/contrib/pgcrypto/meson.build
+++ b/contrib/pgcrypto/meson.build
@@ -69,6 +69,12 @@ else
   pgcrypto_regress += 'pgp-zlib-DISABLED'
 endif
 
+if host_system == 'windows'
+  pgcrypto_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pgcrypto',
+    '--FILEDESC', 'pgcrypto - cryptographic functions',])
+endif
+
 pgcrypto = shared_module('pgcrypto',
   pgcrypto_sources,
   link_with: pgcrypto_link_with,
diff --git a/contrib/pgrowlocks/meson.build b/contrib/pgrowlocks/meson.build
index 1b41691a2a3..8092f0d4a64 100644
--- a/contrib/pgrowlocks/meson.build
+++ b/contrib/pgrowlocks/meson.build
@@ -1,7 +1,15 @@
+pgrowlocks_sources = files(
+  'pgrowlocks.c',
+)
+
+if host_system == 'windows'
+  pgrowlocks_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pgrowlocks',
+    '--FILEDESC', 'pgrowlocks - display row locking information',])
+endif
+
 pgrowlocks = shared_module('pgrowlocks',
-  files(
-    'pgrowlocks.c',
-  ),
+  pgrowlocks_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += pgrowlocks
diff --git a/contrib/pgstattuple/meson.build b/contrib/pgstattuple/meson.build
index 8e828692d5c..05e4cd46a5c 100644
--- a/contrib/pgstattuple/meson.build
+++ b/contrib/pgstattuple/meson.build
@@ -1,9 +1,17 @@
+pgstattuple_sources = files(
+  'pgstatapprox.c',
+  'pgstatindex.c',
+  'pgstattuple.c',
+)
+
+if host_system == 'windows'
+  pgstattuple_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pgstattuple',
+    '--FILEDESC', 'pgstattuple - tuple-level statistics',])
+endif
+
 pgstattuple = shared_module('pgstattuple',
-  files(
-    'pgstatapprox.c',
-    'pgstatindex.c',
-    'pgstattuple.c',
-  ),
+  pgstattuple_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += pgstattuple
diff --git a/contrib/postgres_fdw/meson.build b/contrib/postgres_fdw/meson.build
index 378885ec93b..d3746ff135c 100644
--- a/contrib/postgres_fdw/meson.build
+++ b/contrib/postgres_fdw/meson.build
@@ -6,6 +6,12 @@ postgres_fdw_sources = files(
   'shippable.c',
 )
 
+if host_system == 'windows'
+  postgres_fdw_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'postgres_fdw',
+    '--FILEDESC', 'postgres_fdw - foreign data wrapper for PostgreSQL',])
+endif
+
 postgres_fdw = shared_module('postgres_fdw',
   postgres_fdw_sources,
   kwargs: contrib_mod_args + {
diff --git a/contrib/seg/meson.build b/contrib/seg/meson.build
index e476eab2a77..c6fbb22999b 100644
--- a/contrib/seg/meson.build
+++ b/contrib/seg/meson.build
@@ -17,6 +17,12 @@ seg_parse = custom_target('segparse',
 generated_sources += seg_parse.to_list()
 seg_sources += seg_parse
 
+if host_system == 'windows'
+  seg_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'seg',
+    '--FILEDESC', 'seg - line segment data type',])
+endif
+
 seg = shared_module('seg',
   seg_sources,
   include_directories: include_directories('.'),
diff --git a/contrib/sepgsql/meson.build b/contrib/sepgsql/meson.build
index 60a95e17c2f..8bef239e3c2 100644
--- a/contrib/sepgsql/meson.build
+++ b/contrib/sepgsql/meson.build
@@ -14,6 +14,12 @@ sepgsql_sources = files(
   'uavc.c',
 )
 
+if host_system == 'windows'
+  sepgsql_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'sepgsql',
+    '--FILEDESC', 'sepgsql - SELinux integration',])
+endif
+
 sepgsql = shared_module('sepgsql',
   sepgsql_sources,
   kwargs: contrib_mod_args + {
diff --git a/contrib/spi/meson.build b/contrib/spi/meson.build
index 98008980ec2..e7d78189ef5 100644
--- a/contrib/spi/meson.build
+++ b/contrib/spi/meson.build
@@ -1,5 +1,15 @@
+autoinc_sources = files(
+  'autoinc.c',
+)
+
+if host_system == 'windows'
+  autoinc_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'autoinc',
+    '--FILEDESC', 'spi - examples of using SPI and triggers',])
+endif
+
 autoinc = shared_module('autoinc',
-  ['autoinc.c'],
+  autoinc_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += autoinc
@@ -9,8 +19,18 @@ install_data('autoinc.control', 'autoinc--1.0.sql',
 )
 
 
+insert_username_sources = files(
+  'insert_username.c',
+)
+
+if host_system == 'windows'
+  insert_username_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'insert_username',
+    '--FILEDESC', 'spi - examples of using SPI and triggers',])
+endif
+
 insert_username = shared_module('insert_username',
-  ['insert_username.c'],
+  insert_username_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += insert_username
@@ -22,8 +42,18 @@ install_data(
 )
 
 
+moddatetime_sources = files(
+  'moddatetime.c',
+)
+
+if host_system == 'windows'
+  moddatetime_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'moddatetime',
+    '--FILEDESC', 'spi - examples of using SPI and triggers',])
+endif
+
 moddatetime = shared_module('moddatetime',
-  ['moddatetime.c'],
+  moddatetime_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += moddatetime
@@ -38,8 +68,18 @@ install_data(
 # comment out if you want a quieter refint package for other uses
 refint_cflags = ['-DREFINT_VERBOSE']
 
+refint_sources = files(
+  'refint.c',
+)
+
+if host_system == 'windows'
+  refint_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'refint',
+    '--FILEDESC', 'spi - examples of using SPI and triggers',])
+endif
+
 refint = shared_module('refint',
-  ['refint.c'],
+  refint_sources,
   c_args: refint_cflags,
   kwargs: contrib_mod_args,
 )
diff --git a/contrib/sslinfo/meson.build b/contrib/sslinfo/meson.build
index 53f752a08ac..136983e783d 100644
--- a/contrib/sslinfo/meson.build
+++ b/contrib/sslinfo/meson.build
@@ -2,10 +2,18 @@ if not ssl.found()
   subdir_done()
 endif
 
+sslinfo_sources = files(
+  'sslinfo.c',
+)
+
+if host_system == 'windows'
+  sslinfo_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'sslinfo',
+    '--FILEDESC', 'sslinfo - information about client SSL certificate',])
+endif
+
 sslinfo = shared_module('sslinfo',
-  files(
-    'sslinfo.c',
-  ),
+  sslinfo_sources,
   kwargs: contrib_mod_args + {
     'dependencies': [ssl, contrib_mod_args['dependencies']],
   }
diff --git a/contrib/tablefunc/meson.build b/contrib/tablefunc/meson.build
index f4230096c0c..d2ddc8d3b39 100644
--- a/contrib/tablefunc/meson.build
+++ b/contrib/tablefunc/meson.build
@@ -1,7 +1,15 @@
+tablefunc_sources = files(
+  'tablefunc.c',
+)
+
+if host_system == 'windows'
+  tablefunc_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'tablefunc',
+    '--FILEDESC', 'tablefunc - various functions that return tables',])
+endif
+
 tablefunc = shared_module('tablefunc',
-  files(
-    'tablefunc.c',
-  ),
+  tablefunc_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += tablefunc
diff --git a/contrib/tcn/meson.build b/contrib/tcn/meson.build
index c3a025247d4..71261c3b0a2 100644
--- a/contrib/tcn/meson.build
+++ b/contrib/tcn/meson.build
@@ -1,7 +1,15 @@
+tcn_sources = files(
+  'tcn.c',
+)
+
+if host_system == 'windows'
+  tcn_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'tcn',
+    '--FILEDESC', 'tcn - trigger function notifying listeners',])
+endif
+
 tcn = shared_module('tcn',
-  files(
-    'tcn.c',
-  ),
+  tcn_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += tcn
diff --git a/contrib/test_decoding/meson.build b/contrib/test_decoding/meson.build
index dd7cb0101ad..6376103c689 100644
--- a/contrib/test_decoding/meson.build
+++ b/contrib/test_decoding/meson.build
@@ -2,6 +2,12 @@ test_decoding_sources = files(
   'test_decoding.c',
 )
 
+if host_system == 'windows'
+  test_decoding_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_decoding',
+    '--FILEDESC', 'test_decoding - example of a logical decoding output plugin',])
+endif
+
 test_decoding = shared_module('test_decoding',
   test_decoding_sources,
   kwargs: contrib_mod_args,
diff --git a/contrib/tsm_system_rows/meson.build b/contrib/tsm_system_rows/meson.build
index b9cd42115a8..380abb49883 100644
--- a/contrib/tsm_system_rows/meson.build
+++ b/contrib/tsm_system_rows/meson.build
@@ -1,7 +1,15 @@
+tsm_system_rows_sources = files(
+  'tsm_system_rows.c',
+)
+
+if host_system == 'windows'
+  tsm_system_rows_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'tsm_system_rows',
+    '--FILEDESC', 'tsm_system_rows - TABLESAMPLE method which accepts number of rows as a limit',])
+endif
+
 tsm_system_rows = shared_module('tsm_system_rows',
-  files(
-    'tsm_system_rows.c',
-  ),
+  tsm_system_rows_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += tsm_system_rows
diff --git a/contrib/tsm_system_time/meson.build b/contrib/tsm_system_time/meson.build
index 18015912ffb..e57a2702c60 100644
--- a/contrib/tsm_system_time/meson.build
+++ b/contrib/tsm_system_time/meson.build
@@ -1,7 +1,15 @@
+tsm_system_time_sources = files(
+  'tsm_system_time.c',
+)
+
+if host_system == 'windows'
+  tsm_system_time_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'tsm_system_time',
+    '--FILEDESC', 'tsm_system_time - TABLESAMPLE method which accepts time in milliseconds as a limit',])
+endif
+
 tsm_system_time = shared_module('tsm_system_time',
-  files(
-    'tsm_system_time.c',
-  ),
+  tsm_system_time_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += tsm_system_time
diff --git a/contrib/unaccent/meson.build b/contrib/unaccent/meson.build
index 872b76e3223..438035132f8 100644
--- a/contrib/unaccent/meson.build
+++ b/contrib/unaccent/meson.build
@@ -1,7 +1,15 @@
+unaccent_sources = files(
+  'unaccent.c',
+)
+
+if host_system == 'windows'
+  unaccent_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'unaccent',
+    '--FILEDESC', 'unaccent - text search dictionary that removes accents',])
+endif
+
 unaccent = shared_module('unaccent',
-  files(
-    'unaccent.c',
-  ),
+  unaccent_sources,
   kwargs: contrib_mod_args,
 )
 contrib_targets += unaccent
diff --git a/contrib/uuid-ossp/meson.build b/contrib/uuid-ossp/meson.build
index da6d1d75c12..28730f398f0 100644
--- a/contrib/uuid-ossp/meson.build
+++ b/contrib/uuid-ossp/meson.build
@@ -2,10 +2,18 @@ if not uuid.found()
   subdir_done()
 endif
 
+uuid_ossp_sources = files(
+  'uuid-ossp.c',
+)
+
+if host_system == 'windows'
+  uuid_ossp_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'uuid-ossp',
+    '--FILEDESC', 'uuid-ossp - UUID generation',])
+endif
+
 uuid_ossp = shared_module('uuid-ossp',
-  files(
-    'uuid-ossp.c',
-  ),
+  uuid_ossp_sources,
   kwargs: contrib_mod_args + {
     'dependencies': [uuid, contrib_mod_args['dependencies']],
   },
diff --git a/contrib/vacuumlo/meson.build b/contrib/vacuumlo/meson.build
index 7a632b87d1b..846de47dbd1 100644
--- a/contrib/vacuumlo/meson.build
+++ b/contrib/vacuumlo/meson.build
@@ -1,5 +1,15 @@
+vacuumlo_sources = files(
+  'vacuumlo.c',
+)
+
+if host_system == 'windows'
+  vacuumlo_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'vacuumlo',
+    '--FILEDESC', 'vacuumlo - removes orphaned large objects',])
+endif
+
 vacuumlo = executable('vacuumlo',
-  ['vacuumlo.c'],
+  vacuumlo_sources,
   dependencies: [frontend_code, libpq],
   kwargs: default_bin_args,
 )
diff --git a/contrib/xml2/meson.build b/contrib/xml2/meson.build
index 9c0b56f01f6..89b0d677516 100644
--- a/contrib/xml2/meson.build
+++ b/contrib/xml2/meson.build
@@ -2,11 +2,19 @@ if not libxml.found()
   subdir_done()
 endif
 
+xml2_sources = files(
+  'xpath.c',
+  'xslt_proc.c',
+)
+
+if host_system == 'windows'
+  xml2_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pgxml',
+    '--FILEDESC', 'xml2 - XPath querying and XSLT',])
+endif
+
 xml2 = shared_module('pgxml',
-  files(
-    'xpath.c',
-    'xslt_proc.c',
-  ),
+  xml2_sources,
   kwargs: contrib_mod_args + {
     'dependencies': [libxml, libxslt, contrib_mod_args['dependencies']],
   },
diff --git a/src/interfaces/ecpg/compatlib/meson.build b/src/interfaces/ecpg/compatlib/meson.build
index b803000c708..4d6454381b5 100644
--- a/src/interfaces/ecpg/compatlib/meson.build
+++ b/src/interfaces/ecpg/compatlib/meson.build
@@ -7,6 +7,12 @@ ecpg_compat_inc = [include_directories('.'), ecpg_inc, libpq_inc]
 ecpg_compat_c_args = ['-DSO_MAJOR_VERSION=3']
 export_file = custom_target('libecpg_compat.exports', kwargs: gen_export_kwargs)
 
+if host_system == 'windows'
+  ecpg_compat_so_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'libecpg_compat',
+    '--FILEDESC', 'ECPG compat - compatibility library for ECPG',])
+endif
+
 # see src/interfaces/libpq/meson.build
 ecpg_compat_st = static_library('libecpg_compat',
   ecpg_compat_sources,
diff --git a/src/interfaces/ecpg/ecpglib/meson.build b/src/interfaces/ecpg/ecpglib/meson.build
index 6fdf019149c..7e6e6fbf5c0 100644
--- a/src/interfaces/ecpg/ecpglib/meson.build
+++ b/src/interfaces/ecpg/ecpglib/meson.build
@@ -16,6 +16,12 @@ ecpglib_inc = [include_directories('.'), ecpg_inc]
 ecpglib_c_args = ['-DSO_MAJOR_VERSION=6']
 export_file = custom_target('libecpg.exports', kwargs: gen_export_kwargs)
 
+if host_system == 'windows'
+  ecpglib_so_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'libecpg',
+    '--FILEDESC', 'ECPG - embedded SQL in C',])
+endif
+
 # see src/interfaces/libpq/meson.build
 ecpglib_st = static_library('libecpg',
   ecpglib_sources,
diff --git a/src/interfaces/ecpg/pgtypeslib/meson.build b/src/interfaces/ecpg/pgtypeslib/meson.build
index 8e5d235810a..530dd2c602d 100644
--- a/src/interfaces/ecpg/pgtypeslib/meson.build
+++ b/src/interfaces/ecpg/pgtypeslib/meson.build
@@ -12,6 +12,12 @@ export_file = custom_target('libpgtypes.exports', kwargs: gen_export_kwargs)
 ecpg_pgtypes_inc = [include_directories('.'), ecpg_inc]
 ecpg_pgtypes_c_args = ['-DSO_MAJOR_VERSION=3']
 
+if host_system == 'windows'
+  ecpg_pgtypes_so_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pgtypes',
+    '--FILEDESC', 'pgtypes - library for data type mapping',])
+endif
+
 # see src/interfaces/libpq/meson.build
 ecpg_pgtypes_st = static_library('libpgtypes',
   ecpg_pgtypes_sources,
diff --git a/src/interfaces/ecpg/preproc/meson.build b/src/interfaces/ecpg/preproc/meson.build
index 1be49c8c27f..74876f039c9 100644
--- a/src/interfaces/ecpg/preproc/meson.build
+++ b/src/interfaces/ecpg/preproc/meson.build
@@ -93,6 +93,12 @@ ecpg_kwlist = custom_target('ecpg_kwlist_d.h',
 generated_sources += ecpg_kwlist
 ecpg_sources += ecpg_kwlist
 
+if host_system == 'windows'
+  ecpg_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'ecpg',
+    '--FILEDESC', 'ecpg - embedded SQL precompiler for C',])
+endif
+
 ecpg_exe = executable('ecpg',
   ecpg_sources,
   include_directories: ['.', ecpg_inc, postgres_inc, libpq_inc],
diff --git a/src/interfaces/ecpg/test/meson.build b/src/interfaces/ecpg/test/meson.build
index 8904aa7fd90..94b26d10314 100644
--- a/src/interfaces/ecpg/test/meson.build
+++ b/src/interfaces/ecpg/test/meson.build
@@ -7,6 +7,11 @@ pg_regress_ecpg_sources = pg_regress_c + files(
   'pg_regress_ecpg.c',
 )
 
+if host_system == 'windows'
+  pg_regress_ecpg_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_regress_ecpg',])
+endif
+
 pg_regress_ecpg = executable('pg_regress_ecpg',
   pg_regress_ecpg_sources,
   c_args: pg_regress_cflags,
diff --git a/src/test/isolation/meson.build b/src/test/isolation/meson.build
index c7656fd4609..ba27b8c1d44 100644
--- a/src/test/isolation/meson.build
+++ b/src/test/isolation/meson.build
@@ -23,6 +23,12 @@ spec_parser = custom_target('specparse',
 isolationtester_sources += spec_parser
 generated_sources += spec_parser.to_list()
 
+if host_system == 'windows'
+  isolation_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_isolation_regress',
+    '--FILEDESC', 'pg_isolation_regress - multi-client test driver',])
+endif
+
 pg_isolation_regress = executable('pg_isolation_regress',
   isolation_sources,
   c_args: pg_regress_cflags,
@@ -34,6 +40,13 @@ pg_isolation_regress = executable('pg_isolation_regress',
 )
 bin_targets += pg_isolation_regress
 
+
+if host_system == 'windows'
+  isolationtester_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'isolationtester',
+    '--FILEDESC', 'isolationtester - multi-client test driver',])
+endif
+
 isolationtester = executable('isolationtester',
   isolationtester_sources,
   include_directories: include_directories('.'),
diff --git a/src/test/modules/delay_execution/meson.build b/src/test/modules/delay_execution/meson.build
index cf4bdaba637..a0c3ab6afe7 100644
--- a/src/test/modules/delay_execution/meson.build
+++ b/src/test/modules/delay_execution/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+delay_execution_sources = files(
+  'delay_execution.c',
+)
+
+if host_system == 'windows'
+  delay_execution_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'delay_execution',
+    '--FILEDESC', 'delay_execution - allow delay between parsing and execution',])
+endif
+
 delay_execution = shared_module('delay_execution',
-  ['delay_execution.c'],
+  delay_execution_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += delay_execution
diff --git a/src/test/modules/dummy_index_am/meson.build b/src/test/modules/dummy_index_am/meson.build
index 56ff5f48001..4ce82491135 100644
--- a/src/test/modules/dummy_index_am/meson.build
+++ b/src/test/modules/dummy_index_am/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+dummy_index_am_sources = files(
+  'dummy_index_am.c',
+)
+
+if host_system == 'windows'
+  dummy_index_am_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'dummy_index_am',
+    '--FILEDESC', 'dummy_index_am - index access method template',])
+endif
+
 dummy_index_am = shared_module('dummy_index_am',
-  ['dummy_index_am.c'],
+  dummy_index_am_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += dummy_index_am
diff --git a/src/test/modules/dummy_seclabel/meson.build b/src/test/modules/dummy_seclabel/meson.build
index 21b7cf8f353..81b626e496c 100644
--- a/src/test/modules/dummy_seclabel/meson.build
+++ b/src/test/modules/dummy_seclabel/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+dummy_seclabel_sources = files(
+  'dummy_seclabel.c',
+)
+
+if host_system == 'windows'
+  dummy_seclabel_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'dummy_seclabel',
+    '--FILEDESC', 'dummy_seclabel - regression testing of the SECURITY LABEL statement',])
+endif
+
 dummy_seclabel = shared_module('dummy_seclabel',
-  ['dummy_seclabel.c'],
+  dummy_seclabel_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += dummy_seclabel
diff --git a/src/test/modules/libpq_pipeline/meson.build b/src/test/modules/libpq_pipeline/meson.build
index 8384b6e3b2a..de0e2d15626 100644
--- a/src/test/modules/libpq_pipeline/meson.build
+++ b/src/test/modules/libpq_pipeline/meson.build
@@ -1,7 +1,15 @@
+libpq_pipeline_sources = files(
+  'libpq_pipeline.c',
+)
+
+if host_system == 'windows'
+  libpq_pipeline_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'libpq_pipeline',
+    '--FILEDESC', 'libpq_pipeline - test program for pipeline execution',])
+endif
+
 libpq_pipeline = executable('libpq_pipeline',
-  files(
-    'libpq_pipeline.c',
-  ),
+  libpq_pipeline_sources,
   dependencies: [frontend_code, libpq],
   kwargs: default_bin_args + {
     'install': false,
diff --git a/src/test/modules/plsample/meson.build b/src/test/modules/plsample/meson.build
index 45de3f1990d..e1ea2c7a16f 100644
--- a/src/test/modules/plsample/meson.build
+++ b/src/test/modules/plsample/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+plsample_sources = files(
+  'plsample.c',
+)
+
+if host_system == 'windows'
+  plsample_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'plsample',
+    '--FILEDESC', 'PL/Sample - template for procedural language',])
+endif
+
 plsample = shared_module('plsample',
-  ['plsample.c'],
+  plsample_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += plsample
diff --git a/src/test/modules/spgist_name_ops/meson.build b/src/test/modules/spgist_name_ops/meson.build
index 857fc7e140e..445296fee0b 100644
--- a/src/test/modules/spgist_name_ops/meson.build
+++ b/src/test/modules/spgist_name_ops/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+spgist_name_ops_sources = files(
+  'spgist_name_ops.c',
+)
+
+if host_system == 'windows'
+  spgist_name_ops_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'spgist_name_ops',
+    '--FILEDESC', 'spgist_name_ops - test opclass for SP-GiST',])
+endif
+
 spgist_name_ops = shared_module('spgist_name_ops',
-  ['spgist_name_ops.c'],
+  spgist_name_ops_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += spgist_name_ops
diff --git a/src/test/modules/ssl_passphrase_callback/meson.build b/src/test/modules/ssl_passphrase_callback/meson.build
index a57bd0693a3..a9eb4c564da 100644
--- a/src/test/modules/ssl_passphrase_callback/meson.build
+++ b/src/test/modules/ssl_passphrase_callback/meson.build
@@ -3,8 +3,19 @@ if not ssl.found()
 endif
 
 # FIXME: prevent install during main install, but not during test :/
+
+ssl_passphrase_callback_sources = files(
+  'ssl_passphrase_func.c',
+)
+
+if host_system == 'windows'
+  ssl_passphrase_callback_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'ssl_passphrase_func',
+    '--FILEDESC', 'callback function to provide a passphrase',])
+endif
+
 ssl_passphrase_callback = shared_module('ssl_passphrase_func',
-  ['ssl_passphrase_func.c'],
+  ssl_passphrase_callback_sources,
   kwargs: pg_mod_args + {
     'dependencies': [ssl, pg_mod_args['dependencies']],
   },
diff --git a/src/test/modules/test_bloomfilter/meson.build b/src/test/modules/test_bloomfilter/meson.build
index 945eb5a70c4..3cf6b05754f 100644
--- a/src/test/modules/test_bloomfilter/meson.build
+++ b/src/test/modules/test_bloomfilter/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_bloomfilter_sources = files(
+  'test_bloomfilter.c',
+)
+
+if host_system == 'windows'
+  test_bloomfilter_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_bloomfilter',
+    '--FILEDESC', 'test_bloomfilter - test code for Bloom filter library',])
+endif
+
 test_bloomfilter = shared_module('test_bloomfilter',
-  ['test_bloomfilter.c'],
+  test_bloomfilter_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_bloomfilter
diff --git a/src/test/modules/test_ddl_deparse/meson.build b/src/test/modules/test_ddl_deparse/meson.build
index 81ad5adc526..54d44f9b2b4 100644
--- a/src/test/modules/test_ddl_deparse/meson.build
+++ b/src/test/modules/test_ddl_deparse/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_ddl_deparse_sources = files(
+  'test_ddl_deparse.c',
+)
+
+if host_system == 'windows'
+  test_ddl_deparse_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_ddl_deparse',
+    '--FILEDESC', 'test_ddl_deparse - regression testing for DDL deparsing',])
+endif
+
 test_ddl_deparse = shared_module('test_ddl_deparse',
-  ['test_ddl_deparse.c'],
+  test_ddl_deparse_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_ddl_deparse
diff --git a/src/test/modules/test_ginpostinglist/meson.build b/src/test/modules/test_ginpostinglist/meson.build
index abf0a3b0430..b3b49c56122 100644
--- a/src/test/modules/test_ginpostinglist/meson.build
+++ b/src/test/modules/test_ginpostinglist/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_ginpostinglist_sources = files(
+  'test_ginpostinglist.c',
+)
+
+if host_system == 'windows'
+  test_ginpostinglist_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_ginpostinglist',
+    '--FILEDESC', 'test_ginpostinglist - test code for src/backend/access/gin//ginpostinglist.c',])
+endif
+
 test_ginpostinglist = shared_module('test_ginpostinglist',
-  ['test_ginpostinglist.c'],
+  test_ginpostinglist_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_ginpostinglist
diff --git a/src/test/modules/test_integerset/meson.build b/src/test/modules/test_integerset/meson.build
index c32c469c69a..4bd75af4b5e 100644
--- a/src/test/modules/test_integerset/meson.build
+++ b/src/test/modules/test_integerset/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_integerset_sources = files(
+  'test_integerset.c',
+)
+
+if host_system == 'windows'
+  test_integerset_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_integerset',
+    '--FILEDESC', 'test_integerset - test code for src/backend/lib/integerset.c',])
+endif
+
 test_integerset = shared_module('test_integerset',
-  ['test_integerset.c'],
+  test_integerset_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_integerset
diff --git a/src/test/modules/test_lfind/meson.build b/src/test/modules/test_lfind/meson.build
index a388de1156a..c5405b8f878 100644
--- a/src/test/modules/test_lfind/meson.build
+++ b/src/test/modules/test_lfind/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_lfind_sources = files(
+  'test_lfind.c',
+)
+
+if host_system == 'windows'
+  test_lfind_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_lfind',
+    '--FILEDESC', 'test_lfind - test code for optimized linear search functions',])
+endif
+
 test_lfind = shared_module('test_lfind',
-  ['test_lfind.c'],
+  test_lfind_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_lfind
diff --git a/src/test/modules/test_oat_hooks/meson.build b/src/test/modules/test_oat_hooks/meson.build
index 5faf0459777..8802bbbac55 100644
--- a/src/test/modules/test_oat_hooks/meson.build
+++ b/src/test/modules/test_oat_hooks/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_oat_hooks_sources = files(
+  'test_oat_hooks.c',
+)
+
+if host_system == 'windows'
+  test_oat_hooks_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_oat_hooks',
+    '--FILEDESC', 'test_oat_hooks - example use of object access hooks',])
+endif
+
 test_oat_hooks = shared_module('test_oat_hooks',
-  ['test_oat_hooks.c'],
+  test_oat_hooks_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_oat_hooks
diff --git a/src/test/modules/test_parser/meson.build b/src/test/modules/test_parser/meson.build
index b59960f615e..1c17113347f 100644
--- a/src/test/modules/test_parser/meson.build
+++ b/src/test/modules/test_parser/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_parser_sources = files(
+  'test_parser.c',
+)
+
+if host_system == 'windows'
+  test_parser_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_parser',
+    '--FILEDESC', 'test_parser - example of a custom parser for full-text search',])
+endif
+
 test_parser = shared_module('test_parser',
-  ['test_parser.c'],
+  test_parser_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_parser
diff --git a/src/test/modules/test_predtest/meson.build b/src/test/modules/test_predtest/meson.build
index 1cfa84b3609..9a5be43c9c0 100644
--- a/src/test/modules/test_predtest/meson.build
+++ b/src/test/modules/test_predtest/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_predtest_sources = files(
+  'test_predtest.c',
+)
+
+if host_system == 'windows'
+  test_predtest_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_predtest',
+    '--FILEDESC', 'test_predtest - test code for optimizer/util/predtest.c',])
+endif
+
 test_predtest = shared_module('test_predtest',
-  ['test_predtest.c'],
+  test_predtest_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_predtest
diff --git a/src/test/modules/test_rbtree/meson.build b/src/test/modules/test_rbtree/meson.build
index 34cbc3e1624..f067e08d321 100644
--- a/src/test/modules/test_rbtree/meson.build
+++ b/src/test/modules/test_rbtree/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_rbtree_sources = files(
+  'test_rbtree.c',
+)
+
+if host_system == 'windows'
+  test_rbtree_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_rbtree',
+    '--FILEDESC', 'test_rbtree - test code for red-black tree library',])
+endif
+
 test_rbtree = shared_module('test_rbtree',
-  ['test_rbtree.c'],
+  test_rbtree_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_rbtree
diff --git a/src/test/modules/test_regex/meson.build b/src/test/modules/test_regex/meson.build
index 867a64e57c3..cfb938d9f1e 100644
--- a/src/test/modules/test_regex/meson.build
+++ b/src/test/modules/test_regex/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_regex_sources = files(
+  'test_regex.c',
+)
+
+if host_system == 'windows'
+  test_regex_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_regex',
+    '--FILEDESC', 'test_regex - test code for backend/regex/',])
+endif
+
 test_regex = shared_module('test_regex',
-  ['test_regex.c'],
+  test_regex_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_regex
diff --git a/src/test/modules/test_rls_hooks/meson.build b/src/test/modules/test_rls_hooks/meson.build
index 80d8adda332..3fb273b2934 100644
--- a/src/test/modules/test_rls_hooks/meson.build
+++ b/src/test/modules/test_rls_hooks/meson.build
@@ -1,6 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_rls_hooks_sources = files(
+  'test_rls_hooks.c',
+)
+
+if host_system == 'windows'
+  test_rls_hooks_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_rls_hooks',
+    '--FILEDESC', 'test_rls_hooks - example use of RLS hooks',])
+endif
+
 test_rls_hooks = shared_module('test_rls_hooks',
-  ['test_rls_hooks.c'],
+  test_rls_hooks_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_rls_hooks
diff --git a/src/test/modules/test_shm_mq/meson.build b/src/test/modules/test_shm_mq/meson.build
index b663543d616..16c8fdb57f4 100644
--- a/src/test/modules/test_shm_mq/meson.build
+++ b/src/test/modules/test_shm_mq/meson.build
@@ -1,10 +1,19 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_shm_mq_sources = files(
+  'setup.c',
+  'test.c',
+  'worker.c',
+)
+
+if host_system == 'windows'
+  test_shm_mq_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_shm_mq',
+    '--FILEDESC', 'test_shm_mq - example use of shared memory message queue',])
+endif
+
 test_shm_mq = shared_module('test_shm_mq',
-  files(
-    'setup.c',
-    'test.c',
-    'worker.c',
-  ),
+  test_shm_mq_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_shm_mq
diff --git a/src/test/modules/worker_spi/meson.build b/src/test/modules/worker_spi/meson.build
index 32acad883b2..a4a158c75b9 100644
--- a/src/test/modules/worker_spi/meson.build
+++ b/src/test/modules/worker_spi/meson.build
@@ -1,8 +1,17 @@
 # FIXME: prevent install during main install, but not during test :/
+
+test_worker_spi_sources = files(
+  'worker_spi.c',
+)
+
+if host_system == 'windows'
+  test_worker_spi_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'worker_spi',
+    '--FILEDESC', 'worker_spi - background worker example',])
+endif
+
 test_worker_spi = shared_module('worker_spi',
-  files(
-    'worker_spi.c',
-  ),
+  test_worker_spi_sources,
   kwargs: pg_mod_args,
 )
 testprep_targets += test_worker_spi
diff --git a/src/test/regress/meson.build b/src/test/regress/meson.build
index 03de591b0c7..3dcfc11278f 100644
--- a/src/test/regress/meson.build
+++ b/src/test/regress/meson.build
@@ -17,6 +17,12 @@ host_tuple = '@0@-@1@-@2@'.format(host_cpu, host_system, host_tuple_cc)
 
 pg_regress_cflags = ['-DHOST_TUPLE="@0@"'.format(host_tuple), '-DSHELLPROG="/bin/sh"']
 
+if host_system == 'windows'
+  regress_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pg_regress',
+    '--FILEDESC', 'pg_regress - test driver',])
+endif
+
 pg_regress = executable('pg_regress',
   regress_sources,
   c_args: pg_regress_cflags,
diff --git a/meson.build b/meson.build
index c709643fe5e..25a6fa941cc 100644
--- a/meson.build
+++ b/meson.build
@@ -2564,6 +2564,65 @@ gen_export_kwargs = {
 
 
 
+###
+### windows resources related stuff
+###
+
+if host_system == 'windows'
+  pg_ico = meson.source_root() / 'src' / 'port' / 'win32.ico'
+  win32ver_rc = files('src/port/win32ver.rc')
+  rcgen = find_program('src/tools/rcgen', native: true)
+
+  rcgen_base_args = [
+    '--srcdir', '@SOURCE_DIR@',
+    '--builddir', meson.build_root(),
+    '--rcout', '@OUTPUT0@',
+    '--out', '@OUTPUT1@',
+    '--input', '@INPUT@',
+    '@EXTRA_ARGS@',
+  ]
+
+  if cc.get_argument_syntax() == 'msvc'
+    rc = find_program('rc', required: true)
+    rcgen_base_args += ['--rc', rc.path()]
+    rcgen_outputs = ['@[email protected]', '@[email protected]']
+  else
+    windres = find_program('windres', required: true)
+    rcgen_base_args += ['--windres', windres.path()]
+    rcgen_outputs = ['@[email protected]', '@[email protected]']
+  endif
+
+  # msbuild backend doesn't support this atm
+  if meson.backend() == 'ninja'
+    rcgen_base_args += ['--depfile', '@DEPFILE@']
+  endif
+
+  rcgen_bin_args = rcgen_base_args + [
+    '--VFT_TYPE', 'VFT_APP',
+    '--FILEENDING', 'exe',
+    '--ICO', pg_ico
+  ]
+
+  rcgen_lib_args = rcgen_base_args + [
+    '--VFT_TYPE', 'VFT_DLL',
+    '--FILEENDING', 'dll',
+  ]
+
+  rc_bin_gen = generator(rcgen,
+    depfile: '@[email protected]',
+    arguments: rcgen_bin_args,
+    output: rcgen_outputs,
+  )
+
+  rc_lib_gen = generator(rcgen,
+    depfile: '@[email protected]',
+    arguments: rcgen_lib_args,
+    output: rcgen_outputs,
+  )
+endif
+
+
+
 # headers that the whole build tree depends on
 generated_headers = []
 # headers that the backend build depends on
diff --git a/src/timezone/meson.build b/src/timezone/meson.build
index 16f082ecfa8..9e0934c000b 100644
--- a/src/timezone/meson.build
+++ b/src/timezone/meson.build
@@ -28,6 +28,12 @@ if get_option('system_tzdata') == ''
   if meson.is_cross_build()
     zic = find_program(get_option('ZIC'), native: true, required: true)
   else
+    if host_system == 'windows'
+      zic_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
+        '--NAME', 'zic',
+        '--FILEDESC', 'zic - time zone compiler',])
+    endif
+
     zic = executable('zic', zic_sources,
                      dependencies: [frontend_code],
                      kwargs: default_bin_args + {'install': false}
diff --git a/src/tools/rcgen b/src/tools/rcgen
new file mode 100755
index 00000000000..0c84772163c
--- /dev/null
+++ b/src/tools/rcgen
@@ -0,0 +1,105 @@
+#!/usr/bin/env python3
+
+# Helper for building resource files when building for windows. Always
+# generates a .rc from the input .rc file. When building with msvc we
+# additionally generate a .res file with 'rc', when building with gcc, we use
+# windres to directly generate a .o.  Additionally we generate basic
+# dependencies if depfile is specified.
+
+import argparse
+import os
+import subprocess
+import sys
+
+parser = argparse.ArgumentParser(description='generate PostgreSQL rc file')
+
+parser.add_argument('--srcdir', type=os.path.abspath,
+                    required=True)
+parser.add_argument('--builddir', type=os.path.abspath,
+                    required=True)
+
+binaries = parser.add_argument_group('binaries')
+binaries.add_argument('--windres', type=os.path.abspath)
+binaries.add_argument('--rc', type=os.path.abspath)
+
+inout = parser.add_argument_group('inout')
+inout.add_argument('--depfile', type=argparse.FileType('w'))
+inout.add_argument('--input', type=argparse.FileType('r'),
+                   required=True)
+inout.add_argument('--rcout', type=argparse.FileType('w'),
+                   required=True)
+inout.add_argument('--out', type=str,
+                   required=True)
+
+replacements = parser.add_argument_group('replacements')
+replacements.add_argument('--FILEDESC', type=str)
+replacements.add_argument('--NAME', type=str, required=True)
+replacements.add_argument('--VFT_TYPE', type=str, required=True)
+replacements.add_argument('--FILEENDING', type=str, required=True)
+replacements.add_argument('--ICO', type=str)
+
+args = parser.parse_args()
+
+# determine replacement strings
+
+internal_name = '"{0}"'.format(args.NAME)
+original_name = '"{0}.{1}"'.format(args.NAME, args.FILEENDING)
+
+# if no description is passed in, generate one based on the name
+if args.FILEDESC:
+    filedesc = args.FILEDESC
+elif args.NAME:
+    if args.VFT_TYPE == 'VFT_DLL':
+        filedesc = 'PostgreSQL {0} library'.format(args.NAME)
+    else:
+        filedesc = 'PostgreSQL {0} binary'.format(args.NAME)
+filedesc = '"{0}"'.format(filedesc)
+
+
+if args.ICO:
+    ico = 'IDI_ICON ICON "{0}"'.format(args.ICO)
+    if args.depfile:
+        args.depfile.write("{0} : {1}\n".format(args.rcout.name, args.ICO))
+else:
+    ico = ''
+
+
+data = args.input.read()
+
+data = data.replace('VFT_APP', args.VFT_TYPE)
+data = data.replace('_INTERNAL_NAME_', internal_name)
+data = data.replace('_ORIGINAL_NAME_', original_name)
+data = data.replace('FILEDESC', filedesc)
+data = data.replace("_ICO_", ico)
+
+args.rcout.write(data)
+args.rcout.close()
+
+if args.windres:
+    cmd = [
+        args.windres,
+        '-I{0}/src/include/'.format(args.builddir),
+        '-I{0}/src/include/'.format(args.srcdir),
+        '-o', args.out, '-i', args.rcout.name,
+    ]
+elif args.rc:
+    cmd = [
+        args.rc, '/nologo',
+        '-I{0}/src/include/'.format(args.builddir),
+        '-I{0}/src/include/'.format(args.srcdir),
+        '/fo', args.out, args.rcout.name,
+    ]
+else:
+    sys.exit('either --windres or --rc needs to be specified')
+
+sp = subprocess.run(cmd)
+if sp.returncode != 0:
+    sys.exit(sp.returncode)
+
+# It'd be nicer if we could generate correct dependencies here, but 'rc'
+# doesn't support doing so. It's unlikely we'll ever need more, so...
+if args.depfile:
+    args.depfile.write("{0} : {1}\n".format(
+        args.rcout.name, args.input.name))
+    args.depfile.write("{0} : {1}/{2}\n".format(
+        args.out, args.builddir, 'src/include/pg_config.h'))
-- 
2.37.3.542.gdd3f6c4cae


--bu2u4tjq2bvmz2x5--





^ permalink  raw  reply  [nested|flat] 23+ messages in thread


end of thread, other threads:[~2022-10-01 18:54 UTC | newest]

Thread overview: 23+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2022-02-13 03:51 Re: Fix overflow in DecodeInterval Andres Freund <[email protected]>
2022-02-13 14:35 ` Joseph Koshakow <[email protected]>
2022-02-13 19:34   ` Joseph Koshakow <[email protected]>
2022-02-13 20:30   ` Andres Freund <[email protected]>
2022-02-13 20:38     ` Tom Lane <[email protected]>
2022-02-13 22:12       ` Joseph Koshakow <[email protected]>
2022-02-15 11:44         ` Joseph Koshakow <[email protected]>
2022-02-15 16:28           ` Andres Freund <[email protected]>
2022-02-18 02:45             ` Joseph Koshakow <[email protected]>
2022-02-19 19:26               ` Joseph Koshakow <[email protected]>
2022-02-20 00:16                 ` Joseph Koshakow <[email protected]>
2022-02-20 23:37                   ` Tom Lane <[email protected]>
2022-02-21 02:53                     ` Joseph Koshakow <[email protected]>
2022-03-06 16:14                       ` Joseph Koshakow <[email protected]>
2022-03-08 00:00                         ` Joseph Koshakow <[email protected]>
2022-03-22 00:31                       ` Tom Lane <[email protected]>
2022-03-24 00:27                         ` Joseph Koshakow <[email protected]>
2022-03-24 01:20                           ` Tom Lane <[email protected]>
2022-04-02 19:08                       ` Tom Lane <[email protected]>
2022-09-21 18:03 [PATCH v15 05/14] meson: Add windows resource files Andres Freund <[email protected]>
2022-09-21 18:03 [PATCH v16 04/16] meson: Add windows resource files Andres Freund <[email protected]>
2022-09-26 17:32 [PATCH v17 06/23] meson: Add windows resource files Andres Freund <[email protected]>
2022-10-01 18:54 [PATCH v18 04/22] meson: Add windows resource files Andres Freund <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox