Files
turso/bindings/go/rs_src/statement.rs
Pekka Enberg e47240705c Merge 'bindings/go: Progress on Go driver, first working incremental state' from Preston Thorpe
This PR brings the Go database/sql driver to it's first working state
and adds a Go package to demonstrate.
The example pkg demonstrates (in it's most bare/naive form at this
point):
1. Open database (memory, in this case)
2. Create connection
3. Prepare statement (Create table)
4. `Exec`
5. Prepare statement (Insert, bind 3 arguments (int, string, blob) (
6. `Exec`
7. Prepare statement (Select *)
8. `Columns` -> print columns
9. `Query` -> print rows
10. Close db connection
![image](https://github.com/user-
attachments/assets/b59ec9f5-9ac6-4a59-9cad-fbb57893fbf8)
still tons of work to do but I at least wanted to get it to a state
where it's not totally broken.
I'll add some actual tests tomorrow

Closes #796
2025-01-29 13:10:52 +02:00

158 lines
4.6 KiB
Rust

use crate::rows::LimboRows;
use crate::types::{AllocPool, LimboValue, ResultCode};
use crate::LimboConn;
use limbo_core::{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(_) => 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) => {
stmt.conn.conn.total_changes();
if !changes.is_null() {
unsafe {
*changes = stmt.conn.conn.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(_) => {
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 {
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 transfered to the LimboRows object.
LimboRows::new(statement, stmt.conn).to_ptr()
}
pub struct LimboStatement<'conn> {
/// If 'query' is ran on the statement, ownership is transfered to the LimboRows object,
/// and this is set to true. `stmt_close` should never be called on a statement that has
/// been used to create a LimboRows object.
pub statement: Option<Statement>,
pub conn: &'conn mut LimboConn,
}
#[no_mangle]
pub extern "C" fn stmt_close(ctx: *mut c_void) -> ResultCode {
if !ctx.is_null() {
let stmt = LimboStatement::from_ptr(ctx);
if stmt.statement.is_none() {
return ResultCode::Error;
} else {
let _ = unsafe { Box::from_raw(ctx as *mut LimboStatement) };
return ResultCode::Ok;
}
}
ResultCode::Invalid
}
impl<'conn> LimboStatement<'conn> {
pub fn new(statement: Option<Statement>, conn: &'conn mut LimboConn) -> Self {
LimboStatement { statement, conn }
}
#[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) -> &'static mut LimboStatement<'conn> {
if ptr.is_null() {
panic!("Null pointer");
}
unsafe { &mut *(ptr as *mut LimboStatement) }
}
}