pg_stat_statements: Fix crash in list squashing with Vars
authorMichael Paquier <michael@paquier.xyz>
Mon, 19 Jan 2026 23:11:12 +0000 (08:11 +0900)
committerMichael Paquier <michael@paquier.xyz>
Mon, 19 Jan 2026 23:11:12 +0000 (08:11 +0900)
When IN/ANY clauses contain both constants and variable expressions, the
optimizer transforms them into separate structures: constants become
an array expression while variables become individual OR conditions.

This transformation was creating an overlap with the token locations,
causing pg_stat_statements query normalization to crash because it
could not calculate the amount of bytes remaining to write for the
normalized query.

This commit disables squashing for mixed IN list expressions when
constructing a scalar array op, by setting list_start and list_end
to -1 when both variables and non-variables are present.  Some
regression tests are added to PGSS to verify these patterns.

Author: Sami Imseih <samimseih@gmail.com>
Reviewed-by: Dmitry Dolgov <9erthalion6@gmail.com>
Discussion: https://postgr.es/m/CAA5RZ0ts9qiONnHjjHxPxtePs22GBo4d3jZ_s2BQC59AN7XbAA@mail.gmail.com
Backpatch-through: 18

contrib/pg_stat_statements/expected/squashing.out
contrib/pg_stat_statements/sql/squashing.sql
src/backend/parser/parse_expr.c

index d5bb67c7222faeb6a165e10b93f7333325fbc8db..6963a434db975c9b42ad4a0261556cf4611d2261 100644 (file)
@@ -872,6 +872,23 @@ SELECT (ROW(ARRAY[1, 2], ARRAY[1, $1, 3])).*, 1 \bind 1
  {1,2} | {1,1,3} |        1
 (1 row)
 
+-- IN and ANY clauses with Vars are not squashed.
+SELECT * FROM test_squash a, test_squash b WHERE a.id IN (1, 2, 3, b.id, b.id + 1);
+ id | data | id | data 
+----+------+----+------
+(0 rows)
+
+SELECT * FROM test_squash a, test_squash b WHERE a.id = ANY (array[1, ((b.id + b.id * 2)), 5]);
+ id | data | id | data 
+----+------+----+------
+(0 rows)
+
+SELECT * FROM test_squash a, test_squash b WHERE a.id IN ($1, $2, $3, b.id, b.id + $4) \bind 1 2 3 1
+;
+ id | data | id | data 
+----+------+----+------
+(0 rows)
+
 SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C";
                                                     query                                                    | calls 
 -------------------------------------------------------------------------------------------------------------+-------
@@ -884,8 +901,11 @@ SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C";
  SELECT (ROW(ARRAY[$1 /*, ... */])).*                                                                        |     1
  SELECT (ROW(ARRAY[$1 /*, ... */], ARRAY[$2 /*, ... */])).*                                                  |     1
  SELECT (ROW(ARRAY[$1 /*, ... */], ARRAY[$2 /*, ... */])).*, $3                                              |     1
+ SELECT * FROM test_squash a, test_squash b WHERE a.id = ANY (array[$1, ((b.id + b.id * $2)), $3])           |     1
+ SELECT * FROM test_squash a, test_squash b WHERE a.id IN ($1, $2, $3, b.id, b.id + $4)                      |     1
+ SELECT * FROM test_squash a, test_squash b WHERE a.id IN ($1, $2, $3, b.id, b.id + $4)                      |     1
  SELECT pg_stat_statements_reset() IS NOT NULL AS t                                                          |     1
-(8 rows)
+(11 rows)
 
 --
 -- cleanup
index 03b0515f87285ec08e1374a5d239723089eadf31..2100f2d83fd0bd860354ff32e6a3d0e99a2c1f28 100644 (file)
@@ -313,6 +313,12 @@ SELECT (ROW(ARRAY[1, 2], ARRAY[1, 2, 3])).*;
 SELECT 1, 2, (ROW(ARRAY[1, 2], ARRAY[1, 2, 3])).*, 3, 4;
 SELECT (ROW(ARRAY[1, 2], ARRAY[1, $1, 3])).*, 1 \bind 1
 ;
+
+-- IN and ANY clauses with Vars are not squashed.
+SELECT * FROM test_squash a, test_squash b WHERE a.id IN (1, 2, 3, b.id, b.id + 1);
+SELECT * FROM test_squash a, test_squash b WHERE a.id = ANY (array[1, ((b.id + b.id * 2)), 5]);
+SELECT * FROM test_squash a, test_squash b WHERE a.id IN ($1, $2, $3, b.id, b.id + $4) \bind 1 2 3 1
+;
 SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C";
 
 --
index 56826db4c2612cb5309a8f2aceb8c4961ba03e40..dcfe1acc4c3221118e49d1bf771f72185ce41210 100644 (file)
@@ -1132,6 +1132,7 @@ transformAExprIn(ParseState *pstate, A_Expr *a)
        List       *rnonvars;
        bool            useOr;
        ListCell   *l;
+       bool            has_rvars = false;
 
        /*
         * If the operator is <>, combine with AND not OR.
@@ -1160,7 +1161,10 @@ transformAExprIn(ParseState *pstate, A_Expr *a)
 
                rexprs = lappend(rexprs, rexpr);
                if (contain_vars_of_level(rexpr, 0))
+               {
                        rvars = lappend(rvars, rexpr);
+                       has_rvars = true;
+               }
                else
                        rnonvars = lappend(rnonvars, rexpr);
        }
@@ -1225,10 +1229,15 @@ transformAExprIn(ParseState *pstate, A_Expr *a)
                        newa->element_typeid = scalar_type;
                        newa->elements = aexprs;
                        newa->multidims = false;
-                       newa->list_start = a->rexpr_list_start;
-                       newa->list_end = a->rexpr_list_end;
                        newa->location = -1;
 
+                       /*
+                        * If the IN expression contains Vars, disable query jumbling
+                        * squashing.  Vars cannot be safely jumbled.
+                        */
+                       newa->list_start = has_rvars ? -1 : a->rexpr_list_start;
+                       newa->list_end = has_rvars ? -1 : a->rexpr_list_end;
+
                        result = (Node *) make_scalar_array_op(pstate,
                                                                                                   a->name,
                                                                                                   useOr,