mirror of
https://github.com/timescale/timescaledb.git
synced 2025-05-16 02:23:49 +08:00
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:
parent
2715b5564a
commit
1fbe2eb36f
@ -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)
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user