Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 58 additions & 62 deletions src/backend/gpopt/translate/CTranslatorDXLToPlStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
{
Expand Down Expand Up @@ -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++;
}
}
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);

Expand Down
57 changes: 57 additions & 0 deletions src/test/regress/expected/gp_locale.out
Original file line number Diff line number Diff line change
Expand Up @@ -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;
23 changes: 23 additions & 0 deletions src/test/regress/sql/gp_locale.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Loading