Disable transforms that replaced AT TIME ZONE with RelabelType.
authorTom Lane <[email protected]>
Wed, 18 Jan 2017 20:21:52 +0000 (15:21 -0500)
committerTom Lane <[email protected]>
Wed, 18 Jan 2017 20:21:52 +0000 (15:21 -0500)
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 b8a18ad48.  Back-patch to 9.5 where that came in.

Report: https://postgr.es/m/20170118144828[email protected]
Discussion: https://postgr.es/m/18771.1484759439@sss.pgh.pa.us

src/backend/utils/adt/timestamp.c
src/test/regress/expected/timestamptz.out
src/test/regress/sql/timestamptz.sql

index a153b98bab1899b1f097bc8feaf84fe8ea2d2db3..ab9359fb466ceacc1d9f69ac8d7ac7929f299607 100644 (file)
@@ -4920,84 +4920,15 @@ interval_part(PG_FUNCTION_ARGS)
 
 
 /* timestamp_zone_transform()
- * If the zone argument of a timestamp_zone() or timestamptz_zone() call is a
- * plan-time constant denoting a zone equivalent to UTC, the call will always
- * return its second argument unchanged.  Simplify the expression tree
- * accordingly.  Civil time zones almost never qualify, because jurisdictions
- * that follow UTC today have not done so continuously.
+ * The original optimization here caused problems by relabeling Vars that
+ * could be matched to index entries.  It might be possible to resurrect it
+ * at some point by teaching the planner to be less cavalier with RelabelType
+ * nodes, but that will take careful analysis.
  */
 Datum
 timestamp_zone_transform(PG_FUNCTION_ARGS)
 {
-       Node       *func_node = (Node *) PG_GETARG_POINTER(0);
-       FuncExpr   *expr = (FuncExpr *) func_node;
-       Node       *ret = NULL;
-       Node       *zone_node;
-
-       Assert(IsA(expr, FuncExpr));
-       Assert(list_length(expr->args) == 2);
-
-       zone_node = (Node *) linitial(expr->args);
-
-       if (IsA(zone_node, Const) &&!((Const *) zone_node)->constisnull)
-       {
-               text       *zone = DatumGetTextPP(((Const *) zone_node)->constvalue);
-               char            tzname[TZ_STRLEN_MAX + 1];
-               char       *lowzone;
-               int                     type,
-                                       abbrev_offset;
-               pg_tz      *tzp;
-               bool            noop = false;
-
-               /*
-                * If the timezone is forever UTC+0, the FuncExpr function call is a
-                * no-op for all possible timestamps.  This passage mirrors code in
-                * timestamp_zone().
-                */
-               text_to_cstring_buffer(zone, tzname, sizeof(tzname));
-               lowzone = downcase_truncate_identifier(tzname,
-                                                                                          strlen(tzname),
-                                                                                          false);
-               type = DecodeTimezoneAbbrev(0, lowzone, &abbrev_offset, &tzp);
-               if (type == TZ || type == DTZ)
-                       noop = (abbrev_offset == 0);
-               else if (type == DYNTZ)
-               {
-                       /*
-                        * An abbreviation of a single-offset timezone ought not to be
-                        * configured as a DYNTZ, so don't bother checking.
-                        */
-               }
-               else
-               {
-                       long            tzname_offset;
-
-                       tzp = pg_tzset(tzname);
-                       if (tzp && pg_get_timezone_offset(tzp, &tzname_offset))
-                               noop = (tzname_offset == 0);
-               }
-
-               if (noop)
-               {
-                       Node       *timestamp = (Node *) lsecond(expr->args);
-
-                       /* Strip any existing RelabelType node(s) */
-                       while (timestamp && IsA(timestamp, RelabelType))
-                               timestamp = (Node *) ((RelabelType *) timestamp)->arg;
-
-                       /*
-                        * Replace the FuncExpr with its timestamp argument, relabeled as
-                        * though the function call had computed it.
-                        */
-                       ret = (Node *) makeRelabelType((Expr *) timestamp,
-                                                                                  exprType(func_node),
-                                                                                  exprTypmod(func_node),
-                                                                                  exprCollation(func_node),
-                                                                                  COERCE_EXPLICIT_CAST);
-               }
-       }
-
-       PG_RETURN_POINTER(ret);
+       PG_RETURN_POINTER(NULL);
 }
 
 /*     timestamp_zone()
@@ -5090,49 +5021,15 @@ timestamp_zone(PG_FUNCTION_ARGS)
 }
 
 /* timestamp_izone_transform()
- * If we deduce at plan time that a particular timestamp_izone() or
- * timestamptz_izone() call can only compute tz=0, the call will always return
- * its second argument unchanged.  Simplify the expression tree accordingly.
+ * The original optimization here caused problems by relabeling Vars that
+ * could be matched to index entries.  It might be possible to resurrect it
+ * at some point by teaching the planner to be less cavalier with RelabelType
+ * nodes, but that will take careful analysis.
  */
 Datum
 timestamp_izone_transform(PG_FUNCTION_ARGS)
 {
-       Node       *func_node = (Node *) PG_GETARG_POINTER(0);
-       FuncExpr   *expr = (FuncExpr *) func_node;
-       Node       *ret = NULL;
-       Node       *zone_node;
-
-       Assert(IsA(expr, FuncExpr));
-       Assert(list_length(expr->args) == 2);
-
-       zone_node = (Node *) linitial(expr->args);
-
-       if (IsA(zone_node, Const) &&!((Const *) zone_node)->constisnull)
-       {
-               Interval   *zone;
-
-               zone = DatumGetIntervalP(((Const *) zone_node)->constvalue);
-               if (zone->month == 0 && zone->day == 0 && zone->time == 0)
-               {
-                       Node       *timestamp = (Node *) lsecond(expr->args);
-
-                       /* Strip any existing RelabelType node(s) */
-                       while (timestamp && IsA(timestamp, RelabelType))
-                               timestamp = (Node *) ((RelabelType *) timestamp)->arg;
-
-                       /*
-                        * Replace the FuncExpr with its timestamp argument, relabeled as
-                        * though the function call had computed it.
-                        */
-                       ret = (Node *) makeRelabelType((Expr *) timestamp,
-                                                                                  exprType(func_node),
-                                                                                  exprTypmod(func_node),
-                                                                                  exprCollation(func_node),
-                                                                                  COERCE_EXPLICIT_CAST);
-               }
-       }
-
-       PG_RETURN_POINTER(ret);
+       PG_RETURN_POINTER(NULL);
 }
 
 /* timestamp_izone()
index 9714e64e9cbd1820bd4679377d0d329eff8e95a0..3a4fbede2aeb2d3750eefec1d9847dcf32dd8874 100644 (file)
@@ -2573,3 +2573,22 @@ select count(distinct utc_offset) >= 24 as ok from pg_timezone_abbrevs;
  t
 (1 row)
 
+--
+-- Test that AT TIME ZONE isn't misoptimized when using an index (bug #14504)
+--
+create temp table tmptz (f1 timestamptz primary key);
+insert into tmptz values ('2017-01-18 00:00+00');
+explain (costs off)
+select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+                                           QUERY PLAN                                            
+-------------------------------------------------------------------------------------------------
+ Seq Scan on tmptz
+   Filter: (timezone('utc'::text, f1) = 'Wed Jan 18 00:00:00 2017'::timestamp without time zone)
+(2 rows)
+
+select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+              f1              
+------------------------------
+ Tue Jan 17 16:00:00 2017 PST
+(1 row)
+
index c00cc3b527e247f70697ab3415db4df67fd00654..9c75706ff3b23e8e888bf52c8e303e005e6e1748 100644 (file)
@@ -465,3 +465,12 @@ set timezone_abbreviations = 'Australia';
 select count(distinct utc_offset) >= 24 as ok from pg_timezone_abbrevs;
 set timezone_abbreviations = 'India';
 select count(distinct utc_offset) >= 24 as ok from pg_timezone_abbrevs;
+
+--
+-- Test that AT TIME ZONE isn't misoptimized when using an index (bug #14504)
+--
+create temp table tmptz (f1 timestamptz primary key);
+insert into tmptz values ('2017-01-18 00:00+00');
+explain (costs off)
+select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';