Allow user scalar functions to return results.

This removes the need for scalar functions to have direct access to the
context (in order to set the return value).
This commit is contained in:
John Gallagher 2015-12-11 15:08:40 -05:00
parent 81ec7fe7cd
commit 3913e89f94

View File

@ -221,16 +221,12 @@ impl<T: FromValue> FromValue for Option<T> {
} }
} }
// sqlite3_user_data
// sqlite3_get_auxdata
// sqlite3_set_auxdata
unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) { unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) {
let _: Box<T> = Box::from_raw(mem::transmute(p)); let _: Box<T> = Box::from_raw(mem::transmute(p));
} }
pub struct Context<'a> { pub struct Context<'a> {
pub ctx: *mut sqlite3_context, ctx: *mut sqlite3_context,
args: &'a [*mut sqlite3_value], args: &'a [*mut sqlite3_value],
} }
@ -274,31 +270,34 @@ impl<'a> Context<'a> {
} }
impl SqliteConnection { impl SqliteConnection {
pub fn create_scalar_function<F>(&self, pub fn create_scalar_function<F, T>(&self,
fn_name: &str, fn_name: &str,
n_arg: c_int, n_arg: c_int,
deterministic: bool, deterministic: bool,
x_func: F) x_func: F)
-> SqliteResult<()> -> SqliteResult<()>
where F: FnMut(&Context) where F: FnMut(&Context) -> SqliteResult<T>,
T: ToResult
{ {
self.db.borrow_mut().create_scalar_function(fn_name, n_arg, deterministic, x_func) self.db.borrow_mut().create_scalar_function(fn_name, n_arg, deterministic, x_func)
} }
} }
impl InnerSqliteConnection { impl InnerSqliteConnection {
fn create_scalar_function<F>(&mut self, fn create_scalar_function<F, T>(&mut self,
fn_name: &str, fn_name: &str,
n_arg: c_int, n_arg: c_int,
deterministic: bool, deterministic: bool,
x_func: F) x_func: F)
-> SqliteResult<()> -> SqliteResult<()>
where F: FnMut(&Context) where F: FnMut(&Context) -> SqliteResult<T>,
T: ToResult
{ {
extern "C" fn call_boxed_closure<F>(ctx: *mut sqlite3_context, extern "C" fn call_boxed_closure<F, T>(ctx: *mut sqlite3_context,
argc: c_int, argc: c_int,
argv: *mut *mut sqlite3_value) argv: *mut *mut sqlite3_value)
where F: FnMut(&Context) where F: FnMut(&Context) -> SqliteResult<T>,
T: ToResult
{ {
unsafe { unsafe {
let ctx = Context { let ctx = Context {
@ -307,7 +306,15 @@ impl InnerSqliteConnection {
}; };
let boxed_f: *mut F = mem::transmute(ffi::sqlite3_user_data(ctx.ctx)); let boxed_f: *mut F = mem::transmute(ffi::sqlite3_user_data(ctx.ctx));
assert!(!boxed_f.is_null(), "Internal error - null function pointer"); assert!(!boxed_f.is_null(), "Internal error - null function pointer");
(*boxed_f)(&ctx); 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) {
ffi::sqlite3_result_error(ctx.ctx, cstr.as_ptr(), -1);
}
},
}
} }
} }
@ -323,7 +330,7 @@ impl InnerSqliteConnection {
n_arg, n_arg,
flags, flags,
mem::transmute(boxed_f), mem::transmute(boxed_f),
Some(call_boxed_closure::<F>), Some(call_boxed_closure::<F, T>),
None, None,
None, None,
Some(mem::transmute(free_boxed_value::<F>))) Some(mem::transmute(free_boxed_value::<F>)))
@ -336,20 +343,17 @@ impl InnerSqliteConnection {
mod test { mod test {
extern crate regex; extern crate regex;
use std::ffi::CString;
use libc::c_double; use libc::c_double;
use self::regex::Regex; use self::regex::Regex;
use SqliteConnection; use {SqliteConnection, SqliteError, SqliteResult};
use ffi; use ffi;
use functions::{Context, ToResult}; use functions::Context;
fn half(ctx: &Context) { fn half(ctx: &Context) -> SqliteResult<c_double> {
assert!(ctx.len() == 1, "called with unexpected number of arguments"); assert!(ctx.len() == 1, "called with unexpected number of arguments");
match ctx.get::<c_double>(0) { let value = try!(ctx.get::<c_double>(0));
Ok(value) => unsafe { (value / 2f64).set_result(ctx.ctx) }, Ok(value / 2f64)
Err(err) => unsafe { ffi::sqlite3_result_error_code(ctx.ctx, err.code) },
}
} }
#[test] #[test]
@ -361,50 +365,34 @@ mod test {
assert_eq!(3f64, result.unwrap()); assert_eq!(3f64, result.unwrap());
} }
fn regexp(ctx: &Context) { fn regexp(ctx: &Context) -> SqliteResult<bool> {
assert!(ctx.len() == 2, "called with unexpected number of arguments"); assert!(ctx.len() == 2, "called with unexpected number of arguments");
let saved_re: Option<&Regex> = unsafe { ctx.get_aux(0) }; let saved_re: Option<&Regex> = unsafe { ctx.get_aux(0) };
let new_re = match saved_re { let new_re = match saved_re {
None => unsafe { None => {
let raw = ctx.get::<String>(0); let s = try!(ctx.get::<String>(0));
if raw.is_err() { let r = try!(Regex::new(&s).map_err(|e| SqliteError {
let msg = CString::new(format!("{}", raw.unwrap_err())).unwrap(); code: ffi::SQLITE_ERROR,
ffi::sqlite3_result_error(ctx.ctx, msg.as_ptr(), -1); message: format!("Invalid regular expression: {}", e),
return; }));
} Some(r)
let comp = Regex::new(raw.unwrap().as_ref());
if comp.is_err() {
let msg = CString::new(format!("{}", comp.unwrap_err())).unwrap();
ffi::sqlite3_result_error(ctx.ctx, msg.as_ptr(), -1);
return;
}
Some(comp.unwrap())
}, },
Some(_) => None, Some(_) => None,
}; };
{ let is_match = {
let re = saved_re.unwrap_or_else(|| new_re.as_ref().unwrap()); let re = saved_re.unwrap_or_else(|| new_re.as_ref().unwrap());
let text = ctx.get::<String>(1); let text = try!(ctx.get::<String>(1));
if text.is_ok() { re.is_match(&text)
let text = text.unwrap(); };
unsafe {
re.is_match(text.as_ref()).set_result(ctx.ctx);
}
} else {
let msg = CString::new(format!("{}", text.unwrap_err())).unwrap();
unsafe {
ffi::sqlite3_result_error(ctx.ctx, msg.as_ptr(), -1);
}
return;
}
}
if let Some(re) = new_re { if let Some(re) = new_re {
ctx.set_aux(0, re); ctx.set_aux(0, re);
} }
Ok(is_match)
} }
#[test] #[test]