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]

Reply via email to