From 88ac32aaf89e5e77dcff7fc6eda511aaeaaffe73 Mon Sep 17 00:00:00 2001 From: Ayush Tiwari 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