diff --git a/CHANGELOG.md b/CHANGELOG.md index a67c1650a..5c5eee0c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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** diff --git a/src/compat/compat.h b/src/compat/compat.h index 64d9060aa..35f8c9fb3 100644 --- a/src/compat/compat.h +++ b/src/compat/compat.h @@ -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 diff --git a/src/planner/expand_hypertable.c b/src/planner/expand_hypertable.c index 9eaad1246..b73db0922 100644 --- a/src/planner/expand_hypertable.c +++ b/src/planner/expand_hypertable.c @@ -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; diff --git a/tsl/test/shared/expected/constify_timestamptz_op_interval.out b/tsl/test/shared/expected/constify_timestamptz_op_interval.out index e14c14ea4..67614b8b8 100644 --- a/tsl/test/shared/expected/constify_timestamptz_op_interval.out +++ b/tsl/test/shared/expected/constify_timestamptz_op_interval.out @@ -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; diff --git a/tsl/test/shared/sql/constify_timestamptz_op_interval.sql b/tsl/test/shared/sql/constify_timestamptz_op_interval.sql index 49556defd..34c52b666 100644 --- a/tsl/test/shared/sql/constify_timestamptz_op_interval.sql +++ b/tsl/test/shared/sql/constify_timestamptz_op_interval.sql @@ -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; +