From: Mike Bayer Date: Sun, 3 Apr 2022 15:28:57 +0000 (-0400) Subject: TableValuedAlias generation fixes X-Git-Tag: rel_2_0_0b1~378 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c315c7401a2aa00a8a0fa0f7d4189a9976fd7962;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git TableValuedAlias generation fixes Fixed bug in newly implemented :paramref:`.FunctionElement.table_valued.joins_implicitly` feature where the parameter would not automatically propagate from the original :class:`.TableValuedAlias` object to the secondary object produced when calling upon :meth:`.TableValuedAlias.render_derived` or :meth:`.TableValuedAlias.alias`. Additionally repaired these issues in :class:`.TableValuedAlias`: * repaired a potential memory issue which could occur when repeatedly calling :meth:`.TableValuedAlias.render_derived` against successive copies of the same object (for .alias(), we currently have to still continue chaining from the previous element. not sure if this can be improved but this is standard behavior for .alias() elsewhere) * repaired issue where the individual element types would be lost when calling upon :meth:`.TableValuedAlias.render_derived` or :meth:`.TableValuedAlias.alias`. Fixes: #7890 Change-Id: Ie5120c7ff1e5c1bba5aaf77c782a51c637860208 --- diff --git a/doc/build/changelog/unreleased_14/7890.rst b/doc/build/changelog/unreleased_14/7890.rst new file mode 100644 index 0000000000..94a29abde5 --- /dev/null +++ b/doc/build/changelog/unreleased_14/7890.rst @@ -0,0 +1,22 @@ +.. change:: + :tags: bug, sql + :tickets: 7890 + + Fixed bug in newly implemented + :paramref:`.FunctionElement.table_valued.joins_implicitly` feature where + the parameter would not automatically propagate from the original + :class:`.TableValuedAlias` object to the secondary object produced when + calling upon :meth:`.TableValuedAlias.render_derived` or + :meth:`.TableValuedAlias.alias`. + + Additionally repaired these issues in :class:`.TableValuedAlias`: + + * repaired a potential memory issue which could occur when + repeatedly calling :meth:`.TableValuedAlias.render_derived` against + successive copies of the same object (for .alias(), we currently + have to still continue chaining from the previous element. not sure + if this can be improved but this is standard behavior for .alias() + elsewhere) + * repaired issue where the individual element types would be lost when + calling upon :meth:`.TableValuedAlias.render_derived` or + :meth:`.TableValuedAlias.alias`. diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py index 24edc1caef..4f6e3795e1 100644 --- a/lib/sqlalchemy/sql/selectable.py +++ b/lib/sqlalchemy/sql/selectable.py @@ -1534,10 +1534,17 @@ class TableValuedAlias(Alias): """ - tva = TableValuedAlias._construct(self, name=name) + tva = TableValuedAlias._construct( + self, + name=name, + table_value_type=self._tableval_type, + joins_implicitly=self.joins_implicitly, + ) + if self._render_derived: tva._render_derived = True tva._render_derived_w_types = self._render_derived_w_types + return tva def lateral(self, name=None): @@ -1598,7 +1605,15 @@ class TableValuedAlias(Alias): # python id() of the original which can cause name conflicts if # a new anon-name grabs the same identifier as the local anon-name # (just saw it happen on CI) - new_alias = TableValuedAlias._construct(self, name=name) + + # construct against original to prevent memory growth + # for repeated generations + new_alias = TableValuedAlias._construct( + self.element, + name=name, + table_value_type=self._tableval_type, + joins_implicitly=self.joins_implicitly, + ) new_alias._render_derived = True new_alias._render_derived_w_types = with_types return new_alias diff --git a/test/aaa_profiling/test_memusage.py b/test/aaa_profiling/test_memusage.py index f084eac2c0..24cc0b99b4 100644 --- a/test/aaa_profiling/test_memusage.py +++ b/test/aaa_profiling/test_memusage.py @@ -6,6 +6,7 @@ import weakref import sqlalchemy as sa from sqlalchemy import ForeignKey +from sqlalchemy import func from sqlalchemy import inspect from sqlalchemy import Integer from sqlalchemy import MetaData @@ -359,6 +360,18 @@ class MemUsageTest(EnsureZeroed): go() + def test_tv_render_derived(self): + root_expr = func.some_fn().table_valued() + expr = root_expr + + @profile_memory() + def go(): + nonlocal expr + + expr = expr.render_derived() + + go() + @testing.add_to_marker.memory_intensive class MemUsageWBackendTest(fixtures.MappedTest, EnsureZeroed): diff --git a/test/sql/test_from_linter.py b/test/sql/test_from_linter.py index 4a4d907f96..1fa3aff360 100644 --- a/test/sql/test_from_linter.py +++ b/test/sql/test_from_linter.py @@ -165,8 +165,15 @@ class TestFindUnmatchingFroms(fixtures.TablesTest): assert start is p3 assert froms == {p1} + @testing.combinations( + "render_derived", "alias", None, argnames="additional_transformation" + ) @testing.combinations(True, False, argnames="joins_implicitly") - def test_table_valued(self, joins_implicitly): + def test_table_valued( + self, + joins_implicitly, + additional_transformation, + ): """test #7845""" my_table = table( "tbl", @@ -175,9 +182,16 @@ class TestFindUnmatchingFroms(fixtures.TablesTest): ) sub_dict = my_table.c.data["d"] - tv = func.json_each(sub_dict).table_valued( - "key", joins_implicitly=joins_implicitly - ) + + tv = func.json_each(sub_dict) + + tv = tv.table_valued("key", joins_implicitly=joins_implicitly) + + if additional_transformation == "render_derived": + tv = tv.render_derived(name="tv", with_types=True) + elif additional_transformation == "alias": + tv = tv.alias() + has_key = tv.c.key == "f" stmt = select(my_table.c.id).where(has_key) froms, start = find_unmatching_froms(stmt, my_table) diff --git a/test/sql/test_functions.py b/test/sql/test_functions.py index e085264193..c055bc1507 100644 --- a/test/sql/test_functions.py +++ b/test/sql/test_functions.py @@ -26,7 +26,6 @@ from sqlalchemy import Table from sqlalchemy import testing from sqlalchemy import Text from sqlalchemy import true -from sqlalchemy import types as sqltypes from sqlalchemy.dialects import mysql from sqlalchemy.dialects import oracle from sqlalchemy.dialects import postgresql @@ -37,6 +36,7 @@ from sqlalchemy.sql import functions from sqlalchemy.sql import LABEL_STYLE_TABLENAME_PLUS_COL from sqlalchemy.sql import operators from sqlalchemy.sql import quoted_name +from sqlalchemy.sql import sqltypes from sqlalchemy.sql import table from sqlalchemy.sql.compiler import BIND_TEMPLATES from sqlalchemy.sql.functions import FunctionElement @@ -1425,6 +1425,30 @@ class TableValuedCompileTest(fixtures.TestBase, AssertsCompiledSQL): "LEFT OUTER JOIN b ON unnested.unnested = b.ref", ) + def test_render_derived_maintains_tableval_type(self): + fn = func.json_something() + + tv = fn.table_valued(column("x", String)) + + eq_(tv.column.type, testing.eq_type_affinity(sqltypes.TableValueType)) + eq_(tv.column.type._elements[0].type, testing.eq_type_affinity(String)) + + tv = tv.render_derived() + eq_(tv.column.type, testing.eq_type_affinity(sqltypes.TableValueType)) + eq_(tv.column.type._elements[0].type, testing.eq_type_affinity(String)) + + def test_alias_maintains_tableval_type(self): + fn = func.json_something() + + tv = fn.table_valued(column("x", String)) + + eq_(tv.column.type, testing.eq_type_affinity(sqltypes.TableValueType)) + eq_(tv.column.type._elements[0].type, testing.eq_type_affinity(String)) + + tv = tv.alias() + eq_(tv.column.type, testing.eq_type_affinity(sqltypes.TableValueType)) + eq_(tv.column.type._elements[0].type, testing.eq_type_affinity(String)) + def test_star_with_ordinality(self): """ SELECT * FROM generate_series(4,1,-1) WITH ORDINALITY;