mirror of
https://github.com/tursodatabase/limbo.git
synced 2025-07-30 07:43:43 +00:00
180 lines
5.1 KiB
Rust
180 lines
5.1 KiB
Rust
use crate::rows::LimboRows;
|
|
use crate::types::{AllocPool, LimboValue, ResultCode};
|
|
use crate::LimboConn;
|
|
use limbo_core::{LimboError, Statement, StepResult};
|
|
use std::ffi::{c_char, c_void};
|
|
use std::num::NonZero;
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn db_prepare(ctx: *mut c_void, query: *const c_char) -> *mut c_void {
|
|
if ctx.is_null() || query.is_null() {
|
|
return std::ptr::null_mut();
|
|
}
|
|
let query_str = unsafe { std::ffi::CStr::from_ptr(query) }.to_str().unwrap();
|
|
|
|
let db = LimboConn::from_ptr(ctx);
|
|
let stmt = db.conn.prepare(query_str);
|
|
match stmt {
|
|
Ok(stmt) => LimboStatement::new(Some(stmt), db).to_ptr(),
|
|
Err(err) => {
|
|
db.err = Some(err);
|
|
std::ptr::null_mut()
|
|
}
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn stmt_execute(
|
|
ctx: *mut c_void,
|
|
args_ptr: *mut LimboValue,
|
|
arg_count: usize,
|
|
changes: *mut i64,
|
|
) -> ResultCode {
|
|
if ctx.is_null() {
|
|
return ResultCode::Error;
|
|
}
|
|
let stmt = LimboStatement::from_ptr(ctx);
|
|
|
|
let args = if !args_ptr.is_null() && arg_count > 0 {
|
|
unsafe { std::slice::from_raw_parts(args_ptr, arg_count) }
|
|
} else {
|
|
&[]
|
|
};
|
|
let mut pool = AllocPool::new();
|
|
let Some(statement) = stmt.statement.as_mut() else {
|
|
return ResultCode::Error;
|
|
};
|
|
for (i, arg) in args.iter().enumerate() {
|
|
let val = arg.to_value(&mut pool);
|
|
statement.bind_at(NonZero::new(i + 1).unwrap(), val);
|
|
}
|
|
loop {
|
|
match statement.step() {
|
|
Ok(StepResult::Row) => {
|
|
// unexpected row during execution, error out.
|
|
return ResultCode::Error;
|
|
}
|
|
Ok(StepResult::Done) => {
|
|
let total_changes = stmt.conn.conn.total_changes();
|
|
if !changes.is_null() {
|
|
unsafe {
|
|
*changes = total_changes;
|
|
}
|
|
}
|
|
return ResultCode::Done;
|
|
}
|
|
Ok(StepResult::IO) => {
|
|
let _ = stmt.conn.io.run_once();
|
|
}
|
|
Ok(StepResult::Busy) => {
|
|
return ResultCode::Busy;
|
|
}
|
|
Ok(StepResult::Interrupt) => {
|
|
return ResultCode::Interrupt;
|
|
}
|
|
Err(err) => {
|
|
stmt.conn.err = Some(err);
|
|
return ResultCode::Error;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn stmt_parameter_count(ctx: *mut c_void) -> i32 {
|
|
if ctx.is_null() {
|
|
return -1;
|
|
}
|
|
let stmt = LimboStatement::from_ptr(ctx);
|
|
let Some(statement) = stmt.statement.as_ref() else {
|
|
stmt.err = Some(LimboError::InternalError("Statement is closed".to_string()));
|
|
return -1;
|
|
};
|
|
statement.parameters_count() as i32
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn stmt_query(
|
|
ctx: *mut c_void,
|
|
args_ptr: *mut LimboValue,
|
|
args_count: usize,
|
|
) -> *mut c_void {
|
|
if ctx.is_null() {
|
|
return std::ptr::null_mut();
|
|
}
|
|
let stmt = LimboStatement::from_ptr(ctx);
|
|
let args = if !args_ptr.is_null() && args_count > 0 {
|
|
unsafe { std::slice::from_raw_parts(args_ptr, args_count) }
|
|
} else {
|
|
&[]
|
|
};
|
|
let mut pool = AllocPool::new();
|
|
let Some(mut statement) = stmt.statement.take() else {
|
|
return std::ptr::null_mut();
|
|
};
|
|
for (i, arg) in args.iter().enumerate() {
|
|
let val = arg.to_value(&mut pool);
|
|
statement.bind_at(NonZero::new(i + 1).unwrap(), val);
|
|
}
|
|
// ownership of the statement is transferred to the LimboRows object.
|
|
LimboRows::new(statement, stmt.conn).to_ptr()
|
|
}
|
|
|
|
pub struct LimboStatement<'conn> {
|
|
/// If 'query' is ran on the statement, ownership is transferred to the LimboRows object
|
|
pub statement: Option<Statement>,
|
|
pub conn: &'conn mut LimboConn,
|
|
pub err: Option<LimboError>,
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn stmt_close(ctx: *mut c_void) -> ResultCode {
|
|
if !ctx.is_null() {
|
|
let stmt = unsafe { Box::from_raw(ctx as *mut LimboStatement) };
|
|
drop(stmt);
|
|
return ResultCode::Ok;
|
|
}
|
|
ResultCode::Invalid
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn stmt_get_error(ctx: *mut c_void) -> *const c_char {
|
|
if ctx.is_null() {
|
|
return std::ptr::null();
|
|
}
|
|
let stmt = LimboStatement::from_ptr(ctx);
|
|
stmt.get_error()
|
|
}
|
|
|
|
impl<'conn> LimboStatement<'conn> {
|
|
pub fn new(statement: Option<Statement>, conn: &'conn mut LimboConn) -> Self {
|
|
LimboStatement {
|
|
statement,
|
|
conn,
|
|
err: None,
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::wrong_self_convention)]
|
|
fn to_ptr(self) -> *mut c_void {
|
|
Box::into_raw(Box::new(self)) as *mut c_void
|
|
}
|
|
|
|
fn from_ptr(ptr: *mut c_void) -> &'conn mut LimboStatement<'conn> {
|
|
if ptr.is_null() {
|
|
panic!("Null pointer");
|
|
}
|
|
unsafe { &mut *(ptr as *mut LimboStatement) }
|
|
}
|
|
|
|
fn get_error(&mut self) -> *const c_char {
|
|
if let Some(err) = &self.err {
|
|
let err = format!("{}", err);
|
|
let c_str = std::ffi::CString::new(err).unwrap();
|
|
self.err = None;
|
|
c_str.into_raw() as *const c_char
|
|
} else {
|
|
std::ptr::null()
|
|
}
|
|
}
|
|
}
|