@@ -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+
782876def 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
793892def test ():
794893 runner = unittest .TextTestRunner ()
0 commit comments