This is an automated email from the ASF dual-hosted git repository.
my-ship-it pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudberry.git
The following commit(s) were added to refs/heads/main by this push:
new 813779c8d4c ORCA: restore non-ASCII column aliases for all target
entry kinds
813779c8d4c is described below
commit 813779c8d4c4d0e359b8018d0838538f46c00937
Author: Jianghua Yang <[email protected]>
AuthorDate: Fri Jul 10 18:10:38 2026 +0800
ORCA: restore non-ASCII column aliases for all target entry kinds
ORCA represents names as wide characters; when the database LC_CTYPE
cannot decode a multibyte name (e.g. LC_CTYPE='C' with a UTF-8 alias),
clib::Vswprintf substitutes the generic "UNKNOWN" string and the
DXL-to-PlStmt translator restores the original name from the query
tree (commit 6b19c44262b). That restore only ran for Var target
entries, so with the optimizer enabled
SELECT '한글' AS "한글";
returned a column named UNKNOWN: constants, aggregates, set operation
and set-returning-function outputs never took the repair path.
The old repair walked the entire query tree with
update_unknown_locale_walker matching on (resorigtbl, resno). That
match key is ambiguous: TargetEntries nested inside Aggref arguments
or SubLink subqueries can collide with it, either clobbering an
already-restored name (empty column header for the first of two
aggregates) or restoring a name from the wrong query level.
Replace the walker with restore_unknown_locale_resname, which scans
only the top-level query targetList for the non-junk entry with the
same resno. Only the topmost plan node is translated with a context
that carries the original query, and its projection list produces the
query output columns in order, so the positional top-level match is
exact. A legitimate alias literally named "UNKNOWN" self-matches and
the restore is a no-op. Call it for every entry translated by
TranslateDXLProjList, for the inlined Append targetlist (UNION ALL),
and for the ProjectSet targetlists built by SetupAliasParameter
(set-returning functions).
Extend gp_locale with alias cases for constants, expressions,
aggregates, UNION ALL, set-returning functions, a subquery containing
a same-position column, and a legitimate "UNKNOWN" alias.
---
.../gpopt/translate/CTranslatorDXLToPlStmt.cpp | 120 ++++++++++-----------
src/test/regress/expected/gp_locale.out | 57 ++++++++++
src/test/regress/sql/gp_locale.sql | 23 ++++
3 files changed, 138 insertions(+), 62 deletions(-)
diff --git a/src/backend/gpopt/translate/CTranslatorDXLToPlStmt.cpp
b/src/backend/gpopt/translate/CTranslatorDXLToPlStmt.cpp
index 8b7898cc103..3abfd5fed5a 100644
--- a/src/backend/gpopt/translate/CTranslatorDXLToPlStmt.cpp
+++ b/src/backend/gpopt/translate/CTranslatorDXLToPlStmt.cpp
@@ -4031,6 +4031,49 @@ CTranslatorDXLToPlStmt::CreateProjectSetNodeTree(const
CDXLNode *result_dxlnode,
return project_set_parent_plan;
}
+//---------------------------------------------------------------------------
+// @function:
+// restore_unknown_locale_resname
+//
+// @doc:
+// ORCA represents strings using wide characters. Converting a
multibyte
+// name to wide format uses vswprintf(), which depends on the
database
+// LC_CTYPE. When that locale cannot interpret the name (e.g.
LC_CTYPE=C
+// with a UTF-8 alias), ORCA substitutes the generic "UNKNOWN"
string
+// (see gpos::clib::Vswprintf). This function restores the
original name
+// from the query tree.
+//
+// Only the topmost plan node is translated with a context that
carries
+// the original query (see GetPlannedStmtFromDXL); everywhere else
query
+// is NULL and this is a no-op. The topmost projection list
produces the
+// query's output columns in order, so the original name is the
non-junk
+// query targetList entry with the same resno. Matching only
top-level
+// entries (never descending into subqueries or expressions) also
keeps
+// a legitimate alias named "UNKNOWN" intact: its positional match
is
+// the entry itself, making the restore a no-op.
+//---------------------------------------------------------------------------
+static void
+restore_unknown_locale_resname(const Query *query, TargetEntry *target_entry)
+{
+ if (nullptr == query || 0 != strcmp(target_entry->resname, "UNKNOWN"))
+ {
+ return;
+ }
+
+ ListCell *lc;
+ ForEach(lc, query->targetList)
+ {
+ TargetEntry *te = (TargetEntry *) lfirst(lc);
+
+ if (!te->resjunk && nullptr != te->resname &&
+ te->resno == target_entry->resno)
+ {
+ target_entry->resname = te->resname;
+ return;
+ }
+ }
+}
+
//------------------------------------------------------------------------------
// If a result plan node is not required on top of a project set node then the
// alias parameter needs to be set for all the project set nodes else not
@@ -4040,7 +4083,7 @@ CTranslatorDXLToPlStmt::CreateProjectSetNodeTree(const
CDXLNode *result_dxlnode,
void
SetupAliasParameter(const BOOL will_require_result_node,
const CDXLNode *project_list_dxlnode,
- Plan *project_set_parent_plan)
+ Plan *project_set_parent_plan, const
Query *query)
{
if (!will_require_result_node)
{
@@ -4069,6 +4112,9 @@ SetupAliasParameter(const BOOL will_require_result_node,
sc_proj_elem_dxlop->GetMdNameAlias()
->GetMDName()
->GetBuffer());
+
+ // restore aliases that failed the wide character
conversion
+ restore_unknown_locale_resname(query, te);
ul++;
}
}
@@ -4243,7 +4289,7 @@ CTranslatorDXLToPlStmt::TranslateDXLResult(
}
SetupAliasParameter(will_require_result_node, project_list_dxlnode,
- project_set_parent_plan);
+ project_set_parent_plan,
output_context->GetQuery());
Plan *final_plan = nullptr;
@@ -4500,6 +4546,10 @@ CTranslatorDXLToPlStmt::TranslateDXLAppend(
sc_proj_elem_dxlop->GetMdNameAlias()->GetMDName()->GetBuffer());
target_entry->resno = attno;
+ // restore aliases that failed the wide character conversion
+ restore_unknown_locale_resname(output_context->GetQuery(),
+
target_entry);
+
// add column mapping to output translation context
output_context->InsertMapping(sc_proj_elem_dxlop->Id(),
target_entry);
@@ -5923,51 +5973,6 @@ CTranslatorDXLToPlStmt::ProcessDXLTblDescr(
return index;
}
-//---------------------------------------------------------------------------
-// @function:
-// update_unknown_locale_walker
-//
-// @doc:
-// Given an expression tree and a TargetEntry pointer context,
look for a
-// matching target entry in the expression tree and overwrite the
given
-// TargetEntry context's resname with the original found in the
expression
-// tree.
-//
-//---------------------------------------------------------------------------
-static bool
-update_unknown_locale_walker(Node *node, void *context)
-{
- if (node == nullptr)
- {
- return false;
- }
-
- TargetEntry *unknown_target_entry = (TargetEntry *) context;
-
- if (IsA(node, TargetEntry))
- {
- TargetEntry *te = (TargetEntry *) node;
-
- if (te->resorigtbl == unknown_target_entry->resorigtbl &&
- te->resno == unknown_target_entry->resno)
- {
- unknown_target_entry->resname = te->resname;
- return false;
- }
- }
- else if (IsA(node, Query))
- {
- Query *query = (Query *) node;
-
- return gpdb::WalkExpressionTree(
- (Node *) query->targetList,
- (bool (*)(Node *, void *))
update_unknown_locale_walker, (void *) context);
- }
-
- return gpdb::WalkExpressionTree(
- node, (bool (*)(Node *, void *)) update_unknown_locale_walker,
(void *) context);
-}
-
//---------------------------------------------------------------------------
// @function:
// CTranslatorDXLToPlStmt::TranslateDXLProjList
@@ -6072,24 +6077,15 @@ CTranslatorDXLToPlStmt::TranslateDXLProjList(
}
target_entry->resorigtbl =
pteOriginal->resorigtbl;
target_entry->resorigcol =
pteOriginal->resorigcol;
-
- // ORCA represents strings using wide
characters. That can
- // require converting from multibyte characters
using
- // vswprintf(). However, vswprintf() is
dependent on the system
- // locale which is set at the database level.
When that locale
- // cannot interpret the string correctly, it
fails. ORCA
- // bypasses the failure by using a generic
"UNKNOWN" string.
- // When that happens, the following code
translates it back to
- // the original multibyte string.
- if (strcmp(target_entry->resname, "UNKNOWN") ==
0)
- {
- update_unknown_locale_walker(
- (Node *)
output_context->GetQuery(),
- (void *) target_entry);
- }
}
}
+ // restore aliases that failed the wide character conversion;
this
+ // must cover not only Vars but also other expressions (e.g.
Consts
+ // and Aggrefs) whose aliases can equally fail the conversion
+ restore_unknown_locale_resname(output_context->GetQuery(),
+
target_entry);
+
// add column mapping to output translation context
output_context->InsertMapping(sc_proj_elem_dxlop->Id(),
target_entry);
diff --git a/src/test/regress/expected/gp_locale.out
b/src/test/regress/expected/gp_locale.out
index 0d916a93d70..fd2a65c01a3 100644
--- a/src/test/regress/expected/gp_locale.out
+++ b/src/test/regress/expected/gp_locale.out
@@ -87,4 +87,61 @@ SELECT * FROM hi_안녕세계 hi_안녕세계1, hi_안녕세계 hi_안녕세계2
1 | 안녕세계1 first UPDATE | 안녕세2 first | 안녕세계3 first | 1 | 안녕세계1 first
UPDATE | 안녕세2 first | 안녕세계3 first
(1 row)
+-- ALIAS ON CONSTANTS, AGGREGATES AND SET OPERATIONS
+-- These project elements are not Vars, so restoring the alias after a failed
+-- wide character conversion must not depend on the Var-origin lookup.
+SELECT '한글' AS "한글";
+ 한글
+------
+ 한글
+(1 row)
+
+SELECT 1+1 AS 안녕세계표현식;
+ 안녕세계표현식
+----------------
+ 2
+(1 row)
+
+SELECT count(*) AS 안녕세계카운트 FROM hi_안녕세계;
+ 안녕세계카운트
+----------------
+ 1
+(1 row)
+
+SELECT sum(a) AS 안녕세계합계, max(a) AS 안녕세계최대 FROM hi_안녕세계;
+ 안녕세계합계 | 안녕세계최대
+--------------+--------------
+ 1 | 1
+(1 row)
+
+SELECT '안녕' AS 안녕세계유니온 UNION ALL SELECT 'x';
+ 안녕세계유니온
+----------------
+ 안녕
+ x
+(2 rows)
+
+-- SET RETURNING FUNCTION (ProjectSet can be the topmost plan node)
+SELECT generate_series(1,2) AS 안녕세계SRF;
+ 안녕세계srf
+-------------
+ 1
+ 2
+(2 rows)
+
+-- The restore must take the name from the top-level target list entry at the
+-- same position, never from a same-position entry inside a subquery.
+SELECT EXISTS(SELECT a, 안녕세계1 FROM hi_안녕세계) AS c, 안녕세계1 AS 안녕세계별칭 FROM hi_안녕세계;
+ c | 안녕세계별칭
+---+------------------------
+ t | 안녕세계1 first UPDATE
+(1 row)
+
+-- A legitimate alias named "UNKNOWN" (no conversion failure) must survive.
+SELECT EXISTS(SELECT a, 안녕세계1 FROM hi_안녕세계) AS c, a AS "UNKNOWN" FROM hi_안녕세계;
+ c | UNKNOWN
+---+---------
+ t | 1
+(1 row)
+
RESET optimizer_trace_fallback;
diff --git a/src/test/regress/sql/gp_locale.sql
b/src/test/regress/sql/gp_locale.sql
index 444352c9edd..2053242105e 100644
--- a/src/test/regress/sql/gp_locale.sql
+++ b/src/test/regress/sql/gp_locale.sql
@@ -58,4 +58,27 @@ WITH cte(안녕세계x, こんにちわx) AS
-- JOIN
SELECT * FROM hi_안녕세계 hi_안녕세계1, hi_안녕세계 hi_안녕세계2 WHERE hi_안녕세계1.안녕세계1 LIKE
'%UPDATE';
+-- ALIAS ON CONSTANTS, AGGREGATES AND SET OPERATIONS
+-- These project elements are not Vars, so restoring the alias after a failed
+-- wide character conversion must not depend on the Var-origin lookup.
+SELECT '한글' AS "한글";
+
+SELECT 1+1 AS 안녕세계표현식;
+
+SELECT count(*) AS 안녕세계카운트 FROM hi_안녕세계;
+
+SELECT sum(a) AS 안녕세계합계, max(a) AS 안녕세계최대 FROM hi_안녕세계;
+
+SELECT '안녕' AS 안녕세계유니온 UNION ALL SELECT 'x';
+
+-- SET RETURNING FUNCTION (ProjectSet can be the topmost plan node)
+SELECT generate_series(1,2) AS 안녕세계SRF;
+
+-- The restore must take the name from the top-level target list entry at the
+-- same position, never from a same-position entry inside a subquery.
+SELECT EXISTS(SELECT a, 안녕세계1 FROM hi_안녕세계) AS c, 안녕세계1 AS 안녕세계별칭 FROM hi_안녕세계;
+
+-- A legitimate alias named "UNKNOWN" (no conversion failure) must survive.
+SELECT EXISTS(SELECT a, 안녕세계1 FROM hi_안녕세계) AS c, a AS "UNKNOWN" FROM hi_안녕세계;
+
RESET optimizer_trace_fallback;
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]