Support intervals with month component when constifying now()

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.
This commit is contained in:
Sven Klemm 2022-05-28 13:40:10 +02:00 committed by Sven Klemm
parent 2715b5564a
commit 1fbe2eb36f
6 changed files with 182 additions and 49 deletions

View File

@ -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)

View File

@ -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);
}
/*

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;