Skip to content

Commit

Permalink
fix(sqlite): handle empty statements, fixes #231
Browse files Browse the repository at this point in the history
  • Loading branch information
mehcode committed Apr 8, 2020
1 parent b3fd720 commit cd6735b
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 81 deletions.
36 changes: 16 additions & 20 deletions sqlx-core/src/sqlite/arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@ impl Arguments for SqliteArguments {

impl SqliteArgumentValue {
pub(super) fn bind(&self, statement: &mut Statement, index: usize) -> crate::Result<()> {
let handle = unsafe {
if let Some(handle) = statement.handle() {
handle
} else {
// drop all requested bindings for a null/empty statement
// note that this _should_ not happen as argument size for a null statement should be zero
return Ok(());
}
};

// TODO: Handle error of trying to bind too many parameters here
let index = index as c_int;

Expand All @@ -83,13 +93,7 @@ impl SqliteArgumentValue {
let bytes_len = bytes.len() as i32;

unsafe {
sqlite3_bind_blob(
statement.handle(),
index,
bytes_ptr,
bytes_len,
SQLITE_TRANSIENT(),
)
sqlite3_bind_blob(handle, index, bytes_ptr, bytes_len, SQLITE_TRANSIENT())
}
}

Expand All @@ -100,29 +104,21 @@ impl SqliteArgumentValue {
let bytes_len = bytes.len() as i32;

unsafe {
sqlite3_bind_text(
statement.handle(),
index,
bytes_ptr,
bytes_len,
SQLITE_TRANSIENT(),
)
sqlite3_bind_text(handle, index, bytes_ptr, bytes_len, SQLITE_TRANSIENT())
}
}

SqliteArgumentValue::Double(value) => unsafe {
sqlite3_bind_double(statement.handle(), index, *value)
sqlite3_bind_double(handle, index, *value)
},

SqliteArgumentValue::Int(value) => unsafe {
sqlite3_bind_int(statement.handle(), index, *value)
},
SqliteArgumentValue::Int(value) => unsafe { sqlite3_bind_int(handle, index, *value) },

SqliteArgumentValue::Int64(value) => unsafe {
sqlite3_bind_int64(statement.handle(), index, *value)
sqlite3_bind_int64(handle, index, *value)
},

SqliteArgumentValue::Null => unsafe { sqlite3_bind_null(statement.handle(), index) },
SqliteArgumentValue::Null => unsafe { sqlite3_bind_null(handle, index) },
};

if status != SQLITE_OK {
Expand Down
125 changes: 78 additions & 47 deletions sqlx-core/src/sqlite/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub(super) struct SqliteStatementHandle(NonNull<sqlite3_stmt>);
///
/// The statement is finalized ( `sqlite3_finalize` ) on drop.
pub(super) struct Statement {
handle: SqliteStatementHandle,
handle: Option<SqliteStatementHandle>,
pub(super) connection: SqliteConnectionHandle,
pub(super) worker: Worker,
pub(super) tail: usize,
Expand Down Expand Up @@ -94,7 +94,7 @@ impl Statement {
let mut self_ = Self {
worker: conn.worker.clone(),
connection: conn.handle,
handle: SqliteStatementHandle(NonNull::new(statement_handle).unwrap()),
handle: NonNull::new(statement_handle).map(SqliteStatementHandle),
columns: HashMap::new(),
tail,
};
Expand All @@ -113,8 +113,8 @@ impl Statement {

/// Returns a pointer to the raw C pointer backing this statement.
#[inline]
pub(super) unsafe fn handle(&self) -> *mut sqlite3_stmt {
self.handle.0.as_ptr()
pub(super) unsafe fn handle(&self) -> Option<*mut sqlite3_stmt> {
self.handle.map(|handle| handle.0.as_ptr())
}

pub(super) fn data_count(&mut self) -> usize {
Expand All @@ -126,43 +126,59 @@ impl Statement {
// The value is correct only if there was a recent call to
// sqlite3_step that returned SQLITE_ROW.

let count: c_int = unsafe { sqlite3_data_count(self.handle()) };
count as usize
unsafe { self.handle().map_or(0, |handle| sqlite3_data_count(handle)) as usize }
}

pub(super) fn column_count(&mut self) -> usize {
// https://sqlite.org/c3ref/column_count.html
let count = unsafe { sqlite3_column_count(self.handle()) };
count as usize
unsafe {
self.handle()
.map_or(0, |handle| sqlite3_column_count(handle)) as usize
}
}

pub(super) fn column_name(&mut self, index: usize) -> &str {
// https://sqlite.org/c3ref/column_name.html
let name = unsafe {
let ptr = sqlite3_column_name(self.handle(), index as c_int);
debug_assert!(!ptr.is_null());

CStr::from_ptr(ptr)
};

name.to_str().unwrap()
unsafe {
self.handle()
.map(|handle| {
// https://sqlite.org/c3ref/column_name.html
let ptr = sqlite3_column_name(handle, index as c_int);
debug_assert!(!ptr.is_null());

CStr::from_ptr(ptr)
})
.map_or(Ok(""), |name| name.to_str())
.unwrap()
}
}

pub(super) fn column_decltype(&mut self, index: usize) -> Option<&str> {
let name = unsafe {
let ptr = sqlite3_column_decltype(self.handle(), index as c_int);
unsafe {
self.handle()
.and_then(|handle| {
let ptr = sqlite3_column_decltype(handle, index as c_int);

if ptr.is_null() {
None
} else {
Some(CStr::from_ptr(ptr))
}
})
.map(|name| name.to_str().unwrap())
}
}

if ptr.is_null() {
None
pub(super) fn column_not_null(&mut self, index: usize) -> crate::Result<Option<bool>> {
let handle = unsafe {
if let Some(handle) = self.handle() {
handle
} else {
Some(CStr::from_ptr(ptr))
// we do not know the nullablility of a column that doesn't exist on a statement
// that doesn't exist
return Ok(None);
}
};

name.map(|s| s.to_str().unwrap())
}

pub(super) fn column_not_null(&mut self, index: usize) -> crate::Result<Option<bool>> {
unsafe {
// https://sqlite.org/c3ref/column_database_name.html
//
Expand All @@ -171,9 +187,9 @@ impl Statement {
// sqlite3_finalize() or until the statement is automatically reprepared by the
// first call to sqlite3_step() for a particular run or until the same information
// is requested again in a different encoding.
let db_name = sqlite3_column_database_name(self.handle(), index as c_int);
let table_name = sqlite3_column_table_name(self.handle(), index as c_int);
let origin_name = sqlite3_column_origin_name(self.handle(), index as c_int);
let db_name = sqlite3_column_database_name(handle, index as c_int);
let table_name = sqlite3_column_table_name(handle, index as c_int);
let origin_name = sqlite3_column_origin_name(handle, index as c_int);

if db_name.is_null() || table_name.is_null() || origin_name.is_null() {
return Ok(None);
Expand Down Expand Up @@ -213,8 +229,10 @@ impl Statement {

pub(super) fn params(&mut self) -> usize {
// https://www.hwaci.com/sw/sqlite/c3ref/bind_parameter_count.html
let num = unsafe { sqlite3_bind_parameter_count(self.handle()) };
num as usize
unsafe {
self.handle()
.map_or(0, |handle| sqlite3_bind_parameter_count(handle)) as usize
}
}

pub(super) fn bind(&mut self, arguments: &mut SqliteArguments) -> crate::Result<()> {
Expand All @@ -230,36 +248,47 @@ impl Statement {
}

pub(super) fn reset(&mut self) {
let handle = unsafe {
if let Some(handle) = self.handle() {
handle
} else {
// nothing to reset if its null
return;
}
};

// https://sqlite.org/c3ref/reset.html
// https://sqlite.org/c3ref/clear_bindings.html

// the status value of reset is ignored because it merely propagates
// the status of the most recently invoked step function

let _ = unsafe { sqlite3_reset(self.handle()) };

let _ = unsafe { sqlite3_clear_bindings(self.handle()) };
let _ = unsafe { sqlite3_reset(handle) };
let _ = unsafe { sqlite3_clear_bindings(handle) };
}

pub(super) async fn step(&mut self) -> crate::Result<Step> {
// https://sqlite.org/c3ref/step.html

let handle = self.handle;

let status = unsafe {
self.worker
.run(move || sqlite3_step(handle.0.as_ptr()))
.await
};
if let Some(handle) = self.handle {
let status = unsafe {
self.worker
.run(move || sqlite3_step(handle.0.as_ptr()))
.await
};

match status {
SQLITE_DONE => Ok(Step::Done),
match status {
SQLITE_DONE => Ok(Step::Done),

SQLITE_ROW => Ok(Step::Row),
SQLITE_ROW => Ok(Step::Row),

_ => {
return Err(SqliteError::from_connection(self.connection.0.as_ptr()).into());
_ => {
return Err(SqliteError::from_connection(self.connection.0.as_ptr()).into());
}
}
} else {
// An empty (null) query will always emit `Step::Done`
Ok(Step::Done)
}
}
}
Expand All @@ -268,7 +297,9 @@ impl Drop for Statement {
fn drop(&mut self) {
// https://sqlite.org/c3ref/finalize.html
unsafe {
let _ = sqlite3_finalize(self.handle());
if let Some(handle) = self.handle() {
let _ = sqlite3_finalize(handle);
}
}
}
}
59 changes: 45 additions & 14 deletions sqlx-core/src/sqlite/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,14 @@ impl<'c> SqliteValue<'c> {
}

fn r#type(&self) -> Option<SqliteType> {
let type_code = unsafe { sqlite3_column_type(self.statement.handle(), self.index) };
let type_code = unsafe {
if let Some(handle) = self.statement.handle() {
sqlite3_column_type(handle, self.index)
} else {
// unreachable: null statements do not have any values to type
return None;
}
};

// SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB, or SQLITE_NULL
match type_code {
Expand All @@ -47,41 +54,65 @@ impl<'c> SqliteValue<'c> {

/// Returns the 32-bit INTEGER result.
pub(super) fn int(&self) -> i32 {
unsafe { sqlite3_column_int(self.statement.handle(), self.index) }
unsafe {
self.statement
.handle()
.map_or(0, |handle| sqlite3_column_int(handle, self.index))
}
}

/// Returns the 64-bit INTEGER result.
pub(super) fn int64(&self) -> i64 {
unsafe { sqlite3_column_int64(self.statement.handle(), self.index) }
unsafe {
self.statement
.handle()
.map_or(0, |handle| sqlite3_column_int64(handle, self.index))
}
}

/// Returns the 64-bit, REAL result.
pub(super) fn double(&self) -> f64 {
unsafe { sqlite3_column_double(self.statement.handle(), self.index) }
unsafe {
self.statement
.handle()
.map_or(0.0, |handle| sqlite3_column_double(handle, self.index))
}
}

/// Returns the UTF-8 TEXT result.
pub(super) fn text(&self) -> Option<&'c str> {
unsafe {
let ptr = sqlite3_column_text(self.statement.handle(), self.index);

if ptr.is_null() {
None
} else {
Some(from_utf8_unchecked(CStr::from_ptr(ptr as _).to_bytes()))
}
self.statement.handle().and_then(|handle| {
let ptr = sqlite3_column_text(handle, self.index);

if ptr.is_null() {
None
} else {
Some(from_utf8_unchecked(CStr::from_ptr(ptr as _).to_bytes()))
}
})
}
}

fn bytes(&self) -> usize {
// Returns the size of the result in bytes.
let len = unsafe { sqlite3_column_bytes(self.statement.handle(), self.index) };
len as usize
unsafe {
self.statement
.handle()
.map_or(0, |handle| sqlite3_column_bytes(handle, self.index)) as usize
}
}

/// Returns the BLOB result.
pub(super) fn blob(&self) -> &'c [u8] {
let ptr = unsafe { sqlite3_column_blob(self.statement.handle(), self.index) };
let ptr = unsafe {
if let Some(handle) = self.statement.handle() {
sqlite3_column_blob(handle, self.index)
} else {
// Null statements do not exist
return &[];
}
};

if ptr.is_null() {
// Empty BLOBs are received as null pointers
Expand Down

0 comments on commit cd6735b

Please sign in to comment.