From 04dcc5c704dbf0b22705523e263e512c24936175 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 23 Mar 2022 10:07:13 -0400 Subject: Add option to disable from linting for table valued function Added new parameter :paramref:`.FunctionElement.table_valued.joins_implicitly`, for the :meth:`.FunctionElement.table_valued` construct. This parameter indicates that the given table-valued function implicitly joins to the table it refers towards, essentially disabling the "from linting" feature, i.e. the "cartesian product" warning, from taking effect due to the presence of this parameter. May be used for functions such as ``func.json_each()``. Fixes: #7845 Change-Id: I80edcb74efbd4417172132c0db4d9c756fdd5eae --- lib/sqlalchemy/sql/compiler.py | 2 ++ lib/sqlalchemy/sql/functions.py | 29 ++++++++++++++++++++++++++--- lib/sqlalchemy/sql/selectable.py | 10 +++++++++- 3 files changed, 37 insertions(+), 4 deletions(-) (limited to 'lib/sqlalchemy') diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index f8019b9c6..d3e91a8d5 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -3173,6 +3173,8 @@ class SQLCompiler(Compiled): return None def visit_table_valued_alias(self, element, **kw): + if element.joins_implicitly: + kw["from_linter"] = None if element._is_lateral: return self.visit_lateral(element, **kw) else: diff --git a/lib/sqlalchemy/sql/functions.py b/lib/sqlalchemy/sql/functions.py index 563b58418..9e801a99f 100644 --- a/lib/sqlalchemy/sql/functions.py +++ b/lib/sqlalchemy/sql/functions.py @@ -226,8 +226,16 @@ class FunctionElement(Executable, ColumnElement[_T], FromClause, Generative): string name will be added as a column to the .c collection of the resulting :class:`_sql.TableValuedAlias`. + :param joins_implicitly: when True, the table valued function may be + used in the FROM clause without any explicit JOIN to other tables + in the SQL query, and no "cartesian product" warning will be generated. + May be useful for SQL functions such as ``func.json_each()``. + + .. versionadded:: 1.4.33 + .. versionadded:: 1.4.0b2 + .. seealso:: :ref:`tutorial_functions_table_valued` - in the :ref:`unified_tutorial` @@ -248,6 +256,7 @@ class FunctionElement(Executable, ColumnElement[_T], FromClause, Generative): new_func = self._generate() with_ordinality = kw.pop("with_ordinality", None) + joins_implicitly = kw.pop("joins_implicitly", None) name = kw.pop("name", None) if with_ordinality: @@ -258,7 +267,7 @@ class FunctionElement(Executable, ColumnElement[_T], FromClause, Generative): *expr ) - return new_func.alias(name=name) + return new_func.alias(name=name, joins_implicitly=joins_implicitly) def column_valued(self, name=None): """Return this :class:`_functions.FunctionElement` as a column expression that @@ -511,7 +520,7 @@ class FunctionElement(Executable, ColumnElement[_T], FromClause, Generative): return None - def alias(self, name=None): + def alias(self, name=None, joins_implicitly=False): r"""Produce a :class:`_expression.Alias` construct against this :class:`.FunctionElement`. @@ -553,6 +562,17 @@ class FunctionElement(Executable, ColumnElement[_T], FromClause, Generative): .. versionadded:: 1.4.0b2 Added the ``.column`` accessor + :param name: alias name, will be rendered as ``AS `` in the + FROM clause + + :param joins_implicitly: when True, the table valued function may be + used in the FROM clause without any explicit JOIN to other tables + in the SQL query, and no "cartesian product" warning will be + generated. May be useful for SQL functions such as + ``func.json_each()``. + + .. versionadded:: 1.4.33 + .. seealso:: :ref:`tutorial_functions_table_valued` - @@ -568,7 +588,10 @@ class FunctionElement(Executable, ColumnElement[_T], FromClause, Generative): """ return TableValuedAlias._construct( - self, name, table_value_type=self.type + self, + name, + table_value_type=self.type, + joins_implicitly=joins_implicitly, ) def select(self) -> "Select": diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py index e143d1476..80433a283 100644 --- a/lib/sqlalchemy/sql/selectable.py +++ b/lib/sqlalchemy/sql/selectable.py @@ -1477,6 +1477,7 @@ class TableValuedAlias(Alias): _supports_derived_columns = True _render_derived = False _render_derived_w_types = False + joins_implicitly = False _traverse_internals = [ ("element", InternalTraversal.dp_clauseelement), @@ -1486,9 +1487,16 @@ class TableValuedAlias(Alias): ("_render_derived_w_types", InternalTraversal.dp_boolean), ] - def _init(self, selectable, name=None, table_value_type=None): + def _init( + self, + selectable, + name=None, + table_value_type=None, + joins_implicitly=False, + ): super(TableValuedAlias, self)._init(selectable, name=name) + self.joins_implicitly = joins_implicitly self._tableval_type = ( type_api.TABLEVALUE if table_value_type is None -- cgit v1.2.1