summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2022-04-03 11:28:57 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2022-04-03 11:30:09 -0400
commitc315c7401a2aa00a8a0fa0f7d4189a9976fd7962 (patch)
tree0a4b5c4195d15d1e7294cc39589060660f1d5930
parent1dffb7cedeb009ca6c532db558bd0588dd846957 (diff)
downloadsqlalchemy-c315c7401a2aa00a8a0fa0f7d4189a9976fd7962.tar.gz
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
-rw-r--r--doc/build/changelog/unreleased_14/7890.rst22
-rw-r--r--lib/sqlalchemy/sql/selectable.py19
-rw-r--r--test/aaa_profiling/test_memusage.py13
-rw-r--r--test/sql/test_from_linter.py22
-rw-r--r--test/sql/test_functions.py26
5 files changed, 95 insertions, 7 deletions
diff --git a/doc/build/changelog/unreleased_14/7890.rst b/doc/build/changelog/unreleased_14/7890.rst
new file mode 100644
index 000000000..94a29abde
--- /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 24edc1cae..4f6e3795e 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 f084eac2c..24cc0b99b 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 4a4d907f9..1fa3aff36 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 e08526419..c055bc150 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;