mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-23 00:45:37 +01:00
Initial pass on SQLite C ABI
This adds initial SQLite C ABI compatibility to Limbo to make sure we drive the Rust API in the right way that allows us to implement SQLite semantics.
This commit is contained in:
19
sqlite3/Cargo.toml
Normal file
19
sqlite3/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
# Copyright 2024 the Limbo authors. All rights reserved. MIT license.
|
||||
|
||||
[package]
|
||||
name = "limbo_sqlite3"
|
||||
version = "0.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "staticlib"]
|
||||
doc = false
|
||||
|
||||
[build-dependencies]
|
||||
cbindgen = "0.24.0"
|
||||
|
||||
[dependencies]
|
||||
limbo_core = { path = "../core" }
|
||||
8
sqlite3/build.rs
Normal file
8
sqlite3/build.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
use std::path::Path;
|
||||
|
||||
fn main() {
|
||||
let header_file = Path::new("include").join("sqlite3.h");
|
||||
cbindgen::generate(".")
|
||||
.expect("Failed to generate C bindings")
|
||||
.write_to_file(header_file);
|
||||
}
|
||||
7
sqlite3/cbindgen.toml
Normal file
7
sqlite3/cbindgen.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
language = "C"
|
||||
cpp_compat = true
|
||||
include_guard = "LIMBO_SQLITE3_H"
|
||||
line_length = 120
|
||||
no_includes = true
|
||||
style = "type"
|
||||
sys_includes = ["stdint.h"]
|
||||
30
sqlite3/examples/example.c
Normal file
30
sqlite3/examples/example.c
Normal file
@@ -0,0 +1,30 @@
|
||||
#include "sqlite3.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
sqlite3 *db;
|
||||
int rc;
|
||||
|
||||
rc = sqlite3_open("local.db", &db);
|
||||
assert(rc == SQLITE_OK);
|
||||
|
||||
sqlite3_stmt *stmt;
|
||||
|
||||
rc = sqlite3_prepare_v2(db, "SELECT 'hello, world' AS message", -1, &stmt, NULL);
|
||||
assert(rc == SQLITE_OK);
|
||||
|
||||
rc = sqlite3_step(stmt);
|
||||
assert(rc == SQLITE_OK);
|
||||
|
||||
const unsigned char *result = sqlite3_column_text(stmt, 0);
|
||||
|
||||
printf("result = %s\n", result);
|
||||
|
||||
sqlite3_close(db);
|
||||
|
||||
return 0;
|
||||
}
|
||||
44
sqlite3/include/sqlite3.h
Normal file
44
sqlite3/include/sqlite3.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#ifndef LIMBO_SQLITE3_H
|
||||
#define LIMBO_SQLITE3_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define SQLITE_OK 0
|
||||
|
||||
#define SQLITE_ERROR 1
|
||||
|
||||
#define SQLITE_BUSY 5
|
||||
|
||||
#define SQLITE_NOTFOUND 14
|
||||
|
||||
#define SQLITE_MISUSE 21
|
||||
|
||||
#define SQLITE_ROW 100
|
||||
|
||||
#define SQLITE_DONE 101
|
||||
|
||||
typedef struct sqlite3 sqlite3;
|
||||
|
||||
typedef struct sqlite3_stmt sqlite3_stmt;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif // __cplusplus
|
||||
|
||||
int sqlite3_open(const char *filename, sqlite3 **db_out);
|
||||
|
||||
int sqlite3_close(sqlite3 *db);
|
||||
|
||||
int sqlite3_prepare_v2(sqlite3 *db, const char *sql, int len, sqlite3_stmt **out_stmt, const char **tail);
|
||||
|
||||
int sqlite3_finalize(sqlite3_stmt *stmt);
|
||||
|
||||
int sqlite3_step(sqlite3_stmt *db);
|
||||
|
||||
const unsigned char *sqlite3_column_text(sqlite3_stmt *db, int idx);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif // __cplusplus
|
||||
|
||||
#endif /* LIMBO_SQLITE3_H */
|
||||
130
sqlite3/src/lib.rs
Normal file
130
sqlite3/src/lib.rs
Normal file
@@ -0,0 +1,130 @@
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
use std::rc::Rc;
|
||||
use std::ffi;
|
||||
|
||||
pub const SQLITE_OK: ffi::c_int = 0;
|
||||
pub const SQLITE_ERROR: ffi::c_int = 1;
|
||||
pub const SQLITE_BUSY: ffi::c_int = 5;
|
||||
pub const SQLITE_NOTFOUND: ffi::c_int = 14;
|
||||
pub const SQLITE_MISUSE: ffi::c_int = 21;
|
||||
pub const SQLITE_ROW: ffi::c_int = 100;
|
||||
pub const SQLITE_DONE: ffi::c_int = 101;
|
||||
|
||||
pub struct sqlite3 {
|
||||
pub(crate) db: limbo_core::Database,
|
||||
pub(crate) conn: limbo_core::Connection,
|
||||
}
|
||||
|
||||
impl sqlite3 {
|
||||
pub fn new(db: limbo_core::Database, conn: limbo_core::Connection) -> Self {
|
||||
Self { db, conn }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct sqlite3_stmt {
|
||||
pub(crate) stmt: limbo_core::Statement,
|
||||
}
|
||||
|
||||
impl sqlite3_stmt {
|
||||
pub fn new(stmt: limbo_core::Statement) -> Self {
|
||||
Self { stmt }
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn sqlite3_open(
|
||||
filename: *const ffi::c_char,
|
||||
db_out: *mut *mut sqlite3,
|
||||
) -> ffi::c_int {
|
||||
if filename.is_null() {
|
||||
return SQLITE_MISUSE;
|
||||
}
|
||||
if db_out.is_null() {
|
||||
return SQLITE_MISUSE;
|
||||
}
|
||||
let filename = ffi::CStr::from_ptr(filename);
|
||||
let filename = match filename.to_str() {
|
||||
Ok(s) => s,
|
||||
Err(_) => return SQLITE_MISUSE,
|
||||
};
|
||||
let io = match limbo_core::PlatformIO::new() {
|
||||
Ok(io) => Rc::new(io),
|
||||
Err(_) => return SQLITE_MISUSE,
|
||||
};
|
||||
match limbo_core::Database::open_file(io, filename) {
|
||||
Ok(db) => {
|
||||
let conn = db.connect();
|
||||
*db_out = Box::leak(Box::new(sqlite3::new(db, conn)));
|
||||
SQLITE_OK
|
||||
}
|
||||
Err(e) => {
|
||||
SQLITE_NOTFOUND
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn sqlite3_close(db: *mut sqlite3) -> ffi::c_int {
|
||||
if db.is_null() {
|
||||
return SQLITE_OK;
|
||||
}
|
||||
let _ = Box::from_raw(db);
|
||||
SQLITE_OK
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn sqlite3_prepare_v2(
|
||||
db: *mut sqlite3,
|
||||
sql: *const ffi::c_char,
|
||||
len: ffi::c_int,
|
||||
out_stmt: *mut *mut sqlite3_stmt,
|
||||
tail: *mut *const ffi::c_char,
|
||||
) -> ffi::c_int {
|
||||
if db.is_null() || sql.is_null() || out_stmt.is_null() {
|
||||
return SQLITE_MISUSE;
|
||||
}
|
||||
let db: &mut sqlite3 = &mut *db;
|
||||
let sql = ffi::CStr::from_ptr(sql);
|
||||
let sql = match sql.to_str() {
|
||||
Ok(s) => s,
|
||||
Err(_) => return SQLITE_MISUSE,
|
||||
};
|
||||
let stmt = match db.conn.prepare(sql) {
|
||||
Ok(stmt) => stmt,
|
||||
Err(_) => return SQLITE_ERROR,
|
||||
};
|
||||
*out_stmt = Box::leak(Box::new(sqlite3_stmt::new(stmt)));
|
||||
SQLITE_OK
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn sqlite3_finalize(stmt: *mut sqlite3_stmt) -> ffi::c_int {
|
||||
if stmt.is_null() {
|
||||
return SQLITE_MISUSE;
|
||||
}
|
||||
let _ = Box::from_raw(stmt);
|
||||
SQLITE_OK
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn sqlite3_step(db: *mut sqlite3_stmt) -> std::ffi::c_int {
|
||||
let stmt = &mut *db;
|
||||
if let Ok(result) = stmt.stmt.step() {
|
||||
match result {
|
||||
limbo_core::RowResult::IO => SQLITE_BUSY,
|
||||
limbo_core::RowResult::Done => SQLITE_DONE,
|
||||
limbo_core::RowResult::Row(_) => SQLITE_ROW,
|
||||
}
|
||||
} else {
|
||||
SQLITE_ERROR
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn sqlite3_column_text(
|
||||
db: *mut sqlite3_stmt,
|
||||
idx: std::ffi::c_int,
|
||||
) -> *const std::ffi::c_uchar {
|
||||
todo!();
|
||||
}
|
||||
3
sqlite3/tests/.gitignore
vendored
Normal file
3
sqlite3/tests/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
sqlite3-tests
|
||||
*.d
|
||||
*.o
|
||||
38
sqlite3/tests/Makefile
Normal file
38
sqlite3/tests/Makefile
Normal file
@@ -0,0 +1,38 @@
|
||||
V =
|
||||
ifeq ($(strip $(V)),)
|
||||
E = @echo
|
||||
Q = @
|
||||
else
|
||||
E = @\#
|
||||
Q =
|
||||
endif
|
||||
export E Q
|
||||
|
||||
PROGRAM = sqlite3-tests
|
||||
|
||||
CFLAGS = -g -Wall -std=c17 -MMD -MP
|
||||
|
||||
LIBS ?= -lsqlite3
|
||||
|
||||
OBJS += main.o
|
||||
OBJS += test-close.o
|
||||
OBJS += test-open.o
|
||||
OBJS += test-prepare.o
|
||||
|
||||
all: $(PROGRAM)
|
||||
|
||||
%.o: %.c
|
||||
$(E) " CC " $@
|
||||
$(Q) $(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(PROGRAM): $(OBJS)
|
||||
$(E) " LINK " $@
|
||||
$(Q) $(CC) $(LIBS) -o $@ $^
|
||||
|
||||
clean:
|
||||
$(E) " CLEAN"
|
||||
$(Q) rm -f $(PROGRAM)
|
||||
$(Q) rm -f $(OBJS) *.d
|
||||
.PHONY: clean
|
||||
|
||||
-include $(OBJS:.o=.d)
|
||||
11
sqlite3/tests/check.h
Normal file
11
sqlite3/tests/check.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#ifndef CHECK_H
|
||||
|
||||
#define CHECK_EQUAL(expected, actual) \
|
||||
do { \
|
||||
if ((expected) != (actual)) { \
|
||||
fprintf(stderr, "%s:%d: Assertion failed: %d != %d\n", __FILE__, __LINE__, (expected), (actual)); \
|
||||
exit(1); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#endif
|
||||
16
sqlite3/tests/main.c
Normal file
16
sqlite3/tests/main.c
Normal file
@@ -0,0 +1,16 @@
|
||||
extern void test_open_misuse();
|
||||
extern void test_open_not_found();
|
||||
extern void test_open_existing();
|
||||
extern void test_close();
|
||||
extern void test_prepare_misuse();
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
test_open_misuse();
|
||||
test_open_not_found();
|
||||
test_open_existing();
|
||||
test_close();
|
||||
test_prepare_misuse();
|
||||
|
||||
return 0;
|
||||
}
|
||||
11
sqlite3/tests/test-close.c
Normal file
11
sqlite3/tests/test-close.c
Normal file
@@ -0,0 +1,11 @@
|
||||
#include <sqlite3.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "check.h"
|
||||
|
||||
void test_close(void)
|
||||
{
|
||||
CHECK_EQUAL(SQLITE_OK, sqlite3_close(NULL));
|
||||
}
|
||||
30
sqlite3/tests/test-open.c
Normal file
30
sqlite3/tests/test-open.c
Normal file
@@ -0,0 +1,30 @@
|
||||
#include "check.h"
|
||||
|
||||
#include <sqlite3.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
void test_open_misuse(void)
|
||||
{
|
||||
CHECK_EQUAL(SQLITE_MISUSE, sqlite3_open(NULL, NULL));
|
||||
|
||||
CHECK_EQUAL(SQLITE_MISUSE, sqlite3_open("local.db", NULL));
|
||||
}
|
||||
|
||||
void test_open_not_found(void)
|
||||
{
|
||||
sqlite3 *db;
|
||||
|
||||
CHECK_EQUAL(SQLITE_CANTOPEN, sqlite3_open("not-found/local.db", &db));
|
||||
}
|
||||
|
||||
// TODO: test_open_create
|
||||
|
||||
void test_open_existing(void)
|
||||
{
|
||||
sqlite3 *db;
|
||||
|
||||
CHECK_EQUAL(SQLITE_OK, sqlite3_open("../../testing/hello.db", &db));
|
||||
CHECK_EQUAL(SQLITE_OK, sqlite3_close(db));
|
||||
}
|
||||
Reference in New Issue
Block a user