summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/orm/test_update_delete.py253
-rw-r--r--test/requirements.py4
-rw-r--r--test/sql/test_delete.py8
3 files changed, 237 insertions, 28 deletions
diff --git a/test/orm/test_update_delete.py b/test/orm/test_update_delete.py
index 933d2bb1f..adaed8f6f 100644
--- a/test/orm/test_update_delete.py
+++ b/test/orm/test_update_delete.py
@@ -1715,8 +1715,8 @@ class UpdateDeleteFromTest(fixtures.MappedTest):
),
)
- @testing.requires.delete_from
- def test_delete_from_joined_subq_test(self):
+ @testing.requires.delete_using
+ def test_delete_using_joined_subq_test(self):
Document = self.classes.Document
s = fixture_session()
@@ -1958,6 +1958,11 @@ class InheritTest(fixtures.DeclarativeMappedTest):
id = Column(Integer, ForeignKey("person.id"), primary_key=True)
engineer_name = Column(String(50))
+ class Programmer(Engineer):
+ __tablename__ = "programmer"
+ id = Column(Integer, ForeignKey("engineer.id"), primary_key=True)
+ primary_language = Column(String(50))
+
class Manager(Person):
__tablename__ = "manager"
id = Column(Integer, ForeignKey("person.id"), primary_key=True)
@@ -1965,10 +1970,11 @@ class InheritTest(fixtures.DeclarativeMappedTest):
@classmethod
def insert_data(cls, connection):
- Engineer, Person, Manager = (
+ Engineer, Person, Manager, Programmer = (
cls.classes.Engineer,
cls.classes.Person,
cls.classes.Manager,
+ cls.classes.Programmer,
)
s = Session(connection)
s.add_all(
@@ -1977,11 +1983,14 @@ class InheritTest(fixtures.DeclarativeMappedTest):
Manager(name="m1", manager_name="m1"),
Engineer(name="e2", engineer_name="e2"),
Person(name="p1"),
+ Programmer(
+ name="pp1", engineer_name="pp1", primary_language="python"
+ ),
]
)
s.commit()
- @testing.only_on("mysql", "Multi table update")
+ @testing.only_on(["mysql", "mariadb"], "Multi table update")
def test_update_from_join_no_problem(self):
person = self.classes.Person.__table__
engineer = self.classes.Engineer.__table__
@@ -1998,53 +2007,253 @@ class InheritTest(fixtures.DeclarativeMappedTest):
eq_(obj.name, "updated")
eq_(obj.engineer_name, "e2a")
- def test_update_subtable_only(self):
+ @testing.combinations(None, "fetch", "evaluate")
+ def test_update_sub_table_only(self, synchronize_session):
Engineer = self.classes.Engineer
s = Session(testing.db)
- s.query(Engineer).update({"engineer_name": "e5"})
+ s.query(Engineer).update(
+ {"engineer_name": "e5"}, synchronize_session=synchronize_session
+ )
- eq_(s.query(Engineer.engineer_name).all(), [("e5",), ("e5",)])
+ eq_(s.query(Engineer.engineer_name).all(), [("e5",), ("e5",), ("e5",)])
+
+ @testing.combinations(None, "fetch", "evaluate")
+ def test_update_sub_sub_table_only(self, synchronize_session):
+ Programmer = self.classes.Programmer
+ s = Session(testing.db)
+ s.query(Programmer).update(
+ {"primary_language": "c++"},
+ synchronize_session=synchronize_session,
+ )
+
+ eq_(
+ s.query(Programmer.primary_language).all(),
+ [
+ ("c++",),
+ ],
+ )
@testing.requires.update_from
- def test_update_from(self):
+ @testing.combinations(None, "fetch", "fetch_w_hint", "evaluate")
+ def test_update_from(self, synchronize_session):
+ """test an UPDATE that uses multiple tables.
+
+ The limitation that MariaDB has with DELETE does not apply here
+ at the moment as MariaDB doesn't support UPDATE..RETURNING at all.
+ However, the logic from DELETE is still implemented in
+ persistence.py. If MariaDB adds UPDATE...RETURNING, or SQLite adds
+ UPDATE..FROM, etc., then it will be useful.
+
+ """
Engineer = self.classes.Engineer
Person = self.classes.Person
s = Session(testing.db)
- s.query(Engineer).filter(Engineer.id == Person.id).filter(
- Person.name == "e2"
- ).update({"engineer_name": "e5"})
+
+ # we don't have any backends with this combination right now.
+ db_has_hypothetical_limitation = (
+ testing.db.dialect.update_returning
+ and not testing.db.dialect.update_returning_multifrom
+ )
+
+ e2 = s.query(Engineer).filter_by(name="e2").first()
+
+ with self.sql_execution_asserter() as asserter:
+ eq_(e2.engineer_name, "e2")
+ q = (
+ s.query(Engineer)
+ .filter(Engineer.id == Person.id)
+ .filter(Person.name == "e2")
+ )
+ if synchronize_session == "fetch_w_hint":
+ q.execution_options(is_update_from=True).update(
+ {"engineer_name": "e5"},
+ synchronize_session="fetch",
+ )
+ elif (
+ synchronize_session == "fetch"
+ and db_has_hypothetical_limitation
+ ):
+ with expect_raises_message(
+ exc.CompileError,
+ 'Dialect ".*" does not support RETURNING with '
+ "UPDATE..FROM;",
+ ):
+ q.update(
+ {"engineer_name": "e5"},
+ synchronize_session=synchronize_session,
+ )
+ return
+ else:
+ q.update(
+ {"engineer_name": "e5"},
+ synchronize_session=synchronize_session,
+ )
+
+ if synchronize_session is None:
+ eq_(e2.engineer_name, "e2")
+ else:
+ eq_(e2.engineer_name, "e5")
+
+ if synchronize_session in ("fetch", "fetch_w_hint") and (
+ db_has_hypothetical_limitation
+ or not testing.db.dialect.update_returning
+ ):
+ asserter.assert_(
+ CompiledSQL(
+ "SELECT person.id FROM person INNER JOIN engineer "
+ "ON person.id = engineer.id WHERE engineer.id = person.id "
+ "AND person.name = %s",
+ [{"name_1": "e2"}],
+ dialect="mariadb",
+ ),
+ CompiledSQL(
+ "UPDATE engineer, person SET engineer.engineer_name=%s "
+ "WHERE engineer.id = person.id AND person.name = %s",
+ [{"engineer_name": "e5", "name_1": "e2"}],
+ dialect="mariadb",
+ ),
+ )
+ elif synchronize_session in ("fetch", "fetch_w_hint"):
+ asserter.assert_(
+ CompiledSQL(
+ "UPDATE engineer SET engineer_name=%(engineer_name)s "
+ "FROM person WHERE engineer.id = person.id "
+ "AND person.name = %(name_1)s RETURNING engineer.id",
+ [{"engineer_name": "e5", "name_1": "e2"}],
+ dialect="postgresql",
+ ),
+ )
+ else:
+ asserter.assert_(
+ CompiledSQL(
+ "UPDATE engineer SET engineer_name=%(engineer_name)s "
+ "FROM person WHERE engineer.id = person.id "
+ "AND person.name = %(name_1)s",
+ [{"engineer_name": "e5", "name_1": "e2"}],
+ dialect="postgresql",
+ ),
+ )
eq_(
set(s.query(Person.name, Engineer.engineer_name)),
- set([("e1", "e1"), ("e2", "e5")]),
+ set([("e1", "e1"), ("e2", "e5"), ("pp1", "pp1")]),
)
- @testing.requires.delete_from
- def test_delete_from(self):
+ @testing.requires.delete_using
+ @testing.combinations(None, "fetch", "fetch_w_hint", "evaluate")
+ def test_delete_using(self, synchronize_session):
+ """test a DELETE that uses multiple tables.
+
+ due to a limitation in MariaDB, we have an up front "hint" that needs
+ to be passed for this backend if DELETE USING is to be used in
+ conjunction with "fetch" strategy, so that we know before compilation
+ that we won't be able to use RETURNING.
+
+ """
+
Engineer = self.classes.Engineer
Person = self.classes.Person
s = Session(testing.db)
- s.query(Engineer).filter(Engineer.id == Person.id).filter(
- Person.name == "e2"
- ).delete()
+ db_has_mariadb_limitation = (
+ testing.db.dialect.delete_returning
+ and not testing.db.dialect.delete_returning_multifrom
+ )
+
+ e2 = s.query(Engineer).filter_by(name="e2").first()
+
+ with self.sql_execution_asserter() as asserter:
+
+ assert e2 in s
+
+ q = (
+ s.query(Engineer)
+ .filter(Engineer.id == Person.id)
+ .filter(Person.name == "e2")
+ )
+
+ if synchronize_session == "fetch_w_hint":
+ q.execution_options(is_delete_using=True).delete(
+ synchronize_session="fetch"
+ )
+ elif synchronize_session == "fetch" and db_has_mariadb_limitation:
+ with expect_raises_message(
+ exc.CompileError,
+ 'Dialect ".*" does not support RETURNING with '
+ "DELETE..USING;",
+ ):
+ q.delete(synchronize_session=synchronize_session)
+ return
+ else:
+ q.delete(synchronize_session=synchronize_session)
+
+ if synchronize_session is None:
+ assert e2 in s
+ else:
+ assert e2 not in s
+
+ if synchronize_session in ("fetch", "fetch_w_hint") and (
+ db_has_mariadb_limitation
+ or not testing.db.dialect.delete_returning
+ ):
+ asserter.assert_(
+ CompiledSQL(
+ "SELECT person.id FROM person INNER JOIN engineer ON "
+ "person.id = engineer.id WHERE engineer.id = person.id "
+ "AND person.name = %s",
+ [{"name_1": "e2"}],
+ dialect="mariadb",
+ ),
+ CompiledSQL(
+ "DELETE FROM engineer USING engineer, person WHERE "
+ "engineer.id = person.id AND person.name = %s",
+ [{"name_1": "e2"}],
+ dialect="mariadb",
+ ),
+ )
+ elif synchronize_session in ("fetch", "fetch_w_hint"):
+ asserter.assert_(
+ CompiledSQL(
+ "DELETE FROM engineer USING person WHERE "
+ "engineer.id = person.id AND person.name = %(name_1)s "
+ "RETURNING engineer.id",
+ [{"name_1": "e2"}],
+ dialect="postgresql",
+ ),
+ )
+ else:
+ asserter.assert_(
+ CompiledSQL(
+ "DELETE FROM engineer USING person WHERE "
+ "engineer.id = person.id AND person.name = %(name_1)s",
+ [{"name_1": "e2"}],
+ dialect="postgresql",
+ ),
+ )
+
+ # delete actually worked
eq_(
set(s.query(Person.name, Engineer.engineer_name)),
- set([("e1", "e1")]),
+ set([("pp1", "pp1"), ("e1", "e1")]),
)
- @testing.only_on("mysql", "Multi table update")
- def test_update_from_multitable(self):
+ @testing.only_on(["mysql", "mariadb"], "Multi table update")
+ @testing.requires.delete_using
+ @testing.combinations(None, "fetch", "evaluate")
+ def test_update_from_multitable(self, synchronize_session):
Engineer = self.classes.Engineer
Person = self.classes.Person
s = Session(testing.db)
s.query(Engineer).filter(Engineer.id == Person.id).filter(
Person.name == "e2"
- ).update({Person.name: "e22", Engineer.engineer_name: "e55"})
+ ).update(
+ {Person.name: "e22", Engineer.engineer_name: "e55"},
+ synchronize_session=synchronize_session,
+ )
eq_(
set(s.query(Person.name, Engineer.engineer_name)),
- set([("e1", "e1"), ("e22", "e55")]),
+ set([("e1", "e1"), ("e22", "e55"), ("pp1", "pp1")]),
)
diff --git a/test/requirements.py b/test/requirements.py
index c7c5beed9..c6776533c 100644
--- a/test/requirements.py
+++ b/test/requirements.py
@@ -467,11 +467,11 @@ class DefaultRequirements(SuiteRequirements):
)
@property
- def delete_from(self):
+ def delete_using(self):
"""Target must support DELETE FROM..FROM or DELETE..USING syntax"""
return only_on(
["postgresql", "mssql", "mysql", "mariadb"],
- "Backend does not support DELETE..FROM",
+ "Backend does not support DELETE..USING or equivalent",
)
@property
diff --git a/test/sql/test_delete.py b/test/sql/test_delete.py
index 45ead811f..f98e7297d 100644
--- a/test/sql/test_delete.py
+++ b/test/sql/test_delete.py
@@ -307,7 +307,7 @@ class DeleteFromRoundTripTest(fixtures.TablesTest):
),
)
- @testing.requires.delete_from
+ @testing.requires.delete_using
def test_exec_two_table(self, connection):
users, addresses = self.tables.users, self.tables.addresses
dingalings = self.tables.dingalings
@@ -326,7 +326,7 @@ class DeleteFromRoundTripTest(fixtures.TablesTest):
]
self._assert_table(connection, addresses, expected)
- @testing.requires.delete_from
+ @testing.requires.delete_using
def test_exec_three_table(self, connection):
users = self.tables.users
addresses = self.tables.addresses
@@ -342,7 +342,7 @@ class DeleteFromRoundTripTest(fixtures.TablesTest):
expected = [(2, 5, "ding 2/5")]
self._assert_table(connection, dingalings, expected)
- @testing.requires.delete_from
+ @testing.requires.delete_using
def test_exec_two_table_plus_alias(self, connection):
users, addresses = self.tables.users, self.tables.addresses
dingalings = self.tables.dingalings
@@ -359,7 +359,7 @@ class DeleteFromRoundTripTest(fixtures.TablesTest):
expected = [(1, 7, "x", "jack@bean.com"), (5, 9, "x", "fred@fred.com")]
self._assert_table(connection, addresses, expected)
- @testing.requires.delete_from
+ @testing.requires.delete_using
def test_exec_alias_plus_table(self, connection):
users, addresses = self.tables.users, self.tables.addresses
dingalings = self.tables.dingalings