Skip to content

Commit b0930e2

Browse files
authored
Merge pull request #9 from superfly/add-ordinal-map
Cache site id -> ordinal mapping during tx
2 parents 1862686 + 5fd47e7 commit b0930e2

File tree

12 files changed

+401
-85
lines changed

12 files changed

+401
-85
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: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ pub struct crsql_ExtData {
7373
pub pSelectClockTablesStmt: *mut sqlite::stmt,
7474
pub mergeEqualValues: ::core::ffi::c_int,
7575
pub timestamp: ::core::ffi::c_ulonglong,
76+
pub ordinalMap: *mut ::core::ffi::c_void,
7677
}
7778

7879
#[repr(C)]
@@ -108,10 +109,12 @@ extern "C" {
108109
db: *mut sqlite::sqlite3,
109110
pExtData: *mut crsql_ExtData,
110111
) -> c_int;
111-
pub fn crsql_newExtData(
112+
pub fn crsql_newExtData(db: *mut sqlite::sqlite3) -> *mut crsql_ExtData;
113+
pub fn crsql_initSiteIdExt(
112114
db: *mut sqlite::sqlite3,
115+
pExtData: *mut crsql_ExtData,
113116
siteIdBuffer: *mut c_char,
114-
) -> *mut crsql_ExtData;
117+
) -> c_int;
115118
pub fn crsql_freeExtData(pExtData: *mut crsql_ExtData);
116119
pub fn crsql_finalize(pExtData: *mut crsql_ExtData);
117120
}
@@ -268,7 +271,7 @@ fn bindgen_test_layout_crsql_ExtData() {
268271
let ptr = UNINIT.as_ptr();
269272
assert_eq!(
270273
::core::mem::size_of::<crsql_ExtData>(),
271-
160usize,
274+
168usize,
272275
concat!("Size of: ", stringify!(crsql_ExtData))
273276
);
274277
assert_eq!(
@@ -498,4 +501,14 @@ fn bindgen_test_layout_crsql_ExtData() {
498501
stringify!(timestamp)
499502
)
500503
);
504+
assert_eq!(
505+
unsafe { ::core::ptr::addr_of!((*ptr).ordinalMap) as usize - ptr as usize },
506+
160usize,
507+
concat!(
508+
"Offset of field: ",
509+
stringify!(crsql_ExtData),
510+
"::",
511+
stringify!(ordinalMap)
512+
)
513+
);
501514
}

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/commit.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
use alloc::{boxed::Box, collections::BTreeMap, vec::Vec};
12
use core::{
23
ffi::{c_int, c_void},
4+
mem,
35
ptr::null,
46
};
57

@@ -31,4 +33,9 @@ pub unsafe fn commit_or_rollback_reset(ext_data: *mut crsql_ExtData) {
3133
(*ext_data).seq = 0;
3234
(*ext_data).timestamp = 0;
3335
(*ext_data).updatedTableInfosThisTx = 0;
36+
37+
let mut ordinals: mem::ManuallyDrop<Box<BTreeMap<Vec<u8>, i64>>> = mem::ManuallyDrop::new(
38+
Box::from_raw((*ext_data).ordinalMap as *mut BTreeMap<Vec<u8>, i64>),
39+
);
40+
ordinals.clear();
3441
}

core/rs/core/src/db_version.rs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,12 @@ pub extern "C" fn crsql_init_last_db_versions_map(ext_data: *mut crsql_ExtData)
187187
unsafe { (*ext_data).lastDbVersions = Box::into_raw(Box::new(map)) as *mut c_void }
188188
}
189189

190+
#[no_mangle]
191+
pub extern "C" fn crsql_init_ordinal_map(ext_data: *mut crsql_ExtData) {
192+
let map: BTreeMap<Vec<u8>, i64> = BTreeMap::new();
193+
unsafe { (*ext_data).ordinalMap = Box::into_raw(Box::new(map)) as *mut c_void }
194+
}
195+
190196
#[no_mangle]
191197
pub extern "C" fn crsql_drop_last_db_versions_map(ext_data: *mut crsql_ExtData) {
192198
unsafe {
@@ -196,6 +202,15 @@ pub extern "C" fn crsql_drop_last_db_versions_map(ext_data: *mut crsql_ExtData)
196202
}
197203
}
198204

205+
#[no_mangle]
206+
pub extern "C" fn crsql_drop_ordinal_map(ext_data: *mut crsql_ExtData) {
207+
unsafe {
208+
drop(Box::from_raw(
209+
(*ext_data).ordinalMap as *mut BTreeMap<Vec<u8>, i64>,
210+
));
211+
}
212+
}
213+
199214
pub fn insert_db_version(
200215
ext_data: *mut crsql_ExtData,
201216
insert_site_id: &[u8],
@@ -258,6 +273,15 @@ pub unsafe fn get_or_set_site_ordinal(
258273
ext_data: *mut crsql_ExtData,
259274
site_id: &[u8],
260275
) -> Result<i64, ResultCode> {
276+
// check the cache first
277+
let mut ordinals: mem::ManuallyDrop<Box<BTreeMap<Vec<u8>, i64>>> = mem::ManuallyDrop::new(
278+
Box::from_raw((*ext_data).ordinalMap as *mut BTreeMap<Vec<u8>, i64>),
279+
);
280+
281+
if let Some(ordinal) = ordinals.get(site_id) {
282+
return Ok(*ordinal);
283+
}
284+
261285
let bind_result =
262286
(*ext_data)
263287
.pSelectSiteIdOrdinalStmt
@@ -268,11 +292,11 @@ pub unsafe fn get_or_set_site_ordinal(
268292
return Err(rc);
269293
}
270294

271-
match (*ext_data).pSelectSiteIdOrdinalStmt.step() {
295+
let ordinal = match (*ext_data).pSelectSiteIdOrdinalStmt.step() {
272296
Ok(ResultCode::ROW) => {
273297
let ordinal = (*ext_data).pSelectSiteIdOrdinalStmt.column_int64(0);
274298
reset_cached_stmt((*ext_data).pSelectSiteIdOrdinalStmt)?;
275-
Ok(ordinal)
299+
ordinal
276300
}
277301
Ok(_) => {
278302
reset_cached_stmt((*ext_data).pSelectSiteIdOrdinalStmt)?;
@@ -296,7 +320,7 @@ pub unsafe fn get_or_set_site_ordinal(
296320
Ok(_) => {
297321
let ordinal = (*ext_data).pSetSiteIdOrdinalStmt.column_int64(0);
298322
reset_cached_stmt((*ext_data).pSetSiteIdOrdinalStmt)?;
299-
Ok(ordinal)
323+
ordinal
300324
}
301325
Err(rc) => {
302326
reset_cached_stmt((*ext_data).pSetSiteIdOrdinalStmt)?;
@@ -308,5 +332,7 @@ pub unsafe fn get_or_set_site_ordinal(
308332
reset_cached_stmt((*ext_data).pSetSiteIdOrdinalStmt)?;
309333
return Err(rc);
310334
}
311-
}
335+
};
336+
ordinals.insert(site_id.to_vec(), ordinal);
337+
Ok(ordinal)
312338
}

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,

0 commit comments

Comments
 (0)