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
--- /dev/null
+.. 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.
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
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
supports_comments = True
supports_default_values = True
+
+ supports_default_metavalue = True
+
supports_empty_insert = False
supports_multivalues_insert = True
default_paramstyle = "pyformat"
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
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
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(
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
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,
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:
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):
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":
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."""
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):
[(5, "some name"), (6, "some other name")],
)
+ @testing.requires.empty_inserts
def test_single_scalar(self, connection):
users = self.tables.users_autoinc
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)", [{}, {}, {}, {}]
["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
import itertools
import sqlalchemy as sa
-from sqlalchemy import Boolean
from sqlalchemy import cast
from sqlalchemy import DateTime
from sqlalchemy import exc
)
-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
@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)
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(
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