forked from tursodatabase/limbo
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge 'bindings/go: Begin implementation of Go database/sql driver' f…
…rom Preston Thorpe This WIP driver uses the [purego](github.com/ebitengine/purego) library, that supports cross platform `Dlopen`/`Dlsym` and not a whole lot else. I really didn't want to use CGO, have very little experience with WASM and I heard nothing but good things about this library. It's very easy to use and stable especially when you consider the use case here of 3 functions. ![image](https://github.com/user- attachments/assets/ae28c8f2-1d11-4d25-b999-22af8bd65a92) NOTE: The WIP state that this PR is in right at this moment, is not able to run these simple queries. This screengrab was taken from a couple days ago when I wrote up a quick demo to load the library, call a simple query and had it println! the result to make sure everything was working properly. I am opening this so kind of like the Java bindings, I can incrementally work on this. I didn't want to submit a massive PR, try to keep them at ~1k lines max. The state of what's in this PR is highly subject and likely to change. I will update when they are at a working state where they can be tested out and make sure they work across platforms. Closes tursodatabase#776
- Loading branch information
Showing
13 changed files
with
1,304 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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"] } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
module turso | ||
|
||
go 1.23.4 | ||
|
||
require ( | ||
github.com/ebitengine/purego v0.8.2 | ||
golang.org/x/sys/windows v0.29.0 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I= | ||
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= | ||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= | ||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
mod rows; | ||
#[allow(dead_code)] | ||
mod statement; | ||
mod types; | ||
use limbo_core::{Connection, Database, LimboError}; | ||
use std::{ | ||
ffi::{c_char, c_void}, | ||
rc::Rc, | ||
str::FromStr, | ||
sync::Arc, | ||
}; | ||
|
||
/// # Safety | ||
/// Safe to be called from Go with null terminated DSN string. | ||
/// performs null check on the path. | ||
#[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() | ||
} | ||
|
||
#[allow(dead_code)] | ||
struct TursoConn { | ||
conn: Rc<Connection>, | ||
io: Arc<dyn limbo_core::IO>, | ||
} | ||
|
||
impl TursoConn { | ||
fn new(conn: Rc<Connection>, io: Arc<dyn limbo_core::IO>) -> Self { | ||
TursoConn { conn, io } | ||
} | ||
#[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 TursoConn { | ||
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 as *mut TursoConn) }; | ||
} | ||
} | ||
|
||
#[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()?), | ||
_ => { | ||
return Ok(Arc::new(limbo_core::PlatformIO::new()?)); | ||
} | ||
}) | ||
} | ||
|
||
#[allow(dead_code)] | ||
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(), | ||
} | ||
} | ||
} |
Oops, something went wrong.