diff -r fc2eed9fc2d0 Doc/library/sqlite3.rst --- a/Doc/library/sqlite3.rst Tue May 19 01:38:05 2015 +0300 +++ b/Doc/library/sqlite3.rst Mon May 18 23:57:14 2015 -0700 @@ -619,6 +619,10 @@ method. For operations other than ``INSERT`` or when :meth:`executemany` is called, :attr:`lastrowid` is set to :const:`None`. + .. versionchanged:: 3.5 + lastrowid now provides the rowid of the last modified row for + ``REPLACE`` statements. + .. attribute:: description This read-only attribute provides the column names of the last query. To diff -r fc2eed9fc2d0 Doc/whatsnew/3.5.rst --- a/Doc/whatsnew/3.5.rst Tue May 19 01:38:05 2015 +0300 +++ b/Doc/whatsnew/3.5.rst Mon May 18 23:57:14 2015 -0700 @@ -505,6 +505,12 @@ :func:`~shutil.copy2` if there is a need to ignore metadata. (Contributed by Claudiu Popa in :issue:`19840`.) +sqlite3 +------- + +* :func:`~sqlite3.Cursor.lastrowid` now provides the last row id for + ``REPLACE INTO``, an alias for ``INSERT OR REPLACE`` statements + signal ------ diff -r fc2eed9fc2d0 Lib/sqlite3/test/dbapi.py --- a/Lib/sqlite3/test/dbapi.py Tue May 19 01:38:05 2015 +0300 +++ b/Lib/sqlite3/test/dbapi.py Mon May 18 23:57:14 2015 -0700 @@ -185,7 +185,8 @@ def setUp(self): self.cx = sqlite.connect(":memory:") self.cu = self.cx.cursor() - self.cu.execute("create table test(id integer primary key, name text, income number)") + self.cu.execute("create table test(id integer primary key, name text, " + "income number, unique_test text unique)") self.cu.execute("insert into test(name) values (?)", ("foo",)) def tearDown(self): @@ -511,6 +512,87 @@ except TypeError: pass + def CheckLastRowIDOnRplace(self): + """ + Originally added to address http://bugs.python.org/issue16864 + + NOTE: INSERT OR REPLACE and REPLACE INTO should produce the same + behavior. + """ + sql = "{} INTO test(id, unique_test) VALUES (?, ?)" + self.cu.execute(sql.format("INSERT OR REPLACE"), (1, "foo")) + for statement in ["INSERT OR REPLACE", "REPLACE"]: + with self.subTest(statement=statement): + self.cu.execute(sql.format(statement), (1, "foo")) + self.assertEqual(self.cu.lastrowid, 1) + + def CheckLastRowIDOnIgnore(self): + """ + Issue16864 reported that REPLACE statements weren't changing the last + row id after a successful insert. Testing to see if Other INSERT OR + algorithms set the lastrowid correctly. + """ + #test curser values for unique and non-unique insert or replace + self.cu.execute( + "insert or ignore into test(unique_test) values (?)", + ('test',)) + self.assertEqual(self.cu.lastrowid, 2) + self.cu.execute( + "insert or ignore into test(unique_test) values (?)", + ('test',)) + self.assertEqual(self.cu.lastrowid, 2) + + def CheckLastRowIDOnFail(self): + """ + Issue16864 reported that REPLACE statements weren't changing the last + row id after a successful insert. Testing to see if Other INSERT OR + algorithms set the lastrowid correctly. + """ + #test curser values for unique and non-unique insert or replace + self.cu.execute( + "insert or fail into test(unique_test) values (?)", + ('What',)) + self.assertEqual(self.cu.lastrowid, 2) + with self.assertRaises(sqlite.IntegrityError): + self.cu.execute( + "insert or fail into test(unique_test) values (?)", + ('What',)) + self.assertEqual(self.cu.lastrowid, 2) + + def CheckLastRowIDOnAbort(self): + """ + Issue16864 reported that REPLACE statements weren't changing the last + row id after a successful insert. Testing to see if Other INSERT OR + algorithms set the lastrowid correctly. + """ + #test curser values for unique and non-unique insert or replace + self.cu.execute( + "insert or abort into test(unique_test) values (?)", + ('What',)) + self.assertEqual(self.cu.lastrowid, 2) + with self.assertRaises(sqlite.IntegrityError): + self.cu.execute( + "insert or abort into test(unique_test) values (?)", + ('What',)) + self.assertEqual(self.cu.lastrowid, 2) + + def CheckLastRowIDOnRollback(self): + """ + Issue16864 reported that REPLACE statements weren't changing the last + row id after a successful insert. Testing to see if Other INSERT OR + algorithms set the lastrowid correctly. + """ + #test curser values for unique and non-unique insert or replace + self.cu.execute( + "insert or rollback into test(unique_test) values (?)", + ('What',)) + self.assertEqual(self.cu.lastrowid, 2) + with self.assertRaises(sqlite.IntegrityError): + self.cu.execute( + "insert or rollback into test(unique_test) values (?)", + ('What',)) + self.assertEqual(self.cu.lastrowid, 2) + @unittest.skipUnless(threading, 'This test requires threading.') class ThreadTests(unittest.TestCase): def setUp(self): diff -r fc2eed9fc2d0 Modules/_sqlite/cursor.c --- a/Modules/_sqlite/cursor.c Tue May 19 01:38:05 2015 +0300 +++ b/Modules/_sqlite/cursor.c Mon May 18 23:57:14 2015 -0700 @@ -702,7 +702,8 @@ } Py_DECREF(self->lastrowid); - if (!multiple && statement_type == STATEMENT_INSERT) { + if (!multiple && (statement_type == STATEMENT_INSERT || + statement_type == STATEMENT_REPLACE)) { sqlite_int64 lastrowid; Py_BEGIN_ALLOW_THREADS lastrowid = sqlite3_last_insert_rowid(self->connection->db);