Don't elide casting to typmod -1.
authorTom Lane
Mon, 20 Sep 2021 15:48:52 +0000 (11:48 -0400)
committerTom Lane
Mon, 20 Sep 2021 15:48:52 +0000 (11:48 -0400)
Casting a value that's already of a type with a specific typmod
to an unspecified typmod doesn't do anything so far as run-time
behavior is concerned.  However, it really ought to change the
exposed type of the expression to match.  Up to now,
coerce_type_typmod hasn't bothered with that, which creates gotchas
in contexts such as recursive unions.  If for example one side of
the union is numeric(18,3), but it needs to be plain numeric to
match the other side, there's no direct way to express that.

This is easy enough to fix, by inserting a RelabelType to update the
exposed type of the expression.  However, it's a bit nervous-making
to change this behavior, because it's stood for a really long time.
But no complaints have emerged about 14beta3, so go ahead and
back-patch.

Back-patch of 5c056b0c2 into previous supported branches.

Discussion: https://postgr.es/m/CABNQVagu3bZGqiTjb31a8D5Od3fUMs7Oh3gmZMQZVHZ=uWWWfQ@mail.gmail.com
Discussion: https://postgr.es/m/1488389.1631984807@sss.pgh.pa.us

src/backend/parser/parse_coerce.c
src/test/regress/expected/expressions.out
src/test/regress/sql/expressions.sql

index 7e70cc8fbdcbd61335aa1f8d40ebcf486e4ab0e0..33b96a73529cc6af2e4495d2bfba3b3b46ae234f 100644 (file)
@@ -755,25 +755,33 @@ coerce_type_typmod(Node *node, Oid targetTypeId, int32 targetTypMod,
    CoercionPathType pathtype;
    Oid         funcId;
 
-   /*
-    * A negative typmod is assumed to mean that no coercion is wanted. Also,
-    * skip coercion if already done.
-    */
-   if (targetTypMod < 0 || targetTypMod == exprTypmod(node))
+   /* Skip coercion if already done */
+   if (targetTypMod == exprTypmod(node))
        return node;
 
+   /* Suppress display of nested coercion steps */
+   if (hideInputCoercion)
+       hide_coercion_node(node);
+
    pathtype = find_typmod_coercion_function(targetTypeId, &funcId);
 
    if (pathtype != COERCION_PATH_NONE)
    {
-       /* Suppress display of nested coercion steps */
-       if (hideInputCoercion)
-           hide_coercion_node(node);
-
        node = build_coercion_expression(node, pathtype, funcId,
                                         targetTypeId, targetTypMod,
                                         ccontext, cformat, location);
    }
+   else
+   {
+       /*
+        * We don't need to perform any actual coercion step, but we should
+        * apply a RelabelType to ensure that the expression exposes the
+        * intended typmod.
+        */
+       node = applyRelabelType(node, targetTypeId, targetTypMod,
+                               exprCollation(node),
+                               cformat, location, false);
+   }
 
    return node;
 }
index 4f4deaec2231cfc171b419bc53b9695b71d6f5b1..cb73c7f950a08b8c4c2df378a051ddd1f90c86d4 100644 (file)
@@ -158,3 +158,40 @@ select count(*) from date_tbl
     12
 (1 row)
 
+--
+-- Test parsing of a no-op cast to a type with unspecified typmod
+--
+begin;
+create table numeric_tbl (f1 numeric(18,3), f2 numeric);
+create view numeric_view as
+  select
+    f1, f1::numeric(16,4) as f1164, f1::numeric as f1n,
+    f2, f2::numeric(16,4) as f2164, f2::numeric as f2n
+  from numeric_tbl;
+\d+ numeric_view
+                           View "public.numeric_view"
+ Column |     Type      | Collation | Nullable | Default | Storage | Description 
+--------+---------------+-----------+----------+---------+---------+-------------
+ f1     | numeric(18,3) |           |          |         | main    | 
+ f1164  | numeric(16,4) |           |          |         | main    | 
+ f1n    | numeric       |           |          |         | main    | 
+ f2     | numeric       |           |          |         | main    | 
+ f2164  | numeric(16,4) |           |          |         | main    | 
+ f2n    | numeric       |           |          |         | main    | 
+View definition:
+ SELECT numeric_tbl.f1,
+    numeric_tbl.f1::numeric(16,4) AS f1164,
+    numeric_tbl.f1::numeric AS f1n,
+    numeric_tbl.f2,
+    numeric_tbl.f2::numeric(16,4) AS f2164,
+    numeric_tbl.f2 AS f2n
+   FROM numeric_tbl;
+
+explain (verbose, costs off) select * from numeric_view;
+                                                                      QUERY PLAN                                                                       
+-------------------------------------------------------------------------------------------------------------------------------------------------------
+ Seq Scan on public.numeric_tbl
+   Output: numeric_tbl.f1, (numeric_tbl.f1)::numeric(16,4), (numeric_tbl.f1)::numeric, numeric_tbl.f2, (numeric_tbl.f2)::numeric(16,4), numeric_tbl.f2
+(2 rows)
+
+rollback;
index 1ca8bb151c80c43fdd7c8d869f42442cba95e8e5..b2fb67d76634ee356bb654d84000ef4fc98b8345 100644 (file)
@@ -65,3 +65,23 @@ select count(*) from date_tbl
   where f1 not between symmetric '1997-01-01' and '1998-01-01';
 select count(*) from date_tbl
   where f1 not between symmetric '1997-01-01' and '1998-01-01';
+
+
+--
+-- Test parsing of a no-op cast to a type with unspecified typmod
+--
+begin;
+
+create table numeric_tbl (f1 numeric(18,3), f2 numeric);
+
+create view numeric_view as
+  select
+    f1, f1::numeric(16,4) as f1164, f1::numeric as f1n,
+    f2, f2::numeric(16,4) as f2164, f2::numeric as f2n
+  from numeric_tbl;
+
+\d+ numeric_view
+
+explain (verbose, costs off) select * from numeric_view;
+
+rollback;