From: Mike Bayer Date: Wed, 2 Jul 2025 13:18:28 +0000 (-0400) Subject: note that SQL parenthesis are based on precedence X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5b5db1f5f35d9eca6f66db73c05a8ce8302d3a4c;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git note that SQL parenthesis are based on precedence References: #12708 Change-Id: I2401e92c936eb01a64ad6896a86faec1c205bc08 --- diff --git a/doc/build/Makefile b/doc/build/Makefile index e9684a2073..325da5046e 100644 --- a/doc/build/Makefile +++ b/doc/build/Makefile @@ -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 diff --git a/doc/build/core/operators.rst b/doc/build/core/operators.rst index 35c25fe75c..7fa163d6e6 100644 --- a/doc/build/core/operators.rst +++ b/doc/build/core/operators.rst @@ -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() diff --git a/doc/build/faq/sqlexpressions.rst b/doc/build/faq/sqlexpressions.rst index 7a03bdb036..e09fda4a27 100644 --- a/doc/build/faq/sqlexpressions.rst +++ b/doc/build/faq/sqlexpressions.rst @@ -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 diff --git a/doc/build/tutorial/data_select.rst b/doc/build/tutorial/data_select.rst index 5052a5bae3..38faddb61a 100644 --- a/doc/build/tutorial/data_select.rst +++ b/doc/build/tutorial/data_select.rst @@ -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