Skip to content

Commit 31c4705

Browse files
committed
add savepoints and triggers
Signed-off-by: Somtochi Onyekwere <[email protected]>
1 parent 7f682af commit 31c4705

File tree

10 files changed

+316
-110
lines changed

10 files changed

+316
-110
lines changed

core/rs/core/src/bootstrap.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,27 @@ fn create_site_id_and_site_id_table(db: *mut sqlite3) -> Result<[u8; 16], Result
4848
insert_site_id(db)
4949
}
5050

51+
pub fn create_site_id_triggers(db: *mut sqlite3) -> Result<ResultCode, ResultCode> {
52+
db.exec_safe(&format!(
53+
"CREATE TRIGGER IF NOT EXISTS {tbl}_insert_trig AFTER INSERT ON \"{tbl}\"
54+
WHEN NEW.ordinal != 0
55+
BEGIN
56+
VALUES (crsql_update_site_id(NEW.site_id, NEW.ordinal));
57+
END;
58+
CREATE TRIGGER IF NOT EXISTS {tbl}_update_trig AFTER UPDATE ON \"{tbl}\"
59+
WHEN NEW.ordinal != 0
60+
BEGIN
61+
VALUES (crsql_update_site_id(NEW.site_id, NEW.ordinal));
62+
END;
63+
CREATE TRIGGER IF NOT EXISTS {tbl}_delete_trig AFTER DELETE ON \"{tbl}\"
64+
WHEN OLD.ordinal != 0
65+
BEGIN
66+
VALUES (crsql_update_site_id(OLD.site_id, -1));
67+
END;",
68+
tbl = consts::TBL_SITE_ID
69+
))
70+
}
71+
5172
#[no_mangle]
5273
pub extern "C" fn crsql_init_peer_tracking_table(db: *mut sqlite3) -> c_int {
5374
match db.exec_safe("CREATE TABLE IF NOT EXISTS crsql_tracked_peers (\"site_id\" BLOB NOT NULL, \"version\" INTEGER NOT NULL, \"seq\" INTEGER DEFAULT 0, \"tag\" INTEGER, \"event\" INTEGER, PRIMARY KEY (\"site_id\", \"tag\", \"event\")) STRICT;") {

core/rs/core/src/c.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,12 @@ extern "C" {
109109
db: *mut sqlite::sqlite3,
110110
pExtData: *mut crsql_ExtData,
111111
) -> c_int;
112-
pub fn crsql_newExtData(
112+
pub fn crsql_newExtData(db: *mut sqlite::sqlite3) -> *mut crsql_ExtData;
113+
pub fn crsql_initSiteIdExt(
113114
db: *mut sqlite::sqlite3,
115+
pExtData: *mut crsql_ExtData,
114116
siteIdBuffer: *mut c_char,
115-
) -> *mut crsql_ExtData;
117+
) -> c_int;
116118
pub fn crsql_freeExtData(pExtData: *mut crsql_ExtData);
117119
pub fn crsql_finalize(pExtData: *mut crsql_ExtData);
118120
}

core/rs/core/src/changes_vtab.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
extern crate alloc;
2-
use crate::alloc::string::ToString;
2+
use crate::alloc::{collections::BTreeMap, string::ToString};
33
use crate::changes_vtab_write::crsql_merge_insert;
44
use crate::stmt_cache::reset_cached_stmt;
55
use crate::tableinfo::{crsql_ensure_table_infos_are_up_to_date, TableInfo};
@@ -565,3 +565,27 @@ pub extern "C" fn crsql_changes_commit(vtab: *mut sqlite::vtab) -> c_int {
565565
}
566566
ResultCode::OK as c_int
567567
}
568+
569+
#[no_mangle]
570+
pub extern "C" fn crsql_changes_savepoint(_vtab: *mut sqlite::vtab, _n: c_int) -> c_int {
571+
ResultCode::OK as c_int
572+
}
573+
574+
#[no_mangle]
575+
pub extern "C" fn crsql_changes_release(_vtab: *mut sqlite::vtab, _n: c_int) -> c_int {
576+
ResultCode::OK as c_int
577+
}
578+
579+
// clear ordinal cache on rollback so we don't have wrong data in the cache.
580+
#[no_mangle]
581+
pub extern "C" fn crsql_changes_rollback_to(vtab: *mut sqlite::vtab, _: c_int) -> c_int {
582+
let tab = vtab.cast::<crsql_Changes_vtab>();
583+
584+
let mut ordinals = unsafe {
585+
mem::ManuallyDrop::new(Box::from_raw(
586+
(*(*tab).pExtData).ordinalMap as *mut BTreeMap<Vec<u8>, i64>,
587+
))
588+
};
589+
ordinals.clear();
590+
ResultCode::OK as c_int
591+
}

core/rs/core/src/lib.rs

Lines changed: 112 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,17 @@ mod triggers;
4848
mod unpack_columns_vtab;
4949
mod util;
5050

51-
use alloc::borrow::Cow;
5251
use alloc::format;
5352
use alloc::string::ToString;
53+
use alloc::{borrow::Cow, boxed::Box, collections::BTreeMap, vec::Vec};
5454
use core::ffi::c_char;
5555
use core::mem;
5656
use core::ptr::null_mut;
5757
extern crate alloc;
5858
use alter::crsql_compact_post_alter;
5959
use automigrate::*;
6060
use backfill::*;
61-
use c::{crsql_freeExtData, crsql_newExtData};
61+
use c::{crsql_freeExtData, crsql_initSiteIdExt, crsql_newExtData};
6262
use config::{crsql_config_get, crsql_config_set};
6363
use core::ffi::{c_int, c_void, CStr};
6464
use create_crr::create_crr;
@@ -232,6 +232,30 @@ pub extern "C" fn sqlite3_crsqlcore_init(
232232
return null_mut();
233233
}
234234

235+
// allocate ext data earlier in the init process because we need its
236+
// pointer to be available for the crsql_update_site_id function.
237+
let ext_data = unsafe { crsql_newExtData(db) };
238+
if ext_data.is_null() {
239+
return null_mut();
240+
}
241+
242+
let rc = db
243+
.create_function_v2(
244+
"crsql_update_site_id",
245+
2,
246+
sqlite::UTF8 | sqlite::INNOCUOUS | sqlite::DETERMINISTIC,
247+
Some(ext_data as *mut c_void),
248+
Some(x_crsql_update_site_id),
249+
None,
250+
None,
251+
None,
252+
)
253+
.unwrap_or(ResultCode::ERROR);
254+
if rc != ResultCode::OK {
255+
unsafe { crsql_freeExtData(ext_data) };
256+
return null_mut();
257+
}
258+
235259
// TODO: convert this function to a proper rust function
236260
// and have rust free:
237261
// 1. site_id_buffer
@@ -243,12 +267,18 @@ pub extern "C" fn sqlite3_crsqlcore_init(
243267
let rc = crate::bootstrap::crsql_init_site_id(db, site_id_buffer);
244268
if rc != ResultCode::OK as c_int {
245269
sqlite::free(site_id_buffer as *mut c_void);
270+
unsafe { crsql_freeExtData(ext_data) };
246271
return null_mut();
247272
}
248273

249-
let ext_data = unsafe { crsql_newExtData(db, site_id_buffer as *mut c_char) };
250-
if ext_data.is_null() {
251-
// no need to free the site id buffer here, this is cleaned up already.
274+
let rc = unsafe { crsql_initSiteIdExt(db, ext_data, site_id_buffer as *mut c_char) };
275+
if rc != ResultCode::OK as c_int {
276+
unsafe { crsql_freeExtData(ext_data) };
277+
return null_mut();
278+
}
279+
280+
if let Err(_) = crate::bootstrap::create_site_id_triggers(db) {
281+
sqlite::free(site_id_buffer as *mut c_void);
252282
return null_mut();
253283
}
254284

@@ -408,7 +438,7 @@ pub extern "C" fn sqlite3_crsqlcore_init(
408438
let rc = db
409439
.create_function_v2(
410440
"crsql_set_ts",
411-
-1,
441+
1,
412442
sqlite::UTF8 | sqlite::DETERMINISTIC,
413443
Some(ext_data as *mut c_void),
414444
Some(x_crsql_set_ts),
@@ -422,6 +452,24 @@ pub extern "C" fn sqlite3_crsqlcore_init(
422452
return null_mut();
423453
}
424454

455+
#[cfg(feature = "test")]
456+
let rc = db
457+
.create_function_v2(
458+
"crsql_cache_site_ordinal",
459+
1,
460+
sqlite::UTF8 | sqlite::DETERMINISTIC,
461+
Some(ext_data as *mut c_void),
462+
Some(x_crsql_cache_site_ordinal),
463+
None,
464+
None,
465+
None,
466+
)
467+
.unwrap_or(ResultCode::ERROR);
468+
if rc != ResultCode::OK {
469+
unsafe { crsql_freeExtData(ext_data) };
470+
return null_mut();
471+
}
472+
425473
let rc = db
426474
.create_function_v2(
427475
"crsql_set_db_version",
@@ -627,6 +675,32 @@ unsafe extern "C" fn x_crsql_site_id(
627675
sqlite::result_blob(ctx, site_id, consts::SITE_ID_LEN, Destructor::STATIC);
628676
}
629677

678+
/**
679+
* update in-memory map of site ids to ordinals. Only valid within a transaction.
680+
*
681+
* `select crsql_update_site_id(site_id, ordinal)`
682+
*/
683+
unsafe extern "C" fn x_crsql_update_site_id(
684+
ctx: *mut sqlite::context,
685+
argc: i32,
686+
argv: *mut *mut sqlite::value,
687+
) {
688+
let ext_data = ctx.user_data() as *mut c::crsql_ExtData;
689+
let args = sqlite::args!(argc, argv);
690+
let site_id = args[0].blob();
691+
let ordinal = args[1].int64();
692+
let mut ordinals: mem::ManuallyDrop<Box<BTreeMap<Vec<u8>, i64>>> = mem::ManuallyDrop::new(
693+
Box::from_raw((*ext_data).ordinalMap as *mut BTreeMap<Vec<u8>, i64>),
694+
);
695+
696+
if ordinal == -1 {
697+
ordinals.remove(&site_id.to_vec());
698+
} else {
699+
ordinals.insert(site_id.to_vec(), ordinal);
700+
}
701+
ctx.result_text_static("OK");
702+
}
703+
630704
unsafe extern "C" fn x_crsql_finalize(
631705
ctx: *mut sqlite::context,
632706
_argc: i32,
@@ -854,10 +928,7 @@ unsafe extern "C" fn x_crsql_set_ts(
854928
argv: *mut *mut sqlite::value,
855929
) {
856930
if argc == 0 {
857-
ctx.result_error(
858-
"Wrong number of args provided to crsql_begin_alter. Provide the
859-
schema name and table name or just the table name.",
860-
);
931+
ctx.result_error("Wrong number of args provided to x_crsql_set_ts. Provide the timestamp.");
861932
return;
862933
}
863934

@@ -876,6 +947,37 @@ unsafe extern "C" fn x_crsql_set_ts(
876947
ctx.result_text_static("OK");
877948
}
878949

950+
/**
951+
* Get the site ordinal cached in the ext data for the current transaction.
952+
* only used for test to inspect the ordinal map.
953+
*/
954+
#[cfg(feature = "test")]
955+
unsafe extern "C" fn x_crsql_cache_site_ordinal(
956+
ctx: *mut sqlite::context,
957+
argc: i32,
958+
argv: *mut *mut sqlite::value,
959+
) {
960+
if argc == 0 {
961+
ctx.result_error(
962+
"Wrong number of args provided to crsql_cache_site_ordinal. Provide the site id.",
963+
);
964+
return;
965+
}
966+
967+
let ext_data = ctx.user_data() as *mut c::crsql_ExtData;
968+
let args = sqlite::args!(argc, argv);
969+
let site_id = args[0].blob();
970+
971+
let ord_map = mem::ManuallyDrop::new(Box::from_raw(
972+
(*ext_data).ordinalMap as *mut BTreeMap<Vec<u8>, i64>,
973+
));
974+
let res = ord_map.get(site_id).cloned().unwrap_or(-1);
975+
sqlite::result_int64(ctx, res);
976+
}
977+
978+
/**
979+
* Return the timestamp for the current transaction.
980+
*/
879981
unsafe extern "C" fn x_crsql_get_ts(
880982
ctx: *mut sqlite::context,
881983
_argc: i32,

core/rs/integration_check/src/t/tableinfo.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ fn test_ensure_table_infos_are_up_to_date() {
5353
)
5454
.expect("made foo clock");
5555

56-
let ext_data = unsafe { test_exports::c::crsql_newExtData(raw_db, make_site()) };
56+
let ext_data = unsafe { test_exports::c::crsql_newExtData(raw_db) };
57+
let rc = unsafe { test_exports::c::crsql_initSiteIdExt(raw_db, ext_data, make_site()) };
58+
assert_eq!(rc, 0);
5759
test_exports::tableinfo::crsql_ensure_table_infos_are_up_to_date(raw_db, ext_data, err);
5860

5961
let mut table_infos = unsafe {
@@ -359,6 +361,7 @@ fn test_leak_condition() {
359361
}
360362

361363
pub fn run_suite() {
364+
libc_print::libc_println!("Running tableinfo suite");
362365
test_ensure_table_infos_are_up_to_date();
363366
test_pull_table_info();
364367
test_is_table_compatible();

0 commit comments

Comments
 (0)