Skip to content

Commit 4bf580d

Browse files
committed
Issue #21250: Add tests for SQLite's ON CONFLICT clause
Initial patch by Alex LordThorsen.
1 parent 01c340d commit 4bf580d

1 file changed

Lines changed: 100 additions & 1 deletion

File tree

Lib/sqlite3/test/dbapi.py

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,100 @@ def CheckClosed(self):
779779
method = getattr(cur, method_name)
780780
method(*params)
781781

782+
783+
class SqliteOnConflictTests(unittest.TestCase):
784+
"""
785+
Tests for SQLite's "insert on conflict" feature.
786+
787+
See https://www.sqlite.org/lang_conflict.html for details.
788+
"""
789+
790+
def setUp(self):
791+
self.cx = sqlite.connect(":memory:")
792+
self.cu = self.cx.cursor()
793+
self.cu.execute("""
794+
CREATE TABLE test(
795+
id INTEGER PRIMARY KEY, name TEXT, unique_name TEXT UNIQUE
796+
);
797+
""")
798+
799+
def tearDown(self):
800+
self.cu.close()
801+
self.cx.close()
802+
803+
def CheckOnConflictRollbackWithExplicitTransaction(self):
804+
self.cx.isolation_level = None # autocommit mode
805+
self.cu = self.cx.cursor()
806+
# Start an explicit transaction.
807+
self.cu.execute("BEGIN")
808+
self.cu.execute("INSERT INTO test(name) VALUES ('abort_test')")
809+
self.cu.execute("INSERT OR ROLLBACK INTO test(unique_name) VALUES ('foo')")
810+
with self.assertRaises(sqlite.IntegrityError):
811+
self.cu.execute("INSERT OR ROLLBACK INTO test(unique_name) VALUES ('foo')")
812+
# Use connection to commit.
813+
self.cx.commit()
814+
self.cu.execute("SELECT name, unique_name from test")
815+
# Transaction should have rolled back and nothing should be in table.
816+
self.assertEqual(self.cu.fetchall(), [])
817+
818+
def CheckOnConflictAbortRaisesWithExplicitTransactions(self):
819+
# Abort cancels the current sql statement but doesn't change anything
820+
# about the current transaction.
821+
self.cx.isolation_level = None # autocommit mode
822+
self.cu = self.cx.cursor()
823+
# Start an explicit transaction.
824+
self.cu.execute("BEGIN")
825+
self.cu.execute("INSERT INTO test(name) VALUES ('abort_test')")
826+
self.cu.execute("INSERT OR ABORT INTO test(unique_name) VALUES ('foo')")
827+
with self.assertRaises(sqlite.IntegrityError):
828+
self.cu.execute("INSERT OR ABORT INTO test(unique_name) VALUES ('foo')")
829+
self.cx.commit()
830+
self.cu.execute("SELECT name, unique_name FROM test")
831+
# Expect the first two inserts to work, third to do nothing.
832+
self.assertEqual(self.cu.fetchall(), [('abort_test', None), (None, 'foo',)])
833+
834+
def CheckOnConflictRollbackWithoutTransaction(self):
835+
# Start of implicit transaction
836+
self.cu.execute("INSERT INTO test(name) VALUES ('abort_test')")
837+
self.cu.execute("INSERT OR ROLLBACK INTO test(unique_name) VALUES ('foo')")
838+
with self.assertRaises(sqlite.IntegrityError):
839+
self.cu.execute("INSERT OR ROLLBACK INTO test(unique_name) VALUES ('foo')")
840+
self.cu.execute("SELECT name, unique_name FROM test")
841+
# Implicit transaction is rolled back on error.
842+
self.assertEqual(self.cu.fetchall(), [])
843+
844+
def CheckOnConflictAbortRaisesWithoutTransactions(self):
845+
# Abort cancels the current sql statement but doesn't change anything
846+
# about the current transaction.
847+
self.cu.execute("INSERT INTO test(name) VALUES ('abort_test')")
848+
self.cu.execute("INSERT OR ABORT INTO test(unique_name) VALUES ('foo')")
849+
with self.assertRaises(sqlite.IntegrityError):
850+
self.cu.execute("INSERT OR ABORT INTO test(unique_name) VALUES ('foo')")
851+
# Make sure all other values were inserted.
852+
self.cu.execute("SELECT name, unique_name FROM test")
853+
self.assertEqual(self.cu.fetchall(), [('abort_test', None), (None, 'foo',)])
854+
855+
def CheckOnConflictFail(self):
856+
self.cu.execute("INSERT OR FAIL INTO test(unique_name) VALUES ('foo')")
857+
with self.assertRaises(sqlite.IntegrityError):
858+
self.cu.execute("INSERT OR FAIL INTO test(unique_name) VALUES ('foo')")
859+
self.assertEqual(self.cu.fetchall(), [])
860+
861+
def CheckOnConflictIgnore(self):
862+
self.cu.execute("INSERT OR IGNORE INTO test(unique_name) VALUES ('foo')")
863+
# Nothing should happen.
864+
self.cu.execute("INSERT OR IGNORE INTO test(unique_name) VALUES ('foo')")
865+
self.cu.execute("SELECT unique_name FROM test")
866+
self.assertEqual(self.cu.fetchall(), [('foo',)])
867+
868+
def CheckOnConflictReplace(self):
869+
self.cu.execute("INSERT OR REPLACE INTO test(name, unique_name) VALUES ('Data!', 'foo')")
870+
# There shouldn't be an IntegrityError exception.
871+
self.cu.execute("INSERT OR REPLACE INTO test(name, unique_name) VALUES ('Very different data!', 'foo')")
872+
self.cu.execute("SELECT name, unique_name FROM test")
873+
self.assertEqual(self.cu.fetchall(), [('Very different data!', 'foo')])
874+
875+
782876
def suite():
783877
module_suite = unittest.makeSuite(ModuleTests, "Check")
784878
connection_suite = unittest.makeSuite(ConnectionTests, "Check")
@@ -788,7 +882,12 @@ def suite():
788882
ext_suite = unittest.makeSuite(ExtensionTests, "Check")
789883
closed_con_suite = unittest.makeSuite(ClosedConTests, "Check")
790884
closed_cur_suite = unittest.makeSuite(ClosedCurTests, "Check")
791-
return unittest.TestSuite((module_suite, connection_suite, cursor_suite, thread_suite, constructor_suite, ext_suite, closed_con_suite, closed_cur_suite))
885+
on_conflict_suite = unittest.makeSuite(SqliteOnConflictTests, "Check")
886+
return unittest.TestSuite((
887+
module_suite, connection_suite, cursor_suite, thread_suite,
888+
constructor_suite, ext_suite, closed_con_suite, closed_cur_suite,
889+
on_conflict_suite,
890+
))
792891

793892
def test():
794893
runner = unittest.TextTestRunner()

0 commit comments

Comments
 (0)