From: Mike Bayer Date: Wed, 3 Jul 2024 19:46:30 +0000 (-0400) Subject: add special rule to honor UPPERCASE name for TExtualSelect X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=621116bdc5c26319e63590cb5b467f6e853457d0;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git add special rule to honor UPPERCASE name for TExtualSelect Fixed issue in name normalization (e.g. "uppercase" backends like Oracle) where using a :class:`.TextualSelect` would not properly maintain as uppercase column names that were quoted as uppercase, even though the :class:`.TextualSelect` includes a :class:`.Column` that explicitly holds this uppercase name. Fixes: #10788 Change-Id: I542a2313d22cf13db6760fe02ac659c97b5aa29e --- diff --git a/doc/build/changelog/unreleased_21/10788.rst b/doc/build/changelog/unreleased_21/10788.rst new file mode 100644 index 0000000000..63f6af86e6 --- /dev/null +++ b/doc/build/changelog/unreleased_21/10788.rst @@ -0,0 +1,9 @@ +.. change:: + :tags: bug, sql + :tickets: 10788 + + Fixed issue in name normalization (e.g. "uppercase" backends like Oracle) + where using a :class:`.TextualSelect` would not properly maintain as + uppercase column names that were quoted as uppercase, even though + the :class:`.TextualSelect` includes a :class:`.Column` that explicitly + holds this uppercase name. diff --git a/lib/sqlalchemy/engine/cursor.py b/lib/sqlalchemy/engine/cursor.py index 9ff5cdeb86..8a2a47cb89 100644 --- a/lib/sqlalchemy/engine/cursor.py +++ b/lib/sqlalchemy/engine/cursor.py @@ -676,8 +676,6 @@ class CursorResultMetaData(ResultMetaData): dialect.normalize_name if dialect.requires_name_normalize else None ) - self._keys = [] - untranslated = None for idx, rec in enumerate(cursor_description): @@ -697,14 +695,10 @@ class CursorResultMetaData(ResultMetaData): colname = normalize_name(colname) if driver_column_names: - self._keys.append(unnormalized) - - yield idx, colname, unnormalized, coltype + yield idx, colname, unnormalized, unnormalized, coltype else: - self._keys.append(colname) - - yield idx, colname, untranslated, coltype + yield idx, colname, unnormalized, untranslated, coltype def _merge_textual_cols_by_position( self, context, cursor_description, result_columns, driver_column_names @@ -719,9 +713,13 @@ class CursorResultMetaData(ResultMetaData): ) seen = set() + self._keys = [] + + uses_denormalize = context.dialect.requires_name_normalize for ( idx, colname, + unnormalized, untranslated, coltype, ) in self._colnames_from_description( @@ -738,11 +736,43 @@ class CursorResultMetaData(ResultMetaData): "in textual SQL: %r" % obj[0] ) seen.add(obj[0]) + + # special check for all uppercase unnormalized name; + # use the unnormalized name as the key. + # see #10788 + # if these names don't match, then we still honor the + # cursor.description name as the key and not what the + # Column has, see + # test_resultset.py::PositionalTextTest::test_via_column + if ( + uses_denormalize + and unnormalized == ctx_rec[RM_RENDERED_NAME] + ): + result_name = unnormalized + else: + result_name = colname else: mapped_type = sqltypes.NULLTYPE obj = None ridx = None - yield idx, ridx, colname, mapped_type, coltype, obj, untranslated + + result_name = colname + + if driver_column_names: + assert untranslated is not None + self._keys.append(untranslated) + else: + self._keys.append(result_name) + + yield ( + idx, + ridx, + result_name, + mapped_type, + coltype, + obj, + untranslated, + ) def _merge_cols_by_name( self, @@ -757,9 +787,12 @@ class CursorResultMetaData(ResultMetaData): ) mapped_type: TypeEngine[Any] + self._keys = [] + for ( idx, colname, + unnormalized, untranslated, coltype, ) in self._colnames_from_description( @@ -775,6 +808,12 @@ class CursorResultMetaData(ResultMetaData): obj = ctx_rec[1] mapped_type = ctx_rec[2] result_columns_idx = ctx_rec[3] + + if driver_column_names: + assert untranslated is not None + self._keys.append(untranslated) + else: + self._keys.append(colname) yield ( idx, result_columns_idx, @@ -831,14 +870,24 @@ class CursorResultMetaData(ResultMetaData): def _merge_cols_by_none( self, context, cursor_description, driver_column_names ): + self._keys = [] + for ( idx, colname, + unnormalized, untranslated, coltype, ) in self._colnames_from_description( context, cursor_description, driver_column_names ): + + if driver_column_names: + assert untranslated is not None + self._keys.append(untranslated) + else: + self._keys.append(colname) + yield ( idx, None, diff --git a/lib/sqlalchemy/testing/suite/test_results.py b/lib/sqlalchemy/testing/suite/test_results.py index 05e35d0ebf..639a5d056b 100644 --- a/lib/sqlalchemy/testing/suite/test_results.py +++ b/lib/sqlalchemy/testing/suite/test_results.py @@ -228,7 +228,7 @@ class NameDenormalizeTest(fixtures.TablesTest): }, ) else: - if stmt_type.core_select: + if stmt_type.core_select or stmt_type.text_cols: self._assert_row_mapping( row, { @@ -252,11 +252,7 @@ class NameDenormalizeTest(fixtures.TablesTest): "all_lowercase_quoted": 8, "all_uppercase_quoted": 9, }, - include_cols=( - self.tables.denormalize_table.c - if stmt_type.text_cols - else None - ), + include_cols=None, ) else: