Improve jumble squashing through CoerceViaIO and RelabelType
authorÁlvaro Herrera <[email protected]>
Tue, 24 Jun 2025 17:36:12 +0000 (19:36 +0200)
committerÁlvaro Herrera <[email protected]>
Tue, 24 Jun 2025 17:36:12 +0000 (19:36 +0200)
There's no principled reason for query jumbling to only remove the first
layer of RelabelType and CoerceViaIO.  Change it to see through as many
layers as there are.

contrib/pg_stat_statements/expected/squashing.out
contrib/pg_stat_statements/sql/squashing.sql
src/backend/nodes/queryjumblefuncs.c

index 7b935d464ecfffb20b5407ace2370177259fb321..e978564ad7299521ff542548ebca755fda79819f 100644 (file)
@@ -645,7 +645,7 @@ SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C";
  SELECT pg_stat_statements_reset() IS NOT NULL AS t                   |     1
 (2 rows)
 
--- Multiple CoerceViaIO wrapping a constant. Will not squash
+-- Multiple CoerceViaIO are squashed
 SELECT pg_stat_statements_reset() IS NOT NULL AS t;
  t 
 ---
@@ -661,10 +661,10 @@ SELECT WHERE 1 = ANY(ARRAY[1::text::int::text::int, 1::text::int::text::int]);
 (1 row)
 
 SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C";
-                                  query                                  | calls 
--------------------------------------------------------------------------+-------
- SELECT WHERE $1 IN ($2::text::int::text::int, $3::text::int::text::int) |     2
- SELECT pg_stat_statements_reset() IS NOT NULL AS t                      |     1
+                       query                        | calls 
+----------------------------------------------------+-------
+ SELECT WHERE $1 IN ($2 /*, ... */)                 |     2
+ SELECT pg_stat_statements_reset() IS NOT NULL AS t |     1
 (2 rows)
 
 --
@@ -676,7 +676,7 @@ SELECT pg_stat_statements_reset() IS NOT NULL AS t;
  t
 (1 row)
 
--- if there is only one level of RelabelType, the list will be squashable
+-- However many layers of RelabelType there are, the list will be squashable.
 SELECT * FROM test_squash WHERE id IN
    (1::oid, 2::oid, 3::oid, 4::oid, 5::oid, 6::oid, 7::oid, 8::oid, 9::oid);
  id | data 
@@ -689,8 +689,6 @@ SELECT ARRAY[1::oid, 2::oid, 3::oid, 4::oid, 5::oid, 6::oid, 7::oid, 8::oid, 9::
  {1,2,3,4,5,6,7,8,9}
 (1 row)
 
--- if there is at least one element with multiple levels of RelabelType,
--- the list will not be squashable
 SELECT * FROM test_squash WHERE id IN (1::oid, 2::oid::int::oid);
  id | data 
 ----+------
@@ -701,15 +699,25 @@ SELECT * FROM test_squash WHERE id = ANY(ARRAY[1::oid, 2::oid::int::oid]);
 ----+------
 (0 rows)
 
+-- RelabelType together with CoerceViaIO is also squashable
+SELECT * FROM test_squash WHERE id = ANY(ARRAY[1::oid::text::int::oid, 2::oid::int::oid]);
+ id | data 
+----+------
+(0 rows)
+
+SELECT * FROM test_squash WHERE id = ANY(ARRAY[1::text::int::oid, 2::oid::int::oid]);
+ id | data 
+----+------
+(0 rows)
+
 SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C";
-                               query                                | calls 
---------------------------------------------------------------------+-------
- SELECT * FROM test_squash WHERE id IN                             +|     1
-         ($1 /*, ... */)                                            | 
- SELECT * FROM test_squash WHERE id IN ($1::oid, $2::oid::int::oid) |     2
- SELECT ARRAY[$1 /*, ... */]                                        |     1
- SELECT pg_stat_statements_reset() IS NOT NULL AS t                 |     1
-(4 rows)
+                       query                        | calls 
+----------------------------------------------------+-------
+ SELECT * FROM test_squash WHERE id IN             +|     5
+         ($1 /*, ... */)                            | 
+ SELECT ARRAY[$1 /*, ... */]                        |     1
+ SELECT pg_stat_statements_reset() IS NOT NULL AS t |     1
+(3 rows)
 
 --
 -- edge cases
index bd3243ec9cd853e98cf19281141aed3ab335b69c..946e149831c9451eed6b167f8e3dd51ef3899913 100644 (file)
@@ -234,7 +234,7 @@ SELECT * FROM test_squash_jsonb WHERE data = ANY(ARRAY
     (SELECT '"10"')::jsonb]);
 SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C";
 
--- Multiple CoerceViaIO wrapping a constant. Will not squash
+-- Multiple CoerceViaIO are squashed
 SELECT pg_stat_statements_reset() IS NOT NULL AS t;
 SELECT WHERE 1 IN (1::text::int::text::int, 1::text::int::text::int);
 SELECT WHERE 1 = ANY(ARRAY[1::text::int::text::int, 1::text::int::text::int]);
@@ -245,14 +245,15 @@ SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C";
 --
 
 SELECT pg_stat_statements_reset() IS NOT NULL AS t;
--- if there is only one level of RelabelType, the list will be squashable
+-- However many layers of RelabelType there are, the list will be squashable.
 SELECT * FROM test_squash WHERE id IN
    (1::oid, 2::oid, 3::oid, 4::oid, 5::oid, 6::oid, 7::oid, 8::oid, 9::oid);
 SELECT ARRAY[1::oid, 2::oid, 3::oid, 4::oid, 5::oid, 6::oid, 7::oid, 8::oid, 9::oid];
--- if there is at least one element with multiple levels of RelabelType,
--- the list will not be squashable
 SELECT * FROM test_squash WHERE id IN (1::oid, 2::oid::int::oid);
 SELECT * FROM test_squash WHERE id = ANY(ARRAY[1::oid, 2::oid::int::oid]);
+-- RelabelType together with CoerceViaIO is also squashable
+SELECT * FROM test_squash WHERE id = ANY(ARRAY[1::oid::text::int::oid, 2::oid::int::oid]);
+SELECT * FROM test_squash WHERE id = ANY(ARRAY[1::text::int::oid, 2::oid::int::oid]);
 SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C";
 
 --
index fb33e6931ada373c626e47c4db84a99e41f4dd8f..62e3a677cd1911897811cc435a6981a0cbc348c5 100644 (file)
@@ -414,7 +414,7 @@ RecordConstLocation(JumbleState *jstate, int location, int len)
  * Subroutine for _jumbleElements: Verify a few simple cases where we can
  * deduce that the expression is a constant:
  *
- * - Ignore a possible wrapping RelabelType and CoerceViaIO.
+ * - See through any wrapping RelabelType and CoerceViaIO layers.
  * - If it's a FuncExpr, check that the function is a builtin
  *   cast and its arguments are Const.
  * - Otherwise test if the expression is a simple Const.
@@ -422,14 +422,22 @@ RecordConstLocation(JumbleState *jstate, int location, int len)
 static bool
 IsSquashableConstant(Node *element)
 {
-   if (IsA(element, RelabelType))
-       element = (Node *) ((RelabelType *) element)->arg;
-
-   if (IsA(element, CoerceViaIO))
-       element = (Node *) ((CoerceViaIO *) element)->arg;
-
+restart:
    switch (nodeTag(element))
    {
+       case T_RelabelType:
+           /* Unwrap RelabelType */
+           element = (Node *) ((RelabelType *) element)->arg;
+           goto restart;
+
+       case T_CoerceViaIO:
+           /* Unwrap CoerceViaIO */
+           element = (Node *) ((CoerceViaIO *) element)->arg;
+           goto restart;
+
+       case T_Const:
+           return true;
+
        case T_FuncExpr:
            {
                FuncExpr   *func = (FuncExpr *) element;
@@ -468,11 +476,8 @@ IsSquashableConstant(Node *element)
            }
 
        default:
-           if (!IsA(element, Const))
-               return false;
+           return false;
    }
-
-   return true;
 }
 
 /*