mirror of
https://github.com/tursodatabase/limbo.git
synced 2025-08-03 17:48:17 +00:00
Begin work on Go bindings (purego)
This commit is contained in:
parent
aded7d3896
commit
4be1f9c3cc
12 changed files with 674 additions and 0 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -2572,6 +2572,13 @@ version = "0.1.33"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
|
||||
|
||||
[[package]]
|
||||
name = "turso-go"
|
||||
version = "0.0.13"
|
||||
dependencies = [
|
||||
"limbo_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.17.0"
|
||||
|
|
|
@ -7,6 +7,7 @@ members = [
|
|||
"bindings/python",
|
||||
"bindings/rust",
|
||||
"bindings/wasm",
|
||||
"bindings/go",
|
||||
"cli",
|
||||
"core",
|
||||
"extensions/core",
|
||||
|
|
23
bindings/go/Cargo.toml
Normal file
23
bindings/go/Cargo.toml
Normal file
|
@ -0,0 +1,23 @@
|
|||
[package]
|
||||
name = "turso-go"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "_turso_go"
|
||||
crate-type = ["cdylib"]
|
||||
path = "rs_src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = ["io_uring"]
|
||||
io_uring = ["limbo_core/io_uring"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
limbo_core = { path = "../../core/" }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
limbo_core = { path = "../../core/", features = ["io_uring"] }
|
18
bindings/go/cmd/main.go
Normal file
18
bindings/go/cmd/main.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
// package main
|
||||
//
|
||||
// import (
|
||||
// "fmt"
|
||||
// )
|
||||
//
|
||||
// func main() {
|
||||
// conn, err := lc.Open("new.db")
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// fmt.Println("Connected to database")
|
||||
// sql := "select c from t;"
|
||||
// conn.Query(sql)
|
||||
//
|
||||
// conn.Close()
|
||||
// fmt.Println("Connection closed")
|
||||
// }
|
5
bindings/go/go.mod
Normal file
5
bindings/go/go.mod
Normal file
|
@ -0,0 +1,5 @@
|
|||
module turso
|
||||
|
||||
go 1.23.4
|
||||
|
||||
require github.com/ebitengine/purego v0.8.2 // indirect
|
2
bindings/go/go.sum
Normal file
2
bindings/go/go.sum
Normal file
|
@ -0,0 +1,2 @@
|
|||
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
|
||||
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
210
bindings/go/rs_src/lib.rs
Normal file
210
bindings/go/rs_src/lib.rs
Normal file
|
@ -0,0 +1,210 @@
|
|||
mod statement;
|
||||
mod types;
|
||||
use limbo_core::{Connection, Database, LimboError, Value};
|
||||
use std::{
|
||||
ffi::{c_char, c_void},
|
||||
rc::Rc,
|
||||
str::FromStr,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn db_open(path: *const c_char) -> *mut c_void {
|
||||
if path.is_null() {
|
||||
println!("Path is null");
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
let path = unsafe { std::ffi::CStr::from_ptr(path) };
|
||||
let path = path.to_str().unwrap();
|
||||
let db_options = parse_query_str(path);
|
||||
if let Ok(io) = get_io(&db_options.path) {
|
||||
let db = Database::open_file(io.clone(), &db_options.path.to_string());
|
||||
match db {
|
||||
Ok(db) => {
|
||||
println!("Opened database: {}", path);
|
||||
let conn = db.connect();
|
||||
return TursoConn::new(conn, io).to_ptr();
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error opening database: {}", e);
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
};
|
||||
}
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
|
||||
struct TursoConn<'a> {
|
||||
conn: Rc<Connection>,
|
||||
io: Arc<dyn limbo_core::IO>,
|
||||
cursor_idx: usize,
|
||||
cursor: Option<Vec<Value<'a>>>,
|
||||
}
|
||||
|
||||
impl<'a> TursoConn<'_> {
|
||||
fn new(conn: Rc<Connection>, io: Arc<dyn limbo_core::IO>) -> Self {
|
||||
TursoConn {
|
||||
conn,
|
||||
io,
|
||||
cursor_idx: 0,
|
||||
cursor: None,
|
||||
}
|
||||
}
|
||||
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 TursoConn<'a> {
|
||||
if ptr.is_null() {
|
||||
panic!("Null pointer");
|
||||
}
|
||||
unsafe { &mut *(ptr as *mut TursoConn) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Close the database connection
|
||||
/// # Safety
|
||||
/// safely frees the connection's memory
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn db_close(db: *mut c_void) {
|
||||
if !db.is_null() {
|
||||
let _ = unsafe { Box::from_raw(db) };
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::arc_with_non_send_sync)]
|
||||
fn get_io(db_location: &DbType) -> Result<Arc<dyn limbo_core::IO>, LimboError> {
|
||||
Ok(match db_location {
|
||||
DbType::Memory => Arc::new(limbo_core::MemoryIO::new()?),
|
||||
_ => {
|
||||
#[cfg(target_family = "unix")]
|
||||
if cfg!(all(target_os = "linux", feature = "io_uring")) {
|
||||
Arc::new(limbo_core::UringIO::new()?)
|
||||
} else {
|
||||
Arc::new(limbo_core::UnixIO::new()?)
|
||||
}
|
||||
|
||||
#[cfg(target_family = "windows")]
|
||||
Arc::new(limbo_core::WindowsIO::new()?);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
struct DbOptions {
|
||||
path: DbType,
|
||||
params: Parameters,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
enum DbType {
|
||||
File(String),
|
||||
#[default]
|
||||
Memory,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for DbType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
DbType::File(path) => write!(f, "{}", path),
|
||||
DbType::Memory => write!(f, ":memory:"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct Parameters {
|
||||
mode: Mode,
|
||||
cache: Option<Cache>,
|
||||
vfs: Option<String>,
|
||||
nolock: bool,
|
||||
immutable: bool,
|
||||
modeof: Option<String>,
|
||||
}
|
||||
|
||||
impl FromStr for Parameters {
|
||||
type Err = ();
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if !s.contains('?') {
|
||||
return Ok(Parameters::default());
|
||||
}
|
||||
let mut params = Parameters::default();
|
||||
for param in s.split('?').nth(1).unwrap().split('&') {
|
||||
let mut kv = param.split('=');
|
||||
match kv.next() {
|
||||
Some("mode") => params.mode = kv.next().unwrap().parse().unwrap(),
|
||||
Some("cache") => params.cache = Some(kv.next().unwrap().parse().unwrap()),
|
||||
Some("vfs") => params.vfs = Some(kv.next().unwrap().to_string()),
|
||||
Some("nolock") => params.nolock = true,
|
||||
Some("immutable") => params.immutable = true,
|
||||
Some("modeof") => params.modeof = Some(kv.next().unwrap().to_string()),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
enum Cache {
|
||||
Shared,
|
||||
#[default]
|
||||
Private,
|
||||
}
|
||||
|
||||
impl FromStr for Cache {
|
||||
type Err = ();
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"shared" => Ok(Cache::Shared),
|
||||
_ => Ok(Cache::Private),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
enum Mode {
|
||||
ReadOnly,
|
||||
ReadWrite,
|
||||
#[default]
|
||||
ReadWriteCreate,
|
||||
}
|
||||
|
||||
impl FromStr for Mode {
|
||||
type Err = ();
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"readonly" | "ro" => Ok(Mode::ReadOnly),
|
||||
"readwrite" | "rw" => Ok(Mode::ReadWrite),
|
||||
"readwritecreate" | "rwc" => Ok(Mode::ReadWriteCreate),
|
||||
_ => Ok(Mode::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// At this point we don't have configurable parameters but many
|
||||
// DSN's are going to have query parameters
|
||||
fn parse_query_str(mut path: &str) -> DbOptions {
|
||||
if path == ":memory:" {
|
||||
return DbOptions {
|
||||
path: DbType::Memory,
|
||||
params: Parameters::default(),
|
||||
};
|
||||
}
|
||||
if path.starts_with("sqlite://") {
|
||||
path = &path[10..];
|
||||
}
|
||||
if path.contains('?') {
|
||||
let parameters = Parameters::from_str(path).unwrap();
|
||||
let path = &path[..path.find('?').unwrap()];
|
||||
DbOptions {
|
||||
path: DbType::File(path.to_string()),
|
||||
params: parameters,
|
||||
}
|
||||
} else {
|
||||
DbOptions {
|
||||
path: DbType::File(path.to_string()),
|
||||
params: Parameters::default(),
|
||||
}
|
||||
}
|
||||
}
|
160
bindings/go/rs_src/statement.rs
Normal file
160
bindings/go/rs_src/statement.rs
Normal file
|
@ -0,0 +1,160 @@
|
|||
use crate::types::ResultCode;
|
||||
use crate::TursoConn;
|
||||
use limbo_core::{Rows, Statement, StepResult, Value};
|
||||
use std::ffi::{c_char, c_void};
|
||||
|
||||
#[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 = TursoConn::from_ptr(ctx);
|
||||
|
||||
let stmt = db.conn.prepare(query_str.to_string());
|
||||
match stmt {
|
||||
Ok(stmt) => TursoStatement::new(stmt, db).to_ptr(),
|
||||
Err(_) => std::ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
struct TursoStatement<'a> {
|
||||
statement: Statement,
|
||||
conn: &'a TursoConn<'a>,
|
||||
}
|
||||
|
||||
impl<'a> TursoStatement<'a> {
|
||||
fn new(statement: Statement, conn: &'a TursoConn<'a>) -> Self {
|
||||
TursoStatement { statement, conn }
|
||||
}
|
||||
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 TursoStatement<'a> {
|
||||
if ptr.is_null() {
|
||||
panic!("Null pointer");
|
||||
}
|
||||
unsafe { &mut *(ptr as *mut TursoStatement) }
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn db_get_columns(ctx: *mut c_void) -> *const c_void {
|
||||
if ctx.is_null() {
|
||||
return std::ptr::null();
|
||||
}
|
||||
let stmt = TursoStatement::from_ptr(ctx);
|
||||
let columns = stmt.statement.columns();
|
||||
let mut column_names = Vec::new();
|
||||
for column in columns {
|
||||
column_names.push(column.name().to_string());
|
||||
}
|
||||
let c_string = std::ffi::CString::new(column_names.join(",")).unwrap();
|
||||
c_string.into_raw() as *const c_void
|
||||
}
|
||||
|
||||
struct TursoRows<'a> {
|
||||
rows: Rows<'a>,
|
||||
conn: &'a mut TursoConn<'a>,
|
||||
}
|
||||
|
||||
impl<'a> TursoRows<'a> {
|
||||
fn new(rows: Rows<'a>, conn: &'a mut TursoConn<'a>) -> Self {
|
||||
TursoRows { rows, conn }
|
||||
}
|
||||
|
||||
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 TursoRows<'a> {
|
||||
if ptr.is_null() {
|
||||
panic!("Null pointer");
|
||||
}
|
||||
unsafe { &mut *(ptr as *mut TursoRows) }
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rows_next(ctx: *mut c_void, rows_ptr: *mut c_void) -> ResultCode {
|
||||
if rows_ptr.is_null() || ctx.is_null() {
|
||||
return ResultCode::Error;
|
||||
}
|
||||
let rows = unsafe { &mut *(rows_ptr as *mut Rows) };
|
||||
let conn = TursoConn::from_ptr(ctx);
|
||||
|
||||
match rows.next_row() {
|
||||
Ok(StepResult::Row(row)) => {
|
||||
conn.cursor = Some(row.values);
|
||||
ResultCode::Row
|
||||
}
|
||||
Ok(StepResult::Done) => {
|
||||
// No more rows
|
||||
ResultCode::Done
|
||||
}
|
||||
Ok(StepResult::IO) => {
|
||||
let _ = conn.io.run_once();
|
||||
ResultCode::Io
|
||||
}
|
||||
Ok(StepResult::Busy) => ResultCode::Busy,
|
||||
Ok(StepResult::Interrupt) => ResultCode::Interrupt,
|
||||
Err(_) => ResultCode::Error,
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rows_get_value(ctx: *mut c_void, col_idx: usize) -> *const c_char {
|
||||
if ctx.is_null() {
|
||||
return std::ptr::null();
|
||||
}
|
||||
let conn = TursoConn::from_ptr(ctx);
|
||||
|
||||
if let Some(ref cursor) = conn.cursor {
|
||||
if let Some(value) = cursor.get(col_idx) {
|
||||
let c_string = std::ffi::CString::new(value.to_string()).unwrap();
|
||||
return c_string.into_raw(); // Caller must free this pointer
|
||||
}
|
||||
}
|
||||
std::ptr::null() // No data or invalid index
|
||||
}
|
||||
|
||||
// Free the returned string
|
||||
#[no_mangle]
|
||||
pub extern "C" fn free_c_string(s: *mut c_char) {
|
||||
if !s.is_null() {
|
||||
unsafe { drop(std::ffi::CString::from_raw(s)) };
|
||||
}
|
||||
}
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rows_get_string(
|
||||
ctx: *mut c_void,
|
||||
rows_ptr: *mut c_void,
|
||||
col_idx: i32,
|
||||
) -> *const c_char {
|
||||
if rows_ptr.is_null() || ctx.is_null() {
|
||||
return std::ptr::null();
|
||||
}
|
||||
let _rows = unsafe { &mut *(rows_ptr as *mut Rows) };
|
||||
let conn = TursoConn::from_ptr(ctx);
|
||||
if col_idx > conn.cursor_idx as i32 || conn.cursor.is_none() {
|
||||
return std::ptr::null();
|
||||
}
|
||||
if let Some(values) = &conn.cursor {
|
||||
let value = &values[col_idx as usize];
|
||||
match value {
|
||||
Value::Text(s) => {
|
||||
return s.as_ptr() as *const i8;
|
||||
}
|
||||
_ => return std::ptr::null(),
|
||||
}
|
||||
};
|
||||
std::ptr::null()
|
||||
}
|
||||
|
||||
#[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 Rows) };
|
||||
}
|
||||
}
|
14
bindings/go/rs_src/types.rs
Normal file
14
bindings/go/rs_src/types.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
#[repr(C)]
|
||||
pub enum ResultCode {
|
||||
Error = -1,
|
||||
Ok = 0,
|
||||
Row = 1,
|
||||
Busy = 2,
|
||||
Done = 3,
|
||||
Io = 4,
|
||||
Interrupt = 5,
|
||||
Invalid = 6,
|
||||
Null = 7,
|
||||
NoMem = 8,
|
||||
ReadOnly = 9,
|
||||
}
|
79
bindings/go/stmt.go
Normal file
79
bindings/go/stmt.go
Normal file
|
@ -0,0 +1,79 @@
|
|||
package turso
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type stmt struct {
|
||||
ctx uintptr
|
||||
sql string
|
||||
}
|
||||
|
||||
type rows struct {
|
||||
ctx uintptr
|
||||
rowsPtr uintptr
|
||||
columns []string
|
||||
err error
|
||||
}
|
||||
|
||||
func (ls *stmt) Query(args []driver.Value) (driver.Rows, error) {
|
||||
var dbPrepare func(uintptr, uintptr) uintptr
|
||||
getExtFunc(&dbPrepare, "db_prepare")
|
||||
|
||||
queryPtr := toCString(ls.sql)
|
||||
defer freeCString(queryPtr)
|
||||
|
||||
rowsPtr := dbPrepare(ls.ctx, queryPtr)
|
||||
if rowsPtr == 0 {
|
||||
return nil, fmt.Errorf("failed to prepare query")
|
||||
}
|
||||
var colFunc func(uintptr, uintptr) uintptr
|
||||
|
||||
getExtFunc(&colFunc, "columns")
|
||||
|
||||
rows := &rows{
|
||||
ctx: ls.ctx,
|
||||
rowsPtr: rowsPtr,
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func (lr *rows) Columns() []string {
|
||||
return lr.columns
|
||||
}
|
||||
|
||||
func (lr *rows) Close() error {
|
||||
var rowsClose func(uintptr)
|
||||
getExtFunc(&rowsClose, "rows_close")
|
||||
rowsClose(lr.rowsPtr)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lr *rows) Next(dest []driver.Value) error {
|
||||
var rowsNext func(uintptr, uintptr) int32
|
||||
getExtFunc(&rowsNext, "rows_next")
|
||||
|
||||
status := rowsNext(lr.ctx, lr.rowsPtr)
|
||||
switch ResultCode(status) {
|
||||
case Row:
|
||||
for i := range dest {
|
||||
getExtFunc(&rowsGetValue, "rows_get_value")
|
||||
|
||||
valPtr := rowsGetValue(lr.ctx, int32(i))
|
||||
if valPtr != 0 {
|
||||
val := cStringToGoString(valPtr)
|
||||
dest[i] = val
|
||||
freeCString(valPtr)
|
||||
} else {
|
||||
dest[i] = nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
case 0: // No more rows
|
||||
return io.EOF
|
||||
default:
|
||||
return fmt.Errorf("unexpected status: %d", status)
|
||||
}
|
||||
}
|
127
bindings/go/turso.go
Normal file
127
bindings/go/turso.go
Normal file
|
@ -0,0 +1,127 @@
|
|||
package turso
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"os"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/ebitengine/purego"
|
||||
)
|
||||
|
||||
const (
|
||||
turso = "../../target/debug/lib_turso_go.so"
|
||||
)
|
||||
|
||||
func toGoStr(ptr uintptr, length int) string {
|
||||
if ptr == 0 {
|
||||
return ""
|
||||
}
|
||||
uptr := unsafe.Pointer(ptr)
|
||||
s := (*string)(uptr)
|
||||
if s == nil {
|
||||
// redundant
|
||||
return ""
|
||||
}
|
||||
return *s
|
||||
}
|
||||
|
||||
func init() {
|
||||
slib, err := purego.Dlopen(turso, purego.RTLD_LAZY)
|
||||
if err != nil {
|
||||
slog.Error("Error opening turso library: ", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
lib = slib
|
||||
sql.Register("turso", &tursoDriver{})
|
||||
}
|
||||
|
||||
type tursoDriver struct {
|
||||
tursoCtx
|
||||
}
|
||||
|
||||
func toCString(s string) uintptr {
|
||||
b := append([]byte(s), 0)
|
||||
return uintptr(unsafe.Pointer(&b[0]))
|
||||
}
|
||||
|
||||
func getExtFunc(ptr interface{}, name string) {
|
||||
purego.RegisterLibFunc(ptr, lib, name)
|
||||
}
|
||||
|
||||
type conn struct {
|
||||
ctx uintptr
|
||||
sync.Mutex
|
||||
writeTimeFmt string
|
||||
lastInsertID int64
|
||||
lastAffected int64
|
||||
}
|
||||
|
||||
func newConn() *conn {
|
||||
return &conn{
|
||||
0,
|
||||
sync.Mutex{},
|
||||
"2006-01-02 15:04:05",
|
||||
0,
|
||||
0,
|
||||
}
|
||||
}
|
||||
|
||||
func open(dsn string) (*conn, error) {
|
||||
var open func(uintptr) uintptr
|
||||
getExtFunc(&open, ExtDBOpen)
|
||||
c := newConn()
|
||||
path := toCString(dsn)
|
||||
ctx := open(path)
|
||||
c.ctx = ctx
|
||||
return c, nil
|
||||
}
|
||||
|
||||
type tursoCtx struct {
|
||||
conn *conn
|
||||
tx *sql.Tx
|
||||
err error
|
||||
rows *sql.Rows
|
||||
stmt *sql.Stmt
|
||||
}
|
||||
|
||||
func (lc tursoCtx) Open(dsn string) (driver.Conn, error) {
|
||||
conn, err := open(dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nc := tursoCtx{conn: conn}
|
||||
return nc, nil
|
||||
}
|
||||
|
||||
func (lc tursoCtx) Close() error {
|
||||
var closedb func(uintptr) uintptr
|
||||
getExtFunc(&closedb, ExtDBClose)
|
||||
closedb(lc.conn.ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: Begin not implemented
|
||||
func (lc tursoCtx) Begin() (driver.Tx, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (ls tursoCtx) Prepare(sql string) (driver.Stmt, error) {
|
||||
var prepare func(uintptr, uintptr) uintptr
|
||||
getExtFunc(&prepare, ExtDBPrepare)
|
||||
s := toCString(sql)
|
||||
statement := prepare(ls.conn.ctx, s)
|
||||
if statement == 0 {
|
||||
return nil, errors.New("no rows")
|
||||
}
|
||||
ls.stmt = stmt{
|
||||
ctx: statement,
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
return nil, nil
|
||||
}
|
28
bindings/go/types.go
Normal file
28
bindings/go/types.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
package turso
|
||||
|
||||
type ResultCode int
|
||||
|
||||
const (
|
||||
Error ResultCode = -1
|
||||
Ok ResultCode = 0
|
||||
Row ResultCode = 1
|
||||
Busy ResultCode = 2
|
||||
Done ResultCode = 3
|
||||
Io ResultCode = 4
|
||||
Interrupt ResultCode = 5
|
||||
Invalid ResultCode = 6
|
||||
Null ResultCode = 7
|
||||
NoMem ResultCode = 8
|
||||
ReadOnly ResultCode = 9
|
||||
ExtDBOpen string = "db_open"
|
||||
ExtDBClose string = "db_close"
|
||||
ExtDBPrepare string = "db_prepare"
|
||||
)
|
||||
|
||||
var (
|
||||
lib uintptr
|
||||
dbPrepare func(uintptr, uintptr) uintptr
|
||||
rowsNext func(rowsPtr uintptr) int32
|
||||
rowsGetValue func(rowsPtr uintptr, colIdx uint) uintptr
|
||||
freeCString func(strPtr uintptr)
|
||||
)
|
Loading…
Add table
Add a link
Reference in a new issue