summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/build/changelog/unreleased_14/6361.rst9
-rw-r--r--lib/sqlalchemy/sql/crud.py11
-rw-r--r--test/sql/test_insert.py69
-rw-r--r--test/sql/test_sequences.py38
4 files changed, 126 insertions, 1 deletions
diff --git a/doc/build/changelog/unreleased_14/6361.rst b/doc/build/changelog/unreleased_14/6361.rst
new file mode 100644
index 000000000..3b4b137ec
--- /dev/null
+++ b/doc/build/changelog/unreleased_14/6361.rst
@@ -0,0 +1,9 @@
+.. change::
+ :tags: bug, engine
+ :tickets: 6361
+
+ Fixed issue where usage of an explicit :class:`.Sequence` would produce
+ inconsistent "inline" behavior for an :class:`.Insert` construct that
+ includes multiple values phrases; the first seq would be inline but
+ subsequent ones would be "pre-execute", leading to inconsistent sequence
+ ordering. The sequence expressions are now fully inline.
diff --git a/lib/sqlalchemy/sql/crud.py b/lib/sqlalchemy/sql/crud.py
index 5fa82bcd0..25dc0d232 100644
--- a/lib/sqlalchemy/sql/crud.py
+++ b/lib/sqlalchemy/sql/crud.py
@@ -803,7 +803,6 @@ class _multiparam_column(elements.ColumnElement):
def _process_multiparam_default_bind(compiler, stmt, c, index, kw):
-
if not c.default:
raise exc.CompileError(
"INSERT value for column %s is explicitly rendered as a bound"
@@ -812,6 +811,16 @@ def _process_multiparam_default_bind(compiler, stmt, c, index, kw):
)
elif c.default.is_clause_element:
return compiler.process(c.default.arg.self_group(), **kw)
+ elif c.default.is_sequence:
+ # these conditions would have been established
+ # by append_param_insert_(?:hasdefault|pk_returning|pk_no_returning)
+ # in order for us to be here, so these don't need to be
+ # checked
+ # assert compiler.dialect.supports_sequences and (
+ # not c.default.optional
+ # or not compiler.dialect.sequences_optional
+ # )
+ return compiler.process(c.default, **kw)
else:
col = _multiparam_column(c, index)
if isinstance(stmt, dml.Insert):
diff --git a/test/sql/test_insert.py b/test/sql/test_insert.py
index 95a8d02a2..6c2a5d955 100644
--- a/test/sql/test_insert.py
+++ b/test/sql/test_insert.py
@@ -384,6 +384,75 @@ class InsertTest(_InsertTestBase, fixtures.TablesTest, AssertsCompiledSQL):
dialect=postgresql.dialect(),
)
+ def test_insert_seq_pk_multi_values(self):
+ """test #6361"""
+
+ m = MetaData()
+
+ t1 = Table(
+ "t",
+ m,
+ Column("id", Integer, Sequence("id_seq"), primary_key=True),
+ Column("data", String),
+ )
+
+ stmt = t1.insert().values(
+ [{"data": "d1"}, {"data": "d2"}, {"data": "d3"}]
+ )
+
+ self.assert_compile(
+ stmt,
+ "INSERT INTO t (id, data) VALUES (nextval('id_seq'), "
+ "%(data_m0)s), (nextval('id_seq'), %(data_m1)s), "
+ "(nextval('id_seq'), %(data_m2)s)",
+ dialect=postgresql.dialect(),
+ )
+
+ def test_insert_seq_non_pk_multi_values(self):
+ """test #6361"""
+
+ m = MetaData()
+
+ t1 = Table(
+ "t",
+ m,
+ Column("id", Integer, primary_key=True),
+ Column("counter", Sequence("counter_seq")),
+ Column("data", String),
+ )
+
+ stmt = t1.insert().values(
+ [{"data": "d1"}, {"data": "d2"}, {"data": "d3"}]
+ )
+
+ self.assert_compile(
+ stmt,
+ "INSERT INTO t (counter, data) VALUES (nextval('counter_seq'), "
+ "%(data_m0)s), (nextval('counter_seq'), %(data_m1)s), "
+ "(nextval('counter_seq'), %(data_m2)s)",
+ dialect=postgresql.dialect(),
+ )
+
+ def test_insert_seq_pk_multi_values_seq_not_supported(self):
+ m = MetaData()
+
+ t1 = Table(
+ "t",
+ m,
+ Column("id", Integer, Sequence("id_seq"), primary_key=True),
+ Column("data", String),
+ )
+
+ stmt = t1.insert().values(
+ [{"data": "d1"}, {"data": "d2"}, {"data": "d3"}]
+ )
+
+ self.assert_compile(
+ stmt,
+ "INSERT INTO t (data) VALUES (?), (?), (?)",
+ dialect=sqlite.dialect(),
+ )
+
def test_insert_from_select_cte_one(self):
table1 = self.tables.mytable
diff --git a/test/sql/test_sequences.py b/test/sql/test_sequences.py
index 3b25ba2ad..d1d46afa3 100644
--- a/test/sql/test_sequences.py
+++ b/test/sql/test_sequences.py
@@ -204,6 +204,44 @@ class SequenceExecTest(fixtures.TestBase):
else:
eq_(r.inserted_primary_key, (None,))
+ @testing.combinations(
+ ("implicit_returning",),
+ ("no_implicit_returning",),
+ ("explicit_returning", testing.requires.returning),
+ argnames="returning",
+ )
+ @testing.requires.multivalues_inserts
+ def test_seq_multivalues_inline(self, metadata, testing_engine, returning):
+ t1 = Table(
+ "t",
+ metadata,
+ Column("x", Integer, Sequence("my_seq"), primary_key=True),
+ Column("data", String(50)),
+ )
+
+ e = engines.testing_engine(
+ options={
+ "implicit_returning": returning != "no_implicit_returning"
+ }
+ )
+ metadata.create_all(e)
+ with e.begin() as conn:
+
+ stmt = t1.insert().values(
+ [{"data": "d1"}, {"data": "d2"}, {"data": "d3"}]
+ )
+ if returning == "explicit_returning":
+ stmt = stmt.returning(t1.c.x)
+
+ r = conn.execute(stmt)
+ if returning == "explicit_returning":
+ eq_(r.all(), [(1,), (2,), (3,)])
+
+ eq_(
+ conn.execute(t1.select().order_by(t1.c.x)).all(),
+ [(1, "d1"), (2, "d2"), (3, "d3")],
+ )
+
@testing.requires.returning
@testing.provide_metadata
def test_inserted_pk_implicit_returning(self):