]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
note that SQL parenthesis are based on precedence
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 2 Jul 2025 13:18:28 +0000 (09:18 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 2 Jul 2025 13:18:28 +0000 (09:18 -0400)
References: #12708
Change-Id: I2401e92c936eb01a64ad6896a86faec1c205bc08

doc/build/Makefile
doc/build/core/operators.rst
doc/build/faq/sqlexpressions.rst
doc/build/tutorial/data_select.rst

index e9684a20738b01521ec235ffea0c5175ed6232d9..325da5046e665f148c6818f308fcea9111433e22 100644 (file)
@@ -14,6 +14,7 @@ PAPEROPT_letter = -D latex_paper_size=letter
 ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
 # the i18n builder cannot share the environment and doctrees with the others
 I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+AUTOBUILDSPHINXOPTS = -T .
 
 .PHONY: help clean html autobuild dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest dist-html site-mako gettext
 
@@ -48,7 +49,7 @@ html:
        @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
 
 autobuild:
-       $(AUTOBUILD) $(ALLSPHINXOPTS) $(BUILDDIR)/html
+       $(AUTOBUILD) $(AUTOBUILDSPHINXOPTS) $(BUILDDIR)/html
 
 gettext:
        $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
index 35c25fe75c3b199eed3f37fa869befc61f0e695b..7fa163d6e687bad28f897f343e819bfb1971788b 100644 (file)
@@ -757,6 +757,49 @@ The above conjunction functions :func:`_sql.and_`, :func:`_sql.or_`,
 
   ..
 
+.. _operators_parentheses:
+
+Parentheses and Grouping
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Parenthesization of expressions is rendered based on operator precedence,
+not the placement of parentheses in Python code, since there is no means of
+detecting parentheses from interpreted Python expressions.  So an expression
+like::
+
+    >>> expr = or_(
+    ...     User.name == "squidward", and_(Address.user_id == User.id, User.name == "sandy")
+    ... )
+
+won't include parentheses, because the AND operator takes natural precedence over OR::
+
+    >>> print(expr)
+    user_account.name = :name_1 OR address.user_id = user_account.id AND user_account.name = :name_2
+
+Whereas this one, where OR would otherwise not be evaluated before the AND, does::
+
+    >>> expr = and_(
+    ...     Address.user_id == User.id, or_(User.name == "squidward", User.name == "sandy")
+    ... )
+    >>> print(expr)
+    address.user_id = user_account.id AND (user_account.name = :name_1 OR user_account.name = :name_2)
+
+The same behavior takes effect for math operators.  In the parenthesized
+Python expression below, the multiplication operator naturally takes precedence over
+the addition operator, therefore the SQL will not include parentheses::
+
+  >>> print(column("q") + (column("x") * column("y")))
+  {printsql}q + x * y{stop}
+
+Whereas this one, where the addition operator would not otherwise occur before
+the multiplication operator, does get parentheses::
+
+  >>> print(column("q") * (column("x") + column("y")))
+  {printsql}q * (x + y){stop}
+
+More background on this is in the FAQ at :ref:`faq_sql_expression_paren_rules`.
+
+
 ..  Setup code, not for display
 
     >>> conn.close()
index 7a03bdb0362967d360f0ef3cf30c9db41f777cc9..e09fda4a2722c7600a8572f184de5d9a62410d3f 100644 (file)
@@ -486,6 +486,8 @@ an expression that has left/right operands and an operator) using the
     >>> print((column("q1") + column("q2")).self_group().op("->")(column("p")))
     {printsql}(q1 + q2) -> p
 
+.. _faq_sql_expression_paren_rules:
+
 Why are the parentheses rules like this?
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
@@ -555,3 +557,6 @@ Perhaps this change can be made at some point, however for the time being
 keeping the parenthesization rules more internally consistent seems to be
 the safer approach.
 
+.. seealso::
+
+    :ref:`operators_parentheses` - in the Operator Reference
index 5052a5bae3282961855cd7b27e8a2b06ab7afc0d..38faddb61a1c96aaf87ad80ee0f17ec91f8674f3 100644 (file)
@@ -392,6 +392,27 @@ of ORM entities::
     WHERE (user_account.name = :name_1 OR user_account.name = :name_2)
     AND address.user_id = user_account.id
 
+.. tip::
+
+    The rendering of parentheses is based on operator precedence rules (there's no
+    way to detect parentheses from a Python expression at runtime), so if we combine
+    AND and OR in a way that matches the natural precedence of AND, the rendered
+    expression might not have similar looking parentheses as our Python code::
+
+        >>> print(
+        ...     select(Address.email_address).where(
+        ...         or_(
+        ...             User.name == "squidward",
+        ...             and_(Address.user_id == User.id, User.name == "sandy"),
+        ...         )
+        ...     )
+        ... )
+        {printsql}SELECT address.email_address
+        FROM address, user_account
+        WHERE user_account.name = :name_1 OR address.user_id = user_account.id AND user_account.name = :name_2
+
+    More background on parenthesization is in the :ref:`operators_parentheses` in the Operator Reference.
+
 For simple "equality" comparisons against a single entity, there's also a
 popular method known as :meth:`_sql.Select.filter_by` which accepts keyword
 arguments that match to column keys or ORM attribute names.  It will filter