Avoid unnecessary copies/allocations when passing strings to sqlite

This commit is contained in:
Thom Chiovoloni 2019-02-26 19:38:41 -08:00
parent a0bd73221d
commit 04f900059d
5 changed files with 57 additions and 44 deletions

View File

@ -59,6 +59,7 @@ csv = { version = "1.0", optional = true }
lazy_static = { version = "1.0", optional = true } lazy_static = { version = "1.0", optional = true }
byteorder = { version = "1.2", features = ["i128"], optional = true } byteorder = { version = "1.2", features = ["i128"], optional = true }
fallible-streaming-iterator = { version = "0.1", optional = true } fallible-streaming-iterator = { version = "0.1", optional = true }
memchr = "2.2.0"
[dev-dependencies] [dev-dependencies]
tempdir = "0.3" tempdir = "0.3"

View File

@ -7,7 +7,7 @@ use std::rc::Rc;
use crate::ffi; use crate::ffi;
use crate::ffi::sqlite3_context; use crate::ffi::sqlite3_context;
use crate::str_to_cstring; use crate::str_for_sqlite;
use crate::types::{ToSqlOutput, ValueRef}; use crate::types::{ToSqlOutput, ValueRef};
#[cfg(feature = "array")] #[cfg(feature = "array")]
use crate::vtab::array::{free_array, ARRAY_TYPE}; use crate::vtab::array::{free_array, ARRAY_TYPE};
@ -38,25 +38,20 @@ pub(crate) unsafe fn set_result(ctx: *mut sqlite3_context, result: &ToSqlOutput<
ValueRef::Real(r) => ffi::sqlite3_result_double(ctx, r), ValueRef::Real(r) => ffi::sqlite3_result_double(ctx, r),
ValueRef::Text(s) => { ValueRef::Text(s) => {
let length = s.len(); let length = s.len();
if length > ::std::i32::MAX as usize { if length > c_int::max_value() as usize {
ffi::sqlite3_result_error_toobig(ctx); ffi::sqlite3_result_error_toobig(ctx);
} else { } else {
let c_str = match str_to_cstring(s) { let (c_str, len, destructor) = match str_for_sqlite(s) {
Ok(c_str) => c_str, Ok(c_str) => c_str,
// TODO sqlite3_result_error // TODO sqlite3_result_error
Err(_) => return ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_MISUSE), Err(_) => return ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_MISUSE),
}; };
let destructor = if length > 0 { ffi::sqlite3_result_text(ctx, c_str, len, destructor);
ffi::SQLITE_TRANSIENT()
} else {
ffi::SQLITE_STATIC()
};
ffi::sqlite3_result_text(ctx, c_str.as_ptr(), length as c_int, destructor);
} }
} }
ValueRef::Blob(b) => { ValueRef::Blob(b) => {
let length = b.len(); let length = b.len();
if length > ::std::i32::MAX as usize { if length > c_int::max_value() as usize {
ffi::sqlite3_result_error_toobig(ctx); ffi::sqlite3_result_error_toobig(ctx);
} else if length == 0 { } else if length == 0 {
ffi::sqlite3_result_zeroblob(ctx, 0) ffi::sqlite3_result_zeroblob(ctx, 0)

View File

@ -9,7 +9,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, Once, ONCE_INIT}; use std::sync::{Arc, Mutex, Once, ONCE_INIT};
use super::ffi; use super::ffi;
use super::str_to_cstring; use super::{str_to_cstring, str_for_sqlite};
use super::{Connection, InterruptHandle, OpenFlags, Result}; use super::{Connection, InterruptHandle, OpenFlags, Result};
use crate::error::{error_from_handle, error_from_sqlite_code, Error}; use crate::error::{error_from_handle, error_from_sqlite_code, Error};
use crate::raw_statement::RawStatement; use crate::raw_statement::RawStatement;
@ -207,20 +207,16 @@ impl InnerConnection {
} }
pub fn prepare<'a>(&mut self, conn: &'a Connection, sql: &str) -> Result<Statement<'a>> { pub fn prepare<'a>(&mut self, conn: &'a Connection, sql: &str) -> Result<Statement<'a>> {
if sql.len() >= ::std::i32::MAX as usize {
return Err(error_from_sqlite_code(ffi::SQLITE_TOOBIG, None));
}
let mut c_stmt: *mut ffi::sqlite3_stmt = unsafe { mem::uninitialized() }; let mut c_stmt: *mut ffi::sqlite3_stmt = unsafe { mem::uninitialized() };
let c_sql = str_to_cstring(sql)?; let (c_sql, len, _) = str_for_sqlite(sql)?;
let len_with_nul = (sql.len() + 1) as c_int;
let r = unsafe { let r = unsafe {
if cfg!(feature = "unlock_notify") { if cfg!(feature = "unlock_notify") {
let mut rc; let mut rc;
loop { loop {
rc = ffi::sqlite3_prepare_v2( rc = ffi::sqlite3_prepare_v2(
self.db(), self.db(),
c_sql.as_ptr(), c_sql,
len_with_nul, len,
&mut c_stmt, &mut c_stmt,
ptr::null_mut(), ptr::null_mut(),
); );
@ -236,8 +232,8 @@ impl InnerConnection {
} else { } else {
ffi::sqlite3_prepare_v2( ffi::sqlite3_prepare_v2(
self.db(), self.db(),
c_sql.as_ptr(), c_sql,
len_with_nul, len,
&mut c_stmt, &mut c_stmt,
ptr::null_mut(), ptr::null_mut(),
) )

View File

@ -237,6 +237,39 @@ fn str_to_cstring(s: &str) -> Result<CString> {
Ok(CString::new(s)?) Ok(CString::new(s)?)
} }
/// Returns `Ok((string ptr, len as c_int, SQLITE_STATIC | SQLITE_TRANSIENT))`
/// normally.
/// Returns errors if the string has embedded nuls or is too large for sqlite.
/// The `sqlite3_destructor_type` item is always `SQLITE_TRANSIENT` unless
/// the string was empty (in which case it's `SQLITE_STATIC`, and the ptr is
/// static).
fn str_for_sqlite(s: &str) -> Result<(*const c_char, c_int, ffi::sqlite3_destructor_type)> {
let len = len_as_c_int(s.len())?;
if memchr::memchr(0, s.as_bytes()).is_none() {
let (ptr, dtor_info) = if len != 0 {
(s.as_ptr() as *const c_char, ffi::SQLITE_TRANSIENT())
} else {
// Return a pointer guaranteed to live forever
("".as_ptr() as *const c_char, ffi::SQLITE_STATIC())
};
Ok((ptr, len, dtor_info))
} else {
// There's an embedded nul, so we fabricate a NulError.
let e = CString::new(s);
Err(Error::NulError(e.unwrap_err()))
}
}
// Helper to cast to c_int safely, returning the correct error type if the cast
// failed.
fn len_as_c_int(len: usize) -> Result<c_int> {
if len >= (c_int::max_value() as usize) {
Err(Error::SqliteFailure(ffi::Error::new(ffi::SQLITE_TOOBIG), None))
} else {
Ok(len as c_int)
}
}
fn path_to_cstring(p: &Path) -> Result<CString> { fn path_to_cstring(p: &Path) -> Result<CString> {
let s = p.to_str().ok_or_else(|| Error::InvalidPath(p.to_owned()))?; let s = p.to_str().ok_or_else(|| Error::InvalidPath(p.to_owned()))?;
str_to_cstring(s) str_to_cstring(s)

View File

@ -7,7 +7,7 @@ use std::slice::from_raw_parts;
use std::{convert, fmt, mem, ptr, result, str}; use std::{convert, fmt, mem, ptr, result, str};
use super::ffi; use super::ffi;
use super::str_to_cstring; use super::{str_to_cstring, str_for_sqlite, len_as_c_int};
use super::{ use super::{
AndThenRows, Connection, Error, MappedRows, RawStatement, Result, Row, Rows, ValueRef, AndThenRows, Connection, Error, MappedRows, RawStatement, Result, Row, Rows, ValueRef,
}; };
@ -506,37 +506,25 @@ impl Statement<'_> {
ValueRef::Integer(i) => unsafe { ffi::sqlite3_bind_int64(ptr, col as c_int, i) }, ValueRef::Integer(i) => unsafe { ffi::sqlite3_bind_int64(ptr, col as c_int, i) },
ValueRef::Real(r) => unsafe { ffi::sqlite3_bind_double(ptr, col as c_int, r) }, ValueRef::Real(r) => unsafe { ffi::sqlite3_bind_double(ptr, col as c_int, r) },
ValueRef::Text(s) => unsafe { ValueRef::Text(s) => unsafe {
let length = s.len(); let (c_str, len, destructor) = str_for_sqlite(s)?;
if length > ::std::i32::MAX as usize {
ffi::SQLITE_TOOBIG
} else {
let c_str = str_to_cstring(s)?;
let destructor = if length > 0 {
ffi::SQLITE_TRANSIENT()
} else {
ffi::SQLITE_STATIC()
};
ffi::sqlite3_bind_text( ffi::sqlite3_bind_text(
ptr, ptr,
col as c_int, col as c_int,
c_str.as_ptr(), c_str,
length as c_int, len,
destructor, destructor,
) )
}
}, },
ValueRef::Blob(b) => unsafe { ValueRef::Blob(b) => unsafe {
let length = b.len(); let length = len_as_c_int(b.len())?;
if length > ::std::i32::MAX as usize { if length == 0 {
ffi::SQLITE_TOOBIG
} else if length == 0 {
ffi::sqlite3_bind_zeroblob(ptr, col as c_int, 0) ffi::sqlite3_bind_zeroblob(ptr, col as c_int, 0)
} else { } else {
ffi::sqlite3_bind_blob( ffi::sqlite3_bind_blob(
ptr, ptr,
col as c_int, col as c_int,
b.as_ptr() as *const c_void, b.as_ptr() as *const c_void,
length as c_int, length,
ffi::SQLITE_TRANSIENT(), ffi::SQLITE_TRANSIENT(),
) )
} }