Skip to content

Commit 74e67bb

Browse files
committed
Disable transforms that replaced AT TIME ZONE with RelabelType.
These resulted in wrong answers if the relabeled argument could be matched to an index column, as shown in bug #14504 from Evgeniy Kozlov. We might be able to resurrect these optimizations by adjusting the planner's treatment of RelabelType, or by adjusting btree's rules for selecting comparison functions, but either solution will take careful analysis and does not sound like a fit candidate for backpatching. I left the catalog infrastructure in place and just reduced the transform functions to always-return-NULL. This would be necessary anyway in the back branches, and it doesn't seem important to be more invasive in HEAD. Bug introduced by commit b8a18ad. Back-patch to 9.5 where that came in. Report: https://postgr.es/m/20170118144828.1432.52823@wrigleys.postgresql.org Discussion: https://postgr.es/m/18771.1484759439@sss.pgh.pa.us
1 parent dfe348c commit 74e67bb

File tree

3 files changed

+38
-113
lines changed

3 files changed

+38
-113
lines changed

src/backend/utils/adt/timestamp.c

Lines changed: 10 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -4920,84 +4920,15 @@ interval_part(PG_FUNCTION_ARGS)
49204920

49214921

49224922
/* timestamp_zone_transform()
4923-
* If the zone argument of a timestamp_zone() or timestamptz_zone() call is a
4924-
* plan-time constant denoting a zone equivalent to UTC, the call will always
4925-
* return its second argument unchanged. Simplify the expression tree
4926-
* accordingly. Civil time zones almost never qualify, because jurisdictions
4927-
* that follow UTC today have not done so continuously.
4923+
* The original optimization here caused problems by relabeling Vars that
4924+
* could be matched to index entries. It might be possible to resurrect it
4925+
* at some point by teaching the planner to be less cavalier with RelabelType
4926+
* nodes, but that will take careful analysis.
49284927
*/
49294928
Datum
49304929
timestamp_zone_transform(PG_FUNCTION_ARGS)
49314930
{
4932-
Node *func_node = (Node *) PG_GETARG_POINTER(0);
4933-
FuncExpr *expr = (FuncExpr *) func_node;
4934-
Node *ret = NULL;
4935-
Node *zone_node;
4936-
4937-
Assert(IsA(expr, FuncExpr));
4938-
Assert(list_length(expr->args) == 2);
4939-
4940-
zone_node = (Node *) linitial(expr->args);
4941-
4942-
if (IsA(zone_node, Const) &&!((Const *) zone_node)->constisnull)
4943-
{
4944-
text *zone = DatumGetTextPP(((Const *) zone_node)->constvalue);
4945-
char tzname[TZ_STRLEN_MAX + 1];
4946-
char *lowzone;
4947-
int type,
4948-
abbrev_offset;
4949-
pg_tz *tzp;
4950-
bool noop = false;
4951-
4952-
/*
4953-
* If the timezone is forever UTC+0, the FuncExpr function call is a
4954-
* no-op for all possible timestamps. This passage mirrors code in
4955-
* timestamp_zone().
4956-
*/
4957-
text_to_cstring_buffer(zone, tzname, sizeof(tzname));
4958-
lowzone = downcase_truncate_identifier(tzname,
4959-
strlen(tzname),
4960-
false);
4961-
type = DecodeTimezoneAbbrev(0, lowzone, &abbrev_offset, &tzp);
4962-
if (type == TZ || type == DTZ)
4963-
noop = (abbrev_offset == 0);
4964-
else if (type == DYNTZ)
4965-
{
4966-
/*
4967-
* An abbreviation of a single-offset timezone ought not to be
4968-
* configured as a DYNTZ, so don't bother checking.
4969-
*/
4970-
}
4971-
else
4972-
{
4973-
long tzname_offset;
4974-
4975-
tzp = pg_tzset(tzname);
4976-
if (tzp && pg_get_timezone_offset(tzp, &tzname_offset))
4977-
noop = (tzname_offset == 0);
4978-
}
4979-
4980-
if (noop)
4981-
{
4982-
Node *timestamp = (Node *) lsecond(expr->args);
4983-
4984-
/* Strip any existing RelabelType node(s) */
4985-
while (timestamp && IsA(timestamp, RelabelType))
4986-
timestamp = (Node *) ((RelabelType *) timestamp)->arg;
4987-
4988-
/*
4989-
* Replace the FuncExpr with its timestamp argument, relabeled as
4990-
* though the function call had computed it.
4991-
*/
4992-
ret = (Node *) makeRelabelType((Expr *) timestamp,
4993-
exprType(func_node),
4994-
exprTypmod(func_node),
4995-
exprCollation(func_node),
4996-
COERCE_EXPLICIT_CAST);
4997-
}
4998-
}
4999-
5000-
PG_RETURN_POINTER(ret);
4931+
PG_RETURN_POINTER(NULL);
50014932
}
50024933

50034934
/* timestamp_zone()
@@ -5090,49 +5021,15 @@ timestamp_zone(PG_FUNCTION_ARGS)
50905021
}
50915022

50925023
/* timestamp_izone_transform()
5093-
* If we deduce at plan time that a particular timestamp_izone() or
5094-
* timestamptz_izone() call can only compute tz=0, the call will always return
5095-
* its second argument unchanged. Simplify the expression tree accordingly.
5024+
* The original optimization here caused problems by relabeling Vars that
5025+
* could be matched to index entries. It might be possible to resurrect it
5026+
* at some point by teaching the planner to be less cavalier with RelabelType
5027+
* nodes, but that will take careful analysis.
50965028
*/
50975029
Datum
50985030
timestamp_izone_transform(PG_FUNCTION_ARGS)
50995031
{
5100-
Node *func_node = (Node *) PG_GETARG_POINTER(0);
5101-
FuncExpr *expr = (FuncExpr *) func_node;
5102-
Node *ret = NULL;
5103-
Node *zone_node;
5104-
5105-
Assert(IsA(expr, FuncExpr));
5106-
Assert(list_length(expr->args) == 2);
5107-
5108-
zone_node = (Node *) linitial(expr->args);
5109-
5110-
if (IsA(zone_node, Const) &&!((Const *) zone_node)->constisnull)
5111-
{
5112-
Interval *zone;
5113-
5114-
zone = DatumGetIntervalP(((Const *) zone_node)->constvalue);
5115-
if (zone->month == 0 && zone->day == 0 && zone->time == 0)
5116-
{
5117-
Node *timestamp = (Node *) lsecond(expr->args);
5118-
5119-
/* Strip any existing RelabelType node(s) */
5120-
while (timestamp && IsA(timestamp, RelabelType))
5121-
timestamp = (Node *) ((RelabelType *) timestamp)->arg;
5122-
5123-
/*
5124-
* Replace the FuncExpr with its timestamp argument, relabeled as
5125-
* though the function call had computed it.
5126-
*/
5127-
ret = (Node *) makeRelabelType((Expr *) timestamp,
5128-
exprType(func_node),
5129-
exprTypmod(func_node),
5130-
exprCollation(func_node),
5131-
COERCE_EXPLICIT_CAST);
5132-
}
5133-
}
5134-
5135-
PG_RETURN_POINTER(ret);
5032+
PG_RETURN_POINTER(NULL);
51365033
}
51375034

51385035
/* timestamp_izone()

src/test/regress/expected/timestamptz.out

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2573,3 +2573,22 @@ select count(distinct utc_offset) >= 24 as ok from pg_timezone_abbrevs;
25732573
t
25742574
(1 row)
25752575

2576+
--
2577+
-- Test that AT TIME ZONE isn't misoptimized when using an index (bug #14504)
2578+
--
2579+
create temp table tmptz (f1 timestamptz primary key);
2580+
insert into tmptz values ('2017-01-18 00:00+00');
2581+
explain (costs off)
2582+
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
2583+
QUERY PLAN
2584+
-------------------------------------------------------------------------------------------------
2585+
Seq Scan on tmptz
2586+
Filter: (timezone('utc'::text, f1) = 'Wed Jan 18 00:00:00 2017'::timestamp without time zone)
2587+
(2 rows)
2588+
2589+
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
2590+
f1
2591+
------------------------------
2592+
Tue Jan 17 16:00:00 2017 PST
2593+
(1 row)
2594+

src/test/regress/sql/timestamptz.sql

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,3 +465,12 @@ set timezone_abbreviations = 'Australia';
465465
select count(distinct utc_offset) >= 24 as ok from pg_timezone_abbrevs;
466466
set timezone_abbreviations = 'India';
467467
select count(distinct utc_offset) >= 24 as ok from pg_timezone_abbrevs;
468+
469+
--
470+
-- Test that AT TIME ZONE isn't misoptimized when using an index (bug #14504)
471+
--
472+
create temp table tmptz (f1 timestamptz primary key);
473+
insert into tmptz values ('2017-01-18 00:00+00');
474+
explain (costs off)
475+
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
476+
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';

0 commit comments

Comments
 (0)