mirror of
https://github.com/isar/rusqlite.git
synced 2024-11-26 11:31:37 +08:00
Merge pull request #104 from jgallagher/better-error-reporting
More robust error reporting.
This commit is contained in:
commit
bf2a63cc8d
@ -1,5 +1,11 @@
|
||||
# Version UPCOMING (TBD)
|
||||
|
||||
* BREAKING CHANGE: `SqliteError` is now an enum instead of a struct. Previously, we were (ab)using
|
||||
the error code and message to send back both underlying SQLite errors and errors that occurred
|
||||
at the Rust level. Now those have been separated out; SQLite errors are returned as
|
||||
`SqliteFailure` cases (which still include the error code but also include a Rust-friendlier
|
||||
enum as well), and rusqlite-level errors are captured in other cases. Because of this change,
|
||||
`SqliteError` no longer implements `PartialEq`.
|
||||
* BREAKING CHANGE: `SqliteTransactionDeferred`, `SqliteTransactionImmediate`, and
|
||||
`SqliteTransactionExclusive` are no longer exported. Instead, use
|
||||
`TransactionBehavior::Deferred`, `TransactionBehavior::Immediate`, and
|
||||
|
269
libsqlite3-sys/src/error.rs
Normal file
269
libsqlite3-sys/src/error.rs
Normal file
@ -0,0 +1,269 @@
|
||||
use libc::c_int;
|
||||
use std::error;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum ErrorCode {
|
||||
InternalMalfunction,
|
||||
PermissionDenied,
|
||||
OperationAborted,
|
||||
DatabaseBusy,
|
||||
DatabaseLocked,
|
||||
OutOfMemory,
|
||||
ReadOnly,
|
||||
OperationInterrupted,
|
||||
SystemIOFailure,
|
||||
DatabaseCorrupt,
|
||||
NotFound,
|
||||
DiskFull,
|
||||
CannotOpen,
|
||||
FileLockingProtocolFailed,
|
||||
SchemaChanged,
|
||||
TooBig,
|
||||
ConstraintViolation,
|
||||
TypeMismatch,
|
||||
APIMisuse,
|
||||
NoLargeFileSupport,
|
||||
AuthorizationForStatementDenied,
|
||||
ParameterOutOfRange,
|
||||
NotADatabase,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct Error {
|
||||
pub code: ErrorCode,
|
||||
pub extended_code: c_int,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn new(result_code: c_int) -> Error {
|
||||
let code = match result_code & 0xff {
|
||||
SQLITE_INTERNAL => ErrorCode::InternalMalfunction,
|
||||
SQLITE_PERM => ErrorCode::PermissionDenied,
|
||||
SQLITE_ABORT => ErrorCode::OperationAborted,
|
||||
SQLITE_BUSY => ErrorCode::DatabaseBusy,
|
||||
SQLITE_LOCKED => ErrorCode::DatabaseLocked,
|
||||
SQLITE_NOMEM => ErrorCode::OutOfMemory,
|
||||
SQLITE_READONLY => ErrorCode::ReadOnly,
|
||||
SQLITE_INTERRUPT => ErrorCode::OperationInterrupted,
|
||||
SQLITE_IOERR => ErrorCode::SystemIOFailure,
|
||||
SQLITE_CORRUPT => ErrorCode::DatabaseCorrupt,
|
||||
SQLITE_NOTFOUND => ErrorCode::NotFound,
|
||||
SQLITE_FULL => ErrorCode::DiskFull,
|
||||
SQLITE_CANTOPEN => ErrorCode::CannotOpen,
|
||||
SQLITE_PROTOCOL => ErrorCode::FileLockingProtocolFailed,
|
||||
SQLITE_SCHEMA => ErrorCode::SchemaChanged,
|
||||
SQLITE_TOOBIG => ErrorCode::TooBig,
|
||||
SQLITE_CONSTRAINT=> ErrorCode::ConstraintViolation,
|
||||
SQLITE_MISMATCH => ErrorCode::TypeMismatch,
|
||||
SQLITE_MISUSE => ErrorCode::APIMisuse,
|
||||
SQLITE_NOLFS => ErrorCode::NoLargeFileSupport,
|
||||
SQLITE_AUTH => ErrorCode::AuthorizationForStatementDenied,
|
||||
SQLITE_RANGE => ErrorCode::ParameterOutOfRange,
|
||||
SQLITE_NOTADB => ErrorCode::NotADatabase,
|
||||
_ => ErrorCode::Unknown,
|
||||
};
|
||||
|
||||
Error {
|
||||
code: code,
|
||||
extended_code: result_code,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "Error code {}: {}", self.extended_code, code_to_str(self.extended_code))
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error {
|
||||
fn description(&self) -> &str {
|
||||
code_to_str(self.extended_code)
|
||||
}
|
||||
}
|
||||
|
||||
// Result codes.
|
||||
|
||||
pub const SQLITE_OK : c_int = 0;
|
||||
pub const SQLITE_ERROR : c_int = 1;
|
||||
pub const SQLITE_INTERNAL : c_int = 2;
|
||||
pub const SQLITE_PERM : c_int = 3;
|
||||
pub const SQLITE_ABORT : c_int = 4;
|
||||
pub const SQLITE_BUSY : c_int = 5;
|
||||
pub const SQLITE_LOCKED : c_int = 6;
|
||||
pub const SQLITE_NOMEM : c_int = 7;
|
||||
pub const SQLITE_READONLY : c_int = 8;
|
||||
pub const SQLITE_INTERRUPT : c_int = 9;
|
||||
pub const SQLITE_IOERR : c_int = 10;
|
||||
pub const SQLITE_CORRUPT : c_int = 11;
|
||||
pub const SQLITE_NOTFOUND : c_int = 12;
|
||||
pub const SQLITE_FULL : c_int = 13;
|
||||
pub const SQLITE_CANTOPEN : c_int = 14;
|
||||
pub const SQLITE_PROTOCOL : c_int = 15;
|
||||
pub const SQLITE_EMPTY : c_int = 16;
|
||||
pub const SQLITE_SCHEMA : c_int = 17;
|
||||
pub const SQLITE_TOOBIG : c_int = 18;
|
||||
pub const SQLITE_CONSTRAINT: c_int = 19;
|
||||
pub const SQLITE_MISMATCH : c_int = 20;
|
||||
pub const SQLITE_MISUSE : c_int = 21;
|
||||
pub const SQLITE_NOLFS : c_int = 22;
|
||||
pub const SQLITE_AUTH : c_int = 23;
|
||||
pub const SQLITE_FORMAT : c_int = 24;
|
||||
pub const SQLITE_RANGE : c_int = 25;
|
||||
pub const SQLITE_NOTADB : c_int = 26;
|
||||
pub const SQLITE_NOTICE : c_int = 27;
|
||||
pub const SQLITE_WARNING : c_int = 28;
|
||||
pub const SQLITE_ROW : c_int = 100;
|
||||
pub const SQLITE_DONE : c_int = 101;
|
||||
|
||||
// Extended result codes.
|
||||
|
||||
pub const SQLITE_IOERR_READ : c_int = (SQLITE_IOERR | (1<<8));
|
||||
pub const SQLITE_IOERR_SHORT_READ : c_int = (SQLITE_IOERR | (2<<8));
|
||||
pub const SQLITE_IOERR_WRITE : c_int = (SQLITE_IOERR | (3<<8));
|
||||
pub const SQLITE_IOERR_FSYNC : c_int = (SQLITE_IOERR | (4<<8));
|
||||
pub const SQLITE_IOERR_DIR_FSYNC : c_int = (SQLITE_IOERR | (5<<8));
|
||||
pub const SQLITE_IOERR_TRUNCATE : c_int = (SQLITE_IOERR | (6<<8));
|
||||
pub const SQLITE_IOERR_FSTAT : c_int = (SQLITE_IOERR | (7<<8));
|
||||
pub const SQLITE_IOERR_UNLOCK : c_int = (SQLITE_IOERR | (8<<8));
|
||||
pub const SQLITE_IOERR_RDLOCK : c_int = (SQLITE_IOERR | (9<<8));
|
||||
pub const SQLITE_IOERR_DELETE : c_int = (SQLITE_IOERR | (10<<8));
|
||||
pub const SQLITE_IOERR_BLOCKED : c_int = (SQLITE_IOERR | (11<<8));
|
||||
pub const SQLITE_IOERR_NOMEM : c_int = (SQLITE_IOERR | (12<<8));
|
||||
pub const SQLITE_IOERR_ACCESS : c_int = (SQLITE_IOERR | (13<<8));
|
||||
pub const SQLITE_IOERR_CHECKRESERVEDLOCK : c_int = (SQLITE_IOERR | (14<<8));
|
||||
pub const SQLITE_IOERR_LOCK : c_int = (SQLITE_IOERR | (15<<8));
|
||||
pub const SQLITE_IOERR_CLOSE : c_int = (SQLITE_IOERR | (16<<8));
|
||||
pub const SQLITE_IOERR_DIR_CLOSE : c_int = (SQLITE_IOERR | (17<<8));
|
||||
pub const SQLITE_IOERR_SHMOPEN : c_int = (SQLITE_IOERR | (18<<8));
|
||||
pub const SQLITE_IOERR_SHMSIZE : c_int = (SQLITE_IOERR | (19<<8));
|
||||
pub const SQLITE_IOERR_SHMLOCK : c_int = (SQLITE_IOERR | (20<<8));
|
||||
pub const SQLITE_IOERR_SHMMAP : c_int = (SQLITE_IOERR | (21<<8));
|
||||
pub const SQLITE_IOERR_SEEK : c_int = (SQLITE_IOERR | (22<<8));
|
||||
pub const SQLITE_IOERR_DELETE_NOENT : c_int = (SQLITE_IOERR | (23<<8));
|
||||
pub const SQLITE_IOERR_MMAP : c_int = (SQLITE_IOERR | (24<<8));
|
||||
pub const SQLITE_IOERR_GETTEMPPATH : c_int = (SQLITE_IOERR | (25<<8));
|
||||
pub const SQLITE_IOERR_CONVPATH : c_int = (SQLITE_IOERR | (26<<8));
|
||||
pub const SQLITE_IOERR_VNODE : c_int = (SQLITE_IOERR | (27<<8));
|
||||
pub const SQLITE_LOCKED_SHAREDCACHE : c_int = (SQLITE_LOCKED | (1<<8));
|
||||
pub const SQLITE_BUSY_RECOVERY : c_int = (SQLITE_BUSY | (1<<8));
|
||||
pub const SQLITE_BUSY_SNAPSHOT : c_int = (SQLITE_BUSY | (2<<8));
|
||||
pub const SQLITE_CANTOPEN_NOTEMPDIR : c_int = (SQLITE_CANTOPEN | (1<<8));
|
||||
pub const SQLITE_CANTOPEN_ISDIR : c_int = (SQLITE_CANTOPEN | (2<<8));
|
||||
pub const SQLITE_CANTOPEN_FULLPATH : c_int = (SQLITE_CANTOPEN | (3<<8));
|
||||
pub const SQLITE_CANTOPEN_CONVPATH : c_int = (SQLITE_CANTOPEN | (4<<8));
|
||||
pub const SQLITE_CORRUPT_VTAB : c_int = (SQLITE_CORRUPT | (1<<8));
|
||||
pub const SQLITE_READONLY_RECOVERY : c_int = (SQLITE_READONLY | (1<<8));
|
||||
pub const SQLITE_READONLY_CANTLOCK : c_int = (SQLITE_READONLY | (2<<8));
|
||||
pub const SQLITE_READONLY_ROLLBACK : c_int = (SQLITE_READONLY | (3<<8));
|
||||
pub const SQLITE_READONLY_DBMOVED : c_int = (SQLITE_READONLY | (4<<8));
|
||||
pub const SQLITE_ABORT_ROLLBACK : c_int = (SQLITE_ABORT | (2<<8));
|
||||
pub const SQLITE_CONSTRAINT_CHECK : c_int = (SQLITE_CONSTRAINT | (1<<8));
|
||||
pub const SQLITE_CONSTRAINT_COMMITHOOK : c_int = (SQLITE_CONSTRAINT | (2<<8));
|
||||
pub const SQLITE_CONSTRAINT_FOREIGNKEY : c_int = (SQLITE_CONSTRAINT | (3<<8));
|
||||
pub const SQLITE_CONSTRAINT_FUNCTION : c_int = (SQLITE_CONSTRAINT | (4<<8));
|
||||
pub const SQLITE_CONSTRAINT_NOTNULL : c_int = (SQLITE_CONSTRAINT | (5<<8));
|
||||
pub const SQLITE_CONSTRAINT_PRIMARYKEY : c_int = (SQLITE_CONSTRAINT | (6<<8));
|
||||
pub const SQLITE_CONSTRAINT_TRIGGER : c_int = (SQLITE_CONSTRAINT | (7<<8));
|
||||
pub const SQLITE_CONSTRAINT_UNIQUE : c_int = (SQLITE_CONSTRAINT | (8<<8));
|
||||
pub const SQLITE_CONSTRAINT_VTAB : c_int = (SQLITE_CONSTRAINT | (9<<8));
|
||||
pub const SQLITE_CONSTRAINT_ROWID : c_int = (SQLITE_CONSTRAINT |(10<<8));
|
||||
pub const SQLITE_NOTICE_RECOVER_WAL : c_int = (SQLITE_NOTICE | (1<<8));
|
||||
pub const SQLITE_NOTICE_RECOVER_ROLLBACK : c_int = (SQLITE_NOTICE | (2<<8));
|
||||
pub const SQLITE_WARNING_AUTOINDEX : c_int = (SQLITE_WARNING | (1<<8));
|
||||
pub const SQLITE_AUTH_USER : c_int = (SQLITE_AUTH | (1<<8));
|
||||
|
||||
pub fn code_to_str(code: c_int) -> &'static str {
|
||||
match code {
|
||||
SQLITE_OK => "Successful result",
|
||||
SQLITE_ERROR => "SQL error or missing database",
|
||||
SQLITE_INTERNAL => "Internal logic error in SQLite",
|
||||
SQLITE_PERM => "Access permission denied",
|
||||
SQLITE_ABORT => "Callback routine requested an abort",
|
||||
SQLITE_BUSY => "The database file is locked",
|
||||
SQLITE_LOCKED => "A table in the database is locked",
|
||||
SQLITE_NOMEM => "A malloc() failed",
|
||||
SQLITE_READONLY => "Attempt to write a readonly database",
|
||||
SQLITE_INTERRUPT => "Operation terminated by sqlite3_interrupt()",
|
||||
SQLITE_IOERR => "Some kind of disk I/O error occurred",
|
||||
SQLITE_CORRUPT => "The database disk image is malformed",
|
||||
SQLITE_NOTFOUND => "Unknown opcode in sqlite3_file_control()",
|
||||
SQLITE_FULL => "Insertion failed because database is full",
|
||||
SQLITE_CANTOPEN => "Unable to open the database file",
|
||||
SQLITE_PROTOCOL => "Database lock protocol error",
|
||||
SQLITE_EMPTY => "Database is empty",
|
||||
SQLITE_SCHEMA => "The database schema changed",
|
||||
SQLITE_TOOBIG => "String or BLOB exceeds size limit",
|
||||
SQLITE_CONSTRAINT=> "Abort due to constraint violation",
|
||||
SQLITE_MISMATCH => "Data type mismatch",
|
||||
SQLITE_MISUSE => "Library used incorrectly",
|
||||
SQLITE_NOLFS => "Uses OS features not supported on host",
|
||||
SQLITE_AUTH => "Authorization denied",
|
||||
SQLITE_FORMAT => "Auxiliary database format error",
|
||||
SQLITE_RANGE => "2nd parameter to sqlite3_bind out of range",
|
||||
SQLITE_NOTADB => "File opened that is not a database file",
|
||||
SQLITE_NOTICE => "Notifications from sqlite3_log()",
|
||||
SQLITE_WARNING => "Warnings from sqlite3_log()",
|
||||
SQLITE_ROW => "sqlite3_step() has another row ready",
|
||||
SQLITE_DONE => "sqlite3_step() has finished executing",
|
||||
|
||||
SQLITE_IOERR_READ => "Error reading from disk",
|
||||
SQLITE_IOERR_SHORT_READ => "Unable to obtain number of requested bytes (file truncated?)",
|
||||
SQLITE_IOERR_WRITE => "Error writing to disk",
|
||||
SQLITE_IOERR_FSYNC => "Error flushing data to persistent storage (fsync)",
|
||||
SQLITE_IOERR_DIR_FSYNC => "Error calling fsync on a directory",
|
||||
SQLITE_IOERR_TRUNCATE => "Error attempting to truncate file",
|
||||
SQLITE_IOERR_FSTAT => "Error invoking fstat to get file metadata",
|
||||
SQLITE_IOERR_UNLOCK => "I/O error within xUnlock of a VFS object",
|
||||
SQLITE_IOERR_RDLOCK => "I/O error within xLock of a VFS object (trying to obtain a read lock)",
|
||||
SQLITE_IOERR_DELETE => "I/O error within xDelete of a VFS object",
|
||||
SQLITE_IOERR_BLOCKED => "SQLITE_IOERR_BLOCKED", // no longer used
|
||||
SQLITE_IOERR_NOMEM => "Out of memory in I/O layer",
|
||||
SQLITE_IOERR_ACCESS => "I/O error within xAccess of a VFS object",
|
||||
SQLITE_IOERR_CHECKRESERVEDLOCK => "I/O error within then xCheckReservedLock method",
|
||||
SQLITE_IOERR_LOCK => "I/O error in the advisory file locking layer",
|
||||
SQLITE_IOERR_CLOSE => "I/O error within the xClose method",
|
||||
SQLITE_IOERR_DIR_CLOSE => "SQLITE_IOERR_DIR_CLOSE", // no longer used
|
||||
SQLITE_IOERR_SHMOPEN => "I/O error within the xShmMap method (trying to open a new shared-memory segment)",
|
||||
SQLITE_IOERR_SHMSIZE => "I/O error within the xShmMap method (trying to resize an existing shared-memory segment)",
|
||||
SQLITE_IOERR_SHMLOCK => "SQLITE_IOERR_SHMLOCK", // no longer used
|
||||
SQLITE_IOERR_SHMMAP => "I/O error within the xShmMap method (trying to map a shared-memory segment into process address space)",
|
||||
SQLITE_IOERR_SEEK => "I/O error within the xRead or xWrite (trying to seek within a file)",
|
||||
SQLITE_IOERR_DELETE_NOENT => "File being deleted does not exist",
|
||||
SQLITE_IOERR_MMAP => "I/O error while trying to map or unmap part of the database file into process address space",
|
||||
SQLITE_IOERR_GETTEMPPATH => "VFS is unable to determine a suitable directory for temporary files",
|
||||
SQLITE_IOERR_CONVPATH => "cygwin_conv_path() system call failed",
|
||||
SQLITE_IOERR_VNODE => "SQLITE_IOERR_VNODE", // not documented?
|
||||
SQLITE_LOCKED_SHAREDCACHE => "Locking conflict due to another connection with a shared cache",
|
||||
SQLITE_BUSY_RECOVERY => "Another process is recovering a WAL mode database file",
|
||||
SQLITE_BUSY_SNAPSHOT => "Cannot promote read transaction to write transaction because of writes by another connection",
|
||||
SQLITE_CANTOPEN_NOTEMPDIR => "SQLITE_CANTOPEN_NOTEMPDIR", // no longer used
|
||||
SQLITE_CANTOPEN_ISDIR => "Attempted to open directory as file",
|
||||
SQLITE_CANTOPEN_FULLPATH => "Unable to convert filename into full pathname",
|
||||
SQLITE_CANTOPEN_CONVPATH => "cygwin_conv_path() system call failed",
|
||||
SQLITE_CORRUPT_VTAB => "Content in the virtual table is corrupt",
|
||||
SQLITE_READONLY_RECOVERY => "WAL mode database file needs recovery (requires write access)",
|
||||
SQLITE_READONLY_CANTLOCK => "Shared-memory file associated with WAL mode database is read-only",
|
||||
SQLITE_READONLY_ROLLBACK => "Database has hot journal that must be rolled back (requires write access)",
|
||||
SQLITE_READONLY_DBMOVED => "Database cannot be modified because database file has moved",
|
||||
SQLITE_ABORT_ROLLBACK => "Transaction was rolled back",
|
||||
SQLITE_CONSTRAINT_CHECK => "A CHECK constraint failed",
|
||||
SQLITE_CONSTRAINT_COMMITHOOK => "Commit hook caused rollback",
|
||||
SQLITE_CONSTRAINT_FOREIGNKEY => "Foreign key constraint failed",
|
||||
SQLITE_CONSTRAINT_FUNCTION => "Error returned from extension function",
|
||||
SQLITE_CONSTRAINT_NOTNULL => "A NOT NULL constraint failed",
|
||||
SQLITE_CONSTRAINT_PRIMARYKEY => "A PRIMARY KEY constraint failed",
|
||||
SQLITE_CONSTRAINT_TRIGGER => "A RAISE function within a trigger fired",
|
||||
SQLITE_CONSTRAINT_UNIQUE => "A UNIQUE constraint failed",
|
||||
SQLITE_CONSTRAINT_VTAB => "An application-defined virtual table error occurred",
|
||||
SQLITE_CONSTRAINT_ROWID => "A non-unique rowid occurred",
|
||||
SQLITE_NOTICE_RECOVER_WAL => "A WAL mode database file was recovered",
|
||||
SQLITE_NOTICE_RECOVER_ROLLBACK => "Hot journal was rolled back",
|
||||
SQLITE_WARNING_AUTOINDEX => "Automatic indexing used - database might benefit from additional indexes",
|
||||
SQLITE_AUTH_USER => "SQLITE_AUTH_USER", // not documented?
|
||||
|
||||
_ => "Unknown error code",
|
||||
}
|
||||
}
|
@ -5,43 +5,13 @@
|
||||
extern crate libc;
|
||||
|
||||
pub use self::bindgen::*;
|
||||
pub use self::error::*;
|
||||
|
||||
use std::mem;
|
||||
use libc::c_int;
|
||||
|
||||
mod bindgen;
|
||||
|
||||
pub const SQLITE_OK : c_int = 0;
|
||||
pub const SQLITE_ERROR : c_int = 1;
|
||||
pub const SQLITE_INTERNAL : c_int = 2;
|
||||
pub const SQLITE_PERM : c_int = 3;
|
||||
pub const SQLITE_ABORT : c_int = 4;
|
||||
pub const SQLITE_BUSY : c_int = 5;
|
||||
pub const SQLITE_LOCKED : c_int = 6;
|
||||
pub const SQLITE_NOMEM : c_int = 7;
|
||||
pub const SQLITE_READONLY : c_int = 8;
|
||||
pub const SQLITE_INTERRUPT : c_int = 9;
|
||||
pub const SQLITE_IOERR : c_int = 10;
|
||||
pub const SQLITE_CORRUPT : c_int = 11;
|
||||
pub const SQLITE_NOTFOUND : c_int = 12;
|
||||
pub const SQLITE_FULL : c_int = 13;
|
||||
pub const SQLITE_CANTOPEN : c_int = 14;
|
||||
pub const SQLITE_PROTOCOL : c_int = 15;
|
||||
pub const SQLITE_EMPTY : c_int = 16;
|
||||
pub const SQLITE_SCHEMA : c_int = 17;
|
||||
pub const SQLITE_TOOBIG : c_int = 18;
|
||||
pub const SQLITE_CONSTRAINT: c_int = 19;
|
||||
pub const SQLITE_MISMATCH : c_int = 20;
|
||||
pub const SQLITE_MISUSE : c_int = 21;
|
||||
pub const SQLITE_NOLFS : c_int = 22;
|
||||
pub const SQLITE_AUTH : c_int = 23;
|
||||
pub const SQLITE_FORMAT : c_int = 24;
|
||||
pub const SQLITE_RANGE : c_int = 25;
|
||||
pub const SQLITE_NOTADB : c_int = 26;
|
||||
pub const SQLITE_NOTICE : c_int = 27;
|
||||
pub const SQLITE_WARNING : c_int = 28;
|
||||
pub const SQLITE_ROW : c_int = 100;
|
||||
pub const SQLITE_DONE : c_int = 101;
|
||||
mod error;
|
||||
|
||||
// SQLite datatype constants.
|
||||
pub const SQLITE_INTEGER : c_int = 1;
|
||||
@ -58,43 +28,6 @@ pub fn SQLITE_TRANSIENT() -> sqlite3_destructor_type {
|
||||
Some(unsafe { mem::transmute(-1isize) })
|
||||
}
|
||||
|
||||
pub fn code_to_str(code: c_int) -> &'static str {
|
||||
match code {
|
||||
SQLITE_OK => "Successful result",
|
||||
SQLITE_ERROR => "SQL error or missing database",
|
||||
SQLITE_INTERNAL => "Internal logic error in SQLite",
|
||||
SQLITE_PERM => "Access permission denied",
|
||||
SQLITE_ABORT => "Callback routine requested an abort",
|
||||
SQLITE_BUSY => "The database file is locked",
|
||||
SQLITE_LOCKED => "A table in the database is locked",
|
||||
SQLITE_NOMEM => "A malloc() failed",
|
||||
SQLITE_READONLY => "Attempt to write a readonly database",
|
||||
SQLITE_INTERRUPT => "Operation terminated by sqlite3_interrupt()",
|
||||
SQLITE_IOERR => "Some kind of disk I/O error occurred",
|
||||
SQLITE_CORRUPT => "The database disk image is malformed",
|
||||
SQLITE_NOTFOUND => "Unknown opcode in sqlite3_file_control()",
|
||||
SQLITE_FULL => "Insertion failed because database is full",
|
||||
SQLITE_CANTOPEN => "Unable to open the database file",
|
||||
SQLITE_PROTOCOL => "Database lock protocol error",
|
||||
SQLITE_EMPTY => "Database is empty",
|
||||
SQLITE_SCHEMA => "The database schema changed",
|
||||
SQLITE_TOOBIG => "String or BLOB exceeds size limit",
|
||||
SQLITE_CONSTRAINT=> "Abort due to constraint violation",
|
||||
SQLITE_MISMATCH => "Data type mismatch",
|
||||
SQLITE_MISUSE => "Library used incorrectly",
|
||||
SQLITE_NOLFS => "Uses OS features not supported on host",
|
||||
SQLITE_AUTH => "Authorization denied",
|
||||
SQLITE_FORMAT => "Auxiliary database format error",
|
||||
SQLITE_RANGE => "2nd parameter to sqlite3_bind out of range",
|
||||
SQLITE_NOTADB => "File opened that is not a database file",
|
||||
SQLITE_NOTICE => "Notifications from sqlite3_log()",
|
||||
SQLITE_WARNING => "Warnings from sqlite3_log()",
|
||||
SQLITE_ROW => "sqlite3_step() has another row ready",
|
||||
SQLITE_DONE => "sqlite3_step() has finished executing",
|
||||
_ => "Unknown error code",
|
||||
}
|
||||
}
|
||||
|
||||
pub const SQLITE_CONFIG_LOG : c_int = 16;
|
||||
pub const SQLITE_UTF8 : c_int = 1;
|
||||
pub const SQLITE_DETERMINISTIC : c_int = 0x800;
|
||||
|
@ -36,7 +36,8 @@ use std::time::Duration;
|
||||
|
||||
use ffi;
|
||||
|
||||
use {DatabaseName, Connection, Error, Result};
|
||||
use {DatabaseName, Connection, Result};
|
||||
use error::{error_from_sqlite_code, error_from_handle};
|
||||
|
||||
impl Connection {
|
||||
/// Back up the `name` database to the given destination path.
|
||||
@ -70,8 +71,8 @@ impl Connection {
|
||||
|
||||
match r {
|
||||
Done => Ok(()),
|
||||
Busy => Err(Error::from_handle(ptr::null_mut(), ffi::SQLITE_BUSY)),
|
||||
Locked => Err(Error::from_handle(ptr::null_mut(), ffi::SQLITE_LOCKED)),
|
||||
Busy => Err(error_from_handle(ptr::null_mut(), ffi::SQLITE_BUSY)),
|
||||
Locked => Err(error_from_handle(ptr::null_mut(), ffi::SQLITE_LOCKED)),
|
||||
More => unreachable!(),
|
||||
}
|
||||
}
|
||||
@ -115,8 +116,8 @@ impl Connection {
|
||||
|
||||
match r {
|
||||
Done => Ok(()),
|
||||
Busy => Err(Error::from_handle(ptr::null_mut(), ffi::SQLITE_BUSY)),
|
||||
Locked => Err(Error::from_handle(ptr::null_mut(), ffi::SQLITE_LOCKED)),
|
||||
Busy => Err(error_from_handle(ptr::null_mut(), ffi::SQLITE_BUSY)),
|
||||
Locked => Err(error_from_handle(ptr::null_mut(), ffi::SQLITE_LOCKED)),
|
||||
More => unreachable!(),
|
||||
}
|
||||
}
|
||||
@ -201,7 +202,7 @@ impl<'a, 'b> Backup<'a, 'b> {
|
||||
from.db.borrow_mut().db,
|
||||
from_name.as_ptr());
|
||||
if b.is_null() {
|
||||
return Err(Error::from_handle(to_db, ffi::sqlite3_errcode(to_db)));
|
||||
return Err(error_from_handle(to_db, ffi::sqlite3_errcode(to_db)));
|
||||
}
|
||||
b
|
||||
};
|
||||
@ -244,12 +245,7 @@ impl<'a, 'b> Backup<'a, 'b> {
|
||||
ffi::SQLITE_OK => Ok(More),
|
||||
ffi::SQLITE_BUSY => Ok(Busy),
|
||||
ffi::SQLITE_LOCKED => Ok(Locked),
|
||||
rc => {
|
||||
Err(Error {
|
||||
code: rc,
|
||||
message: ffi::code_to_str(rc).into(),
|
||||
})
|
||||
}
|
||||
_ => Err(error_from_sqlite_code(rc, None)),
|
||||
}
|
||||
}
|
||||
|
||||
|
157
src/error.rs
Normal file
157
src/error.rs
Normal file
@ -0,0 +1,157 @@
|
||||
use std::error;
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
use std::str;
|
||||
use libc::c_int;
|
||||
use {ffi, errmsg_to_string};
|
||||
|
||||
/// Old name for `Error`. `SqliteError` is deprecated.
|
||||
pub type SqliteError = Error;
|
||||
|
||||
/// Enum listing possible errors from rusqlite.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// An error from an underlying SQLite call.
|
||||
SqliteFailure(ffi::Error, Option<String>),
|
||||
|
||||
/// An error case available for implementors of the `FromSql` trait.
|
||||
FromSqlConversionFailure(Box<error::Error + Send + Sync>),
|
||||
|
||||
/// Error converting a string to UTF-8.
|
||||
Utf8Error(str::Utf8Error),
|
||||
|
||||
/// Error converting a string to a C-compatible string because it contained an embedded nul.
|
||||
NulError(::std::ffi::NulError),
|
||||
|
||||
/// Error when using SQL named parameters and passing a parameter name not present in the SQL.
|
||||
InvalidParameterName(String),
|
||||
|
||||
/// Error converting a file path to a string.
|
||||
InvalidPath(PathBuf),
|
||||
|
||||
/// Error returned when an `execute` call returns rowss.
|
||||
ExecuteReturnedResults,
|
||||
|
||||
/// Error when a query that was expected to return at least one row (e.g., for `query_row`)
|
||||
/// did not return any.
|
||||
QueryReturnedNoRows,
|
||||
|
||||
/// Error when trying to access a `Row` after stepping past it. See the discussion on
|
||||
/// the `Rows` type for more details.
|
||||
GetFromStaleRow,
|
||||
|
||||
/// Error when the value of a particular column is requested, but the index is out of range
|
||||
/// for the statement.
|
||||
InvalidColumnIndex(c_int),
|
||||
|
||||
/// Error when the value of a particular column is requested, but the type of the result in
|
||||
/// that column cannot be converted to the requested Rust type.
|
||||
InvalidColumnType,
|
||||
|
||||
/// Error returned by `functions::Context::get` when the function argument cannot be converted
|
||||
/// to the requested type.
|
||||
#[cfg(feature = "functions")]
|
||||
InvalidFunctionParameterType,
|
||||
|
||||
/// An error case available for implementors of custom user functions (e.g.,
|
||||
/// `create_scalar_function`).
|
||||
#[cfg(feature = "functions")]
|
||||
#[allow(dead_code)]
|
||||
UserFunctionError(Box<error::Error + Send + Sync>),
|
||||
}
|
||||
|
||||
impl From<str::Utf8Error> for Error {
|
||||
fn from(err: str::Utf8Error) -> Error {
|
||||
Error::Utf8Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<::std::ffi::NulError> for Error {
|
||||
fn from(err: ::std::ffi::NulError) -> Error {
|
||||
Error::NulError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
&Error::SqliteFailure(ref err, None) => err.fmt(f),
|
||||
&Error::SqliteFailure(_, Some(ref s)) => write!(f, "{}", s),
|
||||
&Error::FromSqlConversionFailure(ref err) => err.fmt(f),
|
||||
&Error::Utf8Error(ref err) => err.fmt(f),
|
||||
&Error::NulError(ref err) => err.fmt(f),
|
||||
&Error::InvalidParameterName(ref name) => write!(f, "Invalid parameter name: {}", name),
|
||||
&Error::InvalidPath(ref p) => write!(f, "Invalid path: {}", p.to_string_lossy()),
|
||||
&Error::ExecuteReturnedResults => write!(f, "Execute returned results - did you mean to call query?"),
|
||||
&Error::QueryReturnedNoRows => write!(f, "Query returned no rows"),
|
||||
&Error::GetFromStaleRow => write!(f, "Attempted to get a value from a stale row"),
|
||||
&Error::InvalidColumnIndex(i) => write!(f, "Invalid column index: {}", i),
|
||||
&Error::InvalidColumnType => write!(f, "Invalid column type"),
|
||||
|
||||
#[cfg(feature = "functions")]
|
||||
&Error::InvalidFunctionParameterType => write!(f, "Invalid function parameter type"),
|
||||
#[cfg(feature = "functions")]
|
||||
&Error::UserFunctionError(ref err) => err.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error {
|
||||
fn description(&self) -> &str {
|
||||
match self {
|
||||
&Error::SqliteFailure(ref err, None) => err.description(),
|
||||
&Error::SqliteFailure(_, Some(ref s)) => s,
|
||||
&Error::FromSqlConversionFailure(ref err) => err.description(),
|
||||
&Error::Utf8Error(ref err) => err.description(),
|
||||
&Error::InvalidParameterName(_) => "invalid parameter name",
|
||||
&Error::NulError(ref err) => err.description(),
|
||||
&Error::InvalidPath(_) => "invalid path",
|
||||
&Error::ExecuteReturnedResults => "execute returned results - did you mean to call query?",
|
||||
&Error::QueryReturnedNoRows => "query returned no rows",
|
||||
&Error::GetFromStaleRow => "attempted to get a value from a stale row",
|
||||
&Error::InvalidColumnIndex(_) => "invalid column index",
|
||||
&Error::InvalidColumnType => "invalid column type",
|
||||
|
||||
#[cfg(feature = "functions")]
|
||||
&Error::InvalidFunctionParameterType => "invalid function parameter type",
|
||||
#[cfg(feature = "functions")]
|
||||
&Error::UserFunctionError(ref err) => err.description(),
|
||||
}
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&error::Error> {
|
||||
match self {
|
||||
&Error::SqliteFailure(ref err, _) => Some(err),
|
||||
&Error::FromSqlConversionFailure(ref err) => Some(&**err),
|
||||
&Error::Utf8Error(ref err) => Some(err),
|
||||
&Error::NulError(ref err) => Some(err),
|
||||
&Error::InvalidParameterName(_) => None,
|
||||
&Error::InvalidPath(_) => None,
|
||||
&Error::ExecuteReturnedResults => None,
|
||||
&Error::QueryReturnedNoRows => None,
|
||||
&Error::GetFromStaleRow => None,
|
||||
&Error::InvalidColumnIndex(_) => None,
|
||||
&Error::InvalidColumnType => None,
|
||||
|
||||
#[cfg(feature = "functions")]
|
||||
&Error::InvalidFunctionParameterType => None,
|
||||
#[cfg(feature = "functions")]
|
||||
&Error::UserFunctionError(ref err) => Some(&**err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// These are public but not re-exported by lib.rs, so only visible within crate.
|
||||
|
||||
pub fn error_from_sqlite_code(code: c_int, message: Option<String>) -> Error {
|
||||
Error::SqliteFailure(ffi::Error::new(code), message)
|
||||
}
|
||||
|
||||
pub fn error_from_handle(db: *mut ffi::Struct_sqlite3, code: c_int) -> Error {
|
||||
let message = if db.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(unsafe { errmsg_to_string(ffi::sqlite3_errmsg(db)) })
|
||||
};
|
||||
error_from_sqlite_code(code, message)
|
||||
}
|
@ -26,11 +26,10 @@
|
||||
//! match entry {
|
||||
//! Occupied(occ) => occ.into_mut(),
|
||||
//! Vacant(vac) => {
|
||||
//! let r = try!(Regex::new(®ex_s).map_err(|e| Error {
|
||||
//! code: libsqlite3_sys::SQLITE_ERROR,
|
||||
//! message: format!("Invalid regular expression: {}", e),
|
||||
//! }));
|
||||
//! vac.insert(r)
|
||||
//! match Regex::new(®ex_s) {
|
||||
//! Ok(r) => vac.insert(r),
|
||||
//! Err(err) => return Err(Error::UserFunctionError(Box::new(err))),
|
||||
//! }
|
||||
//! }
|
||||
//! }
|
||||
//! };
|
||||
@ -50,6 +49,7 @@
|
||||
//! assert!(is_match);
|
||||
//! }
|
||||
//! ```
|
||||
use std::error::Error as StdError;
|
||||
use std::ffi::CStr;
|
||||
use std::mem;
|
||||
use std::ptr;
|
||||
@ -225,14 +225,8 @@ impl FromValue for String {
|
||||
Ok("".to_string())
|
||||
} else {
|
||||
let c_slice = CStr::from_ptr(c_text as *const c_char).to_bytes();
|
||||
let utf8_str = str::from_utf8(c_slice);
|
||||
utf8_str.map(|s| s.to_string())
|
||||
.map_err(|e| {
|
||||
Error {
|
||||
code: 0,
|
||||
message: e.to_string(),
|
||||
}
|
||||
})
|
||||
let utf8_str = try!(str::from_utf8(c_slice));
|
||||
Ok(utf8_str.into())
|
||||
}
|
||||
}
|
||||
|
||||
@ -302,10 +296,7 @@ impl<'a> Context<'a> {
|
||||
if T::parameter_has_valid_sqlite_type(arg) {
|
||||
T::parameter_value(arg)
|
||||
} else {
|
||||
Err(Error {
|
||||
code: ffi::SQLITE_MISMATCH,
|
||||
message: "Invalid value type".to_string(),
|
||||
})
|
||||
Err(Error::InvalidFunctionParameterType)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -421,9 +412,15 @@ impl InnerConnection {
|
||||
assert!(!boxed_f.is_null(), "Internal error - null function pointer");
|
||||
match (*boxed_f)(&ctx) {
|
||||
Ok(r) => r.set_result(ctx.ctx),
|
||||
Err(e) => {
|
||||
ffi::sqlite3_result_error_code(ctx.ctx, e.code);
|
||||
if let Ok(cstr) = str_to_cstring(&e.message) {
|
||||
Err(Error::SqliteFailure(err, s)) => {
|
||||
ffi::sqlite3_result_error_code(ctx.ctx, err.extended_code);
|
||||
if let Some(Ok(cstr)) = s.map(|s| str_to_cstring(&s)) {
|
||||
ffi::sqlite3_result_error(ctx.ctx, cstr.as_ptr(), -1);
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
ffi::sqlite3_result_error_code(ctx.ctx, ffi::SQLITE_CONSTRAINT_FUNCTION);
|
||||
if let Ok(cstr) = str_to_cstring(err.description()) {
|
||||
ffi::sqlite3_result_error(ctx.ctx, cstr.as_ptr(), -1);
|
||||
}
|
||||
}
|
||||
@ -476,7 +473,6 @@ mod test {
|
||||
use self::regex::Regex;
|
||||
|
||||
use {Connection, Error, Result};
|
||||
use ffi;
|
||||
use functions::Context;
|
||||
|
||||
fn half(ctx: &Context) -> Result<c_double> {
|
||||
@ -516,13 +512,10 @@ mod test {
|
||||
let new_re = match saved_re {
|
||||
None => {
|
||||
let s = try!(ctx.get::<String>(0));
|
||||
let r = try!(Regex::new(&s).map_err(|e| {
|
||||
Error {
|
||||
code: ffi::SQLITE_ERROR,
|
||||
message: format!("Invalid regular expression: {}", e),
|
||||
match Regex::new(&s) {
|
||||
Ok(r) => Some(r),
|
||||
Err(err) => return Err(Error::UserFunctionError(Box::new(err))),
|
||||
}
|
||||
}));
|
||||
Some(r)
|
||||
}
|
||||
Some(_) => None,
|
||||
};
|
||||
@ -591,11 +584,10 @@ mod test {
|
||||
match entry {
|
||||
Occupied(occ) => occ.into_mut(),
|
||||
Vacant(vac) => {
|
||||
let r = try!(Regex::new(®ex_s).map_err(|e| Error {
|
||||
code: ffi::SQLITE_ERROR,
|
||||
message: format!("Invalid regular expression: {}", e),
|
||||
}));
|
||||
vac.insert(r)
|
||||
match Regex::new(®ex_s) {
|
||||
Ok(r) => vac.insert(r),
|
||||
Err(err) => return Err(Error::UserFunctionError(Box::new(err))),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
237
src/lib.rs
237
src/lib.rs
@ -64,7 +64,6 @@ use std::mem;
|
||||
use std::ptr;
|
||||
use std::fmt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::error;
|
||||
use std::rc::Rc;
|
||||
use std::cell::{RefCell, Cell};
|
||||
use std::ffi::{CStr, CString};
|
||||
@ -73,8 +72,10 @@ use std::str;
|
||||
use libc::{c_int, c_char};
|
||||
|
||||
use types::{ToSql, FromSql};
|
||||
use error::{error_from_sqlite_code, error_from_handle};
|
||||
|
||||
pub use transaction::{SqliteTransaction, Transaction, TransactionBehavior};
|
||||
pub use error::{SqliteError, Error};
|
||||
|
||||
#[cfg(feature = "load_extension")]
|
||||
pub use load_extension_guard::{SqliteLoadExtensionGuard, LoadExtensionGuard};
|
||||
@ -82,6 +83,7 @@ pub use load_extension_guard::{SqliteLoadExtensionGuard, LoadExtensionGuard};
|
||||
pub mod types;
|
||||
mod transaction;
|
||||
mod named_params;
|
||||
mod error;
|
||||
#[cfg(feature = "load_extension")]mod load_extension_guard;
|
||||
#[cfg(feature = "trace")]pub mod trace;
|
||||
#[cfg(feature = "backup")]pub mod backup;
|
||||
@ -100,62 +102,12 @@ unsafe fn errmsg_to_string(errmsg: *const c_char) -> String {
|
||||
utf8_str.unwrap_or("Invalid string encoding").to_string()
|
||||
}
|
||||
|
||||
/// Old name for `Error`. `SqliteError` is deprecated.
|
||||
pub type SqliteError = Error;
|
||||
|
||||
/// Encompasses an error result from a call to the SQLite C API.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Error {
|
||||
/// The error code returned by a SQLite C API call. See [SQLite Result
|
||||
/// Codes](http://www.sqlite.org/rescode.html) for details.
|
||||
pub code: c_int,
|
||||
|
||||
/// The error message provided by [sqlite3_errmsg](http://www.sqlite.org/c3ref/errcode.html),
|
||||
/// if possible, or a generic error message based on `code` otherwise.
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{} (SQLite error {})", self.message, self.code)
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error {
|
||||
fn description(&self) -> &str {
|
||||
&self.message
|
||||
}
|
||||
}
|
||||
|
||||
impl Error {
|
||||
fn from_handle(db: *mut ffi::Struct_sqlite3, code: c_int) -> Error {
|
||||
let message = if db.is_null() {
|
||||
ffi::code_to_str(code).to_string()
|
||||
} else {
|
||||
unsafe { errmsg_to_string(ffi::sqlite3_errmsg(db)) }
|
||||
};
|
||||
Error {
|
||||
code: code,
|
||||
message: message,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn str_to_cstring(s: &str) -> Result<CString> {
|
||||
CString::new(s).map_err(|_| {
|
||||
Error {
|
||||
code: ffi::SQLITE_MISUSE,
|
||||
message: format!("Could not convert string {} to C-combatible string", s),
|
||||
}
|
||||
})
|
||||
Ok(try!(CString::new(s)))
|
||||
}
|
||||
|
||||
fn path_to_cstring(p: &Path) -> Result<CString> {
|
||||
let s = try!(p.to_str().ok_or(Error {
|
||||
code: ffi::SQLITE_MISUSE,
|
||||
message: format!("Could not convert path {} to UTF-8 string",
|
||||
p.to_string_lossy()),
|
||||
}));
|
||||
let s = try!(p.to_str().ok_or(Error::InvalidPath(p.to_owned())));
|
||||
str_to_cstring(s)
|
||||
}
|
||||
|
||||
@ -598,12 +550,9 @@ impl InnerConnection {
|
||||
let r = ffi::sqlite3_open_v2(c_path.as_ptr(), &mut db, flags.bits(), ptr::null());
|
||||
if r != ffi::SQLITE_OK {
|
||||
let e = if db.is_null() {
|
||||
Error {
|
||||
code: r,
|
||||
message: ffi::code_to_str(r).to_string(),
|
||||
}
|
||||
error_from_sqlite_code(r, None)
|
||||
} else {
|
||||
let e = Error::from_handle(db, r);
|
||||
let e = error_from_handle(db, r);
|
||||
ffi::sqlite3_close(db);
|
||||
e
|
||||
};
|
||||
@ -612,10 +561,14 @@ impl InnerConnection {
|
||||
}
|
||||
let r = ffi::sqlite3_busy_timeout(db, 5000);
|
||||
if r != ffi::SQLITE_OK {
|
||||
let e = Error::from_handle(db, r);
|
||||
let e = error_from_handle(db, r);
|
||||
ffi::sqlite3_close(db);
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
// attempt to turn on extended results code; don't fail if we can't.
|
||||
ffi::sqlite3_extended_result_codes(db, 1);
|
||||
|
||||
Ok(InnerConnection { db: db })
|
||||
}
|
||||
}
|
||||
@ -628,7 +581,7 @@ impl InnerConnection {
|
||||
if code == ffi::SQLITE_OK {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::from_handle(self.db(), code))
|
||||
Err(error_from_handle(self.db(), code))
|
||||
}
|
||||
}
|
||||
|
||||
@ -677,10 +630,7 @@ impl InnerConnection {
|
||||
} else {
|
||||
let message = errmsg_to_string(&*errmsg);
|
||||
ffi::sqlite3_free(errmsg as *mut libc::c_void);
|
||||
Err(Error {
|
||||
code: r,
|
||||
message: message,
|
||||
})
|
||||
Err(error_from_sqlite_code(r, Some(message)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -694,10 +644,7 @@ impl InnerConnection {
|
||||
sql: &str)
|
||||
-> Result<Statement<'a>> {
|
||||
if sql.len() >= ::std::i32::MAX as usize {
|
||||
return Err(Error {
|
||||
code: ffi::SQLITE_TOOBIG,
|
||||
message: "statement too long".to_string(),
|
||||
});
|
||||
return Err(error_from_sqlite_code(ffi::SQLITE_TOOBIG, None));
|
||||
}
|
||||
let mut c_stmt: *mut ffi::sqlite3_stmt = unsafe { mem::uninitialized() };
|
||||
let c_sql = try!(str_to_cstring(sql));
|
||||
@ -793,21 +740,12 @@ impl<'conn> Statement<'conn> {
|
||||
match r {
|
||||
ffi::SQLITE_DONE => {
|
||||
if self.column_count != 0 {
|
||||
Err(Error {
|
||||
code: ffi::SQLITE_MISUSE,
|
||||
message: "Unexpected column count - did you mean to call query?"
|
||||
.to_string(),
|
||||
})
|
||||
Err(Error::ExecuteReturnedResults)
|
||||
} else {
|
||||
Ok(self.conn.changes())
|
||||
}
|
||||
}
|
||||
ffi::SQLITE_ROW => {
|
||||
Err(Error {
|
||||
code: r,
|
||||
message: "Unexpected row result - did you mean to call query?".to_string(),
|
||||
})
|
||||
}
|
||||
},
|
||||
ffi::SQLITE_ROW => Err(Error::ExecuteReturnedResults),
|
||||
_ => Err(self.conn.decode_result(r).unwrap_err()),
|
||||
}
|
||||
}
|
||||
@ -1048,12 +986,7 @@ impl<'stmt> Rows<'stmt> {
|
||||
fn get_expected_row(&mut self) -> Result<Row<'stmt>> {
|
||||
match self.next() {
|
||||
Some(row) => row,
|
||||
None => {
|
||||
Err(Error {
|
||||
code: ffi::SQLITE_NOTICE,
|
||||
message: "Query did not return a row".to_string(),
|
||||
})
|
||||
}
|
||||
None => Err(Error::QueryReturnedNoRows),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1138,26 +1071,17 @@ impl<'stmt> Row<'stmt> {
|
||||
/// for this row or if this row is stale.
|
||||
pub fn get_checked<T: FromSql>(&self, idx: c_int) -> Result<T> {
|
||||
if self.row_idx != self.current_row.get() {
|
||||
return Err(Error {
|
||||
code: ffi::SQLITE_MISUSE,
|
||||
message: "Cannot get values from a row after advancing to next row".to_string(),
|
||||
});
|
||||
return Err(Error::GetFromStaleRow);
|
||||
}
|
||||
unsafe {
|
||||
if idx < 0 || idx >= self.stmt.column_count {
|
||||
return Err(Error {
|
||||
code: ffi::SQLITE_MISUSE,
|
||||
message: "Invalid column index".to_string(),
|
||||
});
|
||||
return Err(Error::InvalidColumnIndex(idx));
|
||||
}
|
||||
|
||||
if T::column_has_valid_sqlite_type(self.stmt.stmt, idx) {
|
||||
FromSql::column_result(self.stmt.stmt, idx)
|
||||
} else {
|
||||
Err(Error {
|
||||
code: ffi::SQLITE_MISMATCH,
|
||||
message: "Invalid column type".to_string(),
|
||||
})
|
||||
Err(Error::InvalidColumnType)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1260,8 +1184,10 @@ mod test {
|
||||
fn test_execute_select() {
|
||||
let db = checked_memory_handle();
|
||||
let err = db.execute("SELECT 1 WHERE 1 < ?", &[&1i32]).unwrap_err();
|
||||
assert!(err.code == ffi::SQLITE_MISUSE);
|
||||
assert!(err.message == "Unexpected column count - did you mean to call query?");
|
||||
match err {
|
||||
Error::ExecuteReturnedResults => (),
|
||||
_ => panic!("Unexpected error: {}", err),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1360,10 +1286,10 @@ mod test {
|
||||
.unwrap());
|
||||
|
||||
let result = db.query_row("SELECT x FROM foo WHERE x > 5", &[], |r| r.get::<i64>(0));
|
||||
let error = result.unwrap_err();
|
||||
|
||||
assert!(error.code == ffi::SQLITE_NOTICE);
|
||||
assert!(error.message == "Query did not return a row");
|
||||
match result.unwrap_err() {
|
||||
Error::QueryReturnedNoRows => (),
|
||||
err => panic!("Unexpected error {}", err),
|
||||
}
|
||||
|
||||
let bad_query_result = db.query_row("NOT A PROPER QUERY; test123", &[], |_| ());
|
||||
|
||||
@ -1376,7 +1302,7 @@ mod test {
|
||||
db.execute_batch("CREATE TABLE foo(x INTEGER);").unwrap();
|
||||
|
||||
let err = db.prepare("SELECT * FROM does_not_exist").unwrap_err();
|
||||
assert!(err.message.contains("does_not_exist"));
|
||||
assert!(format!("{}", err).contains("does_not_exist"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1393,8 +1319,10 @@ mod test {
|
||||
|
||||
assert_eq!(2i32, second.get(0));
|
||||
|
||||
let result = first.get_checked::<i32>(0);
|
||||
assert!(result.unwrap_err().message.contains("advancing to next row"));
|
||||
match first.get_checked::<i32>(0).unwrap_err() {
|
||||
Error::GetFromStaleRow => (),
|
||||
err => panic!("Unexpected error {}", err),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1421,11 +1349,34 @@ mod test {
|
||||
assert!(format!("{:?}", stmt).contains(query));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_notnull_constraint_error() {
|
||||
let db = checked_memory_handle();
|
||||
db.execute_batch("CREATE TABLE foo(x NOT NULL)").unwrap();
|
||||
|
||||
let result = db.execute("INSERT INTO foo (x) VALUES (NULL)", &[]);
|
||||
assert!(result.is_err());
|
||||
|
||||
match result.unwrap_err() {
|
||||
Error::SqliteFailure(err, _) => {
|
||||
assert_eq!(err.code, ffi::ErrorCode::ConstraintViolation);
|
||||
|
||||
// extended error codes for constraints were added in SQLite 3.7.16; if we're
|
||||
// running on a version at least that new, check for the extended code
|
||||
let version = unsafe { ffi::sqlite3_libversion_number() };
|
||||
if version >= 3007016 {
|
||||
assert_eq!(err.extended_code, ffi::SQLITE_CONSTRAINT_NOTNULL)
|
||||
}
|
||||
},
|
||||
err => panic!("Unexpected error {}", err),
|
||||
}
|
||||
}
|
||||
|
||||
mod query_and_then_tests {
|
||||
extern crate libsqlite3_sys as ffi;
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug)]
|
||||
enum CustomError {
|
||||
SomeError,
|
||||
Sqlite(Error),
|
||||
@ -1496,27 +1447,23 @@ mod test {
|
||||
db.execute_batch(sql).unwrap();
|
||||
|
||||
let mut query = db.prepare("SELECT x, y FROM foo ORDER BY x DESC").unwrap();
|
||||
let bad_type: Result<Vec<f64>> = query.query_and_then(&[],
|
||||
|row| row.get_checked(1))
|
||||
let bad_type: Result<Vec<f64>> = query.query_and_then(&[], |row| row.get_checked(1))
|
||||
.unwrap()
|
||||
.collect();
|
||||
|
||||
assert_eq!(bad_type,
|
||||
Err(Error {
|
||||
code: ffi::SQLITE_MISMATCH,
|
||||
message: "Invalid column type".to_owned(),
|
||||
}));
|
||||
match bad_type.unwrap_err() {
|
||||
Error::InvalidColumnType => (),
|
||||
err => panic!("Unexpected error {}", err),
|
||||
}
|
||||
|
||||
let bad_idx: Result<Vec<String>> = query.query_and_then(&[],
|
||||
|row| row.get_checked(3))
|
||||
let bad_idx: Result<Vec<String>> = query.query_and_then(&[], |row| row.get_checked(3))
|
||||
.unwrap()
|
||||
.collect();
|
||||
|
||||
assert_eq!(bad_idx,
|
||||
Err(Error {
|
||||
code: ffi::SQLITE_MISUSE,
|
||||
message: "Invalid column index".to_owned(),
|
||||
}));
|
||||
match bad_idx.unwrap_err() {
|
||||
Error::InvalidColumnIndex(_) => (),
|
||||
err => panic!("Unexpected error {}", err),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1564,11 +1511,10 @@ mod test {
|
||||
.unwrap()
|
||||
.collect();
|
||||
|
||||
assert_eq!(bad_type,
|
||||
Err(CustomError::Sqlite(Error {
|
||||
code: ffi::SQLITE_MISMATCH,
|
||||
message: "Invalid column type".to_owned(),
|
||||
})));
|
||||
match bad_type.unwrap_err() {
|
||||
CustomError::Sqlite(Error::InvalidColumnType) => (),
|
||||
err => panic!("Unexpected error {}", err),
|
||||
}
|
||||
|
||||
let bad_idx: CustomResult<Vec<String>> = query.query_and_then(&[], |row| {
|
||||
row.get_checked(3)
|
||||
@ -1577,11 +1523,10 @@ mod test {
|
||||
.unwrap()
|
||||
.collect();
|
||||
|
||||
assert_eq!(bad_idx,
|
||||
Err(CustomError::Sqlite(Error {
|
||||
code: ffi::SQLITE_MISUSE,
|
||||
message: "Invalid column index".to_owned(),
|
||||
})));
|
||||
match bad_idx.unwrap_err() {
|
||||
CustomError::Sqlite(Error::InvalidColumnIndex(_)) => (),
|
||||
err => panic!("Unexpected error {}", err),
|
||||
}
|
||||
|
||||
let non_sqlite_err: CustomResult<Vec<String>> = query.query_and_then(&[], |_| {
|
||||
Err(CustomError::SomeError)
|
||||
@ -1589,7 +1534,10 @@ mod test {
|
||||
.unwrap()
|
||||
.collect();
|
||||
|
||||
assert_eq!(non_sqlite_err, Err(CustomError::SomeError));
|
||||
match non_sqlite_err.unwrap_err() {
|
||||
CustomError::SomeError => (),
|
||||
err => panic!("Unexpected error {}", err),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1625,27 +1573,28 @@ mod test {
|
||||
row.get_checked(1).map_err(CustomError::Sqlite)
|
||||
});
|
||||
|
||||
assert_eq!(bad_type,
|
||||
Err(CustomError::Sqlite(Error {
|
||||
code: ffi::SQLITE_MISMATCH,
|
||||
message: "Invalid column type".to_owned(),
|
||||
})));
|
||||
match bad_type.unwrap_err() {
|
||||
CustomError::Sqlite(Error::InvalidColumnType) => (),
|
||||
err => panic!("Unexpected error {}", err),
|
||||
}
|
||||
|
||||
let bad_idx: CustomResult<String> = db.query_row_and_then(query, &[], |row| {
|
||||
row.get_checked(3).map_err(CustomError::Sqlite)
|
||||
});
|
||||
|
||||
assert_eq!(bad_idx,
|
||||
Err(CustomError::Sqlite(Error {
|
||||
code: ffi::SQLITE_MISUSE,
|
||||
message: "Invalid column index".to_owned(),
|
||||
})));
|
||||
match bad_idx.unwrap_err() {
|
||||
CustomError::Sqlite(Error::InvalidColumnIndex(_)) => (),
|
||||
err => panic!("Unexpected error {}", err),
|
||||
}
|
||||
|
||||
let non_sqlite_err: CustomResult<String> = db.query_row_and_then(query, &[], |_| {
|
||||
Err(CustomError::SomeError)
|
||||
});
|
||||
|
||||
assert_eq!(non_sqlite_err, Err(CustomError::SomeError));
|
||||
match non_sqlite_err.unwrap_err() {
|
||||
CustomError::SomeError => (),
|
||||
err => panic!("Unexpected error {}", err),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -133,10 +133,7 @@ impl<'conn> Statement<'conn> {
|
||||
if let Some(i) = try!(self.parameter_index(name)) {
|
||||
try!(self.conn.decode_result(unsafe { value.bind_parameter(self.stmt, i) }));
|
||||
} else {
|
||||
return Err(Error {
|
||||
code: ffi::SQLITE_MISUSE,
|
||||
message: format!("Invalid parameter name: {}", name),
|
||||
});
|
||||
return Err(Error::InvalidParameterName(name.into()));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
14
src/trace.rs
14
src/trace.rs
@ -8,7 +8,8 @@ use std::str;
|
||||
use std::time::Duration;
|
||||
|
||||
use super::ffi;
|
||||
use {Error, Result, Connection};
|
||||
use {Result, Connection};
|
||||
use error::error_from_sqlite_code;
|
||||
|
||||
/// Set up the process-wide SQLite error logging callback.
|
||||
/// This function is marked unsafe for two reasons:
|
||||
@ -42,14 +43,11 @@ pub unsafe fn config_log(callback: Option<fn(c_int, &str)>) -> Result<()> {
|
||||
}
|
||||
};
|
||||
|
||||
if rc != ffi::SQLITE_OK {
|
||||
return Err(Error {
|
||||
code: rc,
|
||||
message: "sqlite3_config(SQLITE_CONFIG_LOG, ...)".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
if rc == ffi::SQLITE_OK {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(error_from_sqlite_code(rc, None))
|
||||
}
|
||||
}
|
||||
|
||||
/// Write a message into the error log established by `config_log`.
|
||||
|
114
src/types.rs
114
src/types.rs
@ -232,14 +232,8 @@ impl FromSql for String {
|
||||
Ok("".to_string())
|
||||
} else {
|
||||
let c_slice = CStr::from_ptr(c_text as *const c_char).to_bytes();
|
||||
let utf8_str = str::from_utf8(c_slice);
|
||||
utf8_str.map(|s| s.to_string())
|
||||
.map_err(|e| {
|
||||
Error {
|
||||
code: 0,
|
||||
message: e.to_string(),
|
||||
}
|
||||
})
|
||||
let utf8_str = try!(str::from_utf8(c_slice));
|
||||
Ok(utf8_str.into())
|
||||
}
|
||||
}
|
||||
|
||||
@ -272,15 +266,11 @@ impl FromSql for time::Timespec {
|
||||
unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result<time::Timespec> {
|
||||
let col_str = FromSql::column_result(stmt, col);
|
||||
col_str.and_then(|txt: String| {
|
||||
time::strptime(&txt, SQLITE_DATETIME_FMT)
|
||||
.map(|tm| tm.to_timespec())
|
||||
.map_err(|parse_error| {
|
||||
Error {
|
||||
code: ffi::SQLITE_MISMATCH,
|
||||
message: format!("{}", parse_error),
|
||||
match time::strptime(&txt, SQLITE_DATETIME_FMT) {
|
||||
Ok(tm) => Ok(tm.to_timespec()),
|
||||
Err(err) => Err(Error::FromSqlConversionFailure(Box::new(err))),
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool {
|
||||
@ -306,8 +296,8 @@ impl<T: FromSql> FromSql for Option<T> {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use Connection;
|
||||
use ffi;
|
||||
use super::time;
|
||||
use Error;
|
||||
use libc::{c_int, c_double};
|
||||
|
||||
fn checked_memory_handle() -> Connection {
|
||||
@ -380,6 +370,13 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_mismatched_types() {
|
||||
fn is_invalid_column_type(err: Error) -> bool {
|
||||
match err {
|
||||
Error::InvalidColumnType => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
let db = checked_memory_handle();
|
||||
|
||||
db.execute("INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)",
|
||||
@ -403,69 +400,42 @@ mod test {
|
||||
// check some invalid types
|
||||
|
||||
// 0 is actually a blob (Vec<u8>)
|
||||
assert_eq!(row.get_checked::<c_int>(0).err().unwrap().code,
|
||||
ffi::SQLITE_MISMATCH);
|
||||
assert_eq!(row.get_checked::<i64>(0).err().unwrap().code,
|
||||
ffi::SQLITE_MISMATCH);
|
||||
assert_eq!(row.get_checked::<c_double>(0).err().unwrap().code,
|
||||
ffi::SQLITE_MISMATCH);
|
||||
assert_eq!(row.get_checked::<String>(0).err().unwrap().code,
|
||||
ffi::SQLITE_MISMATCH);
|
||||
assert_eq!(row.get_checked::<time::Timespec>(0).err().unwrap().code,
|
||||
ffi::SQLITE_MISMATCH);
|
||||
assert_eq!(row.get_checked::<Option<c_int>>(0).err().unwrap().code,
|
||||
ffi::SQLITE_MISMATCH);
|
||||
assert!(is_invalid_column_type(row.get_checked::<c_int>(0).err().unwrap()));
|
||||
assert!(is_invalid_column_type(row.get_checked::<c_int>(0).err().unwrap()));
|
||||
assert!(is_invalid_column_type(row.get_checked::<i64>(0).err().unwrap()));
|
||||
assert!(is_invalid_column_type(row.get_checked::<c_double>(0).err().unwrap()));
|
||||
assert!(is_invalid_column_type(row.get_checked::<String>(0).err().unwrap()));
|
||||
assert!(is_invalid_column_type(row.get_checked::<time::Timespec>(0).err().unwrap()));
|
||||
assert!(is_invalid_column_type(row.get_checked::<Option<c_int>>(0).err().unwrap()));
|
||||
|
||||
// 1 is actually a text (String)
|
||||
assert_eq!(row.get_checked::<c_int>(1).err().unwrap().code,
|
||||
ffi::SQLITE_MISMATCH);
|
||||
assert_eq!(row.get_checked::<i64>(1).err().unwrap().code,
|
||||
ffi::SQLITE_MISMATCH);
|
||||
assert_eq!(row.get_checked::<c_double>(1).err().unwrap().code,
|
||||
ffi::SQLITE_MISMATCH);
|
||||
assert_eq!(row.get_checked::<Vec<u8>>(1).err().unwrap().code,
|
||||
ffi::SQLITE_MISMATCH);
|
||||
assert_eq!(row.get_checked::<Option<c_int>>(1).err().unwrap().code,
|
||||
ffi::SQLITE_MISMATCH);
|
||||
assert!(is_invalid_column_type(row.get_checked::<c_int>(1).err().unwrap()));
|
||||
assert!(is_invalid_column_type(row.get_checked::<i64>(1).err().unwrap()));
|
||||
assert!(is_invalid_column_type(row.get_checked::<c_double>(1).err().unwrap()));
|
||||
assert!(is_invalid_column_type(row.get_checked::<Vec<u8>>(1).err().unwrap()));
|
||||
assert!(is_invalid_column_type(row.get_checked::<Option<c_int>>(1).err().unwrap()));
|
||||
|
||||
// 2 is actually an integer
|
||||
assert_eq!(row.get_checked::<c_double>(2).err().unwrap().code,
|
||||
ffi::SQLITE_MISMATCH);
|
||||
assert_eq!(row.get_checked::<String>(2).err().unwrap().code,
|
||||
ffi::SQLITE_MISMATCH);
|
||||
assert_eq!(row.get_checked::<Vec<u8>>(2).err().unwrap().code,
|
||||
ffi::SQLITE_MISMATCH);
|
||||
assert_eq!(row.get_checked::<time::Timespec>(2).err().unwrap().code,
|
||||
ffi::SQLITE_MISMATCH);
|
||||
assert_eq!(row.get_checked::<Option<c_double>>(2).err().unwrap().code,
|
||||
ffi::SQLITE_MISMATCH);
|
||||
assert!(is_invalid_column_type(row.get_checked::<c_double>(2).err().unwrap()));
|
||||
assert!(is_invalid_column_type(row.get_checked::<String>(2).err().unwrap()));
|
||||
assert!(is_invalid_column_type(row.get_checked::<Vec<u8>>(2).err().unwrap()));
|
||||
assert!(is_invalid_column_type(row.get_checked::<time::Timespec>(2).err().unwrap()));
|
||||
assert!(is_invalid_column_type(row.get_checked::<Option<c_double>>(2).err().unwrap()));
|
||||
|
||||
// 3 is actually a float (c_double)
|
||||
assert_eq!(row.get_checked::<c_int>(3).err().unwrap().code,
|
||||
ffi::SQLITE_MISMATCH);
|
||||
assert_eq!(row.get_checked::<i64>(3).err().unwrap().code,
|
||||
ffi::SQLITE_MISMATCH);
|
||||
assert_eq!(row.get_checked::<String>(3).err().unwrap().code,
|
||||
ffi::SQLITE_MISMATCH);
|
||||
assert_eq!(row.get_checked::<Vec<u8>>(3).err().unwrap().code,
|
||||
ffi::SQLITE_MISMATCH);
|
||||
assert_eq!(row.get_checked::<time::Timespec>(3).err().unwrap().code,
|
||||
ffi::SQLITE_MISMATCH);
|
||||
assert_eq!(row.get_checked::<Option<c_int>>(3).err().unwrap().code,
|
||||
ffi::SQLITE_MISMATCH);
|
||||
assert!(is_invalid_column_type(row.get_checked::<c_int>(3).err().unwrap()));
|
||||
assert!(is_invalid_column_type(row.get_checked::<i64>(3).err().unwrap()));
|
||||
assert!(is_invalid_column_type(row.get_checked::<String>(3).err().unwrap()));
|
||||
assert!(is_invalid_column_type(row.get_checked::<Vec<u8>>(3).err().unwrap()));
|
||||
assert!(is_invalid_column_type(row.get_checked::<time::Timespec>(3).err().unwrap()));
|
||||
assert!(is_invalid_column_type(row.get_checked::<Option<c_int>>(3).err().unwrap()));
|
||||
|
||||
// 4 is actually NULL
|
||||
assert_eq!(row.get_checked::<c_int>(4).err().unwrap().code,
|
||||
ffi::SQLITE_MISMATCH);
|
||||
assert_eq!(row.get_checked::<i64>(4).err().unwrap().code,
|
||||
ffi::SQLITE_MISMATCH);
|
||||
assert_eq!(row.get_checked::<c_double>(4).err().unwrap().code,
|
||||
ffi::SQLITE_MISMATCH);
|
||||
assert_eq!(row.get_checked::<String>(4).err().unwrap().code,
|
||||
ffi::SQLITE_MISMATCH);
|
||||
assert_eq!(row.get_checked::<Vec<u8>>(4).err().unwrap().code,
|
||||
ffi::SQLITE_MISMATCH);
|
||||
assert_eq!(row.get_checked::<time::Timespec>(4).err().unwrap().code,
|
||||
ffi::SQLITE_MISMATCH);
|
||||
assert!(is_invalid_column_type(row.get_checked::<c_int>(4).err().unwrap()));
|
||||
assert!(is_invalid_column_type(row.get_checked::<i64>(4).err().unwrap()));
|
||||
assert!(is_invalid_column_type(row.get_checked::<c_double>(4).err().unwrap()));
|
||||
assert!(is_invalid_column_type(row.get_checked::<String>(4).err().unwrap()));
|
||||
assert!(is_invalid_column_type(row.get_checked::<Vec<u8>>(4).err().unwrap()));
|
||||
assert!(is_invalid_column_type(row.get_checked::<time::Timespec>(4).err().unwrap()));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user