diff --git a/core/rs/core/src/c.rs b/core/rs/core/src/c.rs index ffe556fe..b9368603 100644 --- a/core/rs/core/src/c.rs +++ b/core/rs/core/src/c.rs @@ -46,8 +46,8 @@ pub enum ChangeRowType { } #[repr(C)] -#[derive(Debug, Copy, Clone)] #[allow(non_snake_case, non_camel_case_types)] +#[derive(Debug, Copy, Clone)] pub struct crsql_ExtData { pub pPragmaSchemaVersionStmt: *mut sqlite::stmt, pub pPragmaDataVersionStmt: *mut sqlite::stmt, @@ -259,20 +259,20 @@ fn bindgen_test_layout_crsql_Changes_cursor() { #[test] #[allow(non_snake_case)] fn bindgen_test_layout_crsql_ExtData() { - const UNINIT: ::core::mem::MaybeUninit = ::core::mem::MaybeUninit::uninit(); + const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); let ptr = UNINIT.as_ptr(); assert_eq!( - ::core::mem::size_of::(), + ::std::mem::size_of::(), 136usize, concat!("Size of: ", stringify!(crsql_ExtData)) ); assert_eq!( - ::core::mem::align_of::(), + ::std::mem::align_of::(), 8usize, concat!("Alignment of ", stringify!(crsql_ExtData)) ); assert_eq!( - unsafe { ::core::ptr::addr_of!((*ptr).pPragmaSchemaVersionStmt) as usize - ptr as usize }, + unsafe { ::std::ptr::addr_of!((*ptr).pPragmaSchemaVersionStmt) as usize - ptr as usize }, 0usize, concat!( "Offset of field: ", @@ -282,7 +282,7 @@ fn bindgen_test_layout_crsql_ExtData() { ) ); assert_eq!( - unsafe { ::core::ptr::addr_of!((*ptr).pPragmaDataVersionStmt) as usize - ptr as usize }, + unsafe { ::std::ptr::addr_of!((*ptr).pPragmaDataVersionStmt) as usize - ptr as usize }, 8usize, concat!( "Offset of field: ", @@ -292,7 +292,7 @@ fn bindgen_test_layout_crsql_ExtData() { ) ); assert_eq!( - unsafe { ::core::ptr::addr_of!((*ptr).pragmaDataVersion) as usize - ptr as usize }, + unsafe { ::std::ptr::addr_of!((*ptr).pragmaDataVersion) as usize - ptr as usize }, 16usize, concat!( "Offset of field: ", @@ -302,7 +302,7 @@ fn bindgen_test_layout_crsql_ExtData() { ) ); assert_eq!( - unsafe { ::core::ptr::addr_of!((*ptr).dbVersion) as usize - ptr as usize }, + unsafe { ::std::ptr::addr_of!((*ptr).dbVersion) as usize - ptr as usize }, 24usize, concat!( "Offset of field: ", @@ -312,7 +312,7 @@ fn bindgen_test_layout_crsql_ExtData() { ) ); assert_eq!( - unsafe { ::core::ptr::addr_of!((*ptr).pendingDbVersion) as usize - ptr as usize }, + unsafe { ::std::ptr::addr_of!((*ptr).pendingDbVersion) as usize - ptr as usize }, 32usize, concat!( "Offset of field: ", @@ -322,7 +322,7 @@ fn bindgen_test_layout_crsql_ExtData() { ) ); assert_eq!( - unsafe { ::core::ptr::addr_of!((*ptr).pragmaSchemaVersion) as usize - ptr as usize }, + unsafe { ::std::ptr::addr_of!((*ptr).pragmaSchemaVersion) as usize - ptr as usize }, 40usize, concat!( "Offset of field: ", @@ -332,7 +332,7 @@ fn bindgen_test_layout_crsql_ExtData() { ) ); assert_eq!( - unsafe { ::core::ptr::addr_of!((*ptr).updatedTableInfosThisTx) as usize - ptr as usize }, + unsafe { ::std::ptr::addr_of!((*ptr).updatedTableInfosThisTx) as usize - ptr as usize }, 44usize, concat!( "Offset of field: ", @@ -343,7 +343,7 @@ fn bindgen_test_layout_crsql_ExtData() { ); assert_eq!( unsafe { - ::core::ptr::addr_of!((*ptr).pragmaSchemaVersionForTableInfos) as usize - ptr as usize + ::std::ptr::addr_of!((*ptr).pragmaSchemaVersionForTableInfos) as usize - ptr as usize }, 48usize, concat!( @@ -354,7 +354,7 @@ fn bindgen_test_layout_crsql_ExtData() { ) ); assert_eq!( - unsafe { ::core::ptr::addr_of!((*ptr).siteId) as usize - ptr as usize }, + unsafe { ::std::ptr::addr_of!((*ptr).siteId) as usize - ptr as usize }, 56usize, concat!( "Offset of field: ", @@ -364,7 +364,7 @@ fn bindgen_test_layout_crsql_ExtData() { ) ); assert_eq!( - unsafe { ::core::ptr::addr_of!((*ptr).pDbVersionStmt) as usize - ptr as usize }, + unsafe { ::std::ptr::addr_of!((*ptr).pDbVersionStmt) as usize - ptr as usize }, 64usize, concat!( "Offset of field: ", @@ -374,7 +374,7 @@ fn bindgen_test_layout_crsql_ExtData() { ) ); assert_eq!( - unsafe { ::core::ptr::addr_of!((*ptr).tableInfos) as usize - ptr as usize }, + unsafe { ::std::ptr::addr_of!((*ptr).tableInfos) as usize - ptr as usize }, 72usize, concat!( "Offset of field: ", @@ -384,7 +384,7 @@ fn bindgen_test_layout_crsql_ExtData() { ) ); assert_eq!( - unsafe { ::core::ptr::addr_of!((*ptr).rowsImpacted) as usize - ptr as usize }, + unsafe { ::std::ptr::addr_of!((*ptr).rowsImpacted) as usize - ptr as usize }, 80usize, concat!( "Offset of field: ", @@ -394,7 +394,7 @@ fn bindgen_test_layout_crsql_ExtData() { ) ); assert_eq!( - unsafe { ::core::ptr::addr_of!((*ptr).seq) as usize - ptr as usize }, + unsafe { ::std::ptr::addr_of!((*ptr).seq) as usize - ptr as usize }, 84usize, concat!( "Offset of field: ", @@ -404,7 +404,7 @@ fn bindgen_test_layout_crsql_ExtData() { ) ); assert_eq!( - unsafe { ::core::ptr::addr_of!((*ptr).pSetSyncBitStmt) as usize - ptr as usize }, + unsafe { ::std::ptr::addr_of!((*ptr).pSetSyncBitStmt) as usize - ptr as usize }, 88usize, concat!( "Offset of field: ", @@ -414,7 +414,7 @@ fn bindgen_test_layout_crsql_ExtData() { ) ); assert_eq!( - unsafe { ::core::ptr::addr_of!((*ptr).pClearSyncBitStmt) as usize - ptr as usize }, + unsafe { ::std::ptr::addr_of!((*ptr).pClearSyncBitStmt) as usize - ptr as usize }, 96usize, concat!( "Offset of field: ", @@ -424,7 +424,7 @@ fn bindgen_test_layout_crsql_ExtData() { ) ); assert_eq!( - unsafe { ::core::ptr::addr_of!((*ptr).pSetSiteIdOrdinalStmt) as usize - ptr as usize }, + unsafe { ::std::ptr::addr_of!((*ptr).pSetSiteIdOrdinalStmt) as usize - ptr as usize }, 104usize, concat!( "Offset of field: ", @@ -434,7 +434,7 @@ fn bindgen_test_layout_crsql_ExtData() { ) ); assert_eq!( - unsafe { ::core::ptr::addr_of!((*ptr).pSelectSiteIdOrdinalStmt) as usize - ptr as usize }, + unsafe { ::std::ptr::addr_of!((*ptr).pSelectSiteIdOrdinalStmt) as usize - ptr as usize }, 112usize, concat!( "Offset of field: ", @@ -444,7 +444,7 @@ fn bindgen_test_layout_crsql_ExtData() { ) ); assert_eq!( - unsafe { ::core::ptr::addr_of!((*ptr).pSelectClockTablesStmt) as usize - ptr as usize }, + unsafe { ::std::ptr::addr_of!((*ptr).pSelectClockTablesStmt) as usize - ptr as usize }, 120usize, concat!( "Offset of field: ", @@ -454,7 +454,7 @@ fn bindgen_test_layout_crsql_ExtData() { ) ); assert_eq!( - unsafe { ::core::ptr::addr_of!((*ptr).tieBreakSameColValue) as usize - ptr as usize }, + unsafe { ::std::ptr::addr_of!((*ptr).tieBreakSameColValue) as usize - ptr as usize }, 128usize, concat!( "Offset of field: ", diff --git a/core/rs/core/src/config.rs b/core/rs/core/src/config.rs index e8ee0bd3..81961a86 100644 --- a/core/rs/core/src/config.rs +++ b/core/rs/core/src/config.rs @@ -1,9 +1,13 @@ -use sqlite::Context; +use alloc::format; + +use sqlite::{Connection, Context}; use sqlite_nostd as sqlite; use sqlite_nostd::{ResultCode, Value}; use crate::c::crsql_ExtData; +pub const MERGE_EQUAL_VALUES: &str = "merge-equal-values"; + pub extern "C" fn crsql_config_set( ctx: *mut sqlite::context, argc: i32, @@ -13,18 +17,69 @@ pub extern "C" fn crsql_config_set( let name = args[0].text(); - match name { - "merge-equal-values" => { - let value = args[1].int(); + let value = match name { + MERGE_EQUAL_VALUES => { + let value = args[1]; let ext_data = ctx.user_data() as *mut crsql_ExtData; - unsafe { (*ext_data).tieBreakSameColValue = value }; + unsafe { (*ext_data).tieBreakSameColValue = value.int() }; + value } _ => { ctx.result_error("Unknown setting name"); ctx.result_error_code(ResultCode::ERROR); return; } + }; + + let db = ctx.db_handle(); + match insert_config_setting(db, name, value) { + Ok(value) => { + ctx.result_value(value); + } + Err(rc) => { + ctx.result_error("Could not persist config in database"); + ctx.result_error_code(rc); + return; + } } +} + +fn insert_config_setting( + db: *mut sqlite_nostd::sqlite3, + name: &str, + value: *mut sqlite::value, +) -> Result<*mut sqlite::value, ResultCode> { + let stmt = + db.prepare_v2("INSERT OR REPLACE INTO crsql_master VALUES (?, ?) RETURNING value")?; + + stmt.bind_text(1, &format!("config.{name}"), sqlite::Destructor::TRANSIENT)?; + stmt.bind_value(2, value)?; + + if let ResultCode::ROW = stmt.step()? { + stmt.column_value(0) + } else { + Err(ResultCode::ERROR) + } +} + +pub extern "C" fn crsql_config_get( + ctx: *mut sqlite::context, + argc: i32, + argv: *mut *mut sqlite::value, +) { + let args = sqlite::args!(argc, argv); - ctx.result_error_code(ResultCode::OK); + let name = args[0].text(); + + match name { + MERGE_EQUAL_VALUES => { + let ext_data = ctx.user_data() as *mut crsql_ExtData; + ctx.result_int(unsafe { (*ext_data).tieBreakSameColValue }); + } + _ => { + ctx.result_error("Unknown setting name"); + ctx.result_error_code(ResultCode::ERROR); + return; + } + } } diff --git a/core/rs/core/src/lib.rs b/core/rs/core/src/lib.rs index 2e475481..57f38b6c 100644 --- a/core/rs/core/src/lib.rs +++ b/core/rs/core/src/lib.rs @@ -54,7 +54,7 @@ use alter::crsql_compact_post_alter; use automigrate::*; use backfill::*; use c::{crsql_freeExtData, crsql_newExtData}; -use config::crsql_config_set; +use config::{crsql_config_get, crsql_config_set}; use core::ffi::{c_int, c_void, CStr}; use create_crr::create_crr; use db_version::{crsql_fill_db_version_if_needed, crsql_next_db_version}; @@ -489,6 +489,22 @@ pub extern "C" fn sqlite3_crsqlcore_init( return null_mut(); } + let rc = db + .create_function_v2( + "crsql_config_get", + 1, + sqlite::UTF8, + Some(ext_data as *mut c_void), + Some(crsql_config_get), + None, + None, + None, + ) + .unwrap_or(sqlite::ResultCode::ERROR); + if rc != ResultCode::OK { + return null_mut(); + } + return ext_data as *mut c_void; } diff --git a/core/src/ext-data.c b/core/src/ext-data.c index fc14406c..db82feba 100644 --- a/core/src/ext-data.c +++ b/core/src/ext-data.c @@ -1,5 +1,7 @@ #include "ext-data.h" +#include + #include "consts.h" void crsql_clear_stmt_cache(crsql_ExtData *pExtData); @@ -52,15 +54,45 @@ crsql_ExtData *crsql_newExtData(sqlite3 *db, unsigned char *siteIdBuffer) { pExtData->updatedTableInfosThisTx = 0; crsql_init_table_info_vec(pExtData); - int pv = crsql_fetchPragmaDataVersion(db, pExtData); - if (pv == -1 || rc != SQLITE_OK) { + sqlite3_stmt *pStmt; + + rc += sqlite3_prepare_v2(db, + "SELECT ltrim(key, 'config.'), value FROM " + "crsql_master WHERE key LIKE 'config.%';", + -1, &pStmt, 0); + + if (rc != SQLITE_OK) { crsql_freeExtData(pExtData); return 0; } - // default to not tie-breaking + // set defaults! pExtData->tieBreakSameColValue = 0; + while (sqlite3_step(pStmt) == SQLITE_ROW) { + const unsigned char *name = sqlite3_column_text(pStmt, 1); + if (sqlite3_stricmp(name, "merge-equal-values")) { + if (sqlite3_column_type(pStmt, 2) == SQLITE_INTEGER) { + const int value = sqlite3_column_int(pStmt, 2); + pExtData->tieBreakSameColValue = value; + } else { + // broken setting... + crsql_freeExtData(pExtData); + return 0; + } + } else { + // unhandled config setting + } + } + + sqlite3_finalize(pStmt); + + int pv = crsql_fetchPragmaDataVersion(db, pExtData); + if (pv == -1 || rc != SQLITE_OK) { + crsql_freeExtData(pExtData); + return 0; + } + return pExtData; } diff --git a/py/correctness/tests/test_config.py b/py/correctness/tests/test_config.py new file mode 100644 index 00000000..01268081 --- /dev/null +++ b/py/correctness/tests/test_config.py @@ -0,0 +1,14 @@ +from crsql_correctness import connect, close, get_site_id +import pprint + +def test_config_merge_equal_values(): + db = connect(":memory:") + value = db.execute("SELECT crsql_config_set('merge-equal-values', 1);").fetchone() + assert (value == (1,)) + db.commit() + + value = db.execute("SELECT value FROM crsql_master WHERE key = 'config.merge-equal-values'").fetchone() + assert (value == (1,)) + + value = db.execute("SELECT crsql_config_get('merge-equal-values');").fetchone() + assert (value == (1,)) \ No newline at end of file