From 6d2621b0ff32525f8454c4130b13d6e1e8000d20 Mon Sep 17 00:00:00 2001
From: Ubuntu <ubuntu@ip-172-31-46-230.ec2.internal>
Date: Tue, 13 Jan 2026 03:52:17 +0000
Subject: [PATCH v1 1/1] pg_stat_statements: Fix crash in with mixed IN clause
 expressions

When IN 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 can create overlapping token locations that cause
pg_stat_statements query normalization to crash.

This fix 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.

Add regression tests to verify this case is handled correctly.
---
 .../pg_stat_statements/expected/squashing.out    | 16 +++++++++++++++-
 contrib/pg_stat_statements/sql/squashing.sql     |  5 +++++
 src/backend/parser/parse_expr.c                  | 13 +++++++++++--
 3 files changed, 31 insertions(+), 3 deletions(-)

diff --git a/contrib/pg_stat_statements/expected/squashing.out b/contrib/pg_stat_statements/expected/squashing.out
index d5bb67c7222..13c184d5018 100644
--- a/contrib/pg_stat_statements/expected/squashing.out
+++ b/contrib/pg_stat_statements/expected/squashing.out
@@ -872,6 +872,18 @@ SELECT (ROW(ARRAY[1, 2], ARRAY[1, $1, 3])).*, 1 \bind 1
  {1,2} | {1,1,3} |        1
 (1 row)
 
+-- Mixed Const and non-Const expressions in the list will not be 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 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 +896,10 @@ 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 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)
+(10 rows)
 
 --
 -- cleanup
diff --git a/contrib/pg_stat_statements/sql/squashing.sql b/contrib/pg_stat_statements/sql/squashing.sql
index 03b0515f872..c3f2f506955 100644
--- a/contrib/pg_stat_statements/sql/squashing.sql
+++ b/contrib/pg_stat_statements/sql/squashing.sql
@@ -313,6 +313,11 @@ 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
 ;
+
+-- Mixed Const and non-Const expressions in the list will not be 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 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";
 
 --
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 56826db4c26..5fc46c696c3 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -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,9 +1229,14 @@ 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 also contains ars and non-Vars, disable
+			 * query jumbling squashing by setting list start and end
+			 * locations to -1. We cannot sanely jumble Var expressions.
+			 */
+			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,
-- 
2.43.0

