Skip to content

Commit 4ca85f1

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

File tree

4 files changed

+287
-19
lines changed

4 files changed

+287
-19
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: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ use types::{
1414
libsql_row_t, libsql_rows, libsql_rows_future_t, libsql_rows_t, libsql_stmt, libsql_stmt_t,
1515
replicated, stmt,
1616
};
17+
use std::ffi::c_int;
18+
use crate::types::{libsql_tx, libsql_tx_t};
19+
1720

1821
lazy_static! {
1922
static ref RT: Runtime = tokio::runtime::Runtime::new().unwrap();
@@ -154,6 +157,108 @@ fn maybe_remove_offline_query_param(url: &str) -> anyhow::Result<Option<String>>
154157
Ok(Some(url[..query_idx].to_owned() + "?" + &query))
155158
}
156159

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

bindings/c/src/types.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,3 +308,42 @@ 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 { ptr: std::ptr::null() }
325+
}
326+
327+
pub fn is_null(&self) -> bool {
328+
self.ptr.is_null()
329+
}
330+
331+
#[inline]
332+
pub(crate) fn as_const_ptr(&self) -> *const libsql_tx {
333+
self.ptr
334+
}
335+
}
336+
337+
#[allow(clippy::from_over_into)]
338+
impl From<&libsql_tx> for libsql_tx_t {
339+
fn from(value: &libsql_tx) -> Self {
340+
Self { ptr: value }
341+
}
342+
}
343+
344+
#[allow(clippy::from_over_into)]
345+
impl From<&mut libsql_tx> for libsql_tx_t {
346+
fn from(value: &mut libsql_tx) -> Self {
347+
Self { ptr: value }
348+
}
349+
}

0 commit comments

Comments
 (0)