]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Support DEFAULT VALUES and VALUES(DEFAULT) individually
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 13 Apr 2021 14:52:00 +0000 (10:52 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 14 Apr 2021 16:51:58 +0000 (12:51 -0400)
Fixed regression where the introduction of the INSERT syntax "INSERT...
VALUES (DEFAULT)" was not supported on some backends that do however
support "INSERT..DEFAULT VALUES", including SQLite. The two syntaxes are
now each individually supported or non-supported for each dialect, for
example MySQL supports "VALUES (DEFAULT)" but not "DEFAULT VALUES".
Support for Oracle is still not enabled as there are unresolved issues
in using RETURNING at the same time.

Fixes: #6254
Change-Id: I47959bc826e3d9d2396ccfa290eb084841b02e77

17 files changed:
doc/build/changelog/unreleased_14/6254.rst [new file with mode: 0644]
lib/sqlalchemy/dialects/mysql/base.py
lib/sqlalchemy/dialects/oracle/base.py
lib/sqlalchemy/dialects/postgresql/base.py
lib/sqlalchemy/dialects/sqlite/base.py
lib/sqlalchemy/engine/default.py
lib/sqlalchemy/sql/compiler.py
lib/sqlalchemy/sql/crud.py
lib/sqlalchemy/testing/assertions.py
lib/sqlalchemy/testing/assertsql.py
lib/sqlalchemy/testing/requirements.py
lib/sqlalchemy/testing/suite/test_insert.py
test/engine/test_deprecations.py
test/orm/inheritance/test_basic.py
test/requirements.py
test/sql/test_defaults.py
test/sql/test_insert.py

diff --git a/doc/build/changelog/unreleased_14/6254.rst b/doc/build/changelog/unreleased_14/6254.rst
new file mode 100644 (file)
index 0000000..8cdfc1b
--- /dev/null
@@ -0,0 +1,10 @@
+.. change::
+    :tags: bug, regression, sql, sqlite
+    :tickets: 6254
+
+    Fixed regression where the introduction of the INSERT syntax "INSERT...
+    VALUES (DEFAULT)" was not supported on some backends that do however
+    support "INSERT..DEFAULT VALUES", including SQLite. The two syntaxes are
+    now each individually supported or non-supported for each dialect, for
+    example MySQL supports "VALUES (DEFAULT)" but not "DEFAULT VALUES".
+    Support for Oracle has also been enabled.
index 3966126e2d670750c910e452fce185d1d84f5484..b576fa95903b3404f990e6f44ab07d068c404a67 100644 (file)
@@ -2534,6 +2534,11 @@ class MySQLDialect(default.DefaultDialect):
     supports_for_update_of = False  # default for MySQL ...
     # ... may be updated to True for MySQL 8+ in initialize()
 
+    # MySQL doesn't support "DEFAULT VALUES" but *does* support
+    # "VALUES (DEFAULT)"
+    supports_default_values = False
+    supports_default_metavalue = True
+
     supports_sane_rowcount = True
     supports_sane_multi_rowcount = False
     supports_multivalues_insert = True
index 11ad61675e5aec08c4dc4ab74742d9f3b53e668c..c25cfb781797509230e906d9e052eb2f0874b2cf 100644 (file)
@@ -1453,7 +1453,11 @@ class OracleDialect(default.DefaultDialect):
     requires_name_normalize = True
 
     supports_comments = True
+
+    # Oracle supports these syntaxes but I'm not able to get them
+    # to work with RETURNING which we usually need
     supports_default_values = False
+    supports_default_metavalue = True
     supports_empty_insert = False
 
     statement_compiler = OracleCompiler
index d0915a0c9752736150c84c686af886f8db75c652..c6f71c00cdcde8079ebda30f4c8e9196e2f4e41a 100644 (file)
@@ -3079,6 +3079,9 @@ class PGDialect(default.DefaultDialect):
 
     supports_comments = True
     supports_default_values = True
+
+    supports_default_metavalue = True
+
     supports_empty_insert = False
     supports_multivalues_insert = True
     default_paramstyle = "pyformat"
index 83c2a8ea755f6d3d7560174ce1b2bda8f361d173..17cd37b49c5e7d9736f00c699a8f7162159111e5 100644 (file)
@@ -1791,7 +1791,12 @@ class SQLiteDialect(default.DefaultDialect):
     supports_alter = False
     supports_unicode_statements = True
     supports_unicode_binds = True
+
+    # SQlite supports "DEFAULT VALUES" but *does not* support
+    # "VALUES (DEFAULT)"
     supports_default_values = True
+    supports_default_metavalue = False
+
     supports_empty_insert = False
     supports_cast = True
     supports_multivalues_insert = True
index d45b6d7a7efb4ed0ed8f156c608662a9b79caa79..0bd01c5880da77f61b27b11d9dcbec3bf0843455 100644 (file)
@@ -140,8 +140,18 @@ class DefaultDialect(interfaces.Dialect):
     supports_sane_multi_rowcount = True
     colspecs = {}
     default_paramstyle = "named"
+
     supports_default_values = False
+    """dialect supports INSERT... DEFAULT VALUES syntax"""
+
+    supports_default_metavalue = False
+    """dialect supports INSERT... VALUES (DEFAULT) syntax"""
+
+    # not sure if this is a real thing but the compiler will deliver it
+    # if this is the only flag enabled.
     supports_empty_insert = True
+    """dialect supports INSERT () VALUES ()"""
+
     supports_multivalues_insert = False
 
     supports_is_distinct_from = True
index 8eefa10d178806a9158beffc61ead614fef6d510..0c701cb52336195b5dcae41f041915426e933a86 100644 (file)
@@ -3480,6 +3480,7 @@ class SQLCompiler(Compiled):
         if (
             not crud_params
             and not self.dialect.supports_default_values
+            and not self.dialect.supports_default_metavalue
             and not self.dialect.supports_empty_insert
         ):
             raise exc.CompileError(
index 174a1c1316952a1d680779f297bab1330c1dc209..5fa82bcd09c9e0e87f81cf17e69a0e5c731864be 100644 (file)
@@ -174,7 +174,11 @@ def _get_crud_params(compiler, stmt, compile_state, **kw):
         values = _extend_values_for_multiparams(
             compiler, stmt, compile_state, values, kw
         )
-    elif not values and compiler.for_executemany:
+    elif (
+        not values
+        and compiler.for_executemany
+        and compiler.dialect.supports_default_metavalue
+    ):
         # convert an "INSERT DEFAULT VALUES"
         # into INSERT (firstcol) VALUES (DEFAULT) which can be turned
         # into an in-place multi values.  This supports
index 02137474b4566ef006a16a1d32f3c7c30767506f..9ff2f76eb52bed89cde77b5771426293b84a0ae8 100644 (file)
@@ -400,6 +400,7 @@ class AssertsCompiledSQL(object):
         use_default_dialect=False,
         allow_dialect_select=False,
         supports_default_values=True,
+        supports_default_metavalue=True,
         literal_binds=False,
         render_postcompile=False,
         schema_translate_map=None,
@@ -410,6 +411,7 @@ class AssertsCompiledSQL(object):
         if use_default_dialect:
             dialect = default.DefaultDialect()
             dialect.supports_default_values = supports_default_values
+            dialect.supports_default_metavalue = supports_default_metavalue
         elif allow_dialect_select:
             dialect = None
         else:
@@ -421,6 +423,7 @@ class AssertsCompiledSQL(object):
             elif dialect == "default":
                 dialect = default.DefaultDialect()
                 dialect.supports_default_values = supports_default_values
+                dialect.supports_default_metavalue = supports_default_metavalue
             elif dialect == "default_enhanced":
                 dialect = default.StrCompileDialect()
             elif isinstance(dialect, util.string_types):
index 1bdd1158589ed51100bc69063e9f6ee3203d64eb..98261a374210cedcaea30b32ea3ae2a6f47792b1 100644 (file)
@@ -76,7 +76,11 @@ class CompiledSQL(SQLMatchRule):
 
     def _compile_dialect(self, execute_observed):
         if self.dialect == "default":
-            return DefaultDialect()
+            dialect = DefaultDialect()
+            # this is currently what tests are expecting
+            # dialect.supports_default_values = True
+            dialect.supports_default_metavalue = True
+            return dialect
         else:
             # ugh
             if self.dialect == "postgresql":
index f82d5f06552ab6956610279493e27efcc1544765..8a70cc69247196faf84f8a828e2de6100567885e 100644 (file)
@@ -335,10 +335,18 @@ class SuiteRequirements(Requirements):
 
         return exclusions.only_if(
             lambda config: config.db.dialect.supports_empty_insert
-            or config.db.dialect.supports_default_values,
+            or config.db.dialect.supports_default_values
+            or config.db.dialect.supports_default_metavalue,
             "empty inserts not supported",
         )
 
+    @property
+    def empty_inserts_executemany(self):
+        """target platform supports INSERT with no values, i.e.
+        INSERT DEFAULT VALUES or equivalent, within executemany()"""
+
+        return self.empty_inserts
+
     @property
     def insert_from_select(self):
         """target platform supports INSERT from a SELECT."""
index 35f3315c72a7c701bdec26bae52a0578c77a633b..3c033a7744907f63a1b8311c9bb7b07e7cd9ed6d 100644 (file)
@@ -167,8 +167,21 @@ class InsertBehaviorTest(fixtures.TablesTest):
                 self.tables.autoinc_pk.c.id != None
             )
         )
+        eq_(len(r.all()), 1)
 
-        assert len(r.fetchall())
+    @requirements.empty_inserts_executemany
+    def test_empty_insert_multiple(self, connection):
+        r = connection.execute(self.tables.autoinc_pk.insert(), [{}, {}, {}])
+        assert r._soft_closed
+        assert not r.closed
+
+        r = connection.execute(
+            self.tables.autoinc_pk.select().where(
+                self.tables.autoinc_pk.c.id != None
+            )
+        )
+
+        eq_(len(r.all()), 3)
 
     @requirements.insert_from_select
     def test_insert_from_select_autoinc(self, connection):
index a08725921a415d909953f9deb15d0add32fe39c5..fce9946dd7a1cf65b86d1c98139dc07031fb00c6 100644 (file)
@@ -1589,6 +1589,7 @@ class DeprecatedExecParamsTest(fixtures.TablesTest):
             [(5, "some name"), (6, "some other name")],
         )
 
+    @testing.requires.empty_inserts
     def test_single_scalar(self, connection):
 
         users = self.tables.users_autoinc
index 8769d2b3909ba64302f0cc4f90d7b8d9ecbdc34b..1b4a367ac18a409a87273c929266b1f0bcdf60a8 100644 (file)
@@ -1835,7 +1835,8 @@ class JoinedNoFKSortingTest(fixtures.MappedTest):
             testing.db,
             sess.flush,
             Conditional(
-                testing.db.dialect.insert_executemany_returning,
+                testing.db.dialect.insert_executemany_returning
+                and testing.db.dialect.supports_default_metavalue,
                 [
                     CompiledSQL(
                         "INSERT INTO a (id) VALUES (DEFAULT)", [{}, {}, {}, {}]
index eca9e0518f13fef915967c3ba97f86d687b6afc8..29dd55b45004bab5ba12f8eff2efa8cb248939a7 100644 (file)
@@ -846,6 +846,11 @@ class DefaultRequirements(SuiteRequirements):
             ["oracle"], "oracle converts empty strings to a blank space"
         )
 
+    @property
+    def empty_inserts_executemany(self):
+        # waiting on https://jira.mariadb.org/browse/CONPY-152
+        return skip_if(["mariadb+mariadbconnector"]) + self.empty_inserts
+
     @property
     def expressions_against_unbounded_text(self):
         """target database supports use of an unbounded textual field in a
index 543ae1f98c51b58a9b95e2be6ddbe27086ada421..007dc157b90fc9ea2afd34c17415fd823f419e3e 100644 (file)
@@ -2,7 +2,6 @@ import datetime
 import itertools
 
 import sqlalchemy as sa
-from sqlalchemy import Boolean
 from sqlalchemy import cast
 from sqlalchemy import DateTime
 from sqlalchemy import exc
@@ -1062,25 +1061,6 @@ class PKIncrementTest(fixtures.TablesTest):
         )
 
 
-class EmptyInsertTest(fixtures.TestBase):
-    __backend__ = True
-
-    @testing.fails_on("oracle", "FIXME: unknown")
-    def test_empty_insert(self, metadata, connection):
-        t1 = Table(
-            "t1",
-            metadata,
-            Column("is_true", Boolean, server_default=("1")),
-        )
-        metadata.create_all(connection)
-        connection.execute(t1.insert())
-        eq_(
-            1,
-            connection.scalar(select(func.count(text("*"))).select_from(t1)),
-        )
-        eq_(True, connection.scalar(t1.select()))
-
-
 class AutoIncrementTest(fixtures.TestBase):
 
     __backend__ = True
@@ -1088,7 +1068,11 @@ class AutoIncrementTest(fixtures.TestBase):
     @testing.requires.empty_inserts
     def test_autoincrement_single_col(self, metadata, connection):
         single = Table(
-            "single", self.metadata, Column("id", Integer, primary_key=True)
+            "single",
+            self.metadata,
+            Column(
+                "id", Integer, primary_key=True, test_needs_autoincrement=True
+            ),
         )
         self.metadata.create_all(connection)
 
index a128db8a9d14dc0dcc5e99e05f9c6c3e0601b562..95a8d02a27465d7e6b5f8f005f9816a2040f1bd7 100644 (file)
@@ -951,7 +951,9 @@ class EmptyTest(_InsertTestBase, fixtures.TablesTest, AssertsCompiledSQL):
         table1 = self.tables.mytable
 
         dialect = default.DefaultDialect()
-        dialect.supports_empty_insert = dialect.supports_default_values = True
+        dialect.supports_empty_insert = False
+        dialect.supports_default_values = True
+        dialect.supports_default_metavalue = True
 
         stmt = table1.insert().values({})
         self.assert_compile(
@@ -961,6 +963,14 @@ class EmptyTest(_InsertTestBase, fixtures.TablesTest, AssertsCompiledSQL):
             for_executemany=True,
         )
 
+        dialect.supports_default_metavalue = False
+        self.assert_compile(
+            stmt,
+            "INSERT INTO mytable DEFAULT VALUES",
+            dialect=dialect,
+            for_executemany=True,
+        )
+
     def test_supports_empty_insert_false(self):
         table1 = self.tables.mytable