0001-Fix-column-name-escaping-in-postgres_fdw-stats-import.patch
application/octet-stream
Filename: 0001-Fix-column-name-escaping-in-postgres_fdw-stats-import.patch
Type: application/octet-stream
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch 0001
Subject: Fix column name escaping in postgres_fdw stats import
| File | + | − |
|---|---|---|
| contrib/postgres_fdw/expected/postgres_fdw.out | 13 | 0 |
| contrib/postgres_fdw/postgres_fdw.c | 4 | 4 |
| contrib/postgres_fdw/sql/postgres_fdw.sql | 12 | 0 |
From 88ac32aaf89e5e77dcff7fc6eda511aaeaaffe73 Mon Sep 17 00:00:00 2001
From: Ayush Tiwari <ayushtiwari.slg01@gmail.com>
Date: Tue, 21 Apr 2026 01:41:22 +0530
Subject: [PATCH] Fix column name escaping in postgres_fdw stats import
build_remattrmap() used quote_identifier() to format column names for
the remote stats-fetching query, but the column list was embedded inside
a single-quoted string literal (the text[] array literal). Since
quote_identifier() only escapes double quotes, any single quote in a
column name (from the column_name FDW option or the remote table itself)
would break the string literal, producing malformed SQL on the remote
server.
Switch from a text array literal to an ARRAY[] constructor with each
column name individually escaped by deparseStringLiteral(), matching the
escaping already used for schemaname and tablename in the same query.
Introduced by 28972b6fc3d (import statistics from remote servers).
---
contrib/postgres_fdw/expected/postgres_fdw.out | 13 +++++++++++++
contrib/postgres_fdw/postgres_fdw.c | 8 ++++----
contrib/postgres_fdw/sql/postgres_fdw.sql | 12 ++++++++++++
3 files changed, 29 insertions(+), 4 deletions(-)
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 10e87acabef..8be5e57bf16 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -12917,7 +12917,20 @@ CREATE FOREIGN TABLE simport_fview (c1 int, c2 text)
ALTER FOREIGN TABLE simport_fview OPTIONS (ADD restore_stats 'true');
ANALYZE simport_fview; -- should fail
WARNING: could not import statistics for foreign table "public.simport_fview" --- remote table "public.simport_view" is of relkind "v" which cannot have statistics
+-- Test that single quotes in column names are handled correctly when
+-- fetching remote statistics.
+CREATE TABLE simport_tbl_quote ("col'quote" int, c2 int);
+INSERT INTO simport_tbl_quote SELECT g, g FROM generate_series(1,100) g;
+ANALYZE simport_tbl_quote;
+CREATE FOREIGN TABLE simport_ft_quote ("col'quote" int, c2 int)
+ SERVER loopback OPTIONS (table_name 'simport_tbl_quote',
+ restore_stats 'true');
+ANALYZE VERBOSE simport_ft_quote; -- should work, not syntax error
+INFO: importing statistics for foreign table "public.simport_ft_quote"
+INFO: finished importing statistics for foreign table "public.simport_ft_quote"
-- cleanup
+DROP FOREIGN TABLE simport_ft_quote;
+DROP TABLE simport_tbl_quote;
DROP FOREIGN TABLE simport_ftable;
DROP FOREIGN TABLE simport_fview;
DROP VIEW simport_view;
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 0f20f38c83e..8a624794aa1 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -5886,7 +5886,7 @@ fetch_attstats(PGconn *conn, int server_version_num,
" AND tablename = ");
deparseStringLiteral(&sql, remote_relname);
appendStringInfo(&sql,
- " AND attname = ANY('%s'::text[])",
+ " AND attname = ANY(%s)",
column_list);
/* inherited is supported since Postgres 9.0 */
@@ -5921,7 +5921,7 @@ build_remattrmap(Relation relation, List *va_cols,
remattrmap = palloc_array(RemoteAttributeMapping, tupdesc->natts);
initStringInfo(column_list);
- appendStringInfoChar(column_list, '{');
+ appendStringInfoString(column_list, "ARRAY[");
for (int i = 0; i < tupdesc->natts; i++)
{
Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
@@ -5954,7 +5954,7 @@ build_remattrmap(Relation relation, List *va_cols,
if (attrcnt > 0)
appendStringInfoString(column_list, ", ");
- appendStringInfoString(column_list, quote_identifier(remote_attname));
+ deparseStringLiteral(column_list, remote_attname);
remattrmap[attrcnt].local_attnum = attnum;
strncpy(remattrmap[attrcnt].local_attname, attname, NAMEDATALEN);
@@ -5962,7 +5962,7 @@ build_remattrmap(Relation relation, List *va_cols,
remattrmap[attrcnt].res_index = -1;
attrcnt++;
}
- appendStringInfoChar(column_list, '}');
+ appendStringInfoChar(column_list, ']');
/* Sort mapping by remote attribute name if needed. */
if (attrcnt > 1)
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 79ad5be8bf9..d73dc6a0dc6 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -4573,7 +4573,19 @@ ALTER FOREIGN TABLE simport_fview OPTIONS (ADD restore_stats 'true');
ANALYZE simport_fview; -- should fail
+-- Test that single quotes in column names are handled correctly when
+-- fetching remote statistics.
+CREATE TABLE simport_tbl_quote ("col'quote" int, c2 int);
+INSERT INTO simport_tbl_quote SELECT g, g FROM generate_series(1,100) g;
+ANALYZE simport_tbl_quote;
+CREATE FOREIGN TABLE simport_ft_quote ("col'quote" int, c2 int)
+ SERVER loopback OPTIONS (table_name 'simport_tbl_quote',
+ restore_stats 'true');
+ANALYZE VERBOSE simport_ft_quote; -- should work, not syntax error
+
-- cleanup
+DROP FOREIGN TABLE simport_ft_quote;
+DROP TABLE simport_tbl_quote;
DROP FOREIGN TABLE simport_ftable;
DROP FOREIGN TABLE simport_fview;
DROP VIEW simport_view;
--
2.34.1