mirror of https://github.com/python/cpython
bpo-24139: Add support for SQLite extended result codes (GH-28076)
This commit is contained in:
parent
a459a81530
commit
456e27ac0a
|
@ -242,11 +242,12 @@ sqlite3
|
|||
now raise :exc:`UnicodeEncodeError` instead of :exc:`sqlite3.ProgrammingError`.
|
||||
(Contributed by Erlend E. Aasland in :issue:`44688`.)
|
||||
|
||||
* :mod:`sqlite3` exceptions now include the SQLite error code as
|
||||
* :mod:`sqlite3` exceptions now include the SQLite extended error code as
|
||||
:attr:`~sqlite3.Error.sqlite_errorcode` and the SQLite error name as
|
||||
:attr:`~sqlite3.Error.sqlite_errorname`.
|
||||
(Contributed by Aviv Palivoda, Daniel Shahaf, and Erlend E. Aasland in
|
||||
:issue:`16379`.)
|
||||
:issue:`16379` and :issue:`24139`.)
|
||||
|
||||
|
||||
* Add :meth:`~sqlite3.Connection.setlimit` and
|
||||
:meth:`~sqlite3.Connection.getlimit` to :class:`sqlite3.Connection` for
|
||||
|
|
|
@ -157,6 +157,7 @@ class ModuleTests(unittest.TestCase):
|
|||
"SQLITE_PERM",
|
||||
"SQLITE_PRAGMA",
|
||||
"SQLITE_PROTOCOL",
|
||||
"SQLITE_RANGE",
|
||||
"SQLITE_READ",
|
||||
"SQLITE_READONLY",
|
||||
"SQLITE_REINDEX",
|
||||
|
@ -187,18 +188,142 @@ class ModuleTests(unittest.TestCase):
|
|||
if sqlite.sqlite_version_info >= (3, 8, 7):
|
||||
consts.append("SQLITE_LIMIT_WORKER_THREADS")
|
||||
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, 2):
|
||||
consts.append("SQLITE_CONSTRAINT_ROWID")
|
||||
if sqlite.version_info >= (3, 8, 3):
|
||||
consts.append("SQLITE_READONLY_DBMOVED")
|
||||
if sqlite.version_info >= (3, 8, 7):
|
||||
consts.append("SQLITE_AUTH_USER")
|
||||
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")
|
||||
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, 7, 16),
|
||||
"Requires SQLite 3.7.16 or newer")
|
||||
def test_extended_error_code_on_exception(self):
|
||||
with managed_connect(":memory:", in_mem=True) as con:
|
||||
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.
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Add support for SQLite extended result codes in :exc:`sqlite3.Error`. Patch
|
||||
by Erlend E. Aasland.
|
|
@ -280,7 +280,22 @@ static PyMethodDef module_methods[] = {
|
|||
{NULL, NULL}
|
||||
};
|
||||
|
||||
/* SQLite API error codes */
|
||||
/* SQLite C API result codes. See also:
|
||||
* - https://www.sqlite.org/c3ref/c_abort_rollback.html
|
||||
* - https://sqlite.org/changes.html#version_3_3_8
|
||||
* - https://sqlite.org/changes.html#version_3_7_16
|
||||
* - https://sqlite.org/changes.html#version_3_7_17
|
||||
* - https://sqlite.org/changes.html#version_3_8_0
|
||||
* - https://sqlite.org/changes.html#version_3_8_3
|
||||
* - https://sqlite.org/changes.html#version_3_14
|
||||
*
|
||||
* Note: the SQLite changelogs rarely mention new result codes, so in order to
|
||||
* keep the 'error_codes' table in sync with SQLite, we must manually inspect
|
||||
* sqlite3.h for every release.
|
||||
*
|
||||
* We keep the SQLITE_VERSION_NUMBER checks in order to easily declutter the
|
||||
* code when we adjust the SQLite version requirement.
|
||||
*/
|
||||
static const struct {
|
||||
const char *name;
|
||||
long value;
|
||||
|
@ -311,6 +326,7 @@ static const struct {
|
|||
DECLARE_ERROR_CODE(SQLITE_OK),
|
||||
DECLARE_ERROR_CODE(SQLITE_PERM),
|
||||
DECLARE_ERROR_CODE(SQLITE_PROTOCOL),
|
||||
DECLARE_ERROR_CODE(SQLITE_RANGE),
|
||||
DECLARE_ERROR_CODE(SQLITE_READONLY),
|
||||
DECLARE_ERROR_CODE(SQLITE_ROW),
|
||||
DECLARE_ERROR_CODE(SQLITE_SCHEMA),
|
||||
|
@ -318,6 +334,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 >= 3008002
|
||||
DECLARE_ERROR_CODE(SQLITE_CONSTRAINT_ROWID),
|
||||
#endif
|
||||
#if SQLITE_VERSION_NUMBER >= 3008003
|
||||
DECLARE_ERROR_CODE(SQLITE_READONLY_DBMOVED),
|
||||
#endif
|
||||
#if SQLITE_VERSION_NUMBER >= 3008007
|
||||
DECLARE_ERROR_CODE(SQLITE_AUTH_USER),
|
||||
#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
|
||||
#undef DECLARE_ERROR_CODE
|
||||
{NULL, 0},
|
||||
|
|
|
@ -72,6 +72,8 @@ get_exception_class(pysqlite_state *state, int errorcode)
|
|||
return state->IntegrityError;
|
||||
case SQLITE_MISUSE:
|
||||
return state->ProgrammingError;
|
||||
case SQLITE_RANGE:
|
||||
return state->InterfaceError;
|
||||
default:
|
||||
return state->DatabaseError;
|
||||
}
|
||||
|
@ -139,9 +141,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
|
||||
|
|
Loading…
Reference in New Issue