diff --git a/CHANGELOG.md b/CHANGELOG.md index 756267cde..9729e7e28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ accidentally triggering the load of a previous DB version.** **Features** * #4374 Remove constified now() constraints from plan * #4393 Support intervals with day component when constifying now() +* #4397 Support intervals with month component when constifying now() ## 2.7.0 (2022-05-24) diff --git a/src/planner/constify_now.c b/src/planner/constify_now.c index f3c2bfef9..ddc49f276 100644 --- a/src/planner/constify_now.c +++ b/src/planner/constify_now.c @@ -33,8 +33,7 @@ * - Var >= now() - Interval * - Var >= now() + Interval * - * Additionally Interval needs to be Const and not contain a month - * component. + * Interval needs to be Const in those expressions. */ static const Dimension * get_hypertable_dimension(Oid relid) @@ -93,15 +92,6 @@ is_valid_now_expr(OpExpr *op, List *rtable) if (c->constisnull || c->consttype != INTERVALOID) return false; - Interval *offset = DatumGetIntervalP(c->constvalue); - /* - * We don't consider month intervals safe here as - * they are affected by timezones and therefore not - * safe to evaluate during planning. - */ - if (offset->month != 0) - return false; - return true; } @@ -152,7 +142,8 @@ constify_now_expr(PlannerInfo *root, OpExpr *op) * end here if it isn't since this is checked in is_valid_now_expr. */ Assert(linitial_node(FuncExpr, op_inner->args)->funcid == F_NOW); - linitial(op_inner->args) = make_now_const(); + Const *now = make_now_const(); + linitial(op_inner->args) = now; /* * If the interval has a day component then the calculation needs @@ -163,13 +154,25 @@ constify_now_expr(PlannerInfo *root, OpExpr *op) * will be repeated with exact values during execution. * Since dst switches seem to range between -1 and 2 hours we set * the safety buffer to 4 hours. + * When dealing with Intervals with month component timezone changes + * can result in multiple day differences in the outcome of these + * calculations due to different month lengths. When dealing with + * months we add a 7 day safety buffer. + * For all these calculations it is fine if we exclude less chunks + * than strictly required for the operation, additional exclusion + * with exact values will happen in the executor. But under no + * circumstances must we exclude too much cause there would be + * no way for the executor to get those chunks back. */ - if (offset->day != 0) + if (offset->day != 0 || offset->month != 0) { - Const *now = linitial_node(Const, op_inner->args); - int64 now_value = DatumGetInt64(now->constvalue); - now_value -= 4 * USECS_PER_HOUR; - now->constvalue = Int64GetDatum(now_value); + TimestampTz now_value = DatumGetTimestampTz(now->constvalue); + if (offset->month != 0) + now_value -= 7 * USECS_PER_DAY; + if (offset->day != 0) + now_value -= 4 * USECS_PER_HOUR; + + now->constvalue = TimestampTzGetDatum(now_value); } /* diff --git a/tsl/test/shared/expected/constify_now-12.out b/tsl/test/shared/expected/constify_now-12.out index f897e9c14..1d80aaf52 100644 --- a/tsl/test/shared/expected/constify_now-12.out +++ b/tsl/test/shared/expected/constify_now-12.out @@ -78,6 +78,12 @@ QUERY PLAN Index Cond: ("time" > (now() - '@ 7 days'::interval)) (2 rows) +:PREFIX SELECT FROM const_now WHERE time > now() - '1month'::interval; +QUERY PLAN + Index Only Scan using _hyper_X_X_chunk_const_now_time_idx on _hyper_X_X_chunk + Index Cond: ("time" > (now() - '@ 1 mon'::interval)) +(2 rows) + -- test bitmapheapscan SET enable_indexscan TO false; :PREFIX SELECT FROM const_now WHERE time > now(); @@ -111,16 +117,6 @@ QUERY PLAN (2 rows) -- variants we don't optimize --- we do not allow interval with month components -:PREFIX SELECT FROM const_now WHERE time > now() - '1month'::interval; -QUERY PLAN - Append - -> Index Only Scan using _hyper_X_X_chunk_const_now_time_idx on _hyper_X_X_chunk - Index Cond: ("time" > (now() - '@ 1 mon'::interval)) - -> Index Only Scan using _hyper_X_X_chunk_const_now_time_idx on _hyper_X_X_chunk - Index Cond: ("time" > (now() - '@ 1 mon'::interval)) -(5 rows) - :PREFIX SELECT FROM const_now WHERE time > now()::date; QUERY PLAN Append @@ -412,4 +408,47 @@ QUERY PLAN Index Cond: ("time" > (now() - '@ 1 day'::interval)) (5 rows) +TRUNCATE const_now_dst; +SELECT set_chunk_time_interval('const_now_dst','1 day'::interval, 'time'); + set_chunk_time_interval + +(1 row) + +-- test month calculation safety buffer +SET timescaledb.current_timestamp_mock TO '2001-03-1 0:30:00+00'; +INSERT INTO const_now_dst SELECT generate_series('2001-01-28'::timestamptz, '2001-02-01', '1day'::interval); +set timezone to 'utc+1'; +-- must have 5 chunks in plan +:PREFIX SELECT * FROM const_now_dst WHERE time > now() - '1 month'::interval; +QUERY PLAN + Append + -> Index Only Scan using _hyper_X_X_chunk_const_now_dst_time_idx on _hyper_X_X_chunk + Index Cond: ("time" > (now() - '@ 1 mon'::interval)) + -> Index Only Scan using _hyper_X_X_chunk_const_now_dst_time_idx on _hyper_X_X_chunk + Index Cond: ("time" > (now() - '@ 1 mon'::interval)) + -> Index Only Scan using _hyper_X_X_chunk_const_now_dst_time_idx on _hyper_X_X_chunk + Index Cond: ("time" > (now() - '@ 1 mon'::interval)) + -> Index Only Scan using _hyper_X_X_chunk_const_now_dst_time_idx on _hyper_X_X_chunk + Index Cond: ("time" > (now() - '@ 1 mon'::interval)) + -> Index Only Scan using _hyper_X_X_chunk_const_now_dst_time_idx on _hyper_X_X_chunk + Index Cond: ("time" > (now() - '@ 1 mon'::interval)) +(11 rows) + +set timezone to 'utc-1'; +-- must have 5 chunks in plan +:PREFIX SELECT * FROM const_now_dst WHERE time > now() - '1 month'::interval; +QUERY PLAN + Append + -> Index Only Scan using _hyper_X_X_chunk_const_now_dst_time_idx on _hyper_X_X_chunk + Index Cond: ("time" > (now() - '@ 1 mon'::interval)) + -> Index Only Scan using _hyper_X_X_chunk_const_now_dst_time_idx on _hyper_X_X_chunk + Index Cond: ("time" > (now() - '@ 1 mon'::interval)) + -> Index Only Scan using _hyper_X_X_chunk_const_now_dst_time_idx on _hyper_X_X_chunk + Index Cond: ("time" > (now() - '@ 1 mon'::interval)) + -> Index Only Scan using _hyper_X_X_chunk_const_now_dst_time_idx on _hyper_X_X_chunk + Index Cond: ("time" > (now() - '@ 1 mon'::interval)) + -> Index Only Scan using _hyper_X_X_chunk_const_now_dst_time_idx on _hyper_X_X_chunk + Index Cond: ("time" > (now() - '@ 1 mon'::interval)) +(11 rows) + DROP TABLE const_now_dst; diff --git a/tsl/test/shared/expected/constify_now-13.out b/tsl/test/shared/expected/constify_now-13.out index 5be002851..0cc18563c 100644 --- a/tsl/test/shared/expected/constify_now-13.out +++ b/tsl/test/shared/expected/constify_now-13.out @@ -78,6 +78,12 @@ QUERY PLAN Index Cond: ("time" > (now() - '@ 7 days'::interval)) (2 rows) +:PREFIX SELECT FROM const_now WHERE time > now() - '1month'::interval; +QUERY PLAN + Index Only Scan using _hyper_X_X_chunk_const_now_time_idx on _hyper_X_X_chunk + Index Cond: ("time" > (now() - '@ 1 mon'::interval)) +(2 rows) + -- test bitmapheapscan SET enable_indexscan TO false; :PREFIX SELECT FROM const_now WHERE time > now(); @@ -111,16 +117,6 @@ QUERY PLAN (2 rows) -- variants we don't optimize --- we do not allow interval with month components -:PREFIX SELECT FROM const_now WHERE time > now() - '1month'::interval; -QUERY PLAN - Append - -> Index Only Scan using _hyper_X_X_chunk_const_now_time_idx on _hyper_X_X_chunk - Index Cond: ("time" > (now() - '@ 1 mon'::interval)) - -> Index Only Scan using _hyper_X_X_chunk_const_now_time_idx on _hyper_X_X_chunk - Index Cond: ("time" > (now() - '@ 1 mon'::interval)) -(5 rows) - :PREFIX SELECT FROM const_now WHERE time > now()::date; QUERY PLAN Append @@ -412,4 +408,47 @@ QUERY PLAN Index Cond: ("time" > (now() - '@ 1 day'::interval)) (5 rows) +TRUNCATE const_now_dst; +SELECT set_chunk_time_interval('const_now_dst','1 day'::interval, 'time'); + set_chunk_time_interval + +(1 row) + +-- test month calculation safety buffer +SET timescaledb.current_timestamp_mock TO '2001-03-1 0:30:00+00'; +INSERT INTO const_now_dst SELECT generate_series('2001-01-28'::timestamptz, '2001-02-01', '1day'::interval); +set timezone to 'utc+1'; +-- must have 5 chunks in plan +:PREFIX SELECT * FROM const_now_dst WHERE time > now() - '1 month'::interval; +QUERY PLAN + Append + -> Index Only Scan using _hyper_X_X_chunk_const_now_dst_time_idx on _hyper_X_X_chunk + Index Cond: ("time" > (now() - '@ 1 mon'::interval)) + -> Index Only Scan using _hyper_X_X_chunk_const_now_dst_time_idx on _hyper_X_X_chunk + Index Cond: ("time" > (now() - '@ 1 mon'::interval)) + -> Index Only Scan using _hyper_X_X_chunk_const_now_dst_time_idx on _hyper_X_X_chunk + Index Cond: ("time" > (now() - '@ 1 mon'::interval)) + -> Index Only Scan using _hyper_X_X_chunk_const_now_dst_time_idx on _hyper_X_X_chunk + Index Cond: ("time" > (now() - '@ 1 mon'::interval)) + -> Index Only Scan using _hyper_X_X_chunk_const_now_dst_time_idx on _hyper_X_X_chunk + Index Cond: ("time" > (now() - '@ 1 mon'::interval)) +(11 rows) + +set timezone to 'utc-1'; +-- must have 5 chunks in plan +:PREFIX SELECT * FROM const_now_dst WHERE time > now() - '1 month'::interval; +QUERY PLAN + Append + -> Index Only Scan using _hyper_X_X_chunk_const_now_dst_time_idx on _hyper_X_X_chunk + Index Cond: ("time" > (now() - '@ 1 mon'::interval)) + -> Index Only Scan using _hyper_X_X_chunk_const_now_dst_time_idx on _hyper_X_X_chunk + Index Cond: ("time" > (now() - '@ 1 mon'::interval)) + -> Index Only Scan using _hyper_X_X_chunk_const_now_dst_time_idx on _hyper_X_X_chunk + Index Cond: ("time" > (now() - '@ 1 mon'::interval)) + -> Index Only Scan using _hyper_X_X_chunk_const_now_dst_time_idx on _hyper_X_X_chunk + Index Cond: ("time" > (now() - '@ 1 mon'::interval)) + -> Index Only Scan using _hyper_X_X_chunk_const_now_dst_time_idx on _hyper_X_X_chunk + Index Cond: ("time" > (now() - '@ 1 mon'::interval)) +(11 rows) + DROP TABLE const_now_dst; diff --git a/tsl/test/shared/expected/constify_now-14.out b/tsl/test/shared/expected/constify_now-14.out index d71a2c26e..313dc1de5 100644 --- a/tsl/test/shared/expected/constify_now-14.out +++ b/tsl/test/shared/expected/constify_now-14.out @@ -78,6 +78,12 @@ QUERY PLAN Index Cond: ("time" > (now() - '@ 7 days'::interval)) (2 rows) +:PREFIX SELECT FROM const_now WHERE time > now() - '1month'::interval; +QUERY PLAN + Index Only Scan using _hyper_X_X_chunk_const_now_time_idx on _hyper_X_X_chunk + Index Cond: ("time" > (now() - '@ 1 mon'::interval)) +(2 rows) + -- test bitmapheapscan SET enable_indexscan TO false; :PREFIX SELECT FROM const_now WHERE time > now(); @@ -111,16 +117,6 @@ QUERY PLAN (2 rows) -- variants we don't optimize --- we do not allow interval with month components -:PREFIX SELECT FROM const_now WHERE time > now() - '1month'::interval; -QUERY PLAN - Append - -> Index Only Scan using _hyper_X_X_chunk_const_now_time_idx on _hyper_X_X_chunk - Index Cond: ("time" > (now() - '@ 1 mon'::interval)) - -> Index Only Scan using _hyper_X_X_chunk_const_now_time_idx on _hyper_X_X_chunk - Index Cond: ("time" > (now() - '@ 1 mon'::interval)) -(5 rows) - :PREFIX SELECT FROM const_now WHERE time > now()::date; QUERY PLAN Append @@ -409,4 +405,47 @@ QUERY PLAN Index Cond: ("time" > (now() - '@ 1 day'::interval)) (5 rows) +TRUNCATE const_now_dst; +SELECT set_chunk_time_interval('const_now_dst','1 day'::interval, 'time'); + set_chunk_time_interval + +(1 row) + +-- test month calculation safety buffer +SET timescaledb.current_timestamp_mock TO '2001-03-1 0:30:00+00'; +INSERT INTO const_now_dst SELECT generate_series('2001-01-28'::timestamptz, '2001-02-01', '1day'::interval); +set timezone to 'utc+1'; +-- must have 5 chunks in plan +:PREFIX SELECT * FROM const_now_dst WHERE time > now() - '1 month'::interval; +QUERY PLAN + Append + -> Index Only Scan using _hyper_X_X_chunk_const_now_dst_time_idx on _hyper_X_X_chunk + Index Cond: ("time" > (now() - '@ 1 mon'::interval)) + -> Index Only Scan using _hyper_X_X_chunk_const_now_dst_time_idx on _hyper_X_X_chunk + Index Cond: ("time" > (now() - '@ 1 mon'::interval)) + -> Index Only Scan using _hyper_X_X_chunk_const_now_dst_time_idx on _hyper_X_X_chunk + Index Cond: ("time" > (now() - '@ 1 mon'::interval)) + -> Index Only Scan using _hyper_X_X_chunk_const_now_dst_time_idx on _hyper_X_X_chunk + Index Cond: ("time" > (now() - '@ 1 mon'::interval)) + -> Index Only Scan using _hyper_X_X_chunk_const_now_dst_time_idx on _hyper_X_X_chunk + Index Cond: ("time" > (now() - '@ 1 mon'::interval)) +(11 rows) + +set timezone to 'utc-1'; +-- must have 5 chunks in plan +:PREFIX SELECT * FROM const_now_dst WHERE time > now() - '1 month'::interval; +QUERY PLAN + Append + -> Index Only Scan using _hyper_X_X_chunk_const_now_dst_time_idx on _hyper_X_X_chunk + Index Cond: ("time" > (now() - '@ 1 mon'::interval)) + -> Index Only Scan using _hyper_X_X_chunk_const_now_dst_time_idx on _hyper_X_X_chunk + Index Cond: ("time" > (now() - '@ 1 mon'::interval)) + -> Index Only Scan using _hyper_X_X_chunk_const_now_dst_time_idx on _hyper_X_X_chunk + Index Cond: ("time" > (now() - '@ 1 mon'::interval)) + -> Index Only Scan using _hyper_X_X_chunk_const_now_dst_time_idx on _hyper_X_X_chunk + Index Cond: ("time" > (now() - '@ 1 mon'::interval)) + -> Index Only Scan using _hyper_X_X_chunk_const_now_dst_time_idx on _hyper_X_X_chunk + Index Cond: ("time" > (now() - '@ 1 mon'::interval)) +(11 rows) + DROP TABLE const_now_dst; diff --git a/tsl/test/shared/sql/constify_now.sql.in b/tsl/test/shared/sql/constify_now.sql.in index d3e9da7e0..f792f09c8 100644 --- a/tsl/test/shared/sql/constify_now.sql.in +++ b/tsl/test/shared/sql/constify_now.sql.in @@ -32,6 +32,7 @@ INSERT INTO const_now SELECT '3000-01-01','3000-01-01',2,0.5; :PREFIX SELECT FROM const_now WHERE time > now() - '2d'::interval; :PREFIX SELECT FROM const_now WHERE time > now() + '3d'::interval; :PREFIX SELECT FROM const_now WHERE time > now() - '1week'::interval; +:PREFIX SELECT FROM const_now WHERE time > now() - '1month'::interval; -- test bitmapheapscan SET enable_indexscan TO false; @@ -44,8 +45,6 @@ RESET enable_indexscan; :PREFIX SELECT FROM const_now WHERE time >= now() + '10m'::interval AND time >= now() - '10m'::interval; -- variants we don't optimize --- we do not allow interval with month components -:PREFIX SELECT FROM const_now WHERE time > now() - '1month'::interval; :PREFIX SELECT FROM const_now WHERE time > now()::date; :PREFIX SELECT FROM const_now WHERE round(EXTRACT(EPOCH FROM now())) > 0.5; @@ -143,5 +142,18 @@ SET timescaledb.current_timestamp_mock TO '2022-03-28 1:15+0'; -- must have 2 chunks in plan :PREFIX SELECT FROM const_now_dst WHERE time > now() - '1day'::interval; +TRUNCATE const_now_dst; +SELECT set_chunk_time_interval('const_now_dst','1 day'::interval, 'time'); + +-- test month calculation safety buffer +SET timescaledb.current_timestamp_mock TO '2001-03-1 0:30:00+00'; +INSERT INTO const_now_dst SELECT generate_series('2001-01-28'::timestamptz, '2001-02-01', '1day'::interval); +set timezone to 'utc+1'; +-- must have 5 chunks in plan +:PREFIX SELECT * FROM const_now_dst WHERE time > now() - '1 month'::interval; +set timezone to 'utc-1'; +-- must have 5 chunks in plan +:PREFIX SELECT * FROM const_now_dst WHERE time > now() - '1 month'::interval; + DROP TABLE const_now_dst;