Skip to content

Commit 1c18225

Browse files
committed
add c bindings for transactions and update example.c
1 parent 268d169 commit 1c18225

File tree

4 files changed

+291
-20
lines changed

4 files changed

+291
-20
lines changed

bindings/c/example.c

Lines changed: 129 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55

66
int main(int argc, char *argv[])
77
{
8-
libsql_connection_t conn;
9-
libsql_rows_t rows;
10-
libsql_row_t row;
11-
libsql_database_t db;
8+
libsql_connection_t conn = NULL;
9+
libsql_rows_t rows = NULL;
10+
libsql_row_t row = NULL;
11+
libsql_database_t db = NULL;
1212
libsql_config config;
1313
const char *err = NULL;
1414
int retval = 0;
@@ -20,9 +20,10 @@ int main(int argc, char *argv[])
2020
char auth_token[1024];
2121
auth_token[0] = '\0';
2222
if (argc > 2) {
23-
strncpy(auth_token, argv[2], strlen(argv[2]));
23+
snprintf(auth_token, sizeof(auth_token), "%s", argv[2]);
2424
}
25-
strncpy(db_path, "test.db", strlen("test.db"));
25+
snprintf(db_path, sizeof(db_path), "%s", "test.db");
26+
memset(&config, 0, sizeof(config));
2627
config.db_path = db_path;
2728
config.primary_url = url;
2829
config.auth_token = auth_token;
@@ -56,16 +57,123 @@ int main(int argc, char *argv[])
5657
goto quit;
5758
}
5859

59-
retval = libsql_execute(conn, "INSERT INTO guest_book_entries VALUES('hi there')", &err);
60-
if (retval != 0) {
61-
fprintf(stderr, "%s\n", err);
62-
goto quit;
60+
// --- ROLLBACK should discard changes
61+
{
62+
libsql_tx_t tx = NULL;
63+
retval = libsql_tx_begin(conn, 0 /* Deferred */, &tx, &err);
64+
if (retval != 0) {
65+
fprintf(stderr, "tx_begin (rollback test): %s\n", err);
66+
goto quit;
67+
}
68+
69+
retval = libsql_execute(conn, "DELETE FROM guest_book_entries", &err);
70+
if (retval != 0) {
71+
fprintf(stderr, "delete before rollback test: %s\n", err);
72+
libsql_tx_free(tx);
73+
goto quit;
74+
}
75+
76+
retval = libsql_execute(conn, "INSERT INTO guest_book_entries VALUES('tx will be rolled back')", &err);
77+
if (retval != 0) {
78+
fprintf(stderr, "insert (rollback test): %s\n", err);
79+
libsql_tx_free(tx);
80+
goto quit;
81+
}
82+
83+
retval = libsql_tx_rollback(tx, &err);
84+
if (retval != 0) {
85+
fprintf(stderr, "tx_rollback: %s\n", err);
86+
libsql_tx_free(tx);
87+
goto quit;
88+
}
89+
tx = NULL;
90+
91+
rows = NULL; row = NULL; err = NULL;
92+
retval = libsql_query(conn, "SELECT COUNT(*) FROM guest_book_entries", &rows, &err);
93+
if (retval != 0) {
94+
fprintf(stderr, "query count after rollback: %s\n", err);
95+
goto quit;
96+
}
97+
retval = libsql_next_row(rows, &row, &err);
98+
if (retval != 0 || !row) {
99+
fprintf(stderr, "next_row (count after rollback): %s\n", err ? err : "no row");
100+
goto quit;
101+
}
102+
long long count = -1;
103+
retval = libsql_get_int(row, 0, &count, &err);
104+
if (retval != 0) {
105+
fprintf(stderr, "get_int (count after rollback): %s\n", err);
106+
goto quit;
107+
}
108+
libsql_free_row(row); row = NULL;
109+
libsql_free_rows(rows); rows = NULL;
110+
111+
if (count != 0) {
112+
fprintf(stderr, "rollback test failed: expected 0 rows, got %lld\n", count);
113+
retval = 1;
114+
goto quit;
115+
} else {
116+
printf("[tx-rollback] OK: count=%lld\n", count);
117+
}
63118
}
64119

65-
retval = libsql_execute(conn, "INSERT INTO guest_book_entries VALUES('some more hi there')", &err);
66-
if (retval != 0) {
67-
fprintf(stderr, "%s\n", err);
68-
goto quit;
120+
// --- COMMIT should persist changes
121+
{
122+
libsql_tx_t tx = NULL;
123+
retval = libsql_tx_begin(conn, 0 /* Deferred */, &tx, &err);
124+
if (retval != 0) {
125+
fprintf(stderr, "tx_begin (commit test): %s\n", err);
126+
goto quit;
127+
}
128+
129+
retval = libsql_execute(conn, "INSERT INTO guest_book_entries VALUES('hello from tx-commit 1')", &err);
130+
if (retval != 0) {
131+
fprintf(stderr, "insert 1 (commit test): %s\n", err);
132+
libsql_tx_free(tx);
133+
goto quit;
134+
}
135+
retval = libsql_execute(conn, "INSERT INTO guest_book_entries VALUES('hello from tx-commit 2')", &err);
136+
if (retval != 0) {
137+
fprintf(stderr, "insert 2 (commit test): %s\n", err);
138+
libsql_tx_free(tx);
139+
goto quit;
140+
}
141+
142+
retval = libsql_tx_commit(tx, &err);
143+
if (retval != 0) {
144+
fprintf(stderr, "tx_commit: %s\n", err);
145+
libsql_tx_free(tx);
146+
goto quit;
147+
}
148+
149+
tx = NULL;
150+
rows = NULL; row = NULL; err = NULL;
151+
retval = libsql_query(conn, "SELECT COUNT(*) FROM guest_book_entries", &rows, &err);
152+
if (retval != 0) {
153+
fprintf(stderr, "query count after commit: %s\n", err);
154+
goto quit;
155+
}
156+
retval = libsql_next_row(rows, &row, &err);
157+
if (retval != 0 || !row) {
158+
fprintf(stderr, "next_row (count after commit): %s\n", err ? err : "no row");
159+
goto quit;
160+
}
161+
long long count = -1;
162+
retval = libsql_get_int(row, 0, &count, &err);
163+
if (retval != 0) {
164+
fprintf(stderr, "get_int (count after commit): %s\n", err);
165+
goto quit;
166+
}
167+
libsql_free_row(row); row = NULL;
168+
libsql_free_rows(rows); rows = NULL;
169+
170+
if (count != 2) {
171+
fprintf(stderr, "commit test failed: expected 2 rows, got %lld\n", count);
172+
retval = 1;
173+
goto quit;
174+
} else {
175+
printf("[tx-commit] OK: count=%lld\n", count);
176+
}
69177
}
70178

71179
retval = libsql_query(conn, "SELECT text FROM guest_book_entries", &rows, &err);
@@ -87,6 +195,9 @@ int main(int argc, char *argv[])
87195
libsql_free_string(value);
88196
value = NULL;
89197
}
198+
199+
libsql_free_row(row);
200+
row = NULL;
90201
err = NULL;
91202
}
92203

@@ -106,9 +217,10 @@ int main(int argc, char *argv[])
106217
}
107218

108219
quit:
109-
libsql_free_rows(rows);
110-
libsql_disconnect(conn);
111-
libsql_close(db);
220+
if (row) libsql_free_row(row);
221+
if (rows) libsql_free_rows(rows);
222+
if (conn) libsql_disconnect(conn);
223+
if (db) libsql_close(db);
112224

113225
return retval;
114226
}

bindings/c/include/libsql.h

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,19 @@ typedef struct libsql_rows_future libsql_rows_future;
2525

2626
typedef struct libsql_stmt libsql_stmt;
2727

28+
typedef struct libsql_tx libsql_tx;
29+
2830
typedef const libsql_database *libsql_database_t;
2931

3032
typedef struct {
3133
int frame_no;
3234
int frames_synced;
3335
} replicated;
3436

37+
typedef const libsql_connection *libsql_connection_t;
38+
39+
typedef const libsql_tx *libsql_tx_t;
40+
3541
typedef struct {
3642
const char *db_path;
3743
const char *primary_url;
@@ -44,8 +50,6 @@ typedef struct {
4450
const char *remote_encryption_key;
4551
} libsql_config;
4652

47-
typedef const libsql_connection *libsql_connection_t;
48-
4953
typedef const libsql_stmt *libsql_stmt_t;
5054

5155
typedef const libsql_rows *libsql_rows_t;
@@ -85,6 +89,14 @@ int libsql_open_sync_with_webpki(const char *db_path,
8589
libsql_database_t *out_db,
8690
const char **out_err_msg);
8791

92+
int libsql_tx_begin(libsql_connection_t conn, int behavior, libsql_tx_t *out_tx, const char **out_err_msg);
93+
94+
int libsql_tx_commit(libsql_tx_t tx, const char **out_err_msg);
95+
96+
int libsql_tx_rollback(libsql_tx_t tx, const char **out_err_msg);
97+
98+
void libsql_tx_free(libsql_tx_t tx);
99+
88100
int libsql_open_sync_with_config(libsql_config config, libsql_database_t *out_db, const char **out_err_msg);
89101

90102
int libsql_open_ext(const char *url, libsql_database_t *out_db, const char **out_err_msg);

bindings/c/src/lib.rs

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ mod types;
88
use crate::types::libsql_config;
99
use http::Uri;
1010
use libsql::{errors, Builder, LoadExtensionGuard};
11+
use std::ffi::c_int;
1112
use tokio::runtime::Runtime;
1213
use types::{
1314
blob, libsql_connection, libsql_connection_t, libsql_database, libsql_database_t, libsql_row,
1415
libsql_row_t, libsql_rows, libsql_rows_future_t, libsql_rows_t, libsql_stmt, libsql_stmt_t,
15-
replicated, stmt,
16+
libsql_tx, libsql_tx_t, replicated, stmt,
1617
};
1718

1819
lazy_static! {
@@ -154,6 +155,111 @@ fn maybe_remove_offline_query_param(url: &str) -> anyhow::Result<Option<String>>
154155
Ok(Some(url[..query_idx].to_owned() + "?" + &query))
155156
}
156157

158+
fn to_tx_behavior(b: c_int) -> libsql::TransactionBehavior {
159+
match b {
160+
1 => libsql::TransactionBehavior::Immediate,
161+
2 => libsql::TransactionBehavior::Exclusive,
162+
3 => libsql::TransactionBehavior::ReadOnly,
163+
_ => libsql::TransactionBehavior::Deferred,
164+
}
165+
}
166+
167+
#[no_mangle]
168+
pub unsafe extern "C" fn libsql_tx_begin(
169+
conn: libsql_connection_t,
170+
behavior: c_int,
171+
out_tx: *mut libsql_tx_t,
172+
out_err_msg: *mut *const std::ffi::c_char,
173+
) -> c_int {
174+
if out_tx.is_null() {
175+
set_err_msg("Null out_tx".to_string(), out_err_msg);
176+
return 1;
177+
}
178+
if conn.is_null() {
179+
set_err_msg("Null connection".to_string(), out_err_msg);
180+
return 2;
181+
}
182+
183+
let c = conn.get_ref();
184+
let beh = to_tx_behavior(behavior);
185+
186+
let tx_res = match beh {
187+
libsql::TransactionBehavior::Deferred => RT.block_on(c.transaction()),
188+
_ => RT.block_on(c.transaction_with_behavior(beh)),
189+
};
190+
191+
match tx_res {
192+
Ok(tx) => {
193+
let handle = Box::new(libsql_tx { tx: Some(tx) });
194+
*out_tx = libsql_tx_t::from(Box::leak(handle));
195+
0
196+
}
197+
Err(e) => {
198+
set_err_msg(format!("Error beginning transaction: {}", e), out_err_msg);
199+
3
200+
}
201+
}
202+
}
203+
204+
#[no_mangle]
205+
pub unsafe extern "C" fn libsql_tx_commit(
206+
tx: libsql_tx_t,
207+
out_err_msg: *mut *const std::ffi::c_char,
208+
) -> c_int {
209+
if tx.is_null() {
210+
set_err_msg("Null transaction".to_string(), out_err_msg);
211+
return 1;
212+
}
213+
214+
let mut handle: Box<libsql_tx> = Box::from_raw(tx.as_const_ptr() as *mut libsql_tx);
215+
let Some(inner) = handle.tx.take() else {
216+
return 0;
217+
};
218+
219+
match RT.block_on(inner.commit()) {
220+
Ok(_) => 0,
221+
Err(e) => {
222+
set_err_msg(format!("Error committing transaction: {}", e), out_err_msg);
223+
2
224+
}
225+
}
226+
}
227+
228+
#[no_mangle]
229+
pub unsafe extern "C" fn libsql_tx_rollback(
230+
tx: libsql_tx_t,
231+
out_err_msg: *mut *const std::ffi::c_char,
232+
) -> c_int {
233+
if tx.is_null() {
234+
set_err_msg("Null transaction".to_string(), out_err_msg);
235+
return 1;
236+
}
237+
238+
let mut handle: Box<libsql_tx> = Box::from_raw(tx.as_const_ptr() as *mut libsql_tx);
239+
let Some(inner) = handle.tx.take() else {
240+
return 0;
241+
};
242+
243+
match RT.block_on(inner.rollback()) {
244+
Ok(_) => 0,
245+
Err(e) => {
246+
set_err_msg(
247+
format!("Error rolling back transaction: {}", e),
248+
out_err_msg,
249+
);
250+
2
251+
}
252+
}
253+
}
254+
255+
#[no_mangle]
256+
pub unsafe extern "C" fn libsql_tx_free(tx: libsql_tx_t) {
257+
if tx.is_null() {
258+
return;
259+
}
260+
let _ = Box::from_raw(tx.as_const_ptr() as *mut libsql_tx);
261+
}
262+
157263
#[cfg(test)]
158264
mod test {
159265
use super::*;

bindings/c/src/types.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,3 +308,44 @@ impl From<&mut libsql_row> for libsql_row_t {
308308
Self { ptr: value }
309309
}
310310
}
311+
312+
pub struct libsql_tx {
313+
pub(crate) tx: Option<libsql::Transaction>,
314+
}
315+
316+
#[derive(Clone, Debug)]
317+
#[repr(transparent)]
318+
pub struct libsql_tx_t {
319+
ptr: *const libsql_tx,
320+
}
321+
322+
impl libsql_tx_t {
323+
pub fn null() -> libsql_tx_t {
324+
libsql_tx_t {
325+
ptr: std::ptr::null(),
326+
}
327+
}
328+
329+
pub fn is_null(&self) -> bool {
330+
self.ptr.is_null()
331+
}
332+
333+
#[inline]
334+
pub(crate) fn as_const_ptr(&self) -> *const libsql_tx {
335+
self.ptr
336+
}
337+
}
338+
339+
#[allow(clippy::from_over_into)]
340+
impl From<&libsql_tx> for libsql_tx_t {
341+
fn from(value: &libsql_tx) -> Self {
342+
Self { ptr: value }
343+
}
344+
}
345+
346+
#[allow(clippy::from_over_into)]
347+
impl From<&mut libsql_tx> for libsql_tx_t {
348+
fn from(value: &mut libsql_tx) -> Self {
349+
Self { ptr: value }
350+
}
351+
}

0 commit comments

Comments
 (0)