mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-23 17:05:36 +01:00
bindings/go: Add error propagation from bindings lib
This commit is contained in:
@@ -29,17 +29,20 @@ var (
|
||||
dbOpen func(string) uintptr
|
||||
dbClose func(uintptr) uintptr
|
||||
connPrepare func(uintptr, string) uintptr
|
||||
connGetError func(uintptr) uintptr
|
||||
freeBlobFunc func(uintptr)
|
||||
freeStringFunc func(uintptr)
|
||||
rowsGetColumns func(uintptr) int32
|
||||
rowsGetColumnName func(uintptr, int32) uintptr
|
||||
rowsGetValue func(uintptr, int32) uintptr
|
||||
rowsGetError func(uintptr) uintptr
|
||||
closeRows func(uintptr) uintptr
|
||||
rowsNext func(uintptr) uintptr
|
||||
stmtQuery func(stmtPtr uintptr, argsPtr uintptr, argCount uint64) uintptr
|
||||
stmtExec func(stmtPtr uintptr, argsPtr uintptr, argCount uint64, changes uintptr) int32
|
||||
stmtParamCount func(uintptr) int32
|
||||
closeStmt func(uintptr) int32
|
||||
stmtGetError func(uintptr) uintptr
|
||||
stmtClose func(uintptr) int32
|
||||
)
|
||||
|
||||
// Register all the symbols on library load
|
||||
@@ -52,6 +55,7 @@ func ensureLibLoaded() error {
|
||||
purego.RegisterLibFunc(&dbOpen, limboLib, FfiDbOpen)
|
||||
purego.RegisterLibFunc(&dbClose, limboLib, FfiDbClose)
|
||||
purego.RegisterLibFunc(&connPrepare, limboLib, FfiDbPrepare)
|
||||
purego.RegisterLibFunc(&connGetError, limboLib, FfiDbGetError)
|
||||
purego.RegisterLibFunc(&freeBlobFunc, limboLib, FfiFreeBlob)
|
||||
purego.RegisterLibFunc(&freeStringFunc, limboLib, FfiFreeCString)
|
||||
purego.RegisterLibFunc(&rowsGetColumns, limboLib, FfiRowsGetColumns)
|
||||
@@ -59,10 +63,12 @@ func ensureLibLoaded() error {
|
||||
purego.RegisterLibFunc(&rowsGetValue, limboLib, FfiRowsGetValue)
|
||||
purego.RegisterLibFunc(&closeRows, limboLib, FfiRowsClose)
|
||||
purego.RegisterLibFunc(&rowsNext, limboLib, FfiRowsNext)
|
||||
purego.RegisterLibFunc(&rowsGetError, limboLib, FfiDbGetError)
|
||||
purego.RegisterLibFunc(&stmtQuery, limboLib, FfiStmtQuery)
|
||||
purego.RegisterLibFunc(&stmtExec, limboLib, FfiStmtExec)
|
||||
purego.RegisterLibFunc(&stmtParamCount, limboLib, FfiStmtParameterCount)
|
||||
purego.RegisterLibFunc(&closeStmt, limboLib, FfiStmtClose)
|
||||
purego.RegisterLibFunc(&stmtGetError, limboLib, FfiDbGetError)
|
||||
purego.RegisterLibFunc(&stmtClose, limboLib, FfiStmtClose)
|
||||
})
|
||||
return loadErr
|
||||
}
|
||||
@@ -104,6 +110,19 @@ func (c *limboConn) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *limboConn) getError() error {
|
||||
if c.ctx == 0 {
|
||||
return errors.New("connection closed")
|
||||
}
|
||||
err := connGetError(c.ctx)
|
||||
if err == 0 {
|
||||
return nil
|
||||
}
|
||||
defer freeStringFunc(err)
|
||||
cpy := fmt.Sprintf("%s", GoString(err))
|
||||
return errors.New(cpy)
|
||||
}
|
||||
|
||||
func (c *limboConn) Prepare(query string) (driver.Stmt, error) {
|
||||
if c.ctx == 0 {
|
||||
return nil, errors.New("connection closed")
|
||||
@@ -112,7 +131,7 @@ func (c *limboConn) Prepare(query string) (driver.Stmt, error) {
|
||||
defer c.Unlock()
|
||||
stmtPtr := connPrepare(c.ctx, query)
|
||||
if stmtPtr == 0 {
|
||||
return nil, fmt.Errorf("failed to prepare query=%q", query)
|
||||
return nil, c.getError()
|
||||
}
|
||||
return newStmt(stmtPtr, query), nil
|
||||
}
|
||||
|
||||
@@ -205,6 +205,81 @@ func TestDuplicateConnection2(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnectionError(t *testing.T) {
|
||||
newConn, err := sql.Open("sqlite3", ":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Error opening new connection: %v", err)
|
||||
}
|
||||
sql := "CREATE TABLE test (foo INTEGER, bar INTEGER, baz BLOB);"
|
||||
newConn.Exec(sql)
|
||||
sql = "INSERT INTO test (foo, bar, baz) VALUES (?, ?, notafunction(?));"
|
||||
_, err = newConn.Prepare(sql)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error, got nil")
|
||||
}
|
||||
expectedErr := "Parse error: unknown function notafunction"
|
||||
if err.Error() != expectedErr {
|
||||
t.Fatalf("Error test failed, expected: %s, found: %v", expectedErr, err)
|
||||
}
|
||||
fmt.Println("Connection error test passed")
|
||||
}
|
||||
|
||||
func TestStatementError(t *testing.T) {
|
||||
newConn, err := sql.Open("sqlite3", ":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Error opening new connection: %v", err)
|
||||
}
|
||||
sql := "CREATE TABLE test (foo INTEGER, bar INTEGER, baz BLOB);"
|
||||
newConn.Exec(sql)
|
||||
sql = "INSERT INTO test (foo, bar, baz) VALUES (?, ?, ?);"
|
||||
stmt, err := newConn.Prepare(sql)
|
||||
if err != nil {
|
||||
t.Fatalf("Error preparing statement: %v", err)
|
||||
}
|
||||
_, err = stmt.Exec(1, 2)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error, got nil")
|
||||
}
|
||||
if err.Error() != "sql: expected 3 arguments, got 2" {
|
||||
t.Fatalf("Unexpected : %v\n", err)
|
||||
}
|
||||
fmt.Println("Statement error test passed")
|
||||
}
|
||||
|
||||
func TestDriverRowsErrorMessages(t *testing.T) {
|
||||
db, err := sql.Open("sqlite3", ":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
_, err = db.Exec("CREATE TABLE test (id INTEGER, name TEXT)")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create table: %v", err)
|
||||
}
|
||||
|
||||
_, err = db.Exec("INSERT INTO test (id, name) VALUES (?, ?)", 1, "Alice")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to insert row: %v", err)
|
||||
}
|
||||
|
||||
rows, err := db.Query("SELECT id, name FROM test")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to query table: %v", err)
|
||||
}
|
||||
|
||||
if !rows.Next() {
|
||||
t.Fatalf("expected at least one row")
|
||||
}
|
||||
var id int
|
||||
var name string
|
||||
err = rows.Scan(&name, &id)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error scanning wrong type: %v", err)
|
||||
}
|
||||
t.Log("Rows error behavior test passed")
|
||||
}
|
||||
|
||||
func slicesAreEq(a, b []byte) bool {
|
||||
if len(a) != len(b) {
|
||||
fmt.Printf("LENGTHS NOT EQUAL: %d != %d\n", len(a), len(b))
|
||||
|
||||
@@ -2,6 +2,7 @@ package limbo
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
@@ -11,6 +12,7 @@ type limboRows struct {
|
||||
mu sync.Mutex
|
||||
ctx uintptr
|
||||
columns []string
|
||||
err error
|
||||
closed bool
|
||||
}
|
||||
|
||||
@@ -18,13 +20,21 @@ func newRows(ctx uintptr) *limboRows {
|
||||
return &limboRows{
|
||||
mu: sync.Mutex{},
|
||||
ctx: ctx,
|
||||
closed: false,
|
||||
columns: nil,
|
||||
err: nil,
|
||||
closed: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *limboRows) Columns() []string {
|
||||
func (r *limboRows) isClosed() bool {
|
||||
if r.ctx == 0 || r.closed {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *limboRows) Columns() []string {
|
||||
if r.isClosed() {
|
||||
return nil
|
||||
}
|
||||
if r.columns == nil {
|
||||
@@ -45,8 +55,9 @@ func (r *limboRows) Columns() []string {
|
||||
}
|
||||
|
||||
func (r *limboRows) Close() error {
|
||||
if r.closed {
|
||||
return nil
|
||||
r.err = errors.New(RowsClosedErr)
|
||||
if r.isClosed() {
|
||||
return r.err
|
||||
}
|
||||
r.mu.Lock()
|
||||
r.closed = true
|
||||
@@ -56,12 +67,21 @@ func (r *limboRows) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *limboRows) Next(dest []driver.Value) error {
|
||||
if r.ctx == 0 || r.closed {
|
||||
return io.EOF
|
||||
func (r *limboRows) Err() error {
|
||||
if r.err == nil {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
r.getError()
|
||||
}
|
||||
return r.err
|
||||
}
|
||||
|
||||
func (r *limboRows) Next(dest []driver.Value) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.isClosed() {
|
||||
return r.err
|
||||
}
|
||||
for {
|
||||
status := rowsNext(r.ctx)
|
||||
switch ResultCode(status) {
|
||||
@@ -69,6 +89,9 @@ func (r *limboRows) Next(dest []driver.Value) error {
|
||||
for i := range dest {
|
||||
valPtr := rowsGetValue(r.ctx, int32(i))
|
||||
val := toGoValue(valPtr)
|
||||
if val == nil {
|
||||
r.getError()
|
||||
}
|
||||
dest[i] = val
|
||||
}
|
||||
return nil
|
||||
@@ -77,7 +100,22 @@ func (r *limboRows) Next(dest []driver.Value) error {
|
||||
case Done:
|
||||
return io.EOF
|
||||
default:
|
||||
return fmt.Errorf("unexpected status: %d", status)
|
||||
return r.getError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// mutex will already be locked. this is always called after FFI
|
||||
func (r *limboRows) getError() error {
|
||||
if r.isClosed() {
|
||||
return r.err
|
||||
}
|
||||
err := rowsGetError(r.ctx)
|
||||
if err == 0 {
|
||||
return nil
|
||||
}
|
||||
defer freeCString(err)
|
||||
cpy := fmt.Sprintf("%s", GoString(err))
|
||||
r.err = errors.New(cpy)
|
||||
return r.err
|
||||
}
|
||||
|
||||
@@ -42,11 +42,16 @@ pub unsafe extern "C" fn db_open(path: *const c_char) -> *mut c_void {
|
||||
struct LimboConn {
|
||||
conn: Rc<Connection>,
|
||||
io: Arc<dyn limbo_core::IO>,
|
||||
err: Option<LimboError>,
|
||||
}
|
||||
|
||||
impl<'conn> LimboConn {
|
||||
fn new(conn: Rc<Connection>, io: Arc<dyn limbo_core::IO>) -> Self {
|
||||
LimboConn { conn, io }
|
||||
LimboConn {
|
||||
conn,
|
||||
io,
|
||||
err: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
@@ -60,6 +65,28 @@ impl<'conn> LimboConn {
|
||||
}
|
||||
unsafe { &mut *(ptr as *mut LimboConn) }
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Get the error value from the connection, if any, as a null
|
||||
/// terminated string. The caller is responsible for freeing the
|
||||
/// memory with `free_string`.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn db_get_error(ctx: *mut c_void) -> *const c_char {
|
||||
if ctx.is_null() {
|
||||
return std::ptr::null();
|
||||
}
|
||||
let conn = LimboConn::from_ptr(ctx);
|
||||
conn.get_error()
|
||||
}
|
||||
|
||||
/// Close the database connection
|
||||
|
||||
@@ -2,21 +2,23 @@ use crate::{
|
||||
types::{LimboValue, ResultCode},
|
||||
LimboConn,
|
||||
};
|
||||
use limbo_core::{Row, Statement, StepResult};
|
||||
use limbo_core::{LimboError, Row, Statement, StepResult};
|
||||
use std::ffi::{c_char, c_void};
|
||||
|
||||
pub struct LimboRows<'a> {
|
||||
pub struct LimboRows<'conn, 'a> {
|
||||
stmt: Box<Statement>,
|
||||
conn: &'a LimboConn,
|
||||
conn: &'conn mut LimboConn,
|
||||
cursor: Option<Row<'a>>,
|
||||
err: Option<LimboError>,
|
||||
}
|
||||
|
||||
impl<'a> LimboRows<'a> {
|
||||
pub fn new(stmt: Statement, conn: &'a LimboConn) -> Self {
|
||||
impl<'conn, 'a> LimboRows<'conn, 'a> {
|
||||
pub fn new(stmt: Statement, conn: &'conn mut LimboConn) -> Self {
|
||||
LimboRows {
|
||||
stmt: Box::new(stmt),
|
||||
cursor: None,
|
||||
conn,
|
||||
err: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,12 +27,23 @@ impl<'a> LimboRows<'a> {
|
||||
Box::into_raw(Box::new(self)) as *mut c_void
|
||||
}
|
||||
|
||||
pub fn from_ptr(ptr: *mut c_void) -> &'static mut LimboRows<'a> {
|
||||
pub fn from_ptr(ptr: *mut c_void) -> &'conn mut LimboRows<'conn, 'a> {
|
||||
if ptr.is_null() {
|
||||
panic!("Null pointer");
|
||||
}
|
||||
unsafe { &mut *(ptr as *mut LimboRows) }
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -52,7 +65,10 @@ pub extern "C" fn rows_next(ctx: *mut c_void) -> ResultCode {
|
||||
}
|
||||
Ok(StepResult::Busy) => ResultCode::Busy,
|
||||
Ok(StepResult::Interrupt) => ResultCode::Interrupt,
|
||||
Err(_) => ResultCode::Error,
|
||||
Err(err) => {
|
||||
ctx.err = Some(err);
|
||||
ResultCode::Error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,18 +124,23 @@ pub extern "C" fn rows_get_column_name(rows_ptr: *mut c_void, idx: i32) -> *cons
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rows_close(rows_ptr: *mut c_void) {
|
||||
if !rows_ptr.is_null() {
|
||||
let _ = unsafe { Box::from_raw(rows_ptr as *mut LimboRows) };
|
||||
pub extern "C" fn rows_get_error(ctx: *mut c_void) -> *const c_char {
|
||||
if ctx.is_null() {
|
||||
return std::ptr::null();
|
||||
}
|
||||
let ctx = LimboRows::from_ptr(ctx);
|
||||
ctx.get_error()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn free_rows(rows: *mut c_void) {
|
||||
if rows.is_null() {
|
||||
return;
|
||||
pub extern "C" fn rows_close(ctx: *mut c_void) {
|
||||
if !ctx.is_null() {
|
||||
let rows = LimboRows::from_ptr(ctx);
|
||||
rows.stmt.reset();
|
||||
rows.cursor = None;
|
||||
rows.err = None;
|
||||
}
|
||||
unsafe {
|
||||
let _ = Box::from_raw(rows as *mut Statement);
|
||||
let _ = Box::from_raw(ctx.cast::<LimboRows>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::rows::LimboRows;
|
||||
use crate::types::{AllocPool, LimboValue, ResultCode};
|
||||
use crate::LimboConn;
|
||||
use limbo_core::{Statement, StepResult};
|
||||
use limbo_core::{LimboError, Statement, StepResult};
|
||||
use std::ffi::{c_char, c_void};
|
||||
use std::num::NonZero;
|
||||
|
||||
@@ -15,8 +15,11 @@ pub extern "C" fn db_prepare(ctx: *mut c_void, query: *const c_char) -> *mut c_v
|
||||
let db = LimboConn::from_ptr(ctx);
|
||||
let stmt = db.conn.prepare(query_str);
|
||||
match stmt {
|
||||
Ok(stmt) => LimboStatement::new(Some(stmt), LimboConn::from_ptr(ctx)).to_ptr(),
|
||||
Err(_) => std::ptr::null_mut(),
|
||||
Ok(stmt) => LimboStatement::new(Some(stmt), db).to_ptr(),
|
||||
Err(err) => {
|
||||
db.err = Some(err);
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +72,8 @@ pub extern "C" fn stmt_execute(
|
||||
Ok(StepResult::Interrupt) => {
|
||||
return ResultCode::Interrupt;
|
||||
}
|
||||
Err(_) => {
|
||||
Err(err) => {
|
||||
stmt.conn.err = Some(err);
|
||||
return ResultCode::Error;
|
||||
}
|
||||
}
|
||||
@@ -83,6 +87,7 @@ pub extern "C" fn stmt_parameter_count(ctx: *mut c_void) -> i32 {
|
||||
}
|
||||
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
|
||||
@@ -116,11 +121,10 @@ pub extern "C" fn stmt_query(
|
||||
}
|
||||
|
||||
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.
|
||||
/// If 'query' is ran on the statement, ownership is transfered to the LimboRows object
|
||||
pub statement: Option<Statement>,
|
||||
pub conn: &'conn mut LimboConn,
|
||||
pub err: Option<LimboError>,
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -133,9 +137,22 @@ pub extern "C" fn stmt_close(ctx: *mut c_void) -> ResultCode {
|
||||
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 }
|
||||
LimboStatement {
|
||||
statement,
|
||||
conn,
|
||||
err: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
@@ -149,4 +166,15 @@ impl<'conn> LimboStatement<'conn> {
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,29 +13,39 @@ type limboStmt struct {
|
||||
mu sync.Mutex
|
||||
ctx uintptr
|
||||
sql string
|
||||
err error
|
||||
}
|
||||
|
||||
func newStmt(ctx uintptr, sql string) *limboStmt {
|
||||
return &limboStmt{
|
||||
ctx: uintptr(ctx),
|
||||
sql: sql,
|
||||
err: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func (ls *limboStmt) NumInput() int {
|
||||
ls.mu.Lock()
|
||||
defer ls.mu.Unlock()
|
||||
return int(stmtParamCount(ls.ctx))
|
||||
res := int(stmtParamCount(ls.ctx))
|
||||
if res < 0 {
|
||||
// set the error from rust
|
||||
_ = ls.getError()
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (ls *limboStmt) Close() error {
|
||||
ls.mu.Lock()
|
||||
res := closeStmt(ls.ctx)
|
||||
ls.mu.Unlock()
|
||||
defer ls.mu.Unlock()
|
||||
if ls.ctx == 0 {
|
||||
return nil
|
||||
}
|
||||
res := stmtClose(ls.ctx)
|
||||
ls.ctx = 0
|
||||
if ResultCode(res) != Ok {
|
||||
return fmt.Errorf("error closing statement: %s", ResultCode(res).String())
|
||||
}
|
||||
ls.ctx = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -52,8 +62,8 @@ func (ls *limboStmt) Exec(args []driver.Value) (driver.Result, error) {
|
||||
}
|
||||
var changes uint64
|
||||
ls.mu.Lock()
|
||||
defer ls.mu.Unlock()
|
||||
rc := stmtExec(ls.ctx, argPtr, argCount, uintptr(unsafe.Pointer(&changes)))
|
||||
ls.mu.Unlock()
|
||||
switch ResultCode(rc) {
|
||||
case Ok, Done:
|
||||
return driver.RowsAffected(changes), nil
|
||||
@@ -66,7 +76,7 @@ func (ls *limboStmt) Exec(args []driver.Value) (driver.Result, error) {
|
||||
case Invalid:
|
||||
return nil, errors.New("invalid statement")
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected status: %d", rc)
|
||||
return nil, ls.getError()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,10 +91,10 @@ func (ls *limboStmt) Query(args []driver.Value) (driver.Rows, error) {
|
||||
argPtr = uintptr(unsafe.Pointer(&queryArgs[0]))
|
||||
}
|
||||
ls.mu.Lock()
|
||||
defer ls.mu.Unlock()
|
||||
rowsPtr := stmtQuery(ls.ctx, argPtr, uint64(len(queryArgs)))
|
||||
ls.mu.Unlock()
|
||||
if rowsPtr == 0 {
|
||||
return nil, fmt.Errorf("query failed for: %q", ls.sql)
|
||||
return nil, ls.getError()
|
||||
}
|
||||
return newRows(rowsPtr), nil
|
||||
}
|
||||
@@ -96,27 +106,26 @@ func (ls *limboStmt) ExecContext(ctx context.Context, query string, args []drive
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ls.mu.Lock()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
ls.mu.Unlock()
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
}
|
||||
var changes uint64
|
||||
ls.mu.Lock()
|
||||
res := stmtExec(ls.ctx, argArray, uint64(len(args)), uintptr(unsafe.Pointer(&changes)))
|
||||
ls.mu.Unlock()
|
||||
switch ResultCode(res) {
|
||||
case Ok, Done:
|
||||
changes := uint64(changes)
|
||||
return driver.RowsAffected(changes), nil
|
||||
case Error:
|
||||
return nil, errors.New("error executing statement")
|
||||
case Busy:
|
||||
return nil, errors.New("busy")
|
||||
case Interrupt:
|
||||
return nil, errors.New("interrupted")
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected status: %d", res)
|
||||
var changes uint64
|
||||
defer ls.mu.Unlock()
|
||||
res := stmtExec(ls.ctx, argArray, uint64(len(args)), uintptr(unsafe.Pointer(&changes)))
|
||||
switch ResultCode(res) {
|
||||
case Ok, Done:
|
||||
changes := uint64(changes)
|
||||
return driver.RowsAffected(changes), nil
|
||||
case Busy:
|
||||
return nil, errors.New("Database is Busy")
|
||||
case Interrupt:
|
||||
return nil, errors.New("Interrupted")
|
||||
default:
|
||||
return nil, ls.getError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,16 +139,38 @@ func (ls *limboStmt) QueryContext(ctx context.Context, args []driver.NamedValue)
|
||||
if len(queryArgs) > 0 {
|
||||
argsPtr = uintptr(unsafe.Pointer(&queryArgs[0]))
|
||||
}
|
||||
ls.mu.Lock()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
ls.mu.Unlock()
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
defer ls.mu.Unlock()
|
||||
rowsPtr := stmtQuery(ls.ctx, argsPtr, uint64(len(queryArgs)))
|
||||
if rowsPtr == 0 {
|
||||
return nil, ls.getError()
|
||||
}
|
||||
return newRows(rowsPtr), nil
|
||||
}
|
||||
ls.mu.Lock()
|
||||
rowsPtr := stmtQuery(ls.ctx, argsPtr, uint64(len(queryArgs)))
|
||||
ls.mu.Unlock()
|
||||
if rowsPtr == 0 {
|
||||
return nil, fmt.Errorf("query failed for: %q", ls.sql)
|
||||
}
|
||||
return newRows(rowsPtr), nil
|
||||
}
|
||||
|
||||
func (ls *limboStmt) Err() error {
|
||||
if ls.err == nil {
|
||||
ls.mu.Lock()
|
||||
defer ls.mu.Unlock()
|
||||
ls.getError()
|
||||
}
|
||||
return ls.err
|
||||
}
|
||||
|
||||
// mutex should always be locked when calling - always called after FFI
|
||||
func (ls *limboStmt) getError() error {
|
||||
err := stmtGetError(ls.ctx)
|
||||
if err == 0 {
|
||||
return nil
|
||||
}
|
||||
defer freeCString(err)
|
||||
cpy := fmt.Sprintf("%s", GoString(err))
|
||||
ls.err = errors.New(cpy)
|
||||
return ls.err
|
||||
}
|
||||
|
||||
@@ -67,9 +67,11 @@ func (rc ResultCode) String() string {
|
||||
const (
|
||||
driverName = "sqlite3"
|
||||
libName = "lib_limbo_go"
|
||||
RowsClosedErr = "sql: Rows closed"
|
||||
FfiDbOpen = "db_open"
|
||||
FfiDbClose = "db_close"
|
||||
FfiDbPrepare = "db_prepare"
|
||||
FfiDbGetError = "db_get_error"
|
||||
FfiStmtExec = "stmt_execute"
|
||||
FfiStmtQuery = "stmt_query"
|
||||
FfiStmtParameterCount = "stmt_parameter_count"
|
||||
|
||||
Reference in New Issue
Block a user