diff options
| author | Alessio Bogon <youtux@gmail.com> | 2019-09-15 11:12:24 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2020-01-22 11:31:23 -0500 |
| commit | 3809a5ecfe785cecbc9d91a8e4e4558e3839c694 (patch) | |
| tree | dbde348bd6673de8acc685f82884f78f6b9f8f67 /lib/sqlalchemy/engine | |
| parent | d3ad35838ede0713d073bbbf78d1bca511806059 (diff) | |
| download | sqlalchemy-3809a5ecfe785cecbc9d91a8e4e4558e3839c694.tar.gz | |
Query linter option
Added "from linting" as a built-in feature to the SQL compiler. This
allows the compiler to maintain graph of all the FROM clauses in a
particular SELECT statement, linked by criteria in either the WHERE
or in JOIN clauses that link these FROM clauses together. If any two
FROM clauses have no path between them, a warning is emitted that the
query may be producing a cartesian product. As the Core expression
language as well as the ORM are built on an "implicit FROMs" model where
a particular FROM clause is automatically added if any part of the query
refers to it, it is easy for this to happen inadvertently and it is
hoped that the new feature helps with this issue.
The original recipe is from:
https://github.com/sqlalchemy/sqlalchemy/wiki/FromLinter
The linter is now enabled for all tests in the test suite as well.
This has necessitated that a lot of the queries be adjusted to
not include cartesian products. Part of the rationale for the
linter to not be enabled for statement compilation only was to reduce
the need for adjustment for the many test case statements throughout
the test suite that are not real-world statements.
This gerrit is adapted from Ib5946e57c9dba6da428c4d1dee6760b3e978dda0.
Fixes: #4737
Change-Id: Ic91fd9774379f895d021c3ad564db6062299211c
Closes: #4830
Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/4830
Pull-request-sha: f8a21aa6262d1bcc9ff0d11a2616e41fba97a47a
Diffstat (limited to 'lib/sqlalchemy/engine')
| -rw-r--r-- | lib/sqlalchemy/engine/base.py | 4 | ||||
| -rw-r--r-- | lib/sqlalchemy/engine/create.py | 16 | ||||
| -rw-r--r-- | lib/sqlalchemy/engine/default.py | 6 |
3 files changed, 24 insertions, 2 deletions
diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py index 88558df5d..462e5f9ec 100644 --- a/lib/sqlalchemy/engine/base.py +++ b/lib/sqlalchemy/engine/base.py @@ -16,6 +16,7 @@ from .. import exc from .. import inspection from .. import log from .. import util +from ..sql import compiler from ..sql import schema from ..sql import util as sql_util @@ -1083,6 +1084,8 @@ class Connection(Connectable): schema_translate_map=self.schema_for_object if not self.schema_for_object.is_default else None, + linting=self.dialect.compiler_linting + | compiler.WARN_LINTING, ) self._execution_options["compiled_cache"][key] = compiled_sql else: @@ -1093,6 +1096,7 @@ class Connection(Connectable): schema_translate_map=self.schema_for_object if not self.schema_for_object.is_default else None, + linting=self.dialect.compiler_linting | compiler.WARN_LINTING, ) ret = self._execute_context( diff --git a/lib/sqlalchemy/engine/create.py b/lib/sqlalchemy/engine/create.py index 58fe91c7e..5198c8cd6 100644 --- a/lib/sqlalchemy/engine/create.py +++ b/lib/sqlalchemy/engine/create.py @@ -13,6 +13,7 @@ from .. import event from .. import exc from .. import pool as poollib from .. import util +from ..sql import compiler @util.deprecated_params( @@ -142,6 +143,16 @@ def create_engine(url, **kwargs): :param empty_in_strategy: No longer used; SQLAlchemy now uses "empty set" behavior for IN in all cases. + :param enable_from_linting: defaults to True. Will emit a warning + if a given SELECT statement is found to have un-linked FROM elements + which would cause a cartesian product. + + .. versionadded:: 1.4 + + .. seealso:: + + :ref:`change_4737` + :param encoding: Defaults to ``utf-8``. This is the string encoding used by SQLAlchemy for string encode/decode operations which occur within SQLAlchemy, **outside of @@ -446,6 +457,11 @@ def create_engine(url, **kwargs): dialect_args["dbapi"] = dbapi + dialect_args.setdefault("compiler_linting", compiler.NO_LINTING) + enable_from_linting = kwargs.pop("enable_from_linting", True) + if enable_from_linting: + dialect_args["compiler_linting"] ^= compiler.COLLECT_CARTESIAN_PRODUCTS + for plugin in plugins: plugin.handle_dialect_kwargs(dialect_cls, dialect_args) diff --git a/lib/sqlalchemy/engine/default.py b/lib/sqlalchemy/engine/default.py index 1c995f05f..378890444 100644 --- a/lib/sqlalchemy/engine/default.py +++ b/lib/sqlalchemy/engine/default.py @@ -31,7 +31,6 @@ from ..sql import expression from ..sql import schema from ..sql.elements import quoted_name - AUTOCOMMIT_REGEXP = re.compile( r"\s*(?:UPDATE|INSERT|CREATE|DELETE|DROP|ALTER)", re.I | re.UNICODE ) @@ -214,6 +213,9 @@ class DefaultDialect(interfaces.Dialect): supports_native_boolean=None, max_identifier_length=None, label_length=None, + # int() is because the @deprecated_params decorator cannot accommodate + # the direct reference to the "NO_LINTING" object + compiler_linting=int(compiler.NO_LINTING), **kwargs ): @@ -249,7 +251,7 @@ class DefaultDialect(interfaces.Dialect): self._user_defined_max_identifier_length ) self.label_length = label_length - + self.compiler_linting = compiler_linting if self.description_encoding == "use_encoding": self._description_decoder = ( processors.to_unicode_processor_factory |
