From: Mike Bayer Date: Tue, 28 Jul 2020 20:46:53 +0000 (-0400) Subject: Consider default FROM DUAL for MySQL X-Git-Tag: rel_1_3_19~17^2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=e41998981c0d1d539091bfd8a30c1f7132237a3d;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Consider default FROM DUAL for MySQL MySQL claims it doesn't require FROM DUAL for no FROM clause even though the issue at #5481 locates a case which requires one. See if FROM DUAL the same way as Oracle without attempting to guess is potentially feasible. Fixes: #5481 Change-Id: I2a28876c10a8ce2d121cd344dcdd837db321d4ab (cherry picked from commit 59b65b3008de5d6c5e4318846d04e50e5851e957) --- diff --git a/doc/build/changelog/unreleased_13/5481.rst b/doc/build/changelog/unreleased_13/5481.rst new file mode 100644 index 0000000000..2ebac73bf7 --- /dev/null +++ b/doc/build/changelog/unreleased_13/5481.rst @@ -0,0 +1,8 @@ +.. change:: + :tags: usecase, mysql + :tickets: 5481 + + The MySQL dialect will render FROM DUAL for a SELECT statement that has no + FROM clause but has a WHERE clause. This allows things like "SELECT 1 WHERE + EXISTS (subuqery)" kinds of queries to be used as well as other use cases. + diff --git a/lib/sqlalchemy/dialects/mysql/base.py b/lib/sqlalchemy/dialects/mysql/base.py index d84e3770f4..87d35587aa 100644 --- a/lib/sqlalchemy/dialects/mysql/base.py +++ b/lib/sqlalchemy/dialects/mysql/base.py @@ -1303,6 +1303,18 @@ class MySQLCompiler(compiler.SQLCompiler): extract_map = compiler.SQLCompiler.extract_map.copy() extract_map.update({"milliseconds": "millisecond"}) + def default_from(self): + """Called when a ``SELECT`` statement has no froms, + and no ``FROM`` clause is to be appended. + + """ + if self.stack: + stmt = self.stack[-1]["selectable"] + if stmt._whereclause is not None: + return " FROM DUAL" + + return "" + def visit_random_func(self, fn, **kw): return "rand%s" % self.function_argspec(fn) diff --git a/lib/sqlalchemy/testing/suite/test_select.py b/lib/sqlalchemy/testing/suite/test_select.py index d29449a519..23e66a3fae 100644 --- a/lib/sqlalchemy/testing/suite/test_select.py +++ b/lib/sqlalchemy/testing/suite/test_select.py @@ -7,9 +7,11 @@ from ..schema import Table from ... import bindparam from ... import case from ... import Computed +from ... import exists from ... import false from ... import func from ... import Integer +from ... import literal from ... import literal_column from ... import null from ... import select @@ -703,6 +705,53 @@ class ComputedColumnTest(fixtures.TablesTest): eq_(res, [(100, 40), (1764, 168)]) +class ExistsTest(fixtures.TablesTest): + __backend__ = True + + @classmethod + def define_tables(cls, metadata): + Table( + "stuff", + metadata, + Column("id", Integer, primary_key=True), + Column("data", String(50)), + ) + + @classmethod + def insert_data(cls, connection): + connection.execute( + cls.tables.stuff.insert(), + [ + {"id": 1, "data": "some data"}, + {"id": 2, "data": "some data"}, + {"id": 3, "data": "some data"}, + {"id": 4, "data": "some other data"}, + ], + ) + + def test_select_exists(self, connection): + stuff = self.tables.stuff + eq_( + connection.execute( + select([literal(1)]).where( + exists().where(stuff.c.data == "some data") + ) + ).fetchall(), + [(1,)], + ) + + def test_select_exists_false(self, connection): + stuff = self.tables.stuff + eq_( + connection.execute( + select([literal(1)]).where( + exists().where(stuff.c.data == "no data") + ) + ).fetchall(), + [], + ) + + class IsOrIsNotDistinctFromTest(fixtures.TablesTest): __backend__ = True __requires__ = ("supports_is_distinct_from",)