diff --git a/core/Makefile b/core/Makefile index f7360296e..421dd1dd0 100644 --- a/core/Makefile +++ b/core/Makefile @@ -71,13 +71,11 @@ TARGET_TEST_ASAN=$(prefix)/test-asan # js/browser/wa-sqlite/Makefile, deps/sqlite/GNUMakefile, core/binding.gyp, core/Makefile ext_files=src/crsqlite.c \ src/util.c \ - src/tableinfo.c \ src/changes-vtab.c \ src/ext-data.c \ src/get-table.c ext_headers=src/crsqlite.h \ src/util.h \ - src/tableinfo.h \ src/changes-vtab.h \ src/ext-data.h diff --git a/core/rs/core/src/bootstrap.rs b/core/rs/core/src/bootstrap.rs index b4649840e..acf3cc80f 100644 --- a/core/rs/core/src/bootstrap.rs +++ b/core/rs/core/src/bootstrap.rs @@ -1,6 +1,9 @@ -use core::ffi::{c_char, c_int, CStr}; +use core::{ + ffi::{c_char, c_int, CStr}, + mem::{self, ManuallyDrop}, +}; -use crate::{c::crsql_TableInfo, consts}; +use crate::{consts, tableinfo::TableInfo}; use alloc::{ffi::CString, format}; use core::slice; use sqlite::{sqlite3, Connection, Destructor, ResultCode}; @@ -192,25 +195,12 @@ fn maybe_update_db_inner( * * @param tableInfo */ -#[no_mangle] -pub extern "C" fn crsql_create_clock_table( - db: *mut sqlite3, - table_info: *mut crsql_TableInfo, - err: *mut *mut c_char, -) -> c_int { - match create_clock_table(db, table_info, err) { - Ok(_) => ResultCode::OK as c_int, - Err(code) => code as c_int, - } -} - pub fn create_clock_table( db: *mut sqlite3, - table_info: *mut crsql_TableInfo, + table_info: &TableInfo, _err: *mut *mut c_char, ) -> Result { - let columns = sqlite::args!((*table_info).pksLen, (*table_info).pks); - let pk_list = crate::util::as_identifier_list(columns, None)?; + let pk_list = crate::util::as_identifier_list(&table_info.pks, None)?; let table_name = unsafe { CStr::from_ptr((*table_info).tblName).to_str() }?; db.exec_safe(&format!( diff --git a/core/rs/core/src/c.rs b/core/rs/core/src/c.rs index c0e1405a4..f230a35e0 100644 --- a/core/rs/core/src/c.rs +++ b/core/rs/core/src/c.rs @@ -47,51 +47,26 @@ pub enum ChangeRowType { PkOnly = 2, } -#[repr(C)] -#[derive(Debug, Copy, Clone)] -#[allow(non_snake_case, non_camel_case_types)] -pub struct crsql_TableInfo { - pub tblName: *mut ::core::ffi::c_char, - pub baseCols: *mut crsql_ColumnInfo, - pub baseColsLen: ::core::ffi::c_int, - pub pks: *mut crsql_ColumnInfo, - pub pksLen: ::core::ffi::c_int, - pub nonPks: *mut crsql_ColumnInfo, - pub nonPksLen: ::core::ffi::c_int, -} - -#[repr(C)] -#[derive(Debug, Copy, Clone)] -#[allow(non_snake_case, non_camel_case_types)] -pub struct crsql_ColumnInfo { - pub cid: ::core::ffi::c_int, - pub name: *mut ::core::ffi::c_char, - pub type_: *mut ::core::ffi::c_char, - pub notnull: ::core::ffi::c_int, - pub pk: ::core::ffi::c_int, -} - #[repr(C)] #[derive(Debug, Copy, Clone)] #[allow(non_snake_case, non_camel_case_types)] pub struct crsql_ExtData { - pub pPragmaSchemaVersionStmt: *mut sqlite::stmt, - pub pPragmaDataVersionStmt: *mut sqlite::stmt, + pub pPragmaSchemaVersionStmt: *mut sqlite3_stmt, + pub pPragmaDataVersionStmt: *mut sqlite3_stmt, pub pragmaDataVersion: ::core::ffi::c_int, - pub dbVersion: sqlite::int64, - pub pendingDbVersion: sqlite::int64, + pub dbVersion: sqlite3_int64, + pub pendingDbVersion: sqlite3_int64, pub pragmaSchemaVersion: ::core::ffi::c_int, pub pragmaSchemaVersionForTableInfos: ::core::ffi::c_int, pub siteId: *mut ::core::ffi::c_uchar, - pub pDbVersionStmt: *mut sqlite::stmt, - pub zpTableInfos: *mut *mut crsql_TableInfo, - pub tableInfosLen: ::core::ffi::c_int, + pub pDbVersionStmt: *mut sqlite3_stmt, + pub tableInfos: *mut ::core::ffi::c_void, pub rowsImpacted: ::core::ffi::c_int, pub seq: ::core::ffi::c_int, - pub pSetSyncBitStmt: *mut sqlite::stmt, - pub pClearSyncBitStmt: *mut sqlite::stmt, - pub pSetSiteIdOrdinalStmt: *mut sqlite::stmt, - pub pSelectSiteIdOrdinalStmt: *mut sqlite::stmt, + pub pSetSyncBitStmt: *mut sqlite3_stmt, + pub pClearSyncBitStmt: *mut sqlite3_stmt, + pub pSetSiteIdOrdinalStmt: *mut sqlite3_stmt, + pub pSelectSiteIdOrdinalStmt: *mut sqlite3_stmt, pub pStmtCache: *mut ::core::ffi::c_void, } @@ -119,16 +94,6 @@ pub struct crsql_Changes_cursor { } extern "C" { - pub fn crsql_indexofTableInfo( - tblInfos: *mut *mut crsql_TableInfo, - len: ::core::ffi::c_int, - tblName: *const ::core::ffi::c_char, - ) -> ::core::ffi::c_int; - pub fn crsql_findTableInfo( - tblInfos: *mut *mut crsql_TableInfo, - len: c_int, - tblName: *const c_char, - ) -> *mut crsql_TableInfo; pub fn crsql_ensureTableInfosAreUpToDate( db: *mut sqlite::sqlite3, pExtData: *mut crsql_ExtData, @@ -142,6 +107,7 @@ extern "C" { } #[test] +#[allow(non_snake_case, non_camel_case_types)] fn bindgen_test_layout_crsql_Changes_vtab() { const UNINIT: ::core::mem::MaybeUninit = ::core::mem::MaybeUninit::uninit(); let ptr = UNINIT.as_ptr(); @@ -188,6 +154,7 @@ fn bindgen_test_layout_crsql_Changes_vtab() { } #[test] +#[allow(non_snake_case)] fn bindgen_test_layout_crsql_Changes_cursor() { const UNINIT: ::core::mem::MaybeUninit = ::core::mem::MaybeUninit::uninit(); @@ -284,177 +251,23 @@ fn bindgen_test_layout_crsql_Changes_cursor() { ); } -#[test] -#[allow(non_snake_case)] -fn bindgen_test_layout_crsql_ColumnInfo() { - const UNINIT: ::core::mem::MaybeUninit = ::core::mem::MaybeUninit::uninit(); - let ptr = UNINIT.as_ptr(); - assert_eq!( - ::core::mem::size_of::(), - 32usize, - concat!("Size of: ", stringify!(crsql_ColumnInfo)) - ); - assert_eq!( - ::core::mem::align_of::(), - 8usize, - concat!("Alignment of ", stringify!(crsql_ColumnInfo)) - ); - assert_eq!( - unsafe { ::core::ptr::addr_of!((*ptr).cid) as usize - ptr as usize }, - 0usize, - concat!( - "Offset of field: ", - stringify!(crsql_ColumnInfo), - "::", - stringify!(cid) - ) - ); - assert_eq!( - unsafe { ::core::ptr::addr_of!((*ptr).name) as usize - ptr as usize }, - 8usize, - concat!( - "Offset of field: ", - stringify!(crsql_ColumnInfo), - "::", - stringify!(name) - ) - ); - assert_eq!( - unsafe { ::core::ptr::addr_of!((*ptr).type_) as usize - ptr as usize }, - 16usize, - concat!( - "Offset of field: ", - stringify!(crsql_ColumnInfo), - "::", - stringify!(type_) - ) - ); - assert_eq!( - unsafe { ::core::ptr::addr_of!((*ptr).notnull) as usize - ptr as usize }, - 24usize, - concat!( - "Offset of field: ", - stringify!(crsql_ColumnInfo), - "::", - stringify!(notnull) - ) - ); - assert_eq!( - unsafe { ::core::ptr::addr_of!((*ptr).pk) as usize - ptr as usize }, - 28usize, - concat!( - "Offset of field: ", - stringify!(crsql_ColumnInfo), - "::", - stringify!(pk) - ) - ); -} - -#[test] -#[allow(non_snake_case)] -fn bindgen_test_layout_crsql_TableInfo() { - const UNINIT: ::core::mem::MaybeUninit = ::core::mem::MaybeUninit::uninit(); - let ptr = UNINIT.as_ptr(); - assert_eq!( - ::core::mem::size_of::(), - 56usize, - concat!("Size of: ", stringify!(crsql_TableInfo)) - ); - assert_eq!( - ::core::mem::align_of::(), - 8usize, - concat!("Alignment of ", stringify!(crsql_TableInfo)) - ); - assert_eq!( - unsafe { ::core::ptr::addr_of!((*ptr).tblName) as usize - ptr as usize }, - 0usize, - concat!( - "Offset of field: ", - stringify!(crsql_TableInfo), - "::", - stringify!(tblName) - ) - ); - assert_eq!( - unsafe { ::core::ptr::addr_of!((*ptr).baseCols) as usize - ptr as usize }, - 8usize, - concat!( - "Offset of field: ", - stringify!(crsql_TableInfo), - "::", - stringify!(baseCols) - ) - ); - assert_eq!( - unsafe { ::core::ptr::addr_of!((*ptr).baseColsLen) as usize - ptr as usize }, - 16usize, - concat!( - "Offset of field: ", - stringify!(crsql_TableInfo), - "::", - stringify!(baseColsLen) - ) - ); - assert_eq!( - unsafe { ::core::ptr::addr_of!((*ptr).pks) as usize - ptr as usize }, - 24usize, - concat!( - "Offset of field: ", - stringify!(crsql_TableInfo), - "::", - stringify!(pks) - ) - ); - assert_eq!( - unsafe { ::core::ptr::addr_of!((*ptr).pksLen) as usize - ptr as usize }, - 32usize, - concat!( - "Offset of field: ", - stringify!(crsql_TableInfo), - "::", - stringify!(pksLen) - ) - ); - assert_eq!( - unsafe { ::core::ptr::addr_of!((*ptr).nonPks) as usize - ptr as usize }, - 40usize, - concat!( - "Offset of field: ", - stringify!(crsql_TableInfo), - "::", - stringify!(nonPks) - ) - ); - assert_eq!( - unsafe { ::core::ptr::addr_of!((*ptr).nonPksLen) as usize - ptr as usize }, - 48usize, - concat!( - "Offset of field: ", - stringify!(crsql_TableInfo), - "::", - stringify!(nonPksLen) - ) - ); -} - #[test] #[allow(non_snake_case)] fn bindgen_test_layout_crsql_ExtData() { - const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); + const UNINIT: ::core::mem::MaybeUninit = ::core::mem::MaybeUninit::uninit(); let ptr = UNINIT.as_ptr(); assert_eq!( - ::std::mem::size_of::(), - 128usize, + ::core::mem::size_of::(), + 120usize, concat!("Size of: ", stringify!(crsql_ExtData)) ); assert_eq!( - ::std::mem::align_of::(), + ::core::mem::align_of::(), 8usize, concat!("Alignment of ", stringify!(crsql_ExtData)) ); assert_eq!( - unsafe { ::std::ptr::addr_of!((*ptr).pPragmaSchemaVersionStmt) as usize - ptr as usize }, + unsafe { ::core::ptr::addr_of!((*ptr).pPragmaSchemaVersionStmt) as usize - ptr as usize }, 0usize, concat!( "Offset of field: ", @@ -464,7 +277,7 @@ fn bindgen_test_layout_crsql_ExtData() { ) ); assert_eq!( - unsafe { ::std::ptr::addr_of!((*ptr).pPragmaDataVersionStmt) as usize - ptr as usize }, + unsafe { ::core::ptr::addr_of!((*ptr).pPragmaDataVersionStmt) as usize - ptr as usize }, 8usize, concat!( "Offset of field: ", @@ -474,7 +287,7 @@ fn bindgen_test_layout_crsql_ExtData() { ) ); assert_eq!( - unsafe { ::std::ptr::addr_of!((*ptr).pragmaDataVersion) as usize - ptr as usize }, + unsafe { ::core::ptr::addr_of!((*ptr).pragmaDataVersion) as usize - ptr as usize }, 16usize, concat!( "Offset of field: ", @@ -484,7 +297,7 @@ fn bindgen_test_layout_crsql_ExtData() { ) ); assert_eq!( - unsafe { ::std::ptr::addr_of!((*ptr).dbVersion) as usize - ptr as usize }, + unsafe { ::core::ptr::addr_of!((*ptr).dbVersion) as usize - ptr as usize }, 24usize, concat!( "Offset of field: ", @@ -494,7 +307,7 @@ fn bindgen_test_layout_crsql_ExtData() { ) ); assert_eq!( - unsafe { ::std::ptr::addr_of!((*ptr).pendingDbVersion) as usize - ptr as usize }, + unsafe { ::core::ptr::addr_of!((*ptr).pendingDbVersion) as usize - ptr as usize }, 32usize, concat!( "Offset of field: ", @@ -504,7 +317,7 @@ fn bindgen_test_layout_crsql_ExtData() { ) ); assert_eq!( - unsafe { ::std::ptr::addr_of!((*ptr).pragmaSchemaVersion) as usize - ptr as usize }, + unsafe { ::core::ptr::addr_of!((*ptr).pragmaSchemaVersion) as usize - ptr as usize }, 40usize, concat!( "Offset of field: ", @@ -515,7 +328,7 @@ fn bindgen_test_layout_crsql_ExtData() { ); assert_eq!( unsafe { - ::std::ptr::addr_of!((*ptr).pragmaSchemaVersionForTableInfos) as usize - ptr as usize + ::core::ptr::addr_of!((*ptr).pragmaSchemaVersionForTableInfos) as usize - ptr as usize }, 44usize, concat!( @@ -526,7 +339,7 @@ fn bindgen_test_layout_crsql_ExtData() { ) ); assert_eq!( - unsafe { ::std::ptr::addr_of!((*ptr).siteId) as usize - ptr as usize }, + unsafe { ::core::ptr::addr_of!((*ptr).siteId) as usize - ptr as usize }, 48usize, concat!( "Offset of field: ", @@ -536,7 +349,7 @@ fn bindgen_test_layout_crsql_ExtData() { ) ); assert_eq!( - unsafe { ::std::ptr::addr_of!((*ptr).pDbVersionStmt) as usize - ptr as usize }, + unsafe { ::core::ptr::addr_of!((*ptr).pDbVersionStmt) as usize - ptr as usize }, 56usize, concat!( "Offset of field: ", @@ -546,28 +359,18 @@ fn bindgen_test_layout_crsql_ExtData() { ) ); assert_eq!( - unsafe { ::std::ptr::addr_of!((*ptr).zpTableInfos) as usize - ptr as usize }, + unsafe { ::core::ptr::addr_of!((*ptr).tableInfos) as usize - ptr as usize }, 64usize, concat!( "Offset of field: ", stringify!(crsql_ExtData), "::", - stringify!(zpTableInfos) + stringify!(tableInfos) ) ); assert_eq!( - unsafe { ::std::ptr::addr_of!((*ptr).tableInfosLen) as usize - ptr as usize }, + unsafe { ::core::ptr::addr_of!((*ptr).rowsImpacted) as usize - ptr as usize }, 72usize, - concat!( - "Offset of field: ", - stringify!(crsql_ExtData), - "::", - stringify!(tableInfosLen) - ) - ); - assert_eq!( - unsafe { ::std::ptr::addr_of!((*ptr).rowsImpacted) as usize - ptr as usize }, - 76usize, concat!( "Offset of field: ", stringify!(crsql_ExtData), @@ -576,8 +379,8 @@ fn bindgen_test_layout_crsql_ExtData() { ) ); assert_eq!( - unsafe { ::std::ptr::addr_of!((*ptr).seq) as usize - ptr as usize }, - 80usize, + unsafe { ::core::ptr::addr_of!((*ptr).seq) as usize - ptr as usize }, + 76usize, concat!( "Offset of field: ", stringify!(crsql_ExtData), @@ -586,8 +389,8 @@ fn bindgen_test_layout_crsql_ExtData() { ) ); assert_eq!( - unsafe { ::std::ptr::addr_of!((*ptr).pSetSyncBitStmt) as usize - ptr as usize }, - 88usize, + unsafe { ::core::ptr::addr_of!((*ptr).pSetSyncBitStmt) as usize - ptr as usize }, + 80usize, concat!( "Offset of field: ", stringify!(crsql_ExtData), @@ -596,8 +399,8 @@ fn bindgen_test_layout_crsql_ExtData() { ) ); assert_eq!( - unsafe { ::std::ptr::addr_of!((*ptr).pClearSyncBitStmt) as usize - ptr as usize }, - 96usize, + unsafe { ::core::ptr::addr_of!((*ptr).pClearSyncBitStmt) as usize - ptr as usize }, + 88usize, concat!( "Offset of field: ", stringify!(crsql_ExtData), @@ -606,8 +409,8 @@ fn bindgen_test_layout_crsql_ExtData() { ) ); assert_eq!( - unsafe { ::std::ptr::addr_of!((*ptr).pSetSiteIdOrdinalStmt) as usize - ptr as usize }, - 104usize, + unsafe { ::core::ptr::addr_of!((*ptr).pSetSiteIdOrdinalStmt) as usize - ptr as usize }, + 96usize, concat!( "Offset of field: ", stringify!(crsql_ExtData), @@ -616,8 +419,8 @@ fn bindgen_test_layout_crsql_ExtData() { ) ); assert_eq!( - unsafe { ::std::ptr::addr_of!((*ptr).pSelectSiteIdOrdinalStmt) as usize - ptr as usize }, - 112usize, + unsafe { ::core::ptr::addr_of!((*ptr).pSelectSiteIdOrdinalStmt) as usize - ptr as usize }, + 104usize, concat!( "Offset of field: ", stringify!(crsql_ExtData), @@ -626,8 +429,8 @@ fn bindgen_test_layout_crsql_ExtData() { ) ); assert_eq!( - unsafe { ::std::ptr::addr_of!((*ptr).pStmtCache) as usize - ptr as usize }, - 120usize, + unsafe { ::core::ptr::addr_of!((*ptr).pStmtCache) as usize - ptr as usize }, + 112usize, concat!( "Offset of field: ", stringify!(crsql_ExtData), @@ -663,9 +466,3 @@ impl CPointer for &str { .unwrap_or(null_mut()) } } - -impl CPointer for crsql_TableInfo { - fn into_c_ptr(self) -> *mut crsql_TableInfo { - Box::into_raw(Box::from(self)) - } -} diff --git a/core/rs/core/src/changes_vtab_read.rs b/core/rs/core/src/changes_vtab_read.rs index 964cf17d5..d06910588 100644 --- a/core/rs/core/src/changes_vtab_read.rs +++ b/core/rs/core/src/changes_vtab_read.rs @@ -1,5 +1,5 @@ extern crate alloc; -use crate::{c::crsql_TableInfo, util}; +use crate::{tableinfo::TableInfo, util}; use alloc::format; use alloc::string::String; use alloc::vec; @@ -12,20 +12,18 @@ use sqlite::ResultCode; use sqlite_nostd as sqlite; -fn crsql_changes_query_for_table(table_info: *mut crsql_TableInfo) -> Result { +fn crsql_changes_query_for_table(table_info: &TableInfo) -> Result { unsafe { - if (*table_info).pksLen == 0 { + if table_info.pks.len() == 0 { // no primary keys? We can't get changes for a table w/o primary keys... // this should be an impossible case. return Err(ResultCode::ABORT); } } - let table_name = unsafe { CStr::from_ptr((*table_info).tblName).to_str()? }; - let pk_columns = - unsafe { slice::from_raw_parts((*table_info).pks, (*table_info).pksLen as usize) }; - let pk_list = crate::util::as_identifier_list(pk_columns, Some("t1."))?; - let self_join = util::map_columns(pk_columns, |c| { + let pk_list = crate::util::as_identifier_list(&table_info.pks, Some("t1."))?; + // TODO: we can remove the self join if we put causal length in the primary key table + let self_join = util::map_columns(&table_info.pks, |c| { format!("t1.\"{c}\" = t2.\"{c}\"", c = crate::util::escape_ident(c)) })? .join(" AND "); @@ -48,41 +46,22 @@ fn crsql_changes_query_for_table(table_info: *mut crsql_TableInfo) -> Result *mut c_char { - if let Ok(idx_str) = unsafe { CStr::from_ptr(idx_str).to_str() } { - let table_infos = sqlite::args!(table_infos_len, table_infos); - let query = changes_union_query(table_infos, idx_str); - if let Ok(query) = query { - // release ownership of the memory - let (ptr, _, _) = query.into_raw_parts(); - // return to c - return ptr as *mut c_char; - } - } - return core::ptr::null_mut() as *mut c_char; -} - pub fn changes_union_query( - table_infos: &[*mut crsql_TableInfo], + table_infos: &Vec, idx_str: &str, ) -> Result { let mut sub_queries = vec![]; for table_info in table_infos { - let query_part = crsql_changes_query_for_table(*table_info)?; + let query_part = crsql_changes_query_for_table(&table_info)?; sub_queries.push(query_part); } @@ -95,34 +74,14 @@ pub fn changes_union_query( )); } -#[no_mangle] -pub extern "C" fn crsql_row_patch_data_query( - table_info: *mut crsql_TableInfo, - col_name: *const c_char, -) -> *mut c_char { - if let Ok(col_name) = unsafe { CStr::from_ptr(col_name).to_str() } { - if let Some(query) = row_patch_data_query(table_info, col_name) { - let (ptr, _, _) = query.into_raw_parts(); - // release ownership of the memory - // return to c - return ptr as *mut c_char; - } - } - return null_mut(); -} - -pub fn row_patch_data_query(table_info: *mut crsql_TableInfo, col_name: &str) -> Option { - let pk_columns = - unsafe { slice::from_raw_parts((*table_info).pks, (*table_info).pksLen as usize) }; - if let Ok(table_name) = unsafe { CStr::from_ptr((*table_info).tblName).to_str() } { - if let Ok(where_list) = crate::util::where_list(pk_columns, None) { - return Some(format!( - "SELECT \"{col_name}\" FROM \"{table_name}\" WHERE {where_list}\0", - col_name = crate::util::escape_ident(col_name), - table_name = crate::util::escape_ident(table_name), - where_list = where_list - )); - } +pub fn row_patch_data_query(table_info: &TableInfo, col_name: &str) -> Option { + if let Ok(where_list) = crate::util::where_list(&table_info.pks, None) { + return Some(format!( + "SELECT \"{col_name}\" FROM \"{table_name}\" WHERE {where_list}\0", + col_name = crate::util::escape_ident(col_name), + table_name = crate::util::escape_ident(&table_info.tbl_name), + where_list = where_list + )); } return None; diff --git a/core/rs/core/src/changes_vtab_write.rs b/core/rs/core/src/changes_vtab_write.rs index 79b823647..879346ac5 100644 --- a/core/rs/core/src/changes_vtab_write.rs +++ b/core/rs/core/src/changes_vtab_write.rs @@ -3,7 +3,7 @@ use alloc::format; use alloc::string::String; use alloc::vec::Vec; use core::ffi::{c_char, c_int, CStr}; -use core::mem::forget; +use core::mem::{self, forget}; use core::ptr::null_mut; use core::slice; use sqlite::{Connection, Stmt}; @@ -12,7 +12,7 @@ use sqlite_nostd::{sqlite3, ResultCode, Value}; use crate::c::crsql_ExtData; use crate::c::{ - crsql_Changes_vtab, crsql_TableInfo, crsql_ensureTableInfosAreUpToDate, crsql_indexofTableInfo, + crsql_Changes_vtab, crsql_ensureTableInfosAreUpToDate, crsql_indexofTableInfo, CrsqlChangesColumn, }; use crate::compare_values::crsql_compare_sqlite_values; @@ -20,15 +20,15 @@ use crate::pack_columns::bind_package_to_stmt; use crate::stmt_cache::{ get_cache_key, get_cached_stmt, reset_cached_stmt, set_cached_stmt, CachedStmtType, }; +use crate::tableinfo::TableInfo; use crate::util::{self, slab_rowid}; use crate::{unpack_columns, ColumnValue}; fn pk_where_list_from_tbl_info( - tbl_info: *mut crsql_TableInfo, + tbl_info: &TableInfo, prefix: Option<&str>, ) -> Result { - let pk_cols = sqlite::args!((*tbl_info).pksLen, (*tbl_info).pks); - util::where_list(pk_cols, prefix) + util::where_list(&tbl_info.pks, prefix) } /** @@ -43,7 +43,7 @@ fn did_cid_win( db: *mut sqlite3, ext_data: *mut crsql_ExtData, insert_tbl: &str, - tbl_info: *mut crsql_TableInfo, + tbl_info: &TableInfo, unpacked_pks: &Vec, col_name: &str, insert_val: *mut sqlite::value, @@ -171,7 +171,7 @@ where fn set_winner_clock( db: *mut sqlite3, ext_data: *mut crsql_ExtData, - tbl_info: *mut crsql_TableInfo, + tbl_info: &TableInfo, unpacked_pks: &Vec, insert_col_name: &str, insert_col_vrsn: sqlite::int64, @@ -179,8 +179,6 @@ fn set_winner_clock( insert_site_id: &[u8], insert_seq: sqlite::int64, ) -> Result { - let tbl_name_str = unsafe { CStr::from_ptr((*tbl_info).tblName).to_str()? }; - // set the site_id ordinal // get the returned ordinal // use that in place of insert_site_id in the metadata table(s) @@ -241,7 +239,7 @@ fn set_winner_clock( ?, ? ) RETURNING _rowid_", - table_name = crate::util::escape_ident(tbl_name_str), + table_name = crate::util::escape_ident(&tbl_info.tbl_name), pk_ident_list = crate::util::as_identifier_list(pk_cols, None)?, pk_bind_list = crate::util::binding_list(pk_cols.len()), )) @@ -287,21 +285,19 @@ fn set_winner_clock( fn merge_sentinel_only_insert( db: *mut sqlite3, ext_data: *mut crsql_ExtData, - tbl_info: *mut crsql_TableInfo, + tbl_info: &TableInfo, unpacked_pks: &Vec, remote_col_vrsn: sqlite::int64, remote_db_vsn: sqlite::int64, remote_site_id: &[u8], remote_seq: sqlite::int64, ) -> Result { - let tbl_name_str = unsafe { CStr::from_ptr((*tbl_info).tblName).to_str()? }; - let stmt_key = get_cache_key(CachedStmtType::MergePkOnlyInsert, tbl_name_str, None)?; let merge_stmt = get_cached_stmt_rt_wt(db, ext_data, stmt_key, || { let pk_cols = sqlite::args!((*tbl_info).pksLen, (*tbl_info).pks); Ok(format!( "INSERT OR IGNORE INTO \"{table_name}\" ({pk_idents}) VALUES ({pk_bindings})", - table_name = crate::util::escape_ident(tbl_name_str), + table_name = crate::util::escape_ident(&tbl_info.tbl_name), pk_idents = crate::util::as_identifier_list(pk_cols, None)?, pk_bindings = crate::util::binding_list(pk_cols.len()), )) @@ -366,7 +362,7 @@ fn zero_clocks_on_resurrect( db: *mut sqlite3, ext_data: *mut crsql_ExtData, table_name: &str, - tbl_info: *mut crsql_TableInfo, + tbl_info: &TableInfo, unpacked_pks: &Vec, insert_db_vrsn: sqlite::int64, ) -> Result { @@ -401,15 +397,14 @@ fn zero_clocks_on_resurrect( unsafe fn merge_delete( db: *mut sqlite3, ext_data: *mut crsql_ExtData, - tbl_info: *mut crsql_TableInfo, + tbl_info: &TableInfo, unpacked_pks: &Vec, remote_col_vrsn: sqlite::int64, remote_db_vrsn: sqlite::int64, remote_site_id: &[u8], remote_seq: sqlite::int64, ) -> Result { - let tbl_name_str = CStr::from_ptr((*tbl_info).tblName).to_str()?; - let stmt_key = get_cache_key(CachedStmtType::MergeDelete, tbl_name_str, None)?; + let stmt_key = get_cache_key(CachedStmtType::MergeDelete, &tbl_info.tbl_name, None)?; let delete_stmt = get_cached_stmt_rt_wt(db, ext_data, stmt_key, || { Ok(format!( "DELETE FROM \"{table_name}\" WHERE {pk_where_list}", @@ -498,7 +493,7 @@ fn get_local_cl( db: *mut sqlite::sqlite3, ext_data: *mut crsql_ExtData, tbl_name: &str, - tbl_info: *mut crsql_TableInfo, + tbl_info: &TableInfo, unpacked_pks: &Vec, ) -> Result { let stmt_key = get_cache_key(CachedStmtType::GetLocalCl, tbl_name, None)?; @@ -595,6 +590,7 @@ unsafe fn merge_insert( } let insert_site_id = insert_site_id.blob(); + let tbl_infos = mem::ManuallyDrop::new(Vec::from_raw_parts(ptr, length, capacity)); let tbl_info_index = crsql_indexofTableInfo( (*(*tab).pExtData).zpTableInfos, (*(*tab).pExtData).tableInfosLen, diff --git a/core/rs/core/src/create_crr.rs b/core/rs/core/src/create_crr.rs index 6121b8d0f..c93b86d0a 100644 --- a/core/rs/core/src/create_crr.rs +++ b/core/rs/core/src/create_crr.rs @@ -4,7 +4,6 @@ use sqlite_nostd as sqlite; use sqlite_nostd::ResultCode; use crate::bootstrap::create_clock_table; -use crate::c::crsql_TableInfo; use crate::tableinfo::{free_table_info, is_table_compatible, pull_table_info}; use crate::triggers::create_triggers; use crate::{backfill_table, is_crr, remove_crr_triggers_if_exist}; @@ -28,17 +27,15 @@ pub fn create_crr( return Ok(ResultCode::OK); } - let mut table_info: *mut crsql_TableInfo = core::ptr::null_mut(); - pull_table_info(db, table, &mut table_info, err)?; + // We do not / can not pull this from the cached set of table infos + // since nothing would exist in it for a table not yet made into a crr. + // TODO: Note: we can optimize out our `ensureTableInfosAreUpToDate` by mutating our ext data + // when upgrading stuff to CRRs + let table_info = pull_table_info(db, table, err)?; - let cleanup = |err: ResultCode| unsafe { - free_table_info(table_info); - err - }; - - create_clock_table(db, table_info, err) + create_clock_table(db, &table_info, err) .and_then(|_| remove_crr_triggers_if_exist(db, table)) - .and_then(|_| create_triggers(db, table_info, err)) + .and_then(|_| create_triggers(db, &table_info, err)) .map_err(cleanup)?; let (non_pk_cols, pk_cols) = unsafe { diff --git a/core/rs/core/src/lib.rs b/core/rs/core/src/lib.rs index 89c96acff..47c00dc42 100644 --- a/core/rs/core/src/lib.rs +++ b/core/rs/core/src/lib.rs @@ -22,7 +22,6 @@ mod triggers; mod unpack_columns_vtab; mod util; -use crate::c::crsql_TableInfo; use core::{ffi::c_char, slice}; extern crate alloc; use alloc::vec::Vec; @@ -232,25 +231,6 @@ pub extern "C" fn crsql_is_table_compatible( } } -#[no_mangle] -pub extern "C" fn crsql_pull_table_info( - db: *mut sqlite::sqlite3, - table: *const c_char, - table_info: *mut *mut crsql_TableInfo, - err: *mut *mut c_char, -) -> c_int { - if let Ok(table) = unsafe { CStr::from_ptr(table).to_str() } { - pull_table_info(db, table, table_info, err).unwrap_or_else(|err| err) as c_int - } else { - (ResultCode::NOMEM as c_int) * -1 - } -} - -#[no_mangle] -pub extern "C" fn crsql_free_table_info(table_info: *mut crsql_TableInfo) { - unsafe { free_table_info(table_info) }; -} - #[no_mangle] pub extern "C" fn crsql_create_crr( db: *mut sqlite::sqlite3, diff --git a/core/rs/core/src/tableinfo.rs b/core/rs/core/src/tableinfo.rs index 368c4ce67..214e9aa01 100644 --- a/core/rs/core/src/tableinfo.rs +++ b/core/rs/core/src/tableinfo.rs @@ -1,5 +1,4 @@ use crate::c::CPointer; -use crate::c::{crsql_ColumnInfo, crsql_TableInfo}; use crate::util::Countable; use alloc::boxed::Box; use alloc::ffi::CString; @@ -7,6 +6,7 @@ use alloc::format; use alloc::vec; use alloc::vec::Vec; use core::ffi::c_char; +use core::mem; use num_traits::ToPrimitive; use sqlite_nostd as sqlite; use sqlite_nostd::Connection; @@ -15,9 +15,24 @@ use sqlite_nostd::StrRef; pub struct TableInfo { pub tbl_name: String, - pub base_columns: Vec, pub pks: Vec, - pub nonPks: Vec, + pub non_pks: Vec, +} + +pub struct ColumnInfo { + pub cid: i32, + pub name: String, + pub type_: String, + pub notnull: bool, + // > 0 if it is a primary key columns + // the value refers to the position in the `PRIMARY KEY (cols...)` statement + pub pk: i32, +} + +#[no_mangle] +pub extern "C" fn crsql_init_table_info_vec(ext_data: *mut crsql_ExtData) { + let vec = vec![]; + let (ptr, len, cap) = vec.into_raw_parts(); } /** @@ -29,9 +44,8 @@ pub struct TableInfo { pub fn pull_table_info( db: *mut sqlite::sqlite3, table: &str, - table_info: *mut *mut crsql_TableInfo, err: *mut *mut c_char, -) -> Result { +) -> Result { let sql = format!("SELECT count(*) FROM pragma_table_info('{table}')"); let columns_len = match db.prepare_v2(&sql).and_then(|stmt| { stmt.step()?; @@ -50,12 +64,12 @@ pub fn pull_table_info( ); let column_infos = match db.prepare_v2(&sql) { Ok(stmt) => { - let mut cols: Vec = vec![]; + let mut cols: Vec = vec![]; while stmt.step()? == ResultCode::ROW { - cols.push(crsql_ColumnInfo { - type_: stmt.column_text(2).map_err(free_cols(&cols))?.into_c_ptr(), - name: stmt.column_text(1).map_err(free_cols(&cols))?.into_c_ptr(), + cols.push(ColumnInfo { + type_: stmt.column_text(2)?.to_string(), + name: stmt.column_text(1)?.to_string(), notnull: stmt.column_int(3)?, cid: stmt.column_int(0)?, pk: stmt.column_int(4)?, @@ -64,7 +78,7 @@ pub fn pull_table_info( if cols.len() != columns_len { err.set("Number of fetched columns did not match expected number of columns"); - return Err(free_cols(&cols)(ResultCode::ERROR)); + return Err(ResultCode::ERROR); } cols } @@ -78,63 +92,11 @@ pub fn pull_table_info( column_infos.clone().into_iter().partition(|x| x.pk > 0); pks.sort_by_key(|x| x.pk); - unsafe { - *table_info = crsql_TableInfo { - baseCols: column_infos.into_c_ptr(), - baseColsLen: columns_len as i32, - tblName: table.into_c_ptr(), - nonPksLen: non_pks.len() as i32, - nonPks: non_pks.into_c_ptr(), - pksLen: pks.len() as i32, - pks: pks.into_c_ptr(), - } - .into_c_ptr(); - } - - return Ok(ResultCode::OK); -} - -fn free_cols<'a>(cols: &'a Vec) -> impl Fn(ResultCode) -> ResultCode + 'a { - move |err: ResultCode| { - for info in cols { - drop(unsafe { CString::from_raw(info.type_) }); - drop(unsafe { CString::from_raw(info.name) }); - } - err - } -} - -pub unsafe fn free_table_info(table_info: *mut crsql_TableInfo) { - if table_info.is_null() { - return; - } - - let info = *table_info; - if !info.tblName.is_null() { - drop(CString::from_raw(info.tblName)); - } - if !info.baseCols.is_null() { - free_cols(&Vec::from_raw_parts( - info.baseCols, - info.baseColsLen as usize, - info.baseColsLen as usize, - ))(ResultCode::OK); - } - if !info.pks.is_null() { - drop(Vec::from_raw_parts( - info.pks, - info.pksLen as usize, - info.pksLen as usize, - )); - } - if !info.nonPks.is_null() { - drop(Vec::from_raw_parts( - info.nonPks, - info.nonPksLen as usize, - info.nonPksLen as usize, - )); - } - drop(Box::from_raw(table_info)); + return Ok(TableInfo { + tbl_name: table.to_string(), + pks, + non_pks, + }); } pub fn is_table_compatible( diff --git a/core/rs/core/src/triggers.rs b/core/rs/core/src/triggers.rs index 85a329344..92397fd5c 100644 --- a/core/rs/core/src/triggers.rs +++ b/core/rs/core/src/triggers.rs @@ -10,25 +10,14 @@ use core::{ str::Utf8Error, }; -use crate::c::crsql_TableInfo; use sqlite::{sqlite3, ResultCode}; use sqlite_nostd as sqlite; -#[no_mangle] -pub extern "C" fn crsql_create_crr_triggers( - db: *mut sqlite3, - table_info: *mut crsql_TableInfo, - err: *mut *mut c_char, -) -> c_int { - match create_triggers(db, table_info, err) { - Ok(code) => code as c_int, - Err(code) => code as c_int, - } -} +use crate::tableinfo::TableInfo; pub fn create_triggers( db: *mut sqlite3, - table_info: *mut crsql_TableInfo, + table_info: &TableInfo, err: *mut *mut c_char, ) -> Result { create_insert_trigger(db, table_info, err)?; @@ -38,15 +27,12 @@ pub fn create_triggers( fn create_insert_trigger( db: *mut sqlite3, - table_info: *mut crsql_TableInfo, + table_info: &TableInfo, _err: *mut *mut c_char, ) -> Result { - let table_name = unsafe { CStr::from_ptr((*table_info).tblName).to_str()? }; - let pk_columns = - unsafe { slice::from_raw_parts((*table_info).pks, (*table_info).pksLen as usize) }; - let pk_list = crate::util::as_identifier_list(pk_columns, None)?; - let pk_new_list = crate::util::as_identifier_list(pk_columns, Some("NEW."))?; - let pk_where_list = crate::util::pk_where_list(pk_columns, Some("NEW."))?; + let pk_list = crate::util::as_identifier_list(&table_info.pks, None)?; + let pk_new_list = crate::util::as_identifier_list(&table_info.pks, Some("NEW."))?; + let pk_where_list = crate::util::pk_where_list(&table_info.pks, Some("NEW."))?; let trigger_body = insert_trigger_body(table_info, table_name, pk_list, pk_new_list, pk_where_list)?; @@ -56,7 +42,7 @@ fn create_insert_trigger( BEGIN {trigger_body} END;", - table_name = crate::util::escape_ident(table_name), + table_name = crate::util::escape_ident(&table_info.tbl_name), trigger_body = trigger_body ); @@ -64,17 +50,15 @@ fn create_insert_trigger( } fn insert_trigger_body( - table_info: *mut crsql_TableInfo, + table_info: &TableInfo, table_name: &str, pk_list: String, pk_new_list: String, pk_where_list: String, ) -> Result { - let non_pk_columns = - unsafe { slice::from_raw_parts((*table_info).nonPks, (*table_info).nonPksLen as usize) }; let mut trigger_components = vec![]; - if non_pk_columns.len() == 0 { + if table_info.non_pks.len() == 0 { // a table that only has primary keys. // we'll need to record a create record in this case. trigger_components.push(format!( @@ -98,7 +82,7 @@ fn insert_trigger_body( __crsql_seq = crsql_get_seq() - 1, __crsql_site_id = NULL;", table_name = crate::util::escape_ident(table_name), - pk_list = pk_list, + pk_list = table_info.pks, pk_new_list = pk_new_list, col_name = crate::c::INSERT_SENTINEL )); @@ -119,13 +103,12 @@ fn insert_trigger_body( )); } - for col in non_pk_columns { - let col_name = unsafe { CStr::from_ptr(col.name).to_str()? }; + for col in table_info.non_pks { trigger_components.push(format_insert_trigger_component( table_name, &pk_list, &pk_new_list, - col_name, + &col.name, )) } @@ -167,12 +150,11 @@ fn format_insert_trigger_component( fn create_update_trigger( db: *mut sqlite3, - table_info: *mut crsql_TableInfo, + table_info: &TableInfo, _err: *mut *mut c_char, ) -> Result { - let table_name = unsafe { CStr::from_ptr((*table_info).tblName).to_str()? }; - let pk_columns = - unsafe { slice::from_raw_parts((*table_info).pks, (*table_info).pksLen as usize) }; + let table_name = &table_info.tbl_name; + let pk_columns = &table_info.pks; let pk_list = crate::util::as_identifier_list(pk_columns, None)?; let pk_new_list = crate::util::as_identifier_list(pk_columns, Some("NEW."))?; let pk_old_list = crate::util::as_identifier_list(pk_columns, Some("OLD."))?; @@ -243,14 +225,13 @@ fn create_update_trigger( } fn update_trigger_body( - table_info: *mut crsql_TableInfo, + table_info: &TableInfo, table_name: &str, pk_list: String, pk_new_list: String, any_pk_differs: String, ) -> Result { - let non_pk_columns = - unsafe { slice::from_raw_parts((*table_info).nonPks, (*table_info).nonPksLen as usize) }; + let non_pk_columns = &table_info.non_pks; let mut trigger_components = vec![]; // If any PK is different, record a create for the row @@ -285,7 +266,6 @@ fn update_trigger_body( )); for col in non_pk_columns { - let col_name = unsafe { CStr::from_ptr(col.name).to_str()? }; trigger_components.push(format!( "INSERT INTO \"{table_name}__crsql_clock\" ( {pk_list}, @@ -310,8 +290,8 @@ fn update_trigger_body( table_name = crate::util::escape_ident(table_name), pk_list = pk_list, pk_new_list = pk_new_list, - col_name_val = crate::util::escape_ident_as_value(col_name), - col_name_ident = crate::util::escape_ident(col_name) + col_name_val = crate::util::escape_ident_as_value(&col.name), + col_name_ident = crate::util::escape_ident(&col.name) )) } @@ -320,12 +300,11 @@ fn update_trigger_body( fn create_delete_trigger( db: *mut sqlite3, - table_info: *mut crsql_TableInfo, + table_info: &TableInfo, _err: *mut *mut c_char, ) -> Result { - let table_name = unsafe { CStr::from_ptr((*table_info).tblName).to_str()? }; - let pk_columns = - unsafe { slice::from_raw_parts((*table_info).pks, (*table_info).pksLen as usize) }; + let table_name = &table_info.tbl_name; + let pk_columns = &table_info.pks; let pk_list = crate::util::as_identifier_list(pk_columns, None)?; let pk_old_list = crate::util::as_identifier_list(pk_columns, Some("OLD."))?; let pk_where_list = crate::util::pk_where_list(pk_columns, Some("OLD."))?; diff --git a/core/rs/core/src/util.rs b/core/rs/core/src/util.rs index f9b0188d1..84c7c432f 100644 --- a/core/rs/core/src/util.rs +++ b/core/rs/core/src/util.rs @@ -1,7 +1,6 @@ extern crate alloc; -use crate::alloc::string::ToString; -use crate::c::crsql_ColumnInfo; +use crate::{alloc::string::ToString, tableinfo::ColumnInfo}; use alloc::format; use alloc::string::String; use alloc::vec; @@ -28,7 +27,7 @@ pub fn get_dflt_value( let notnull = stmt.column_int(1)?; let dflt_column_type = stmt.column_type(0)?; - // if the column is nullable and no default value is specified + // if the column is nullable and no default value is specified // then the default value is null. if notnull == 0 && dflt_column_type == ColumnType::Null { return Ok(Some(String::from("NULL"))); @@ -53,42 +52,42 @@ pub fn slab_rowid(idx: i32, rowid: sqlite::int64) -> sqlite::int64 { } pub fn pk_where_list( - columns: &[crsql_ColumnInfo], + columns: &Vec, rhs_prefix: Option<&str>, ) -> Result { let mut result = vec![]; for c in columns { - let name = unsafe { CStr::from_ptr(c.name) }; + let name = &c.name; result.push(if let Some(prefix) = rhs_prefix { format!( "\"{col_name}\" IS {prefix}\"{col_name}\"", prefix = prefix, - col_name = crate::util::escape_ident(name.to_str()?) + col_name = crate::util::escape_ident(name) ) } else { format!( "\"{col_name}\" = \"{col_name}\"", - col_name = crate::util::escape_ident(name.to_str()?) + col_name = crate::util::escape_ident(name) ) }) } Ok(result.join(" AND ")) } -pub fn where_list(columns: &[crsql_ColumnInfo], prefix: Option<&str>) -> Result { +pub fn where_list(columns: &Vec, prefix: Option<&str>) -> Result { let mut result = vec![]; for c in columns { - let name = unsafe { CStr::from_ptr(c.name) }; + let name = &c.name; if let Some(prefix) = prefix { result.push(format!( "{prefix}\"{col_name}\" IS ?", prefix = prefix, - col_name = crate::util::escape_ident(name.to_str()?) + col_name = crate::util::escape_ident(name) )); } else { result.push(format!( "\"{col_name}\" IS ?", - col_name = crate::util::escape_ident(name.to_str()?) + col_name = crate::util::escape_ident(name) )); } } @@ -105,33 +104,27 @@ pub fn binding_list(num_slots: usize) -> String { } pub fn as_identifier_list( - columns: &[crsql_ColumnInfo], + columns: &Vec, prefix: Option<&str>, ) -> Result { let mut result = vec![]; for c in columns { - let name = unsafe { CStr::from_ptr(c.name) }; result.push(if let Some(prefix) = prefix { - format!( - "{}\"{}\"", - prefix, - crate::util::escape_ident(name.to_str()?) - ) + format!("{}\"{}\"", prefix, crate::util::escape_ident(&c.name)) } else { - format!("\"{}\"", crate::util::escape_ident(name.to_str()?)) + format!("\"{}\"", crate::util::escape_ident(&c.name)) }) } Ok(result.join(",")) } -pub fn map_columns(columns: &[crsql_ColumnInfo], map: F) -> Result, Utf8Error> +pub fn map_columns(columns: &Vec, map: F) -> Result, Utf8Error> where F: Fn(&str) -> String, { let mut result = vec![]; for c in columns { - let name = unsafe { CStr::from_ptr(c.name) }; - result.push(map(name.to_str()?)) + result.push(map(&c.name)) } return Ok(result); diff --git a/core/src/changes-vtab-read.test.c b/core/src/changes-vtab-read.test.c deleted file mode 100644 index 174f0861e..000000000 --- a/core/src/changes-vtab-read.test.c +++ /dev/null @@ -1,143 +0,0 @@ -#include -#include -#include -#include - -#include "consts.h" -#include "crsqlite.h" -#include "rust.h" - -int crsql_close(sqlite3 *db); - -static void testChangesUnionQuery() { - printf("ChangesUnionQuery\n"); - - int rc = SQLITE_OK; - sqlite3 *db; - char *err = 0; - crsql_TableInfo **tblInfos = sqlite3_malloc(2 * sizeof(crsql_TableInfo *)); - rc = sqlite3_open(":memory:", &db); - - rc += sqlite3_exec(db, "create table foo (a primary key, b);", 0, 0, &err); - rc += sqlite3_exec(db, "create table bar (\"x\" primary key, [y]);", 0, 0, - &err); - rc += sqlite3_exec(db, "select crsql_as_crr('foo');", 0, 0, &err); - rc += sqlite3_exec(db, "select crsql_as_crr('bar');", 0, 0, &err); - rc += crsql_pull_table_info(db, "foo", &tblInfos[0], &err); - rc += crsql_pull_table_info(db, "bar", &tblInfos[1], &err); - assert(rc == SQLITE_OK); - - char *query = crsql_changes_union_query(tblInfos, 2, ""); - printf("X:%sX\n", query); - assert( - strcmp(query, - "SELECT tbl, pks, cid, col_vrsn, db_vrsn, site_id, _rowid_, seq, " - "cl FROM (SELECT\n" - " 'foo' as tbl,\n" - " crsql_pack_columns(t1.\"a\") as pks,\n" - " t1.__crsql_col_name as cid,\n" - " t1.__crsql_col_version as col_vrsn,\n" - " t1.__crsql_db_version as db_vrsn,\n" - " t3.site_id as site_id,\n" - " t1._rowid_,\n" - " t1.__crsql_seq as seq,\n" - " COALESCE(t2.__crsql_col_version, 1) as cl\n" - " FROM \"foo__crsql_clock\" AS t1 LEFT JOIN " - "\"foo__crsql_clock\" AS t2 ON\n" - " t1.\"a\" = t2.\"a\" AND t2.__crsql_col_name = '-1' LEFT " - "JOIN crsql_site_id as t3 ON t1.__crsql_site_id = t3.ordinal " - "UNION ALL SELECT\n" - " 'bar' as tbl,\n" - " crsql_pack_columns(t1.\"x\") as pks,\n" - " t1.__crsql_col_name as cid,\n" - " t1.__crsql_col_version as col_vrsn,\n" - " t1.__crsql_db_version as db_vrsn,\n" - " t3.site_id as site_id,\n" - " t1._rowid_,\n" - " t1.__crsql_seq as seq,\n" - " COALESCE(t2.__crsql_col_version, 1) as cl\n" - " FROM \"bar__crsql_clock\" AS t1 LEFT JOIN " - "\"bar__crsql_clock\" AS t2 ON\n" - " t1.\"x\" = t2.\"x\" AND t2.__crsql_col_name = '-1' LEFT " - "JOIN crsql_site_id as t3 ON t1.__crsql_site_id = t3.ordinal) ") == - 0); - sqlite3_free(query); - - query = crsql_changes_union_query(tblInfos, 2, - "WHERE site_id IS ? AND db_vrsn > ?"); - assert(strcmp(query, - "SELECT tbl, pks, cid, col_vrsn, db_vrsn, site_id, _rowid_, " - "seq, cl FROM (SELECT\n" - " 'foo' as tbl,\n" - " crsql_pack_columns(t1.\"a\") as pks,\n" - " t1.__crsql_col_name as cid,\n" - " t1.__crsql_col_version as col_vrsn,\n" - " t1.__crsql_db_version as db_vrsn,\n" - " t3.site_id as site_id,\n" - " t1._rowid_,\n" - " t1.__crsql_seq as seq,\n" - " COALESCE(t2.__crsql_col_version, 1) as cl\n" - " FROM \"foo__crsql_clock\" AS t1 LEFT JOIN " - "\"foo__crsql_clock\" AS t2 ON\n" - " t1.\"a\" = t2.\"a\" AND t2.__crsql_col_name = '-1' LEFT " - "JOIN crsql_site_id as t3 ON t1.__crsql_site_id = t3.ordinal " - "UNION ALL SELECT\n" - " 'bar' as tbl,\n" - " crsql_pack_columns(t1.\"x\") as pks,\n" - " t1.__crsql_col_name as cid,\n" - " t1.__crsql_col_version as col_vrsn,\n" - " t1.__crsql_db_version as db_vrsn,\n" - " t3.site_id as site_id,\n" - " t1._rowid_,\n" - " t1.__crsql_seq as seq,\n" - " COALESCE(t2.__crsql_col_version, 1) as cl\n" - " FROM \"bar__crsql_clock\" AS t1 LEFT JOIN " - "\"bar__crsql_clock\" AS t2 ON\n" - " t1.\"x\" = t2.\"x\" AND t2.__crsql_col_name = '-1' LEFT " - "JOIN crsql_site_id as t3 ON t1.__crsql_site_id = t3.ordinal) " - "WHERE site_id IS ? AND db_vrsn > ?") == 0); - sqlite3_free(query); - - printf("\t\e[0;32mSuccess\e[0m\n"); - sqlite3_free(err); - crsql_freeAllTableInfos(tblInfos, 2); - crsql_close(db); - assert(rc == SQLITE_OK); -} - -static void testRowPatchDataQuery() { - printf("RowPatchDataQuery\n"); - - int rc = SQLITE_OK; - sqlite3 *db; - char *err = 0; - crsql_TableInfo *tblInfo = 0; - rc = sqlite3_open(":memory:", &db); - - rc += sqlite3_exec(db, "create table foo (a primary key, b, c, d);", 0, 0, - &err); - rc += sqlite3_exec(db, "select crsql_as_crr('foo');", 0, 0, &err); - rc += sqlite3_exec(db, "insert into foo values(1, 'cb', 'cc', 'cd')", 0, 0, - &err); - rc += crsql_pull_table_info(db, "foo", &tblInfo, &err); - assert(rc == SQLITE_OK); - - // TC1: single pk table, 1 col change - const char *cid = "b"; - char *pks = "1"; - char *q = crsql_row_patch_data_query(tblInfo, cid); - assert(strcmp(q, "SELECT \"b\" FROM \"foo\" WHERE \"a\" IS ?") == 0); - sqlite3_free(q); - - printf("\t\e[0;32mSuccess\e[0m\n"); - sqlite3_free(err); - crsql_free_table_info(tblInfo); - crsql_close(db); - assert(rc == SQLITE_OK); -} - -void crsqlChangesVtabReadTestSuite() { - printf("\e[47m\e[1;30mSuite: crsql_changesVtabRead\e[0m\n"); - testChangesUnionQuery(); - testRowPatchDataQuery(); -} diff --git a/core/src/changes-vtab.h b/core/src/changes-vtab.h index 30b4b7c2e..40379cce1 100644 --- a/core/src/changes-vtab.h +++ b/core/src/changes-vtab.h @@ -45,7 +45,6 @@ SQLITE_EXTENSION_INIT3 #include "crsqlite.h" #include "ext-data.h" -#include "tableinfo.h" extern sqlite3_module crsql_changesModule; diff --git a/core/src/crsqlite.c b/core/src/crsqlite.c index 52c55f09e..871ef759e 100644 --- a/core/src/crsqlite.c +++ b/core/src/crsqlite.c @@ -11,7 +11,6 @@ SQLITE_EXTENSION_INIT1 #include "consts.h" #include "ext-data.h" #include "rust.h" -#include "tableinfo.h" #include "util.h" // see diff --git a/core/src/crsqlite.h b/core/src/crsqlite.h index 767688e77..e55e2ebd2 100644 --- a/core/src/crsqlite.h +++ b/core/src/crsqlite.h @@ -6,8 +6,6 @@ SQLITE_EXTENSION_INIT3 #include -#include "tableinfo.h" - #ifndef UNIT_TEST #define STATIC static #else diff --git a/core/src/crsqlite.test.c b/core/src/crsqlite.test.c index d923e2d9c..20730ea2b 100644 --- a/core/src/crsqlite.test.c +++ b/core/src/crsqlite.test.c @@ -7,7 +7,6 @@ #include "consts.h" #include "rust.h" -#include "tableinfo.h" #include "util.h" #ifndef CHECK_OK @@ -81,58 +80,58 @@ int syncLeftToRight(sqlite3 *db1, sqlite3 *db2, sqlite3_int64 since) { return SQLITE_OK; } -static void testCreateClockTable() { - printf("CreateClockTable\n"); +// static void testCreateClockTable() { +// printf("CreateClockTable\n"); + +// sqlite3 *db; +// int rc; +// crsql_TableInfo *tc1; +// crsql_TableInfo *tc2; +// crsql_TableInfo *tc3; +// crsql_TableInfo *tc4; +// char *err = 0; + +// rc = sqlite3_open(":memory:", &db); +// sqlite3_exec(db, "CREATE TABLE foo (a, b, primary key (a, b))", 0, 0, 0); +// sqlite3_exec(db, "CREATE TABLE bar (a primary key)", 0, 0, 0); +// sqlite3_exec(db, "CREATE TABLE baz (a primary key, b)", 0, 0, 0); +// sqlite3_exec(db, "CREATE TABLE boo (a primary key, b, c)", 0, 0, 0); + +// rc = crsql_pull_table_info(db, "foo", &tc1, &err); +// CHECK_OK +// rc = crsql_pull_table_info(db, "bar", &tc2, &err); +// CHECK_OK +// rc = crsql_pull_table_info(db, "baz", &tc3, &err); +// CHECK_OK +// rc = crsql_pull_table_info(db, "boo", &tc4, &err); +// CHECK_OK + +// rc = crsql_create_clock_table(db, tc1, &err); +// CHECK_OK +// rc = crsql_create_clock_table(db, tc2, &err); +// CHECK_OK +// rc = crsql_create_clock_table(db, tc3, &err); +// CHECK_OK +// rc = crsql_create_clock_table(db, tc4, &err); +// CHECK_OK + +// crsql_free_table_info(tc1); +// crsql_free_table_info(tc2); +// crsql_free_table_info(tc3); +// crsql_free_table_info(tc4); + +// // TODO: check that the tables have the expected schema - sqlite3 *db; - int rc; - crsql_TableInfo *tc1; - crsql_TableInfo *tc2; - crsql_TableInfo *tc3; - crsql_TableInfo *tc4; - char *err = 0; - - rc = sqlite3_open(":memory:", &db); - sqlite3_exec(db, "CREATE TABLE foo (a, b, primary key (a, b))", 0, 0, 0); - sqlite3_exec(db, "CREATE TABLE bar (a primary key)", 0, 0, 0); - sqlite3_exec(db, "CREATE TABLE baz (a primary key, b)", 0, 0, 0); - sqlite3_exec(db, "CREATE TABLE boo (a primary key, b, c)", 0, 0, 0); - - rc = crsql_pull_table_info(db, "foo", &tc1, &err); - CHECK_OK - rc = crsql_pull_table_info(db, "bar", &tc2, &err); - CHECK_OK - rc = crsql_pull_table_info(db, "baz", &tc3, &err); - CHECK_OK - rc = crsql_pull_table_info(db, "boo", &tc4, &err); - CHECK_OK - - rc = crsql_create_clock_table(db, tc1, &err); - CHECK_OK - rc = crsql_create_clock_table(db, tc2, &err); - CHECK_OK - rc = crsql_create_clock_table(db, tc3, &err); - CHECK_OK - rc = crsql_create_clock_table(db, tc4, &err); - CHECK_OK - - crsql_free_table_info(tc1); - crsql_free_table_info(tc2); - crsql_free_table_info(tc3); - crsql_free_table_info(tc4); - - // TODO: check that the tables have the expected schema - - printf("\t\e[0;32mSuccess\e[0m\n"); - crsql_close(db); - return; +// printf("\t\e[0;32mSuccess\e[0m\n"); +// crsql_close(db); +// return; -fail: - printf("err: %s %d\n", err, rc); - sqlite3_free(err); - crsql_close(db); - assert(rc == SQLITE_OK); -} +// fail: +// printf("err: %s %d\n", err, rc); +// sqlite3_free(err); +// crsql_close(db); +// assert(rc == SQLITE_OK); +// } static char *getQuotedSiteId(sqlite3 *db) { sqlite3_stmt *pStmt = 0; @@ -657,7 +656,7 @@ static void testPullingOnlyLocalChanges() { void crsqlTestSuite() { printf("\e[47m\e[1;30mSuite: crsql\e[0m\n"); - testCreateClockTable(); + // testCreateClockTable(); teste2e(); testSelectChangesAfterChangingColumnName(); // testInsertChangesWithUnkownColumnNames(); diff --git a/core/src/ext-data.c b/core/src/ext-data.c index 807071a6d..772824e6f 100644 --- a/core/src/ext-data.c +++ b/core/src/ext-data.c @@ -43,11 +43,13 @@ crsql_ExtData *crsql_newExtData(sqlite3 *db, unsigned char *siteIdBuffer) { pExtData->pragmaSchemaVersionForTableInfos = -1; pExtData->siteId = siteIdBuffer; pExtData->pDbVersionStmt = 0; - pExtData->zpTableInfos = 0; + pExtData->tableInfos = 0; + pExtData->tableInfosCap = 0; pExtData->tableInfosLen = 0; pExtData->rowsImpacted = 0; pExtData->pStmtCache = 0; crsql_init_stmt_cache(pExtData); + crsql_init_table_info_vec(pExtData); int pv = crsql_fetchPragmaDataVersion(db, pExtData); if (pv == -1 || rc != SQLITE_OK) { @@ -273,47 +275,3 @@ int crsql_getDbVersion(sqlite3 *db, crsql_ExtData *pExtData, char **errmsg) { rc = crsql_fetchDbVersionFromStorage(db, pExtData, errmsg); return rc; } - -/** - * Should only ever be called when absolutely required. - * This can be an expensive operation. - * - * (1) checks if the db schema has changed - * (2) if so, re-pulls table infos after de-allocating previous set of table - * infos - * - * due to 2, nobody should ever save a reference - * to a table info or contained object. - * - * This is called in two cases: - * (1) in `xFilter` of the changes-vtab to ensure we hit the right tables for - * changes (2) in `xUpdate` of the changes-vtab to ensure we apply received - * changed correctly - */ -int crsql_ensureTableInfosAreUpToDate(sqlite3 *db, crsql_ExtData *pExtData, - char **errmsg) { - int rc = SQLITE_OK; - - int bSchemaChanged = - crsql_fetchPragmaSchemaVersion(db, pExtData, TABLE_INFO_SCHEMA_VERSION); - if (bSchemaChanged < 0) { - return SQLITE_ERROR; - } - - if (bSchemaChanged || pExtData->zpTableInfos == 0) { - // clean up old table infos - crsql_freeAllTableInfos(pExtData->zpTableInfos, pExtData->tableInfosLen); - - // re-fetch table infos - rc = crsql_pullAllTableInfos(db, &(pExtData->zpTableInfos), - &(pExtData->tableInfosLen), errmsg); - if (rc != SQLITE_OK) { - crsql_freeAllTableInfos(pExtData->zpTableInfos, pExtData->tableInfosLen); - pExtData->zpTableInfos = 0; - pExtData->tableInfosLen = 0; - return rc; - } - } - - return rc; -} diff --git a/core/src/ext-data.h b/core/src/ext-data.h index 0adf0e5f4..1b4b058a5 100644 --- a/core/src/ext-data.h +++ b/core/src/ext-data.h @@ -3,7 +3,6 @@ #include "sqlite3ext.h" SQLITE_EXTENSION_INIT3 -#include "tableinfo.h" // NOTE: any changes here must be updated in `c.rs` until we've finished porting // to rust. @@ -29,8 +28,9 @@ struct crsql_ExtData { unsigned char *siteId; sqlite3_stmt *pDbVersionStmt; - crsql_TableInfo **zpTableInfos; + void *tableInfos; int tableInfosLen; + int tableInfosCap; // tracks the number of rows impacted by all inserts into crsql_changes in the // current transaction. This number is reset on transaction commit. diff --git a/core/src/rust.h b/core/src/rust.h index a9398819e..23e9ecc1f 100644 --- a/core/src/rust.h +++ b/core/src/rust.h @@ -14,23 +14,13 @@ int crsql_backfill_table(sqlite3 *db, const char *tblName, int isCommitAlter, int noTx); int crsql_is_crr(sqlite3 *db, const char *tblName); int crsql_compare_sqlite_values(const sqlite3_value *l, const sqlite3_value *r); -int crsql_create_crr_triggers(sqlite3 *db, crsql_TableInfo *tableInfo, - char **err); int crsql_remove_crr_triggers_if_exist(sqlite3 *db, const char *tblName); -char *crsql_changes_union_query(crsql_TableInfo **tableInfos, int tableInfosLen, - const char *idxStr); -char *crsql_row_patch_data_query(crsql_TableInfo *tblInfo, const char *colName); -int crsql_create_clock_table(sqlite3 *db, crsql_TableInfo *tableInfo, - char **err); int crsql_init_site_id(sqlite3 *db, unsigned char *ret); int crsql_init_peer_tracking_table(sqlite3 *db); int crsql_create_schema_table_if_not_exists(sqlite3 *db); int crsql_maybe_update_db(sqlite3 *db, char **pzErrMsg); int crsql_is_table_compatible(sqlite3 *db, const char *tblName, char **err); -int crsql_pull_table_info(sqlite3 *db, const char *tblName, - crsql_TableInfo **tableInfo, char **err); -void crsql_free_table_info(crsql_TableInfo *tableInfo); int crsql_create_crr(sqlite3 *db, const char *schemaName, const char *tblName, int isCommitAlter, int noTx, char **err); diff --git a/core/src/tableinfo.c b/core/src/tableinfo.c deleted file mode 100644 index b7f2ca3c4..000000000 --- a/core/src/tableinfo.c +++ /dev/null @@ -1,104 +0,0 @@ -#include "tableinfo.h" - -#include -#include -#include -#include - -#include "consts.h" -#include "crsqlite.h" -#include "get-table.h" -#include "rust.h" -#include "util.h" - -void crsql_freeAllTableInfos(crsql_TableInfo **tableInfos, int len) { - for (int i = 0; i < len; ++i) { - crsql_free_table_info(tableInfos[i]); - } - sqlite3_free(tableInfos); -} - -crsql_TableInfo *crsql_findTableInfo(crsql_TableInfo **tblInfos, int len, - const char *tblName) { - for (int i = 0; i < len; ++i) { - if (strcmp(tblInfos[i]->tblName, tblName) == 0) { - return tblInfos[i]; - } - } - - return 0; -} - -int crsql_indexofTableInfo(crsql_TableInfo **tblInfos, int len, - const char *tblName) { - for (int i = 0; i < len; ++i) { - if (strcmp(tblInfos[i]->tblName, tblName) == 0) { - return i; - } - } - - return -1; -} - -sqlite3_int64 crsql_slabRowid(int idx, sqlite3_int64 rowid) { - if (idx < 0) { - return -1; - } - - sqlite3_int64 modulo = rowid % ROWID_SLAB_SIZE; - return idx * ROWID_SLAB_SIZE + modulo; -} - -/** - * Pulls all table infos for all crrs present in the database. - * Run once at vtab initialization -- see docs on crsql_Changes_vtab - * for the constraints this creates. - */ -int crsql_pullAllTableInfos(sqlite3 *db, crsql_TableInfo ***pzpTableInfos, - int *rTableInfosLen, char **errmsg) { - char **zzClockTableNames = 0; - int rNumCols = 0; - int rNumRows = 0; - int rc = SQLITE_OK; - - // Find all clock tables - rc = crsql_get_table(db, CLOCK_TABLES_SELECT, &zzClockTableNames, &rNumRows, - &rNumCols, 0); - - if (rc != SQLITE_OK) { - *errmsg = sqlite3_mprintf("crsql internal error discovering crr tables."); - crsql_free_table(zzClockTableNames); - return SQLITE_ERROR; - } - - if (rNumRows == 0) { - crsql_free_table(zzClockTableNames); - return SQLITE_OK; - } - - crsql_TableInfo **tableInfos = - sqlite3_malloc(rNumRows * sizeof(crsql_TableInfo *)); - memset(tableInfos, 0, rNumRows * sizeof(crsql_TableInfo *)); - for (int i = 0; i < rNumRows; ++i) { - // +1 since tableNames includes a row for column headers - // Strip __crsql_clock suffix. - char *baseTableName = - crsql_strndup(zzClockTableNames[i + 1], - strlen(zzClockTableNames[i + 1]) - __CRSQL_CLOCK_LEN); - rc = crsql_pull_table_info(db, baseTableName, &tableInfos[i], errmsg); - sqlite3_free(baseTableName); - - if (rc != SQLITE_OK) { - crsql_free_table(zzClockTableNames); - crsql_freeAllTableInfos(tableInfos, rNumRows); - return rc; - } - } - - crsql_free_table(zzClockTableNames); - - *pzpTableInfos = tableInfos; - *rTableInfosLen = rNumRows; - - return SQLITE_OK; -} \ No newline at end of file diff --git a/core/src/tableinfo.h b/core/src/tableinfo.h deleted file mode 100644 index 1fae8de96..000000000 --- a/core/src/tableinfo.h +++ /dev/null @@ -1,49 +0,0 @@ -#ifndef CRSQLITE_TABLEINFO_H -#define CRSQLITE_TABLEINFO_H - -#include "sqlite3ext.h" -SQLITE_EXTENSION_INIT3 - -#include -#include - -// 10 trillion = 10,000,000,000,000 -#define ROWID_SLAB_SIZE 10000000000000 - -typedef struct crsql_ColumnInfo crsql_ColumnInfo; -struct crsql_ColumnInfo { - int cid; - char *name; - char *type; - int notnull; - int pk; -}; - -typedef struct crsql_TableInfo crsql_TableInfo; -struct crsql_TableInfo { - // Name of the table. Owned by this struct. - char *tblName; - - crsql_ColumnInfo *baseCols; - int baseColsLen; - - crsql_ColumnInfo *pks; - int pksLen; - - crsql_ColumnInfo *nonPks; - int nonPksLen; -}; - -crsql_ColumnInfo *crsql_extractBaseCols(crsql_ColumnInfo *colInfos, - int colInfosLen, int *pBaseColsLen); - -void crsql_freeAllTableInfos(crsql_TableInfo **tableInfos, int len); -crsql_TableInfo *crsql_findTableInfo(crsql_TableInfo **tblInfos, int len, - const char *tblName); -int crsql_indexofTableInfo(crsql_TableInfo **tblInfos, int len, - const char *tblName); -sqlite3_int64 crsql_slabRowid(int idx, sqlite3_int64 rowid); -int crsql_pullAllTableInfos(sqlite3 *db, crsql_TableInfo ***pzpTableInfos, - int *rTableInfosLen, char **errmsg); - -#endif \ No newline at end of file diff --git a/core/src/tableinfo.test.c b/core/src/tableinfo.test.c deleted file mode 100644 index e713d5eea..000000000 --- a/core/src/tableinfo.test.c +++ /dev/null @@ -1,301 +0,0 @@ -#include "tableinfo.h" - -#include -#include -#include -#include - -#include "consts.h" -#include "crsqlite.h" -#include "rust.h" -#include "util.h" - -int crsql_close(sqlite3 *db); - -static void testGetTableInfo() { - printf("GetTableInfo\n"); - sqlite3 *db = 0; - crsql_TableInfo *tableInfo = 0; - char *errMsg = 0; - int rc = SQLITE_OK; - - rc = sqlite3_open(":memory:", &db); - - sqlite3_exec(db, "CREATE TABLE foo (a INT NOT NULL, b)", 0, 0, 0); - rc = crsql_pull_table_info(db, "foo", &tableInfo, &errMsg); - - if (rc != SQLITE_OK) { - printf("err: %s %d\n", errMsg, rc); - sqlite3_free(errMsg); - crsql_close(db); - assert(0); - return; - } - - assert(tableInfo->baseColsLen == 2); - assert(tableInfo->baseCols[0].cid == 0); - assert(strcmp(tableInfo->baseCols[0].name, "a") == 0); - assert(strcmp(tableInfo->baseCols[0].type, "INT") == 0); - assert(tableInfo->baseCols[0].notnull == 1); - assert(tableInfo->baseCols[0].pk == 0); - - assert(tableInfo->pksLen == 0); - assert(tableInfo->pks == 0); - - assert(tableInfo->nonPksLen == 2); - assert(tableInfo->nonPks[0].cid == 0); - assert(strcmp(tableInfo->nonPks[0].name, "a") == 0); - assert(strcmp(tableInfo->nonPks[0].type, "INT") == 0); - assert(tableInfo->nonPks[0].notnull == 1); - assert(tableInfo->nonPks[0].pk == 0); - - crsql_free_table_info(tableInfo); - - sqlite3_exec(db, "CREATE TABLE bar (a PRIMARY KEY, b)", 0, 0, 0); - rc = crsql_pull_table_info(db, "bar", &tableInfo, &errMsg); - if (rc != SQLITE_OK) { - printf("err: %s %d\n", errMsg, rc); - sqlite3_free(errMsg); - crsql_close(db); - assert(0); - return; - } - - assert(tableInfo->baseColsLen == 2); - assert(tableInfo->baseCols[0].cid == 0); - assert(strcmp(tableInfo->baseCols[0].name, "a") == 0); - assert(strcmp(tableInfo->baseCols[0].type, "") == 0); - assert(tableInfo->baseCols[0].notnull == 0); - assert(tableInfo->baseCols[0].pk == 1); - - assert(tableInfo->pksLen == 1); - assert(tableInfo->nonPksLen == 1); - - crsql_free_table_info(tableInfo); - - printf("\t\e[0;32mSuccess\e[0m\n"); - crsql_close(db); -} - -// static void testAsIdentifierList() { -// printf("AsIdentifierList\n"); - -// crsql_ColumnInfo tc1[3]; -// tc1[0].name = "one"; -// tc1[1].name = "two"; -// tc1[2].name = "three"; - -// crsql_ColumnInfo tc2[0]; - -// crsql_ColumnInfo tc3[1]; -// tc3[0].name = "one"; -// char *result; - -// result = crsql_asIdentifierList(tc1, 3, 0); -// assert(strcmp(result, "\"one\",\"two\",\"three\"") == 0); -// sqlite3_free(result); - -// result = crsql_asIdentifierList(tc2, 0, 0); -// assert(result == 0); -// sqlite3_free(result); - -// result = crsql_asIdentifierList(tc3, 1, 0); -// assert(strcmp(result, "\"one\"") == 0); -// sqlite3_free(result); - -// printf("\t\e[0;32mSuccess\e[0m\n"); -// } - -static void testFindTableInfo() { - printf("FindTableInfo\n"); - - crsql_TableInfo **tblInfos = sqlite3_malloc(3 * sizeof(crsql_TableInfo *)); - for (int i = 0; i < 3; ++i) { - tblInfos[i] = sqlite3_malloc(sizeof(crsql_TableInfo)); - tblInfos[i]->tblName = sqlite3_mprintf("%d", i); - } - - assert(crsql_findTableInfo(tblInfos, 3, "0") == tblInfos[0]); - assert(crsql_findTableInfo(tblInfos, 3, "1") == tblInfos[1]); - assert(crsql_findTableInfo(tblInfos, 3, "2") == tblInfos[2]); - assert(crsql_findTableInfo(tblInfos, 3, "3") == 0); - - for (int i = 0; i < 3; ++i) { - sqlite3_free(tblInfos[i]->tblName); - sqlite3_free(tblInfos[i]); - } - sqlite3_free(tblInfos); - - printf("\t\e[0;32mSuccess\e[0m\n"); -} - -static void testIsTableCompatible() { - printf("IsTableCompatible\n"); - sqlite3 *db = 0; - char *errmsg = 0; - int rc = SQLITE_OK; - - rc = sqlite3_open(":memory:", &db); - // no pks - rc += sqlite3_exec(db, "CREATE TABLE foo (a)", 0, 0, 0); - assert(rc == SQLITE_OK); - rc = crsql_is_table_compatible(db, "foo", &errmsg); - assert(rc == 0); - sqlite3_free(errmsg); - errmsg = 0; - - // pks - rc = sqlite3_exec(db, "CREATE TABLE bar (a primary key)", 0, 0, 0); - assert(rc == SQLITE_OK); - rc = crsql_is_table_compatible(db, "bar", &errmsg); - assert(rc == 1); - - // pks + other non unique indices - rc = sqlite3_exec(db, "CREATE TABLE baz (a primary key, b)", 0, 0, 0); - rc += sqlite3_exec(db, "CREATE INDEX bar_i ON baz (b)", 0, 0, 0); - assert(rc == SQLITE_OK); - rc = crsql_is_table_compatible(db, "bar", &errmsg); - assert(rc == 1); - - // pks + other unique indices - rc = sqlite3_exec(db, "CREATE TABLE fuzz (a primary key, b)", 0, 0, 0); - rc += sqlite3_exec(db, "CREATE UNIQUE INDEX fuzz_i ON fuzz (b)", 0, 0, 0); - assert(rc == SQLITE_OK); - rc = crsql_is_table_compatible(db, "fuzz", &errmsg); - assert(rc == 0); - sqlite3_free(errmsg); - errmsg = 0; - - // not null and no dflt - rc = sqlite3_exec(db, "CREATE TABLE buzz (a primary key, b NOT NULL)", 0, 0, - 0); - assert(rc == SQLITE_OK); - rc = crsql_is_table_compatible(db, "buzz", &errmsg); - assert(rc == 0); - sqlite3_free(errmsg); - errmsg = 0; - - // not null and dflt - rc = sqlite3_exec( - db, "CREATE TABLE boom (a primary key, b NOT NULL DEFAULT 1)", 0, 0, 0); - assert(rc == SQLITE_OK); - rc = crsql_is_table_compatible(db, "boom", &errmsg); - assert(rc == 1); - - // fk constraint - rc = sqlite3_exec( - db, - "CREATE TABLE zoom (a primary key, b, FOREIGN KEY(b) REFERENCES foo(a))", - 0, 0, 0); - assert(rc == SQLITE_OK); - rc = crsql_is_table_compatible(db, "zoom", &errmsg); - assert(rc == 0); - sqlite3_free(errmsg); - errmsg = 0; - - // strict mode should be ok - rc = sqlite3_exec(db, "CREATE TABLE atable (\"id\" TEXT PRIMARY KEY) STRICT", - 0, 0, 0); - assert(rc == SQLITE_OK); - rc = crsql_is_table_compatible(db, "atable", &errmsg); - assert(rc == 1); - - // no autoincrement - rc = sqlite3_exec( - db, "CREATE TABLE woom (a integer primary key autoincrement)", 0, 0, 0); - assert(rc == SQLITE_OK); - rc = crsql_is_table_compatible(db, "woom", &errmsg); - assert(rc == 0); - sqlite3_free(errmsg); - errmsg = 0; - - // aliased rowid - rc = sqlite3_exec(db, "CREATE TABLE loom (a integer primary key)", 0, 0, 0); - assert(rc == SQLITE_OK); - rc = crsql_is_table_compatible(db, "loom", &errmsg); - assert(rc == 1); - - rc = sqlite3_exec( - db, "CREATE TABLE atable2 (\"id\" TEXT PRIMARY KEY, x TEXT) STRICT;", 0, - 0, 0); - assert(rc == SQLITE_OK); - rc = crsql_is_table_compatible(db, "atable2", &errmsg); - assert(rc == 1); - - rc = sqlite3_exec(db, - "CREATE TABLE ydoc (\ - doc_id TEXT,\ - yhash BLOB,\ - yval BLOB,\ - primary key (doc_id, yhash)\ - ) STRICT;", - 0, 0, 0); - assert(rc == SQLITE_OK); - rc = crsql_is_table_compatible(db, "atable2", &errmsg); - assert(rc == 1); - - printf("\t\e[0;32mSuccess\e[0m\n"); - crsql_close(db); -} - -static void testSlabRowid() { - printf("SlabRowid\n"); - sqlite3 *db = 0; - char *errmsg = 0; - int rc = SQLITE_OK; - - rc = sqlite3_open(":memory:", &db); - rc += sqlite3_exec(db, "CREATE TABLE foo (a PRIMARY KEY)", 0, 0, 0); - rc += sqlite3_exec(db, "CREATE TABLE bar (a PRIMARY KEY)", 0, 0, 0); - rc += sqlite3_exec(db, "CREATE TABLE baz (a PRIMARY KEY)", 0, 0, 0); - assert(rc == SQLITE_OK); - - rc += sqlite3_exec(db, "SELECT crsql_as_crr('foo');", 0, 0, 0); - rc += sqlite3_exec(db, "SELECT crsql_as_crr('bar');", 0, 0, 0); - rc += sqlite3_exec(db, "SELECT crsql_as_crr('baz');", 0, 0, 0); - assert(rc == SQLITE_OK); - - // now pull all table infos - crsql_TableInfo **tblInfos = 0; - int tblInfosLen = 0; - rc = crsql_pullAllTableInfos(db, &tblInfos, &tblInfosLen, &errmsg); - assert(rc == SQLITE_OK); - - // now get the slab rowid for each table - sqlite3_int64 fooSlabRowid = crsql_slabRowid(0, 1); - sqlite3_int64 barSlabRowid = crsql_slabRowid(1, 2); - sqlite3_int64 bazSlabRowid = crsql_slabRowid(2, 3); - - // now assert each one - assert(fooSlabRowid == 1); - assert(barSlabRowid == 2 + ROWID_SLAB_SIZE); - assert(bazSlabRowid == 3 + ROWID_SLAB_SIZE * 2); - - // now test the modulo - assert(crsql_slabRowid(0, ROWID_SLAB_SIZE) == 0); - assert(crsql_slabRowid(0, ROWID_SLAB_SIZE + 1) == 1); - - fooSlabRowid = crsql_slabRowid(0, ROWID_SLAB_SIZE + 1); - barSlabRowid = crsql_slabRowid(1, ROWID_SLAB_SIZE + 2); - bazSlabRowid = crsql_slabRowid(2, ROWID_SLAB_SIZE * 2 + 3); - - assert(fooSlabRowid == 1); - assert(barSlabRowid == 2 + ROWID_SLAB_SIZE); - assert(bazSlabRowid == 3 + ROWID_SLAB_SIZE * 2); - - crsql_freeAllTableInfos(tblInfos, tblInfosLen); - - crsql_close(db); - printf("\t\e[0;32mSuccess\e[0m\n"); -} - -void crsqlTableInfoTestSuite() { - printf("\e[47m\e[1;30mSuite: crsql_tableInfo\e[0m\n"); - - // testAsIdentifierList(); - testGetTableInfo(); - testFindTableInfo(); - testIsTableCompatible(); - testSlabRowid(); - // testPullAllTableInfos(); -} \ No newline at end of file diff --git a/core/src/triggers.test.c b/core/src/triggers.test.c deleted file mode 100644 index 164bac15d..000000000 --- a/core/src/triggers.test.c +++ /dev/null @@ -1,54 +0,0 @@ -#include -#include -#include -#include - -#include "consts.h" -#include "crsqlite.h" -#include "rust.h" -#include "tableinfo.h" -#include "util.h" - -int crsql_close(sqlite3 *db); - -// This would be more testable if we could test -// query construction rather than actual table creation. -// testing actual table creation requires views and base crr to -// be in place. -static void testCreateTriggers() { - printf("CreateTriggers\n"); - - sqlite3 *db = 0; - crsql_TableInfo *tableInfo; - char *errMsg = 0; - int rc = sqlite3_open(":memory:", &db); - - rc = - sqlite3_exec(db, "CREATE TABLE \"foo\" (\"a\" PRIMARY KEY, \"b\", \"c\")", - 0, 0, &errMsg); - rc = crsql_pull_table_info(db, "foo", &tableInfo, &errMsg); - - if (rc == SQLITE_OK) { - rc = crsql_create_crr_triggers(db, tableInfo, &errMsg); - } - - crsql_free_table_info(tableInfo); - if (rc != SQLITE_OK) { - crsql_close(db); - printf("err: %s | rc: %d\n", errMsg, rc); - sqlite3_free(errMsg); - assert(0); - } - - sqlite3_free(errMsg); - crsql_close(db); - - printf("\t\e[0;32mSuccess\e[0m\n"); -} - -void crsqlTriggersTestSuite() { - printf("\e[47m\e[1;30mSuite: crsqlTriggers\e[0m\n"); - - testCreateTriggers(); - // testTriggerSyncBitInteraction(); <-- implemented in rust tests -} \ No newline at end of file diff --git a/core/src/util.test.c b/core/src/util.test.c index b7c8eb125..af4a57b04 100644 --- a/core/src/util.test.c +++ b/core/src/util.test.c @@ -7,7 +7,6 @@ #include "consts.h" #include "crsqlite.h" -#include "tableinfo.h" #ifndef CHECK_OK #define CHECK_OK \