Skip to content

Commit

Permalink
Merge "disable col deduping inside of Bundle" into rel_2_0
Browse files Browse the repository at this point in the history
  • Loading branch information
CaselIT authored and Gerrit Code Review committed May 4, 2024
2 parents 7a3dee5 + 3ade4df commit 4e1c092
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 3 deletions.
13 changes: 13 additions & 0 deletions doc/build/changelog/unreleased_20/11347.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.. change::
:tags: bug, orm
:tickets: 11347

Fixed issue where attribute key names in :class:`_orm.Bundle` would not be
correct when using ORM enabled :class:`_sql.select` vs.
:class:`_orm.Query`, when the statement contained duplicate column names.

.. change::
:tags: bug, typing

Fixed issue in typing for :class:`_orm.Bundle` where creating a nested
:class:`_orm.Bundle` structure were not allowed.
6 changes: 4 additions & 2 deletions lib/sqlalchemy/orm/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ def _column_naming_convention(
) -> _LabelConventionCallable:
if legacy:

def name(col, col_name=None):
def name(col, col_name=None, cancel_dedupe=False):
if col_name:
return col_name
else:
Expand Down Expand Up @@ -3145,7 +3145,9 @@ def __init__(

if is_current_entities:
self._label_name = compile_state._label_convention(
column, col_name=orm_key
column,
col_name=orm_key,
cancel_dedupe=parent_bundle is not None,
)
else:
self._label_name = None
Expand Down
1 change: 1 addition & 0 deletions lib/sqlalchemy/sql/_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ def dialect(self) -> Dialect: ...
_HasClauseElement[_T],
"SQLCoreOperations[_T]",
roles.ExpressionElementRole[_T],
roles.TypedColumnsClauseRole[_T],
Callable[[], "ColumnElement[_T]"],
"LambdaElement",
]
Expand Down
4 changes: 3 additions & 1 deletion lib/sqlalchemy/sql/selectable.py
Original file line number Diff line number Diff line change
Expand Up @@ -4547,6 +4547,7 @@ def _column_naming_convention(
cls, label_style: SelectLabelStyle
) -> _LabelConventionCallable:
table_qualified = label_style is LABEL_STYLE_TABLENAME_PLUS_COL

dedupe = label_style is not LABEL_STYLE_NONE

pa = prefix_anon_map()
Expand All @@ -4555,13 +4556,14 @@ def _column_naming_convention(
def go(
c: Union[ColumnElement[Any], TextClause],
col_name: Optional[str] = None,
cancel_dedupe: bool = False,
) -> Optional[str]:
if is_text_clause(c):
return None
elif TYPE_CHECKING:
assert is_column_element(c)

if not dedupe:
if not dedupe or cancel_dedupe:
name = c._proxy_key
if name is None:
name = "_no_label"
Expand Down
59 changes: 59 additions & 0 deletions test/orm/test_bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,65 @@ def test_c_attr(self):
select(b1.c.d1, b1.c.d2), "SELECT data.d1, data.d2 FROM data"
)

@testing.variation("stmt_type", ["legacy", "newstyle"])
def test_dupe_col_name(self, stmt_type):
"""test #11347"""
Data = self.classes.Data
sess = fixture_session()

b1 = Bundle("b1", Data.d1, Data.d3)

if stmt_type.legacy:
row = (
sess.query(Data.d1, Data.d2, b1)
.filter(Data.d1 == "d0d1")
.one()
)
elif stmt_type.newstyle:
row = sess.execute(
select(Data.d1, Data.d2, b1).filter(Data.d1 == "d0d1")
).one()

eq_(row[2]._mapping, {"d1": "d0d1", "d3": "d0d3"})

@testing.variation("stmt_type", ["legacy", "newstyle"])
def test_dupe_col_name_nested(self, stmt_type):
"""test #11347"""
Data = self.classes.Data
sess = fixture_session()

class DictBundle(Bundle):
def create_row_processor(self, query, procs, labels):
def proc(row):
return dict(zip(labels, (proc(row) for proc in procs)))

return proc

b1 = DictBundle("b1", Data.d1, Data.d3)
b2 = DictBundle("b2", Data.d2, Data.d3)
b3 = DictBundle("b3", Data.d2, Data.d3, b1, b2)

if stmt_type.legacy:
row = (
sess.query(Data.d1, Data.d2, b3)
.filter(Data.d1 == "d0d1")
.one()
)
elif stmt_type.newstyle:
row = sess.execute(
select(Data.d1, Data.d2, b3).filter(Data.d1 == "d0d1")
).one()

eq_(
row[2],
{
"d2": "d0d2",
"d3": "d0d3",
"b1": {"d1": "d0d1", "d3": "d0d3"},
"b2": {"d2": "d0d2", "d3": "d0d3"},
},
)

def test_result(self):
Data = self.classes.Data
sess = fixture_session()
Expand Down
5 changes: 5 additions & 0 deletions test/typing/plain_files/orm/orm_querying.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,8 @@ def test_10937() -> None:
stmt3: ScalarSelect[str] = select(A.data + B.data).scalar_subquery()

select(stmt, stmt2, stmt3, stmt1)


def test_bundles() -> None:
b1 = orm.Bundle("b1", A.id, A.data)
orm.Bundle("b2", A.id, A.data, b1)

0 comments on commit 4e1c092

Please sign in to comment.