Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-24139: Add support for SQLite extended result codes #28076

Merged
merged 23 commits into from
Nov 2, 2021
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 127 additions & 2 deletions Lib/sqlite3/test/test_dbapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,18 +172,143 @@ def test_module_constants(self):
if sqlite.version_info >= (3, 8, 3):
consts.append("SQLITE_RECURSIVE")
consts += ["PARSE_DECLTYPES", "PARSE_COLNAMES"]
# Extended result codes
consts += [
"SQLITE_ABORT_ROLLBACK",
"SQLITE_BUSY_RECOVERY",
"SQLITE_CANTOPEN_FULLPATH",
"SQLITE_CANTOPEN_ISDIR",
"SQLITE_CANTOPEN_NOTEMPDIR",
"SQLITE_CORRUPT_VTAB",
"SQLITE_IOERR_ACCESS",
"SQLITE_IOERR_BLOCKED",
"SQLITE_IOERR_CHECKRESERVEDLOCK",
"SQLITE_IOERR_CLOSE",
"SQLITE_IOERR_DELETE",
"SQLITE_IOERR_DELETE_NOENT",
"SQLITE_IOERR_DIR_CLOSE",
"SQLITE_IOERR_DIR_FSYNC",
"SQLITE_IOERR_FSTAT",
"SQLITE_IOERR_FSYNC",
"SQLITE_IOERR_LOCK",
"SQLITE_IOERR_NOMEM",
"SQLITE_IOERR_RDLOCK",
"SQLITE_IOERR_READ",
"SQLITE_IOERR_SEEK",
"SQLITE_IOERR_SHMLOCK",
"SQLITE_IOERR_SHMMAP",
"SQLITE_IOERR_SHMOPEN",
"SQLITE_IOERR_SHMSIZE",
"SQLITE_IOERR_SHORT_READ",
"SQLITE_IOERR_TRUNCATE",
"SQLITE_IOERR_UNLOCK",
"SQLITE_IOERR_WRITE",
"SQLITE_LOCKED_SHAREDCACHE",
"SQLITE_READONLY_CANTLOCK",
"SQLITE_READONLY_RECOVERY",
]
if sqlite.version_info >= (3, 7, 16):
consts += [
"SQLITE_CONSTRAINT_CHECK",
"SQLITE_CONSTRAINT_COMMITHOOK",
"SQLITE_CONSTRAINT_FOREIGNKEY",
"SQLITE_CONSTRAINT_FUNCTION",
"SQLITE_CONSTRAINT_NOTNULL",
"SQLITE_CONSTRAINT_PRIMARYKEY",
"SQLITE_CONSTRAINT_TRIGGER",
"SQLITE_CONSTRAINT_UNIQUE",
"SQLITE_CONSTRAINT_VTAB",
"SQLITE_READONLY_ROLLBACK",
]
if sqlite.version_info >= (3, 7, 17):
consts += [
"SQLITE_IOERR_MMAP",
"SQLITE_NOTICE_RECOVER_ROLLBACK",
"SQLITE_NOTICE_RECOVER_WAL",
]
if sqlite.version_info >= (3, 8, 0):
consts += [
"SQLITE_BUSY_SNAPSHOT",
"SQLITE_IOERR_GETTEMPPATH",
"SQLITE_WARNING_AUTOINDEX",
]
if sqlite.version_info >= (3, 8, 1):
consts += ["SQLITE_CANTOPEN_CONVPATH", "SQLITE_IOERR_CONVPATH"]
if sqlite.version_info >= (3, 8, 10):
consts += [
"SQLITE_AUTH_USER",
"SQLITE_CONSTRAINT_ROWID",
"SQLITE_READONLY_DBMOVED",
]
if sqlite.version_info >= (3, 9, 0):
consts.append("SQLITE_IOERR_VNODE")
if sqlite.version_info >= (3, 10, 0):
consts.append("SQLITE_IOERR_AUTH")
if sqlite.version_info >= (3, 14, 1):
consts.append("SQLITE_OK_LOAD_PERMANENTLY")
if sqlite.version_info >= (3, 21, 0):
consts += [
"SQLITE_IOERR_BEGIN_ATOMIC",
"SQLITE_IOERR_COMMIT_ATOMIC",
"SQLITE_IOERR_ROLLBACK_ATOMIC",
]
if sqlite.version_info >= (3, 22, 0):
consts += [
"SQLITE_ERROR_MISSING_COLLSEQ",
"SQLITE_ERROR_RETRY",
"SQLITE_READONLY_CANTINIT",
"SQLITE_READONLY_DIRECTORY",
]
if sqlite.version_info >= (3, 24, 0):
consts += ["SQLITE_CORRUPT_SEQUENCE", "SQLITE_LOCKED_VTAB"]
if sqlite.version_info >= (3, 25, 0):
consts += ["SQLITE_CANTOPEN_DIRTYWAL", "SQLITE_ERROR_SNAPSHOT"]
if sqlite.version_info >= (3, 31, 0):
consts += [
"SQLITE_CANTOPEN_SYMLINK",
"SQLITE_CONSTRAINT_PINNED",
"SQLITE_OK_SYMLINK",
]
if sqlite.version_info >= (3, 32, 0):
consts += [
"SQLITE_BUSY_TIMEOUT",
"SQLITE_CORRUPT_INDEX",
"SQLITE_IOERR_DATA",
]
if sqlite.version_info >= (3, 34, 0):
const.append("SQLITE_IOERR_CORRUPTFS")
if sqlite.version_info >= (3, 37, 0):
consts += ["SQLITE_CANTOPEN_EXISTS", "SQLITE_CONSTRAINT_DATATYPE"]
for const in consts:
with self.subTest(const=const):
self.assertTrue(hasattr(sqlite, const))

def test_error_code_on_exception(self):
err_msg = "unable to open database file"
if sys.platform.startswith("win"):
err_code = sqlite.SQLITE_CANTOPEN_ISDIR
else:
err_code = sqlite.SQLITE_CANTOPEN

with temp_dir() as db:
with self.assertRaisesRegex(sqlite.Error, err_msg) as cm:
sqlite.connect(db)
e = cm.exception
self.assertEqual(e.sqlite_errorcode, sqlite.SQLITE_CANTOPEN)
self.assertEqual(e.sqlite_errorname, "SQLITE_CANTOPEN")
self.assertEqual(e.sqlite_errorcode, err_code)
self.assertTrue(e.sqlite_errorname.startswith("SQLITE_CANTOPEN"))

@unittest.skipIf(sqlite.sqlite_version_info <= (3, 10, 0),
"Requires SQLite 3.10.0 or newer")
def test_extended_error_code_on_exception(self):
con = sqlite.connect(":memory:")
with con:
con.execute("create table t(t integer check(t > 0))")
errmsg = "CHECK constraint failed"
with self.assertRaisesRegex(sqlite.IntegrityError, errmsg) as cm:
con.execute("insert into t values(-1)")
exc = cm.exception
self.assertEqual(exc.sqlite_errorcode, sqlite.SQLITE_CONSTRAINT_CHECK)
self.assertEqual(exc.sqlite_errorname, "SQLITE_CONSTRAINT_CHECK")

# sqlite3_enable_shared_cache() is deprecated on macOS and calling it may raise
# OperationalError on some buildbots.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add support for SQLite extended result codes in :exc:`sqlite3.Error`. Patch
by Erlend E. Aasland.
109 changes: 109 additions & 0 deletions Modules/_sqlite/module.c
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,115 @@ static const struct {
#if SQLITE_VERSION_NUMBER >= 3007017
DECLARE_ERROR_CODE(SQLITE_NOTICE),
DECLARE_ERROR_CODE(SQLITE_WARNING),
#endif
// Extended result code list
DECLARE_ERROR_CODE(SQLITE_ABORT_ROLLBACK),
DECLARE_ERROR_CODE(SQLITE_BUSY_RECOVERY),
DECLARE_ERROR_CODE(SQLITE_CANTOPEN_FULLPATH),
DECLARE_ERROR_CODE(SQLITE_CANTOPEN_ISDIR),
DECLARE_ERROR_CODE(SQLITE_CANTOPEN_NOTEMPDIR),
DECLARE_ERROR_CODE(SQLITE_CORRUPT_VTAB),
DECLARE_ERROR_CODE(SQLITE_IOERR_ACCESS),
DECLARE_ERROR_CODE(SQLITE_IOERR_BLOCKED),
DECLARE_ERROR_CODE(SQLITE_IOERR_CHECKRESERVEDLOCK),
DECLARE_ERROR_CODE(SQLITE_IOERR_CLOSE),
DECLARE_ERROR_CODE(SQLITE_IOERR_DELETE),
DECLARE_ERROR_CODE(SQLITE_IOERR_DELETE_NOENT),
DECLARE_ERROR_CODE(SQLITE_IOERR_DIR_CLOSE),
DECLARE_ERROR_CODE(SQLITE_IOERR_DIR_FSYNC),
DECLARE_ERROR_CODE(SQLITE_IOERR_FSTAT),
DECLARE_ERROR_CODE(SQLITE_IOERR_FSYNC),
DECLARE_ERROR_CODE(SQLITE_IOERR_LOCK),
DECLARE_ERROR_CODE(SQLITE_IOERR_NOMEM),
DECLARE_ERROR_CODE(SQLITE_IOERR_RDLOCK),
DECLARE_ERROR_CODE(SQLITE_IOERR_READ),
DECLARE_ERROR_CODE(SQLITE_IOERR_SEEK),
DECLARE_ERROR_CODE(SQLITE_IOERR_SHMLOCK),
DECLARE_ERROR_CODE(SQLITE_IOERR_SHMMAP),
DECLARE_ERROR_CODE(SQLITE_IOERR_SHMOPEN),
DECLARE_ERROR_CODE(SQLITE_IOERR_SHMSIZE),
DECLARE_ERROR_CODE(SQLITE_IOERR_SHORT_READ),
DECLARE_ERROR_CODE(SQLITE_IOERR_TRUNCATE),
DECLARE_ERROR_CODE(SQLITE_IOERR_UNLOCK),
DECLARE_ERROR_CODE(SQLITE_IOERR_WRITE),
DECLARE_ERROR_CODE(SQLITE_LOCKED_SHAREDCACHE),
DECLARE_ERROR_CODE(SQLITE_READONLY_CANTLOCK),
DECLARE_ERROR_CODE(SQLITE_READONLY_RECOVERY),
#if SQLITE_VERSION_NUMBER >= 3007016
DECLARE_ERROR_CODE(SQLITE_CONSTRAINT_CHECK),
DECLARE_ERROR_CODE(SQLITE_CONSTRAINT_COMMITHOOK),
DECLARE_ERROR_CODE(SQLITE_CONSTRAINT_FOREIGNKEY),
DECLARE_ERROR_CODE(SQLITE_CONSTRAINT_FUNCTION),
DECLARE_ERROR_CODE(SQLITE_CONSTRAINT_NOTNULL),
DECLARE_ERROR_CODE(SQLITE_CONSTRAINT_PRIMARYKEY),
DECLARE_ERROR_CODE(SQLITE_CONSTRAINT_TRIGGER),
DECLARE_ERROR_CODE(SQLITE_CONSTRAINT_UNIQUE),
DECLARE_ERROR_CODE(SQLITE_CONSTRAINT_VTAB),
DECLARE_ERROR_CODE(SQLITE_READONLY_ROLLBACK),
#endif
#if SQLITE_VERSION_NUMBER >= 3007017
DECLARE_ERROR_CODE(SQLITE_IOERR_MMAP),
DECLARE_ERROR_CODE(SQLITE_NOTICE_RECOVER_ROLLBACK),
DECLARE_ERROR_CODE(SQLITE_NOTICE_RECOVER_WAL),
#endif
#if SQLITE_VERSION_NUMBER >= 3008000
DECLARE_ERROR_CODE(SQLITE_BUSY_SNAPSHOT),
DECLARE_ERROR_CODE(SQLITE_IOERR_GETTEMPPATH),
DECLARE_ERROR_CODE(SQLITE_WARNING_AUTOINDEX),
#endif
#if SQLITE_VERSION_NUMBER >= 3008001
DECLARE_ERROR_CODE(SQLITE_CANTOPEN_CONVPATH),
DECLARE_ERROR_CODE(SQLITE_IOERR_CONVPATH),
#endif
#if SQLITE_VERSION_NUMBER >= 3008010
DECLARE_ERROR_CODE(SQLITE_AUTH_USER),
DECLARE_ERROR_CODE(SQLITE_CONSTRAINT_ROWID),
DECLARE_ERROR_CODE(SQLITE_READONLY_DBMOVED),
#endif
#if SQLITE_VERSION_NUMBER >= 3009000
DECLARE_ERROR_CODE(SQLITE_IOERR_VNODE),
#endif
#if SQLITE_VERSION_NUMBER >= 3010000
DECLARE_ERROR_CODE(SQLITE_IOERR_AUTH),
#endif
#if SQLITE_VERSION_NUMBER >= 3014001
DECLARE_ERROR_CODE(SQLITE_OK_LOAD_PERMANENTLY),
#endif
#if SQLITE_VERSION_NUMBER >= 3021000
DECLARE_ERROR_CODE(SQLITE_IOERR_BEGIN_ATOMIC),
DECLARE_ERROR_CODE(SQLITE_IOERR_COMMIT_ATOMIC),
DECLARE_ERROR_CODE(SQLITE_IOERR_ROLLBACK_ATOMIC),
#endif
#if SQLITE_VERSION_NUMBER >= 3022000
DECLARE_ERROR_CODE(SQLITE_ERROR_MISSING_COLLSEQ),
DECLARE_ERROR_CODE(SQLITE_ERROR_RETRY),
DECLARE_ERROR_CODE(SQLITE_READONLY_CANTINIT),
DECLARE_ERROR_CODE(SQLITE_READONLY_DIRECTORY),
#endif
#if SQLITE_VERSION_NUMBER >= 3024000
DECLARE_ERROR_CODE(SQLITE_CORRUPT_SEQUENCE),
DECLARE_ERROR_CODE(SQLITE_LOCKED_VTAB),
#endif
#if SQLITE_VERSION_NUMBER >= 3025000
DECLARE_ERROR_CODE(SQLITE_CANTOPEN_DIRTYWAL),
DECLARE_ERROR_CODE(SQLITE_ERROR_SNAPSHOT),
#endif
#if SQLITE_VERSION_NUMBER >= 3031000
DECLARE_ERROR_CODE(SQLITE_CANTOPEN_SYMLINK),
DECLARE_ERROR_CODE(SQLITE_CONSTRAINT_PINNED),
DECLARE_ERROR_CODE(SQLITE_OK_SYMLINK),
#endif
#if SQLITE_VERSION_NUMBER >= 3032000
DECLARE_ERROR_CODE(SQLITE_BUSY_TIMEOUT),
DECLARE_ERROR_CODE(SQLITE_CORRUPT_INDEX),
DECLARE_ERROR_CODE(SQLITE_IOERR_DATA),
#endif
#if SQLITE_VERSION_NUMBER >= 3034000
DECLARE_ERROR_CODE(SQLITE_IOERR_CORRUPTFS),
#endif
#if SQLITE_VERSION_NUMBER >= 3037000
DECLARE_ERROR_CODE(SQLITE_CANTOPEN_EXISTS),
DECLARE_ERROR_CODE(SQLITE_CONSTRAINT_DATATYPE),
#endif
#undef DECLARE_ERROR_CODE
{NULL, 0},
Expand Down
5 changes: 3 additions & 2 deletions Modules/_sqlite/util.c
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,10 @@ _pysqlite_seterror(pysqlite_state *state, sqlite3 *db)
}

/* Create and set the exception. */
int extended_errcode = sqlite3_extended_errcode(db);
const char *errmsg = sqlite3_errmsg(db);
raise_exception(exc_class, errorcode, errmsg);
return errorcode;
raise_exception(exc_class, extended_errcode, errmsg);
return extended_errcode;
}

#ifdef WORDS_BIGENDIAN
Expand Down