Fix chunk exclusion for prepared statements and dst changes

The constify code constifying TIMESTAMPTZ expressions when doing
chunk exclusion did not account for daylight saving time switches
leading to different calculation outcomes when timezone changes.
This patch adds a 4 hour safety buffer to any such calculations.
This commit is contained in:
Sven Klemm 2022-09-16 23:12:47 +02:00 committed by Sven Klemm
parent 217f514657
commit 2529ae3f68
5 changed files with 174 additions and 1 deletions

View File

@ -20,10 +20,11 @@ argument or resolve the type ambiguity by casting to the intended type.
**Bugfixes**
* #4619 Improve handling enum columns in compressed hypertables
* #4673 Fix now() constification for VIEWs
* #4676 Fix a deadlock when decompressing chunks and performing SELECTs
* #4681 Fix compression_chunk_size primary key
* #4685 Improve chunk exclusion for space dimensions
* #4696 Report warning when enabling compression on hypertable
* #4676 Fix a deadlock when decompressing chunks and performing SELECTs
* #4720 Fix chunk exclusion for prepared statements and dst changes
* #4739 Fix continuous aggregate migrate check constraint
**Thanks**

View File

@ -547,6 +547,8 @@ get_reindex_options(ReindexStmt *stmt)
* timestamp_gt.
*/
#if PG14_LT
#define F_TIMESTAMPTZ_LE F_TIMESTAMP_LE
#define F_TIMESTAMPTZ_LT F_TIMESTAMP_LT
#define F_TIMESTAMPTZ_GE F_TIMESTAMP_GE
#define F_TIMESTAMPTZ_GT F_TIMESTAMP_GT
#endif

View File

@ -39,6 +39,7 @@
#include <partitioning/partbounds.h>
#include <utils/date.h>
#include <utils/errcodes.h>
#include <utils/fmgroids.h>
#include <utils/fmgrprotos.h>
#include <utils/syscache.h>
@ -268,6 +269,47 @@ constify_timestamptz_op_interval(PlannerInfo *root, OpExpr *constraint)
constified = DirectFunctionCall2(opfunc, c_ts->constvalue, c_int->constvalue);
/*
* Since constifying intervals with day component does depend on the timezone
* this can lead to different results around daylight saving time switches.
* So we add a safety buffer when the interval has day components to counteract.
*/
if (interval->day != 0)
{
bool add;
TimestampTz constified_tstz = DatumGetTimestampTz(constified);
switch (constraint->opfuncid)
{
case F_TIMESTAMPTZ_LE:
case F_TIMESTAMPTZ_LT:
add = true;
break;
case F_TIMESTAMPTZ_GE:
case F_TIMESTAMPTZ_GT:
add = false;
break;
default:
return constraint;
}
/*
* If Var is on wrong side reverse the direction.
*/
if (!var_on_left)
add = !add;
/*
* The safety buffer is chosen to be 4 hours because daylight saving time
* changes seem to be in the range between -1 and 2 hours.
*/
if (add)
constified_tstz += 4 * USECS_PER_HOUR;
else
constified_tstz -= 4 * USECS_PER_HOUR;
constified = TimestampTzGetDatum(constified_tstz);
}
c_ts = copyObject(c_ts);
c_ts->constvalue = constified;

View File

@ -246,3 +246,89 @@ QUERY PLAN
One-Time Filter: false
(2 rows)
-- test timezone changes in prepared statements
CREATE TABLE dst_test(time timestamptz NOT NULL);
SELECT table_name FROM create_hypertable('dst_test','time',chunk_time_interval:=interval '30 minutes');
table_name
dst_test
(1 row)
INSERT INTO dst_test SELECT generate_series('2022-03-27 22:00:00+01'::timestamptz,'2022-03-28 02:00:00+01'::timestamptz,'30min'::interval);
SET timezone TO UTC;
PREPARE p1 AS SELECT count(*) FROM dst_test WHERE time > '2022-03-27 00:00:00+00'::timestamptz + interval '1 day';
EXECUTE p1;
count
2
(1 row)
set timezone TO 'Europe/Berlin';
EXECUTE p1;
count
4
(1 row)
SELECT count(*) FROM dst_test WHERE time > '2022-03-27 00:00:00+00'::timestamptz + interval '1 day';
count
4
(1 row)
DEALLOCATE p1;
SET timezone TO UTC;
PREPARE p1 AS SELECT count(*) FROM dst_test WHERE time < '2022-03-27 00:00:00+00'::timestamptz + interval '1 day';
EXECUTE p1;
count
6
(1 row)
set timezone TO 'Africa/Casablanca';
EXECUTE p1;
count
8
(1 row)
SELECT count(*) FROM dst_test WHERE time < '2022-03-27 00:00:00+00'::timestamptz + interval '1 day';
count
8
(1 row)
DEALLOCATE p1;
-- do same tests with var on wrong side
SET timezone TO UTC;
PREPARE p1 AS SELECT count(*) FROM dst_test WHERE '2022-03-27 00:00:00+00'::timestamptz + interval '1 day' < time;
EXECUTE p1;
count
2
(1 row)
set timezone TO 'Europe/Berlin';
EXECUTE p1;
count
4
(1 row)
SELECT count(*) FROM dst_test WHERE '2022-03-27 00:00:00+00'::timestamptz + interval '1 day' < time;
count
4
(1 row)
DEALLOCATE p1;
SET timezone TO UTC;
PREPARE p1 AS SELECT count(*) FROM dst_test WHERE '2022-03-27 00:00:00+00'::timestamptz + interval '1 day' > time;
EXECUTE p1;
count
6
(1 row)
set timezone TO 'Africa/Casablanca';
EXECUTE p1;
count
8
(1 row)
SELECT count(*) FROM dst_test WHERE '2022-03-27 00:00:00+00'::timestamptz + interval '1 day' > time;
count
8
(1 row)
DEALLOCATE p1;
DROP TABLE dst_test;

View File

@ -128,3 +128,45 @@ SELECT time
FROM metrics
WHERE time < NULL::timestamptz - NULL::interval;
-- test timezone changes in prepared statements
CREATE TABLE dst_test(time timestamptz NOT NULL);
SELECT table_name FROM create_hypertable('dst_test','time',chunk_time_interval:=interval '30 minutes');
INSERT INTO dst_test SELECT generate_series('2022-03-27 22:00:00+01'::timestamptz,'2022-03-28 02:00:00+01'::timestamptz,'30min'::interval);
SET timezone TO UTC;
PREPARE p1 AS SELECT count(*) FROM dst_test WHERE time > '2022-03-27 00:00:00+00'::timestamptz + interval '1 day';
EXECUTE p1;
set timezone TO 'Europe/Berlin';
EXECUTE p1;
SELECT count(*) FROM dst_test WHERE time > '2022-03-27 00:00:00+00'::timestamptz + interval '1 day';
DEALLOCATE p1;
SET timezone TO UTC;
PREPARE p1 AS SELECT count(*) FROM dst_test WHERE time < '2022-03-27 00:00:00+00'::timestamptz + interval '1 day';
EXECUTE p1;
set timezone TO 'Africa/Casablanca';
EXECUTE p1;
SELECT count(*) FROM dst_test WHERE time < '2022-03-27 00:00:00+00'::timestamptz + interval '1 day';
DEALLOCATE p1;
-- do same tests with var on wrong side
SET timezone TO UTC;
PREPARE p1 AS SELECT count(*) FROM dst_test WHERE '2022-03-27 00:00:00+00'::timestamptz + interval '1 day' < time;
EXECUTE p1;
set timezone TO 'Europe/Berlin';
EXECUTE p1;
SELECT count(*) FROM dst_test WHERE '2022-03-27 00:00:00+00'::timestamptz + interval '1 day' < time;
DEALLOCATE p1;
SET timezone TO UTC;
PREPARE p1 AS SELECT count(*) FROM dst_test WHERE '2022-03-27 00:00:00+00'::timestamptz + interval '1 day' > time;
EXECUTE p1;
set timezone TO 'Africa/Casablanca';
EXECUTE p1;
SELECT count(*) FROM dst_test WHERE '2022-03-27 00:00:00+00'::timestamptz + interval '1 day' > time;
DEALLOCATE p1;
DROP TABLE dst_test;