nocfbot-0011-Fix-quote_identifier-deparse.txt
text/plain
Filename: nocfbot-0011-Fix-quote_identifier-deparse.txt
Type: text/plain
Part: 10
Message:
Re: Row pattern recognition
From 5a5e04eb3becfc5cd185b67243fe15eff5ef6f56 Mon Sep 17 00:00:00 2001
From: Henson Choi <assam258@gmail.com>
Date: Tue, 7 Apr 2026 08:53:46 +0900
Subject: [PATCH 11/40] Fix quote_identifier() for RPR pattern variable name
deparse
Add quote_identifier() to PATTERN and DEFINE variable name output
in ruleutils.c and explain.c. Without quoting, mixed-case or
reserved-word variable names (e.g., "Start", "Up") lose their
case or conflict with keywords in pg_get_viewdef() output,
breaking pg_dump/pg_restore round-trips.
Add regression test with quoted identifiers ("Start", "Up") to
verify correct deparse in both pg_get_viewdef and EXPLAIN output.
---
src/backend/commands/explain.c | 2 +-
src/backend/utils/adt/ruleutils.c | 4 ++--
src/test/regress/expected/rpr_base.out | 24 +++++++++++++++++++++++
src/test/regress/expected/rpr_explain.out | 19 ++++++++++++++++++
src/test/regress/sql/rpr_base.sql | 10 ++++++++++
src/test/regress/sql/rpr_explain.sql | 12 ++++++++++++
6 files changed, 68 insertions(+), 3 deletions(-)
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 7f0367ce546..933eadab71e 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -3176,7 +3176,7 @@ deparse_rpr_var(RPRPattern *pattern, int *idx, StringInfoData *buf,
appendStringInfoChar(buf, ' ');
Assert(elem->varId < pattern->numVars);
- appendStringInfoString(buf, pattern->varNames[elem->varId]);
+ appendStringInfoString(buf, quote_identifier(pattern->varNames[elem->varId]));
append_rpr_quantifier(buf, elem);
*needSpace = true;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index cfe24de43cf..c755a42efd6 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -7160,7 +7160,7 @@ get_rule_pattern_node(RPRPatternNode *node, deparse_context *context)
switch (node->nodeType)
{
case RPR_PATTERN_VAR:
- appendStringInfoString(buf, node->varName);
+ appendStringInfoString(buf, quote_identifier(node->varName));
append_pattern_quantifier(buf, node);
break;
@@ -7229,7 +7229,7 @@ get_rule_define(List *defineClause, bool force_colno, deparse_context *context)
{
TargetEntry *te = (TargetEntry *) lfirst(lc_def);
- appendStringInfo(buf, "%s%s AS ", sep, te->resname);
+ appendStringInfo(buf, "%s%s AS ", sep, quote_identifier(te->resname));
get_rule_expr((Node *) te->expr, context, false);
sep = ",\n ";
}
diff --git a/src/test/regress/expected/rpr_base.out b/src/test/regress/expected/rpr_base.out
index 7452cf1b3ab..6526365dd6a 100644
--- a/src/test/regress/expected/rpr_base.out
+++ b/src/test/regress/expected/rpr_base.out
@@ -2252,6 +2252,30 @@ SELECT pg_get_viewdef('rpr_quant_reluctant_v'::regclass);
b AS (val > 0) );
(1 row)
+-- Quoted identifier round-trip: mixed case and reserved words need quoting
+CREATE VIEW rpr_serial_quoted AS
+SELECT id, val, count(*) OVER w
+FROM rpr_serial
+WINDOW w AS (ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ("Start" "Up"+)
+ DEFINE "Start" AS TRUE, "Up" AS val > PREV(val));
+SELECT pg_get_viewdef('rpr_serial_quoted'::regclass);
+ pg_get_viewdef
+------------------------------------------------------------------------------
+ SELECT id, +
+ val, +
+ count(*) OVER w AS count +
+ FROM rpr_serial +
+ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +
+ AFTER MATCH SKIP PAST LAST ROW +
+ INITIAL +
+ PATTERN ("Start" "Up"+) +
+ DEFINE +
+ "Start" AS true, +
+ "Up" AS (val > prev(val)) );
+(1 row)
+
-- Materialized view (if supported)
CREATE TABLE rpr_mview (id INT, val INT);
INSERT INTO rpr_mview VALUES (1, 10), (2, 20), (3, 30);
diff --git a/src/test/regress/expected/rpr_explain.out b/src/test/regress/expected/rpr_explain.out
index f66caf8908e..a68ec61e10f 100644
--- a/src/test/regress/expected/rpr_explain.out
+++ b/src/test/regress/expected/rpr_explain.out
@@ -301,6 +301,25 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=30.00 loops=1)
(8 rows)
+-- Regression test: Quoted identifiers in EXPLAIN pattern deparse
+-- Mixed case names must be quoted to preserve round-trip safety
+SELECT rpr_explain_filter('
+EXPLAIN (COSTS OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 10) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ("Start" "Up"+)
+ DEFINE "Start" AS TRUE, "Up" AS v > PREV(v)
+);');
+ rpr_explain_filter
+-------------------------------------------------------------------
+ WindowAgg
+ Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: "Start" "Up"+
+ -> Function Scan on generate_series s
+(4 rows)
+
-- ============================================================
-- State Statistics Tests (peak, total, merged)
-- ============================================================
diff --git a/src/test/regress/sql/rpr_base.sql b/src/test/regress/sql/rpr_base.sql
index 8c23c7598a3..3accecb73ba 100644
--- a/src/test/regress/sql/rpr_base.sql
+++ b/src/test/regress/sql/rpr_base.sql
@@ -1559,6 +1559,16 @@ WINDOW w AS (ORDER BY id
DEFINE A AS val > 0, B AS val > 0);
SELECT pg_get_viewdef('rpr_quant_reluctant_v'::regclass);
+-- Quoted identifier round-trip: mixed case and reserved words need quoting
+CREATE VIEW rpr_serial_quoted AS
+SELECT id, val, count(*) OVER w
+FROM rpr_serial
+WINDOW w AS (ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ("Start" "Up"+)
+ DEFINE "Start" AS TRUE, "Up" AS val > PREV(val));
+SELECT pg_get_viewdef('rpr_serial_quoted'::regclass);
+
-- Materialized view (if supported)
CREATE TABLE rpr_mview (id INT, val INT);
diff --git a/src/test/regress/sql/rpr_explain.sql b/src/test/regress/sql/rpr_explain.sql
index 65a775fdad9..703ecd3b23b 100644
--- a/src/test/regress/sql/rpr_explain.sql
+++ b/src/test/regress/sql/rpr_explain.sql
@@ -226,6 +226,18 @@ WINDOW w AS (
DEFINE A AS v % 5 = 1, B AS v % 5 = 2, C AS v % 5 = 3, D AS v % 5 = 4, E AS v % 5 = 0
);');
+-- Regression test: Quoted identifiers in EXPLAIN pattern deparse
+-- Mixed case names must be quoted to preserve round-trip safety
+SELECT rpr_explain_filter('
+EXPLAIN (COSTS OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 10) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ("Start" "Up"+)
+ DEFINE "Start" AS TRUE, "Up" AS v > PREV(v)
+);');
+
-- ============================================================
-- State Statistics Tests (peak, total, merged)
-- ============================================================
--
2.50.1 (Apple Git-155)