diff --git a/libsql-ffi/bundled/SQLite3MultipleCiphers/src/sqlite3.c b/libsql-ffi/bundled/SQLite3MultipleCiphers/src/sqlite3.c index cf812dc9ae..32f26762eb 100644 --- a/libsql-ffi/bundled/SQLite3MultipleCiphers/src/sqlite3.c +++ b/libsql-ffi/bundled/SQLite3MultipleCiphers/src/sqlite3.c @@ -10938,6 +10938,59 @@ SQLITE_API int sqlite3_preupdate_blobwrite(sqlite3 *); */ SQLITE_API void *libsql_close_hook(sqlite3 *db, void (*xClose)(void *pCtx, sqlite3 *db), void *arg); +/* +** CAPI3REF: Disable WAL checkpointing +** METHOD: sqlite3 +** +** ^The [libsql_wal_disable_checkpoint(D)] interface disables automatic +** checkpointing of the WAL file for [database connection] D. +** +** Note: This function disables WAL checkpointing entirely, including when +** the last database connection is closed. This is different from +** sqlite3_wal_autocheckpoint() which only disables automatic checkpoints +** for the current connection, but still allows checkpointing when the +** connection is closed. +*/ +SQLITE_API int libsql_wal_disable_checkpoint(sqlite3 *db); + +/* +** CAPI3REF: Get the number of frames in the WAL file +** METHOD: sqlite3 +** +** ^The [libsql_wal_frame_count(D,P)] interface returns the number of frames +** in the WAL file for [database connection] D into *P. +*/ +SQLITE_API int libsql_wal_frame_count(sqlite3*, unsigned int*); + +/* +** CAPI3REF: Get a frame from the WAL file +** METHOD: sqlite3 +** +** ^The [libsql_wal_get_frame(D,I,P,S)] interface extracts frame I from +** the WAL file for [database connection] D into memory obtained from +** [sqlite3_malloc64()] and returns a pointer to that memory. The size of +** the memory allocated is given by S. +*/ +SQLITE_API int libsql_wal_get_frame(sqlite3*, unsigned int, void*, unsigned int); + +/* +** CAPI3REF: Begin frame insertion into the WAL +** METHOD: sqlite3 +*/ +SQLITE_API int libsql_wal_insert_begin(sqlite3*); + +/* +** CAPI3REF: End frame insertion into the WAL +** METHOD: sqlite3 +*/ +SQLITE_API int libsql_wal_insert_end(sqlite3*); + +/* +** CAPI3REF: Insert a frame into the WAL +** METHOD: sqlite3 +*/ +SQLITE_API int libsql_wal_insert_frame(sqlite3*, unsigned int, void *, unsigned int); + /* ** CAPI3REF: Low-level system error code ** METHOD: sqlite3 @@ -13963,6 +14016,7 @@ typedef struct libsql_wal_methods { /* Read a page from the write-ahead log, if it is present. */ int (*xFindFrame)(wal_impl* pWal, unsigned int, unsigned int *); int (*xReadFrame)(wal_impl* pWal, unsigned int, int, unsigned char *); + int (*xReadFrameRaw)(wal_impl* pWal, unsigned int, int, unsigned char *); /* If the WAL is not empty, return the size of the database. */ unsigned int (*xDbsize)(wal_impl* pWal); @@ -13982,6 +14036,9 @@ typedef struct libsql_wal_methods { ** response to a ROLLBACK TO command. */ int (*xSavepointUndo)(wal_impl* pWal, unsigned int *aWalData); + /* Return the number of frames in the WAL */ + int (*xFrameCount)(wal_impl* pWal, int, unsigned int *); + /* Write a frame or frames to the log. */ int (*xFrames)(wal_impl* pWal, int, libsql_pghdr *, unsigned int, int, int, int*); @@ -16378,6 +16435,12 @@ SQLITE_PRIVATE int sqlite3PagerReadFileheader(Pager*, int, unsigned char*); SQLITE_PRIVATE void sqlite3PagerSetBusyHandler(Pager*, int(*)(void *), void *); SQLITE_PRIVATE int sqlite3PagerSetPagesize(Pager*, u32*, int); SQLITE_PRIVATE Pgno sqlite3PagerMaxPageCount(Pager*, Pgno); +SQLITE_PRIVATE int sqlite3PagerWalFrameCount(Pager *, unsigned int *); +SQLITE_PRIVATE int sqlite3PagerWalReadFrame(Pager *, unsigned int, void *, unsigned int); +SQLITE_PRIVATE int sqlite3PagerWalBeginCommit(Pager*); +SQLITE_PRIVATE int sqlite3PagerWalEndCommit(Pager*); +SQLITE_PRIVATE int sqlite3PagerWalInsert(Pager*, unsigned int, void *, unsigned int); + SQLITE_PRIVATE void sqlite3PagerSetCachesize(Pager*, int); SQLITE_PRIVATE int sqlite3PagerSetSpillsize(Pager*, int); SQLITE_PRIVATE void sqlite3PagerSetMmapLimit(Pager *, sqlite3_int64); @@ -18223,6 +18286,7 @@ struct sqlite3 { PreUpdate *pPreUpdate; /* Context for active pre-update callback */ #endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ #ifndef SQLITE_OMIT_WAL + int walCheckPointDisabled; int (*xWalCallback)(void *, sqlite3 *, const char *, int); void *pWalArg; #endif @@ -57270,6 +57334,7 @@ typedef struct libsql_wal_methods { /* Read a page from the write-ahead log, if it is present. */ int (*xFindFrame)(wal_impl* pWal, unsigned int, unsigned int *); int (*xReadFrame)(wal_impl* pWal, unsigned int, int, unsigned char *); + int (*xReadFrameRaw)(wal_impl* pWal, unsigned int, int, unsigned char *); /* If the WAL is not empty, return the size of the database. */ unsigned int (*xDbsize)(wal_impl* pWal); @@ -57289,6 +57354,9 @@ typedef struct libsql_wal_methods { ** response to a ROLLBACK TO command. */ int (*xSavepointUndo)(wal_impl* pWal, unsigned int *aWalData); + /* Return the number of frames in the WAL */ + int (*xFrameCount)(wal_impl* pWal, int, unsigned int *); + /* Write a frame or frames to the log. */ int (*xFrames)(wal_impl* pWal, int, libsql_pghdr *, unsigned int, int, int, int*); @@ -65214,6 +65282,94 @@ SQLITE_PRIVATE int sqlite3PagerCloseWal(Pager *pPager, sqlite3 *db){ return rc; } +/** +** Return the number of frames in the WAL file. +** +** If the pager is not in WAL mode or we failed to obtain an exclusive write lock, returns -1. +**/ +SQLITE_PRIVATE int sqlite3PagerWalFrameCount(Pager *pPager, unsigned int *pnFrames){ + if( pagerUseWal(pPager) ){ + return pPager->wal->methods.xFrameCount(pPager->wal->pData, 0, pnFrames); + }else{ + return SQLITE_ERROR; + } +} + +SQLITE_PRIVATE int sqlite3PagerWalReadFrameRaw( + Pager *pPager, + unsigned int iFrame, + void *pFrameOut, + unsigned int nFrameOutLen +){ + if( pagerUseWal(pPager) ){ + unsigned int nFrameLen = 24+pPager->pageSize; + if( nFrameOutLen!=nFrameLen ) return SQLITE_MISUSE; + return pPager->wal->methods.xReadFrameRaw(pPager->wal->pData, iFrame, nFrameOutLen, pFrameOut); + }else{ + return SQLITE_ERROR; + } +} + +SQLITE_PRIVATE int sqlite3PagerWalBeginCommit(Pager *pPager) { + int rc; + if (!pagerUseWal(pPager)) { + return SQLITE_ERROR; + } + rc = pagerBeginReadTransaction(pPager); + if (rc != SQLITE_OK) { + return rc; + } + return pPager->wal->methods.xBeginWriteTransaction(pPager->wal->pData); +} + +SQLITE_PRIVATE int sqlite3PagerWalEndCommit(Pager *pPager) { + if (!pagerUseWal(pPager)) { + return SQLITE_ERROR; + } + return pPager->wal->methods.xEndWriteTransaction(pPager->wal->pData); +} + +SQLITE_PRIVATE int sqlite3PagerWalInsert(Pager *pPager, unsigned int iFrame, void *pBuf, unsigned int nBuf) { + int rc = SQLITE_OK; + + if (!pagerUseWal(pPager)) { + return SQLITE_ERROR; + } + unsigned int mxFrame; + rc = pPager->wal->methods.xFrameCount(pPager->wal->pData, 1, &mxFrame); + if (rc != SQLITE_OK) { + return rc; + } + if (iFrame <= mxFrame) { + return SQLITE_OK; + } + u8 *aFrame = (u8*)pBuf; + u32 pgno = sqlite3Get4byte(&aFrame[0]); + u32 nTruncate = sqlite3Get4byte(&aFrame[4]); + u8 *pData = aFrame + 24; + + PgHdr pghdr; + memset(&pghdr, 0, sizeof(PgHdr)); + pghdr.pPage = NULL; + pghdr.pData = pData; + pghdr.pExtra = NULL; + pghdr.pgno = pgno; + pghdr.flags = 0; + + int isCommit = (nTruncate != 0); + + int nFrames = 0; + rc = pPager->wal->methods.xFrames(pPager->wal->pData, + pPager->pageSize, + &pghdr, + nTruncate, + isCommit, + pPager->walSyncFlags, + &nFrames); + assert( nFrames == 1 ); + return rc; +} + #ifdef SQLITE_ENABLE_SETLK_TIMEOUT /* ** If pager pPager is a wal-mode database not in exclusive locking mode, @@ -67601,9 +67757,14 @@ static int sqlite3WalClose( if( pWal->exclusiveMode==WAL_NORMAL_MODE ){ pWal->exclusiveMode = WAL_EXCLUSIVE_MODE; } - rc = sqlite3WalCheckpoint(pWal, db, - SQLITE_CHECKPOINT_PASSIVE, 0, 0, sync_flags, nBuf, zBuf, 0, 0, NULL, NULL - ); + /* Don't checkpoint on close if automatic WAL checkpointing is disabled. */ + if( !db->walCheckPointDisabled ){ + rc = sqlite3WalCheckpoint(pWal, db, + SQLITE_CHECKPOINT_PASSIVE, 0, 0, sync_flags, nBuf, zBuf, 0, 0, NULL, NULL + ); + } else { + rc = SQLITE_ERROR; + } if( rc==SQLITE_OK ){ int bPersist = -1; sqlite3OsFileControlHint( @@ -68731,6 +68892,29 @@ static int sqlite3WalReadFrame( return sqlite3OsRead(pWal->pWalFd, pOut, (nOut>sz ? sz : nOut), iOffset); } +/* +** Read the contents of frame iRead from the wal file into buffer pOut +** (which is nOut bytes in size). Return SQLITE_OK if successful, or an +** error code otherwise. +*/ +static int sqlite3WalReadFrameRaw( + Wal *pWal, /* WAL handle */ + u32 iRead, /* Frame to read */ + int nOut, /* Size of buffer pOut in bytes */ + u8 *pOut /* Buffer to write page data to */ +){ + int sz; + i64 iOffset; + sz = pWal->hdr.szPage; + sz = (sz&0xfe00) + ((sz&0x0001)<<16); + testcase( sz<=32768 ); + testcase( sz>=65536 ); + iOffset = walFrameOffset(iRead, sz); + /* testcase( IS_BIG_INT(iOffset) ); // requires a 4GiB WAL */ + sz += WAL_FRAME_HDRSIZE; + return sqlite3OsRead(pWal->pWalFd, pOut, (nOut>sz ? sz : nOut), iOffset); +} + /* ** Return the size of the database in pages (or zero, if unknown). */ @@ -69341,6 +69525,19 @@ static int walFrames( return rc; } +SQLITE_PRIVATE int sqlite3WalFrameCount(Wal *pWal, int locked, unsigned int *pnFrames){ + int rc = SQLITE_OK; + if( locked==0 ) { + rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1); + if (rc != SQLITE_OK) return rc; + } + *pnFrames = pWal->hdr.mxFrame; + if( locked==0 ) { + walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1); + } + return SQLITE_OK; +} + /* ** Write a set of frames to the log. The caller must hold the write-lock ** on the log file (obtained using sqlite3WalBeginWriteTransaction()). @@ -69840,12 +70037,14 @@ static int sqlite3WalOpen( out->methods.xEndReadTransaction = (void (*)(wal_impl *))sqlite3WalEndReadTransaction; out->methods.xFindFrame = (int (*)(wal_impl *, unsigned int, unsigned int *))sqlite3WalFindFrame; out->methods.xReadFrame = (int (*)(wal_impl *, unsigned int, int, unsigned char *))sqlite3WalReadFrame; + out->methods.xReadFrameRaw = (int (*)(wal_impl *, unsigned int, int, unsigned char *))sqlite3WalReadFrameRaw; out->methods.xDbsize = (unsigned int (*)(wal_impl *))sqlite3WalDbsize; out->methods.xBeginWriteTransaction = (int (*)(wal_impl *))sqlite3WalBeginWriteTransaction; out->methods.xEndWriteTransaction = (int (*)(wal_impl *))sqlite3WalEndWriteTransaction; out->methods.xUndo = (int (*)(wal_impl *, int (*)(void *, unsigned int), void *))sqlite3WalUndo; out->methods.xSavepoint = (void (*)(wal_impl *, unsigned int *))sqlite3WalSavepoint; out->methods.xSavepointUndo = (int (*)(wal_impl *, unsigned int *))sqlite3WalSavepointUndo; + out->methods.xFrameCount = (int (*)(wal_impl *, int, unsigned int *))sqlite3WalFrameCount; out->methods.xFrames = (int (*)(wal_impl *, int, libsql_pghdr *, unsigned int, int, int, int *))sqlite3WalFrames; out->methods.xCheckpoint = (int (*)(wal_impl *, sqlite3 *, int, int (*)(void *), void *, int, int, unsigned char *, int *, int *, int (*)(void*, int, const unsigned char*, int, int, int), void*))sqlite3WalCheckpoint; out->methods.xCallback = (int (*)(wal_impl *))sqlite3WalCallback; @@ -182972,6 +183171,160 @@ void *libsql_close_hook( return pRet; } +/* +** Disable WAL checkpointing. +** +** Note: This function disables WAL checkpointing entirely, including when +** the last database connection is closed. This is different from +** sqlite3_wal_autocheckpoint() which only disables automatic checkpoints +** for the current connection, but still allows checkpointing when the +** connection is closed. +**/ +int libsql_wal_disable_checkpoint(sqlite3 *db) { +#ifndef SQLITE_OMIT_WAL +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + return SQLITE_MISUSE_BKPT; + } +#endif + sqlite3_mutex_enter(db->mutex); + db->walCheckPointDisabled = 1; + db->xWalCallback = 0; + sqlite3_mutex_leave(db->mutex); +#endif + return SQLITE_OK; +} + +/* +** Return the number of frames in the WAL of the given database. +*/ +int libsql_wal_frame_count( + sqlite3* db, + unsigned int *pnFrame +){ + int rc = SQLITE_OK; + Pager *pPager; + +#ifdef SQLITE_OMIT_WAL + *pnFrame = 0; + return SQLITE_OK; +#else +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif + + sqlite3_mutex_enter(db->mutex); + pPager = sqlite3BtreePager(db->aDb[0].pBt); + rc = sqlite3PagerWalFrameCount(pPager, pnFrame); + sqlite3_mutex_leave(db->mutex); + return rc; +#endif +} + +int libsql_wal_get_frame( + sqlite3* db, + unsigned int iFrame, + void *pBuf, + unsigned int nBuf +){ + int rc = SQLITE_OK; + Pager *pPager; + +#ifdef SQLITE_OMIT_WAL + UNUSED_PARAMETER(iFrame); + UNUSED_PARAMETER(nBuf); + UNUSED_PARAMETER(pBuf); + return SQLITE_OK; +#else + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif + + sqlite3_mutex_enter(db->mutex); + pPager = sqlite3BtreePager(db->aDb[0].pBt); + rc = sqlite3PagerWalReadFrameRaw(pPager, iFrame, pBuf, nBuf); + sqlite3_mutex_leave(db->mutex); + + return rc; +#endif +} + +/* +** Begin a WAL commit. +*/ +int libsql_wal_insert_begin(sqlite3 *db) { + Pager *pPager; + int rc; + + sqlite3_mutex_enter(db->mutex); + pPager = sqlite3BtreePager(db->aDb[0].pBt); + rc = sqlite3PagerSharedLock(pPager); + if (rc != SQLITE_OK) { + goto out_unlock; + } + int isOpen = 0; + rc = sqlite3PagerOpenWal(pPager, &isOpen); + if (rc != SQLITE_OK) { + goto out_unlock; + } + rc = sqlite3PagerWalBeginCommit(pPager); + if (rc != SQLITE_OK) { + goto out_unlock; + } +out_unlock: + sqlite3_mutex_leave(db->mutex); + return rc; +} + +int libsql_wal_insert_end(sqlite3 *db) { + Pager *pPager; + int rc; + + sqlite3_mutex_enter(db->mutex); + pPager = sqlite3BtreePager(db->aDb[0].pBt); + rc = sqlite3PagerWalEndCommit(pPager); + if (rc != SQLITE_OK) { + goto out_unlock; + } +out_unlock: + sqlite3_mutex_leave(db->mutex); + return rc; +} + +/* +** Insert a frame into the WAL. +*/ +int libsql_wal_insert_frame( + sqlite3* db, + unsigned int iFrame, + void *pBuf, + unsigned int nBuf +){ + int rc = SQLITE_OK; + Pager *pPager; + +#ifdef SQLITE_OMIT_WAL + *pnFrame = 0; + return SQLITE_OK; +#else +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif + + sqlite3_mutex_enter(db->mutex); + pPager = sqlite3BtreePager(db->aDb[0].pBt); + rc = sqlite3PagerWalInsert(pPager, iFrame, pBuf, nBuf); + if (rc != SQLITE_OK) { + goto out_unlock; + } +out_unlock: + sqlite3_mutex_leave(db->mutex); + + return rc; +#endif +} + /* ** Register a function to be invoked prior to each autovacuum that ** determines the number of pages to vacuum. @@ -183069,6 +183422,7 @@ SQLITE_API void *sqlite3_wal_hook( #endif sqlite3_mutex_enter(db->mutex); pRet = db->pWalArg; + db->walCheckPointDisabled = 0; db->xWalCallback = xCallback; db->pWalArg = pArg; sqlite3_mutex_leave(db->mutex); diff --git a/libsql-ffi/bundled/bindings/bindgen.rs b/libsql-ffi/bundled/bindings/bindgen.rs index cc73807f33..9606aed509 100644 --- a/libsql-ffi/bundled/bindings/bindgen.rs +++ b/libsql-ffi/bundled/bindings/bindgen.rs @@ -940,7 +940,7 @@ extern "C" { extern "C" { pub fn sqlite3_vmprintf( arg1: *const ::std::os::raw::c_char, - arg2: *mut __va_list_tag, + arg2: va_list, ) -> *mut ::std::os::raw::c_char; } extern "C" { @@ -956,7 +956,7 @@ extern "C" { arg1: ::std::os::raw::c_int, arg2: *mut ::std::os::raw::c_char, arg3: *const ::std::os::raw::c_char, - arg4: *mut __va_list_tag, + arg4: va_list, ) -> *mut ::std::os::raw::c_char; } extern "C" { @@ -2503,7 +2503,7 @@ extern "C" { pub fn sqlite3_str_vappendf( arg1: *mut sqlite3_str, zFormat: *const ::std::os::raw::c_char, - arg2: *mut __va_list_tag, + arg2: va_list, ); } extern "C" { @@ -2863,6 +2863,37 @@ extern "C" { arg: *mut ::std::os::raw::c_void, ) -> *mut ::std::os::raw::c_void; } +extern "C" { + pub fn libsql_wal_disable_checkpoint(db: *mut sqlite3) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn libsql_wal_frame_count( + arg1: *mut sqlite3, + arg2: *mut ::std::os::raw::c_uint, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn libsql_wal_get_frame( + arg1: *mut sqlite3, + arg2: ::std::os::raw::c_uint, + arg3: *mut ::std::os::raw::c_void, + arg4: ::std::os::raw::c_uint, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn libsql_wal_insert_begin(arg1: *mut sqlite3) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn libsql_wal_insert_end(arg1: *mut sqlite3) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn libsql_wal_insert_frame( + arg1: *mut sqlite3, + arg2: ::std::os::raw::c_uint, + arg3: *mut ::std::os::raw::c_void, + arg4: ::std::os::raw::c_uint, + ) -> ::std::os::raw::c_int; +} extern "C" { pub fn sqlite3_system_errno(arg1: *mut sqlite3) -> ::std::os::raw::c_int; } @@ -3289,6 +3320,14 @@ pub struct libsql_wal_methods { arg3: *mut ::std::os::raw::c_uchar, ) -> ::std::os::raw::c_int, >, + pub xReadFrameRaw: ::std::option::Option< + unsafe extern "C" fn( + pWal: *mut wal_impl, + arg1: ::std::os::raw::c_uint, + arg2: ::std::os::raw::c_int, + arg3: *mut ::std::os::raw::c_uchar, + ) -> ::std::os::raw::c_int, + >, pub xDbsize: ::std::option::Option ::std::os::raw::c_uint>, pub xBeginWriteTransaction: @@ -3316,6 +3355,13 @@ pub struct libsql_wal_methods { aWalData: *mut ::std::os::raw::c_uint, ) -> ::std::os::raw::c_int, >, + pub xFrameCount: ::std::option::Option< + unsafe extern "C" fn( + pWal: *mut wal_impl, + arg1: ::std::os::raw::c_int, + arg2: *mut ::std::os::raw::c_uint, + ) -> ::std::os::raw::c_int, + >, pub xFrames: ::std::option::Option< unsafe extern "C" fn( pWal: *mut wal_impl, @@ -3524,12 +3570,4 @@ extern "C" { extern "C" { pub static sqlite3_wal_manager: libsql_wal_manager; } -pub type __builtin_va_list = [__va_list_tag; 1usize]; -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct __va_list_tag { - pub gp_offset: ::std::os::raw::c_uint, - pub fp_offset: ::std::os::raw::c_uint, - pub overflow_arg_area: *mut ::std::os::raw::c_void, - pub reg_save_area: *mut ::std::os::raw::c_void, -} +pub type __builtin_va_list = *mut ::std::os::raw::c_char; diff --git a/libsql-ffi/bundled/bindings/session_bindgen.rs b/libsql-ffi/bundled/bindings/session_bindgen.rs index 2ac7e13516..88dd5d2fe2 100644 --- a/libsql-ffi/bundled/bindings/session_bindgen.rs +++ b/libsql-ffi/bundled/bindings/session_bindgen.rs @@ -23,10 +23,11 @@ extern "C" { ) -> ::std::os::raw::c_int; } -pub const SQLITE_VERSION: &[u8; 7] = b"3.44.0\0"; -pub const SQLITE_VERSION_NUMBER: i32 = 3044000; +pub const __GNUC_VA_LIST: i32 = 1; +pub const SQLITE_VERSION: &[u8; 7] = b"3.45.1\0"; +pub const SQLITE_VERSION_NUMBER: i32 = 3045001; pub const SQLITE_SOURCE_ID: &[u8; 85] = - b"2023-11-01 11:23:50 17129ba1ff7f0daf37100ee82d507aef7827cf38de1866e2633096ae6ad8alt1\0"; + b"2024-01-30 16:01:20 e876e51a0ed5c5b3126f52e532044363a014bc594cfefa87ffb5b82257ccalt1\0"; pub const LIBSQL_VERSION: &[u8; 6] = b"0.2.3\0"; pub const SQLITE_OK: i32 = 0; pub const SQLITE_ERROR: i32 = 1; @@ -355,6 +356,7 @@ pub const SQLITE_DETERMINISTIC: i32 = 2048; pub const SQLITE_DIRECTONLY: i32 = 524288; pub const SQLITE_SUBTYPE: i32 = 1048576; pub const SQLITE_INNOCUOUS: i32 = 2097152; +pub const SQLITE_RESULT_SUBTYPE: i32 = 16777216; pub const SQLITE_WIN32_DATA_DIRECTORY_TYPE: i32 = 1; pub const SQLITE_WIN32_TEMP_DIRECTORY_TYPE: i32 = 2; pub const SQLITE_TXN_NONE: i32 = 0; @@ -407,6 +409,7 @@ pub const SQLITE_TESTCTRL_PENDING_BYTE: i32 = 11; pub const SQLITE_TESTCTRL_ASSERT: i32 = 12; pub const SQLITE_TESTCTRL_ALWAYS: i32 = 13; pub const SQLITE_TESTCTRL_RESERVE: i32 = 14; +pub const SQLITE_TESTCTRL_JSON_SELFCHECK: i32 = 14; pub const SQLITE_TESTCTRL_OPTIMIZATIONS: i32 = 15; pub const SQLITE_TESTCTRL_ISKEYWORD: i32 = 16; pub const SQLITE_TESTCTRL_SCRATCHMALLOC: i32 = 17; @@ -516,8 +519,8 @@ pub const FTS5_TOKENIZE_DOCUMENT: i32 = 4; pub const FTS5_TOKENIZE_AUX: i32 = 8; pub const FTS5_TOKEN_COLOCATED: i32 = 1; pub const WAL_SAVEPOINT_NDATA: i32 = 4; -pub type __gnuc_va_list = __builtin_va_list; pub type va_list = __builtin_va_list; +pub type __gnuc_va_list = __builtin_va_list; extern "C" { pub static sqlite3_version: [::std::os::raw::c_char; 0usize]; } @@ -2278,11 +2281,7 @@ pub struct sqlite3_module { pzErr: *mut *mut ::std::os::raw::c_char, ) -> ::std::os::raw::c_int, >, -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct libsql_module { - pub iVersion: ::std::os::raw::c_int, + pub reserved: [::std::option::Option; 5usize], pub xPreparedSql: ::std::option::Option< unsafe extern "C" fn( arg1: *mut sqlite3_vtab_cursor, @@ -2344,16 +2343,6 @@ extern "C" { xDestroy: ::std::option::Option, ) -> ::std::os::raw::c_int; } -extern "C" { - pub fn libsql_create_module( - db: *mut sqlite3, - zName: *const ::std::os::raw::c_char, - p: *const sqlite3_module, - pLibsql: *const libsql_module, - pClientData: *mut ::std::os::raw::c_void, - xDestroy: ::std::option::Option, - ) -> ::std::os::raw::c_int; -} extern "C" { pub fn sqlite3_drop_modules( db: *mut sqlite3, @@ -2364,7 +2353,6 @@ extern "C" { #[derive(Debug, Copy, Clone)] pub struct sqlite3_vtab { pub pModule: *const sqlite3_module, - pub pLibsqlModule: *const libsql_module, pub nRef: ::std::os::raw::c_int, pub zErrMsg: *mut ::std::os::raw::c_char, } @@ -2892,6 +2880,37 @@ extern "C" { arg: *mut ::std::os::raw::c_void, ) -> *mut ::std::os::raw::c_void; } +extern "C" { + pub fn libsql_wal_disable_checkpoint(db: *mut sqlite3) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn libsql_wal_frame_count( + arg1: *mut sqlite3, + arg2: *mut ::std::os::raw::c_uint, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn libsql_wal_get_frame( + arg1: *mut sqlite3, + arg2: ::std::os::raw::c_uint, + arg3: *mut ::std::os::raw::c_void, + arg4: ::std::os::raw::c_uint, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn libsql_wal_insert_begin(arg1: *mut sqlite3) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn libsql_wal_insert_end(arg1: *mut sqlite3) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn libsql_wal_insert_frame( + arg1: *mut sqlite3, + arg2: ::std::os::raw::c_uint, + arg3: *mut ::std::os::raw::c_void, + arg4: ::std::os::raw::c_uint, + ) -> ::std::os::raw::c_int; +} extern "C" { pub fn sqlite3_system_errno(arg1: *mut sqlite3) -> ::std::os::raw::c_int; } @@ -3659,6 +3678,24 @@ pub struct Fts5ExtensionApi { piCol: *mut ::std::os::raw::c_int, ), >, + pub xQueryToken: ::std::option::Option< + unsafe extern "C" fn( + arg1: *mut Fts5Context, + iPhrase: ::std::os::raw::c_int, + iToken: ::std::os::raw::c_int, + ppToken: *mut *const ::std::os::raw::c_char, + pnToken: *mut ::std::os::raw::c_int, + ) -> ::std::os::raw::c_int, + >, + pub xInstToken: ::std::option::Option< + unsafe extern "C" fn( + arg1: *mut Fts5Context, + iIdx: ::std::os::raw::c_int, + iToken: ::std::os::raw::c_int, + arg2: *mut *const ::std::os::raw::c_char, + arg3: *mut ::std::os::raw::c_int, + ) -> ::std::os::raw::c_int, + >, } #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -3795,6 +3832,14 @@ pub struct libsql_wal_methods { arg3: *mut ::std::os::raw::c_uchar, ) -> ::std::os::raw::c_int, >, + pub xReadFrameRaw: ::std::option::Option< + unsafe extern "C" fn( + pWal: *mut wal_impl, + arg1: ::std::os::raw::c_uint, + arg2: ::std::os::raw::c_int, + arg3: *mut ::std::os::raw::c_uchar, + ) -> ::std::os::raw::c_int, + >, pub xDbsize: ::std::option::Option ::std::os::raw::c_uint>, pub xBeginWriteTransaction: @@ -3822,6 +3867,13 @@ pub struct libsql_wal_methods { aWalData: *mut ::std::os::raw::c_uint, ) -> ::std::os::raw::c_int, >, + pub xFrameCount: ::std::option::Option< + unsafe extern "C" fn( + pWal: *mut wal_impl, + arg1: ::std::os::raw::c_int, + arg2: *mut ::std::os::raw::c_uint, + ) -> ::std::os::raw::c_int, + >, pub xFrames: ::std::option::Option< unsafe extern "C" fn( pWal: *mut wal_impl, diff --git a/libsql-ffi/bundled/src/sqlite3.c b/libsql-ffi/bundled/src/sqlite3.c index cf812dc9ae..32f26762eb 100644 --- a/libsql-ffi/bundled/src/sqlite3.c +++ b/libsql-ffi/bundled/src/sqlite3.c @@ -10938,6 +10938,59 @@ SQLITE_API int sqlite3_preupdate_blobwrite(sqlite3 *); */ SQLITE_API void *libsql_close_hook(sqlite3 *db, void (*xClose)(void *pCtx, sqlite3 *db), void *arg); +/* +** CAPI3REF: Disable WAL checkpointing +** METHOD: sqlite3 +** +** ^The [libsql_wal_disable_checkpoint(D)] interface disables automatic +** checkpointing of the WAL file for [database connection] D. +** +** Note: This function disables WAL checkpointing entirely, including when +** the last database connection is closed. This is different from +** sqlite3_wal_autocheckpoint() which only disables automatic checkpoints +** for the current connection, but still allows checkpointing when the +** connection is closed. +*/ +SQLITE_API int libsql_wal_disable_checkpoint(sqlite3 *db); + +/* +** CAPI3REF: Get the number of frames in the WAL file +** METHOD: sqlite3 +** +** ^The [libsql_wal_frame_count(D,P)] interface returns the number of frames +** in the WAL file for [database connection] D into *P. +*/ +SQLITE_API int libsql_wal_frame_count(sqlite3*, unsigned int*); + +/* +** CAPI3REF: Get a frame from the WAL file +** METHOD: sqlite3 +** +** ^The [libsql_wal_get_frame(D,I,P,S)] interface extracts frame I from +** the WAL file for [database connection] D into memory obtained from +** [sqlite3_malloc64()] and returns a pointer to that memory. The size of +** the memory allocated is given by S. +*/ +SQLITE_API int libsql_wal_get_frame(sqlite3*, unsigned int, void*, unsigned int); + +/* +** CAPI3REF: Begin frame insertion into the WAL +** METHOD: sqlite3 +*/ +SQLITE_API int libsql_wal_insert_begin(sqlite3*); + +/* +** CAPI3REF: End frame insertion into the WAL +** METHOD: sqlite3 +*/ +SQLITE_API int libsql_wal_insert_end(sqlite3*); + +/* +** CAPI3REF: Insert a frame into the WAL +** METHOD: sqlite3 +*/ +SQLITE_API int libsql_wal_insert_frame(sqlite3*, unsigned int, void *, unsigned int); + /* ** CAPI3REF: Low-level system error code ** METHOD: sqlite3 @@ -13963,6 +14016,7 @@ typedef struct libsql_wal_methods { /* Read a page from the write-ahead log, if it is present. */ int (*xFindFrame)(wal_impl* pWal, unsigned int, unsigned int *); int (*xReadFrame)(wal_impl* pWal, unsigned int, int, unsigned char *); + int (*xReadFrameRaw)(wal_impl* pWal, unsigned int, int, unsigned char *); /* If the WAL is not empty, return the size of the database. */ unsigned int (*xDbsize)(wal_impl* pWal); @@ -13982,6 +14036,9 @@ typedef struct libsql_wal_methods { ** response to a ROLLBACK TO command. */ int (*xSavepointUndo)(wal_impl* pWal, unsigned int *aWalData); + /* Return the number of frames in the WAL */ + int (*xFrameCount)(wal_impl* pWal, int, unsigned int *); + /* Write a frame or frames to the log. */ int (*xFrames)(wal_impl* pWal, int, libsql_pghdr *, unsigned int, int, int, int*); @@ -16378,6 +16435,12 @@ SQLITE_PRIVATE int sqlite3PagerReadFileheader(Pager*, int, unsigned char*); SQLITE_PRIVATE void sqlite3PagerSetBusyHandler(Pager*, int(*)(void *), void *); SQLITE_PRIVATE int sqlite3PagerSetPagesize(Pager*, u32*, int); SQLITE_PRIVATE Pgno sqlite3PagerMaxPageCount(Pager*, Pgno); +SQLITE_PRIVATE int sqlite3PagerWalFrameCount(Pager *, unsigned int *); +SQLITE_PRIVATE int sqlite3PagerWalReadFrame(Pager *, unsigned int, void *, unsigned int); +SQLITE_PRIVATE int sqlite3PagerWalBeginCommit(Pager*); +SQLITE_PRIVATE int sqlite3PagerWalEndCommit(Pager*); +SQLITE_PRIVATE int sqlite3PagerWalInsert(Pager*, unsigned int, void *, unsigned int); + SQLITE_PRIVATE void sqlite3PagerSetCachesize(Pager*, int); SQLITE_PRIVATE int sqlite3PagerSetSpillsize(Pager*, int); SQLITE_PRIVATE void sqlite3PagerSetMmapLimit(Pager *, sqlite3_int64); @@ -18223,6 +18286,7 @@ struct sqlite3 { PreUpdate *pPreUpdate; /* Context for active pre-update callback */ #endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ #ifndef SQLITE_OMIT_WAL + int walCheckPointDisabled; int (*xWalCallback)(void *, sqlite3 *, const char *, int); void *pWalArg; #endif @@ -57270,6 +57334,7 @@ typedef struct libsql_wal_methods { /* Read a page from the write-ahead log, if it is present. */ int (*xFindFrame)(wal_impl* pWal, unsigned int, unsigned int *); int (*xReadFrame)(wal_impl* pWal, unsigned int, int, unsigned char *); + int (*xReadFrameRaw)(wal_impl* pWal, unsigned int, int, unsigned char *); /* If the WAL is not empty, return the size of the database. */ unsigned int (*xDbsize)(wal_impl* pWal); @@ -57289,6 +57354,9 @@ typedef struct libsql_wal_methods { ** response to a ROLLBACK TO command. */ int (*xSavepointUndo)(wal_impl* pWal, unsigned int *aWalData); + /* Return the number of frames in the WAL */ + int (*xFrameCount)(wal_impl* pWal, int, unsigned int *); + /* Write a frame or frames to the log. */ int (*xFrames)(wal_impl* pWal, int, libsql_pghdr *, unsigned int, int, int, int*); @@ -65214,6 +65282,94 @@ SQLITE_PRIVATE int sqlite3PagerCloseWal(Pager *pPager, sqlite3 *db){ return rc; } +/** +** Return the number of frames in the WAL file. +** +** If the pager is not in WAL mode or we failed to obtain an exclusive write lock, returns -1. +**/ +SQLITE_PRIVATE int sqlite3PagerWalFrameCount(Pager *pPager, unsigned int *pnFrames){ + if( pagerUseWal(pPager) ){ + return pPager->wal->methods.xFrameCount(pPager->wal->pData, 0, pnFrames); + }else{ + return SQLITE_ERROR; + } +} + +SQLITE_PRIVATE int sqlite3PagerWalReadFrameRaw( + Pager *pPager, + unsigned int iFrame, + void *pFrameOut, + unsigned int nFrameOutLen +){ + if( pagerUseWal(pPager) ){ + unsigned int nFrameLen = 24+pPager->pageSize; + if( nFrameOutLen!=nFrameLen ) return SQLITE_MISUSE; + return pPager->wal->methods.xReadFrameRaw(pPager->wal->pData, iFrame, nFrameOutLen, pFrameOut); + }else{ + return SQLITE_ERROR; + } +} + +SQLITE_PRIVATE int sqlite3PagerWalBeginCommit(Pager *pPager) { + int rc; + if (!pagerUseWal(pPager)) { + return SQLITE_ERROR; + } + rc = pagerBeginReadTransaction(pPager); + if (rc != SQLITE_OK) { + return rc; + } + return pPager->wal->methods.xBeginWriteTransaction(pPager->wal->pData); +} + +SQLITE_PRIVATE int sqlite3PagerWalEndCommit(Pager *pPager) { + if (!pagerUseWal(pPager)) { + return SQLITE_ERROR; + } + return pPager->wal->methods.xEndWriteTransaction(pPager->wal->pData); +} + +SQLITE_PRIVATE int sqlite3PagerWalInsert(Pager *pPager, unsigned int iFrame, void *pBuf, unsigned int nBuf) { + int rc = SQLITE_OK; + + if (!pagerUseWal(pPager)) { + return SQLITE_ERROR; + } + unsigned int mxFrame; + rc = pPager->wal->methods.xFrameCount(pPager->wal->pData, 1, &mxFrame); + if (rc != SQLITE_OK) { + return rc; + } + if (iFrame <= mxFrame) { + return SQLITE_OK; + } + u8 *aFrame = (u8*)pBuf; + u32 pgno = sqlite3Get4byte(&aFrame[0]); + u32 nTruncate = sqlite3Get4byte(&aFrame[4]); + u8 *pData = aFrame + 24; + + PgHdr pghdr; + memset(&pghdr, 0, sizeof(PgHdr)); + pghdr.pPage = NULL; + pghdr.pData = pData; + pghdr.pExtra = NULL; + pghdr.pgno = pgno; + pghdr.flags = 0; + + int isCommit = (nTruncate != 0); + + int nFrames = 0; + rc = pPager->wal->methods.xFrames(pPager->wal->pData, + pPager->pageSize, + &pghdr, + nTruncate, + isCommit, + pPager->walSyncFlags, + &nFrames); + assert( nFrames == 1 ); + return rc; +} + #ifdef SQLITE_ENABLE_SETLK_TIMEOUT /* ** If pager pPager is a wal-mode database not in exclusive locking mode, @@ -67601,9 +67757,14 @@ static int sqlite3WalClose( if( pWal->exclusiveMode==WAL_NORMAL_MODE ){ pWal->exclusiveMode = WAL_EXCLUSIVE_MODE; } - rc = sqlite3WalCheckpoint(pWal, db, - SQLITE_CHECKPOINT_PASSIVE, 0, 0, sync_flags, nBuf, zBuf, 0, 0, NULL, NULL - ); + /* Don't checkpoint on close if automatic WAL checkpointing is disabled. */ + if( !db->walCheckPointDisabled ){ + rc = sqlite3WalCheckpoint(pWal, db, + SQLITE_CHECKPOINT_PASSIVE, 0, 0, sync_flags, nBuf, zBuf, 0, 0, NULL, NULL + ); + } else { + rc = SQLITE_ERROR; + } if( rc==SQLITE_OK ){ int bPersist = -1; sqlite3OsFileControlHint( @@ -68731,6 +68892,29 @@ static int sqlite3WalReadFrame( return sqlite3OsRead(pWal->pWalFd, pOut, (nOut>sz ? sz : nOut), iOffset); } +/* +** Read the contents of frame iRead from the wal file into buffer pOut +** (which is nOut bytes in size). Return SQLITE_OK if successful, or an +** error code otherwise. +*/ +static int sqlite3WalReadFrameRaw( + Wal *pWal, /* WAL handle */ + u32 iRead, /* Frame to read */ + int nOut, /* Size of buffer pOut in bytes */ + u8 *pOut /* Buffer to write page data to */ +){ + int sz; + i64 iOffset; + sz = pWal->hdr.szPage; + sz = (sz&0xfe00) + ((sz&0x0001)<<16); + testcase( sz<=32768 ); + testcase( sz>=65536 ); + iOffset = walFrameOffset(iRead, sz); + /* testcase( IS_BIG_INT(iOffset) ); // requires a 4GiB WAL */ + sz += WAL_FRAME_HDRSIZE; + return sqlite3OsRead(pWal->pWalFd, pOut, (nOut>sz ? sz : nOut), iOffset); +} + /* ** Return the size of the database in pages (or zero, if unknown). */ @@ -69341,6 +69525,19 @@ static int walFrames( return rc; } +SQLITE_PRIVATE int sqlite3WalFrameCount(Wal *pWal, int locked, unsigned int *pnFrames){ + int rc = SQLITE_OK; + if( locked==0 ) { + rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1); + if (rc != SQLITE_OK) return rc; + } + *pnFrames = pWal->hdr.mxFrame; + if( locked==0 ) { + walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1); + } + return SQLITE_OK; +} + /* ** Write a set of frames to the log. The caller must hold the write-lock ** on the log file (obtained using sqlite3WalBeginWriteTransaction()). @@ -69840,12 +70037,14 @@ static int sqlite3WalOpen( out->methods.xEndReadTransaction = (void (*)(wal_impl *))sqlite3WalEndReadTransaction; out->methods.xFindFrame = (int (*)(wal_impl *, unsigned int, unsigned int *))sqlite3WalFindFrame; out->methods.xReadFrame = (int (*)(wal_impl *, unsigned int, int, unsigned char *))sqlite3WalReadFrame; + out->methods.xReadFrameRaw = (int (*)(wal_impl *, unsigned int, int, unsigned char *))sqlite3WalReadFrameRaw; out->methods.xDbsize = (unsigned int (*)(wal_impl *))sqlite3WalDbsize; out->methods.xBeginWriteTransaction = (int (*)(wal_impl *))sqlite3WalBeginWriteTransaction; out->methods.xEndWriteTransaction = (int (*)(wal_impl *))sqlite3WalEndWriteTransaction; out->methods.xUndo = (int (*)(wal_impl *, int (*)(void *, unsigned int), void *))sqlite3WalUndo; out->methods.xSavepoint = (void (*)(wal_impl *, unsigned int *))sqlite3WalSavepoint; out->methods.xSavepointUndo = (int (*)(wal_impl *, unsigned int *))sqlite3WalSavepointUndo; + out->methods.xFrameCount = (int (*)(wal_impl *, int, unsigned int *))sqlite3WalFrameCount; out->methods.xFrames = (int (*)(wal_impl *, int, libsql_pghdr *, unsigned int, int, int, int *))sqlite3WalFrames; out->methods.xCheckpoint = (int (*)(wal_impl *, sqlite3 *, int, int (*)(void *), void *, int, int, unsigned char *, int *, int *, int (*)(void*, int, const unsigned char*, int, int, int), void*))sqlite3WalCheckpoint; out->methods.xCallback = (int (*)(wal_impl *))sqlite3WalCallback; @@ -182972,6 +183171,160 @@ void *libsql_close_hook( return pRet; } +/* +** Disable WAL checkpointing. +** +** Note: This function disables WAL checkpointing entirely, including when +** the last database connection is closed. This is different from +** sqlite3_wal_autocheckpoint() which only disables automatic checkpoints +** for the current connection, but still allows checkpointing when the +** connection is closed. +**/ +int libsql_wal_disable_checkpoint(sqlite3 *db) { +#ifndef SQLITE_OMIT_WAL +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + return SQLITE_MISUSE_BKPT; + } +#endif + sqlite3_mutex_enter(db->mutex); + db->walCheckPointDisabled = 1; + db->xWalCallback = 0; + sqlite3_mutex_leave(db->mutex); +#endif + return SQLITE_OK; +} + +/* +** Return the number of frames in the WAL of the given database. +*/ +int libsql_wal_frame_count( + sqlite3* db, + unsigned int *pnFrame +){ + int rc = SQLITE_OK; + Pager *pPager; + +#ifdef SQLITE_OMIT_WAL + *pnFrame = 0; + return SQLITE_OK; +#else +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif + + sqlite3_mutex_enter(db->mutex); + pPager = sqlite3BtreePager(db->aDb[0].pBt); + rc = sqlite3PagerWalFrameCount(pPager, pnFrame); + sqlite3_mutex_leave(db->mutex); + return rc; +#endif +} + +int libsql_wal_get_frame( + sqlite3* db, + unsigned int iFrame, + void *pBuf, + unsigned int nBuf +){ + int rc = SQLITE_OK; + Pager *pPager; + +#ifdef SQLITE_OMIT_WAL + UNUSED_PARAMETER(iFrame); + UNUSED_PARAMETER(nBuf); + UNUSED_PARAMETER(pBuf); + return SQLITE_OK; +#else + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif + + sqlite3_mutex_enter(db->mutex); + pPager = sqlite3BtreePager(db->aDb[0].pBt); + rc = sqlite3PagerWalReadFrameRaw(pPager, iFrame, pBuf, nBuf); + sqlite3_mutex_leave(db->mutex); + + return rc; +#endif +} + +/* +** Begin a WAL commit. +*/ +int libsql_wal_insert_begin(sqlite3 *db) { + Pager *pPager; + int rc; + + sqlite3_mutex_enter(db->mutex); + pPager = sqlite3BtreePager(db->aDb[0].pBt); + rc = sqlite3PagerSharedLock(pPager); + if (rc != SQLITE_OK) { + goto out_unlock; + } + int isOpen = 0; + rc = sqlite3PagerOpenWal(pPager, &isOpen); + if (rc != SQLITE_OK) { + goto out_unlock; + } + rc = sqlite3PagerWalBeginCommit(pPager); + if (rc != SQLITE_OK) { + goto out_unlock; + } +out_unlock: + sqlite3_mutex_leave(db->mutex); + return rc; +} + +int libsql_wal_insert_end(sqlite3 *db) { + Pager *pPager; + int rc; + + sqlite3_mutex_enter(db->mutex); + pPager = sqlite3BtreePager(db->aDb[0].pBt); + rc = sqlite3PagerWalEndCommit(pPager); + if (rc != SQLITE_OK) { + goto out_unlock; + } +out_unlock: + sqlite3_mutex_leave(db->mutex); + return rc; +} + +/* +** Insert a frame into the WAL. +*/ +int libsql_wal_insert_frame( + sqlite3* db, + unsigned int iFrame, + void *pBuf, + unsigned int nBuf +){ + int rc = SQLITE_OK; + Pager *pPager; + +#ifdef SQLITE_OMIT_WAL + *pnFrame = 0; + return SQLITE_OK; +#else +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif + + sqlite3_mutex_enter(db->mutex); + pPager = sqlite3BtreePager(db->aDb[0].pBt); + rc = sqlite3PagerWalInsert(pPager, iFrame, pBuf, nBuf); + if (rc != SQLITE_OK) { + goto out_unlock; + } +out_unlock: + sqlite3_mutex_leave(db->mutex); + + return rc; +#endif +} + /* ** Register a function to be invoked prior to each autovacuum that ** determines the number of pages to vacuum. @@ -183069,6 +183422,7 @@ SQLITE_API void *sqlite3_wal_hook( #endif sqlite3_mutex_enter(db->mutex); pRet = db->pWalArg; + db->walCheckPointDisabled = 0; db->xWalCallback = xCallback; db->pWalArg = pArg; sqlite3_mutex_leave(db->mutex); diff --git a/libsql-ffi/bundled/src/sqlite3.h b/libsql-ffi/bundled/src/sqlite3.h index d526834332..916f3a0b68 100644 --- a/libsql-ffi/bundled/src/sqlite3.h +++ b/libsql-ffi/bundled/src/sqlite3.h @@ -10549,6 +10549,59 @@ SQLITE_API int sqlite3_preupdate_blobwrite(sqlite3 *); */ SQLITE_API void *libsql_close_hook(sqlite3 *db, void (*xClose)(void *pCtx, sqlite3 *db), void *arg); +/* +** CAPI3REF: Disable WAL checkpointing +** METHOD: sqlite3 +** +** ^The [libsql_wal_disable_checkpoint(D)] interface disables automatic +** checkpointing of the WAL file for [database connection] D. +** +** Note: This function disables WAL checkpointing entirely, including when +** the last database connection is closed. This is different from +** sqlite3_wal_autocheckpoint() which only disables automatic checkpoints +** for the current connection, but still allows checkpointing when the +** connection is closed. +*/ +SQLITE_API int libsql_wal_disable_checkpoint(sqlite3 *db); + +/* +** CAPI3REF: Get the number of frames in the WAL file +** METHOD: sqlite3 +** +** ^The [libsql_wal_frame_count(D,P)] interface returns the number of frames +** in the WAL file for [database connection] D into *P. +*/ +SQLITE_API int libsql_wal_frame_count(sqlite3*, unsigned int*); + +/* +** CAPI3REF: Get a frame from the WAL file +** METHOD: sqlite3 +** +** ^The [libsql_wal_get_frame(D,I,P,S)] interface extracts frame I from +** the WAL file for [database connection] D into memory obtained from +** [sqlite3_malloc64()] and returns a pointer to that memory. The size of +** the memory allocated is given by S. +*/ +SQLITE_API int libsql_wal_get_frame(sqlite3*, unsigned int, void*, unsigned int); + +/* +** CAPI3REF: Begin frame insertion into the WAL +** METHOD: sqlite3 +*/ +SQLITE_API int libsql_wal_insert_begin(sqlite3*); + +/* +** CAPI3REF: End frame insertion into the WAL +** METHOD: sqlite3 +*/ +SQLITE_API int libsql_wal_insert_end(sqlite3*); + +/* +** CAPI3REF: Insert a frame into the WAL +** METHOD: sqlite3 +*/ +SQLITE_API int libsql_wal_insert_frame(sqlite3*, unsigned int, void *, unsigned int); + /* ** CAPI3REF: Low-level system error code ** METHOD: sqlite3 @@ -13574,6 +13627,7 @@ typedef struct libsql_wal_methods { /* Read a page from the write-ahead log, if it is present. */ int (*xFindFrame)(wal_impl* pWal, unsigned int, unsigned int *); int (*xReadFrame)(wal_impl* pWal, unsigned int, int, unsigned char *); + int (*xReadFrameRaw)(wal_impl* pWal, unsigned int, int, unsigned char *); /* If the WAL is not empty, return the size of the database. */ unsigned int (*xDbsize)(wal_impl* pWal); @@ -13593,6 +13647,9 @@ typedef struct libsql_wal_methods { ** response to a ROLLBACK TO command. */ int (*xSavepointUndo)(wal_impl* pWal, unsigned int *aWalData); + /* Return the number of frames in the WAL */ + int (*xFrameCount)(wal_impl* pWal, int, unsigned int *); + /* Write a frame or frames to the log. */ int (*xFrames)(wal_impl* pWal, int, libsql_pghdr *, unsigned int, int, int, int*); diff --git a/libsql-replication/src/injector/sqlite_injector/injector_wal.rs b/libsql-replication/src/injector/sqlite_injector/injector_wal.rs index eb92941d96..545899d57a 100644 --- a/libsql-replication/src/injector/sqlite_injector/injector_wal.rs +++ b/libsql-replication/src/injector/sqlite_injector/injector_wal.rs @@ -110,6 +110,10 @@ impl Wal for InjectorWal { self.inner.read_frame(frame_no, buffer) } + fn read_frame_raw(&mut self, frame_no: NonZeroU32, buffer: &mut [u8]) -> Result<()> { + self.inner.read_frame_raw(frame_no, buffer) + } + fn db_size(&self) -> u32 { self.inner.db_size() } @@ -134,6 +138,10 @@ impl Wal for InjectorWal { self.inner.savepoint_undo(rollback_data) } + fn frame_count(&self, locked: i32) -> Result { + self.inner.frame_count(locked) + } + fn insert_frames( &mut self, page_size: c_int, diff --git a/libsql-sqlite3/doc/libsql_extensions.md b/libsql-sqlite3/doc/libsql_extensions.md index 302c6ca0c3..d4602ac542 100644 --- a/libsql-sqlite3/doc/libsql_extensions.md +++ b/libsql-sqlite3/doc/libsql_extensions.md @@ -355,3 +355,32 @@ static sqlite3_module helloModule = { .xPreparedSql = helloPreparedSql, }; ``` + +## WAL API + +The libSQL has the following WAL API functions, which are useful for +syncing the WAL between databases: + +* `libsql_wal_disable_checkpoint` -- Disable checkpointing, including on database close. +* `libsql_wal_frame_count` -- Get the number of frames in the WAL. +* `libsql_wal_get_frame` -- Get a frame from the WAL. +* `libsql_wal_insert_begin` -- Begin WAL insertion. +* `libsql_wal_insert_frame` -- Insert a frame into the WAL. +* `libsql_wal_insert_end` -- End WAL insertion. + +Example usage: + +```c +static void sync_db(sqlite3 *db_primary, sqlite3 *db_backup){ + unsigned int max_frame; + + libsql_wal_frame_count(db_primary, &max_frame); + libsql_wal_begin_commit(db_backup); + for(int i=1; i<=max_frame; i++){ + char frame[4096+24]; + libsql_wal_get_frame(db_primary, i, frame, sizeof(frame)); + libsql_wal_insert_frame(db_backup, i, frame, sizeof(frame)); + } + libsql_wal_end_commit(db_backup); +} +``` diff --git a/libsql-sqlite3/src/main.c b/libsql-sqlite3/src/main.c index 5946501731..42c60a2a59 100644 --- a/libsql-sqlite3/src/main.c +++ b/libsql-sqlite3/src/main.c @@ -2425,6 +2425,160 @@ void *libsql_close_hook( return pRet; } +/* +** Disable WAL checkpointing. +** +** Note: This function disables WAL checkpointing entirely, including when +** the last database connection is closed. This is different from +** sqlite3_wal_autocheckpoint() which only disables automatic checkpoints +** for the current connection, but still allows checkpointing when the +** connection is closed. +**/ +int libsql_wal_disable_checkpoint(sqlite3 *db) { +#ifndef SQLITE_OMIT_WAL +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + return SQLITE_MISUSE_BKPT; + } +#endif + sqlite3_mutex_enter(db->mutex); + db->walCheckPointDisabled = 1; + db->xWalCallback = 0; + sqlite3_mutex_leave(db->mutex); +#endif + return SQLITE_OK; +} + +/* +** Return the number of frames in the WAL of the given database. +*/ +int libsql_wal_frame_count( + sqlite3* db, + unsigned int *pnFrame +){ + int rc = SQLITE_OK; + Pager *pPager; + +#ifdef SQLITE_OMIT_WAL + *pnFrame = 0; + return SQLITE_OK; +#else +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif + + sqlite3_mutex_enter(db->mutex); + pPager = sqlite3BtreePager(db->aDb[0].pBt); + rc = sqlite3PagerWalFrameCount(pPager, pnFrame); + sqlite3_mutex_leave(db->mutex); + return rc; +#endif +} + +int libsql_wal_get_frame( + sqlite3* db, + unsigned int iFrame, + void *pBuf, + unsigned int nBuf +){ + int rc = SQLITE_OK; + Pager *pPager; + +#ifdef SQLITE_OMIT_WAL + UNUSED_PARAMETER(iFrame); + UNUSED_PARAMETER(nBuf); + UNUSED_PARAMETER(pBuf); + return SQLITE_OK; +#else + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif + + sqlite3_mutex_enter(db->mutex); + pPager = sqlite3BtreePager(db->aDb[0].pBt); + rc = sqlite3PagerWalReadFrameRaw(pPager, iFrame, pBuf, nBuf); + sqlite3_mutex_leave(db->mutex); + + return rc; +#endif +} + +/* +** Begin a WAL commit. +*/ +int libsql_wal_insert_begin(sqlite3 *db) { + Pager *pPager; + int rc; + + sqlite3_mutex_enter(db->mutex); + pPager = sqlite3BtreePager(db->aDb[0].pBt); + rc = sqlite3PagerSharedLock(pPager); + if (rc != SQLITE_OK) { + goto out_unlock; + } + int isOpen = 0; + rc = sqlite3PagerOpenWal(pPager, &isOpen); + if (rc != SQLITE_OK) { + goto out_unlock; + } + rc = sqlite3PagerWalBeginCommit(pPager); + if (rc != SQLITE_OK) { + goto out_unlock; + } +out_unlock: + sqlite3_mutex_leave(db->mutex); + return rc; +} + +int libsql_wal_insert_end(sqlite3 *db) { + Pager *pPager; + int rc; + + sqlite3_mutex_enter(db->mutex); + pPager = sqlite3BtreePager(db->aDb[0].pBt); + rc = sqlite3PagerWalEndCommit(pPager); + if (rc != SQLITE_OK) { + goto out_unlock; + } +out_unlock: + sqlite3_mutex_leave(db->mutex); + return rc; +} + +/* +** Insert a frame into the WAL. +*/ +int libsql_wal_insert_frame( + sqlite3* db, + unsigned int iFrame, + void *pBuf, + unsigned int nBuf +){ + int rc = SQLITE_OK; + Pager *pPager; + +#ifdef SQLITE_OMIT_WAL + *pnFrame = 0; + return SQLITE_OK; +#else +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif + + sqlite3_mutex_enter(db->mutex); + pPager = sqlite3BtreePager(db->aDb[0].pBt); + rc = sqlite3PagerWalInsert(pPager, iFrame, pBuf, nBuf); + if (rc != SQLITE_OK) { + goto out_unlock; + } +out_unlock: + sqlite3_mutex_leave(db->mutex); + + return rc; +#endif +} + /* ** Register a function to be invoked prior to each autovacuum that ** determines the number of pages to vacuum. @@ -2522,6 +2676,7 @@ void *sqlite3_wal_hook( #endif sqlite3_mutex_enter(db->mutex); pRet = db->pWalArg; + db->walCheckPointDisabled = 0; db->xWalCallback = xCallback; db->pWalArg = pArg; sqlite3_mutex_leave(db->mutex); diff --git a/libsql-sqlite3/src/pager.c b/libsql-sqlite3/src/pager.c index 8b8348919c..74a3f27ec2 100644 --- a/libsql-sqlite3/src/pager.c +++ b/libsql-sqlite3/src/pager.c @@ -7771,6 +7771,94 @@ int sqlite3PagerCloseWal(Pager *pPager, sqlite3 *db){ return rc; } +/** +** Return the number of frames in the WAL file. +** +** If the pager is not in WAL mode or we failed to obtain an exclusive write lock, returns -1. +**/ +int sqlite3PagerWalFrameCount(Pager *pPager, unsigned int *pnFrames){ + if( pagerUseWal(pPager) ){ + return pPager->wal->methods.xFrameCount(pPager->wal->pData, 0, pnFrames); + }else{ + return SQLITE_ERROR; + } +} + +int sqlite3PagerWalReadFrameRaw( + Pager *pPager, + unsigned int iFrame, + void *pFrameOut, + unsigned int nFrameOutLen +){ + if( pagerUseWal(pPager) ){ + unsigned int nFrameLen = 24+pPager->pageSize; + if( nFrameOutLen!=nFrameLen ) return SQLITE_MISUSE; + return pPager->wal->methods.xReadFrameRaw(pPager->wal->pData, iFrame, nFrameOutLen, pFrameOut); + }else{ + return SQLITE_ERROR; + } +} + +int sqlite3PagerWalBeginCommit(Pager *pPager) { + int rc; + if (!pagerUseWal(pPager)) { + return SQLITE_ERROR; + } + rc = pagerBeginReadTransaction(pPager); + if (rc != SQLITE_OK) { + return rc; + } + return pPager->wal->methods.xBeginWriteTransaction(pPager->wal->pData); +} + +int sqlite3PagerWalEndCommit(Pager *pPager) { + if (!pagerUseWal(pPager)) { + return SQLITE_ERROR; + } + return pPager->wal->methods.xEndWriteTransaction(pPager->wal->pData); +} + +int sqlite3PagerWalInsert(Pager *pPager, unsigned int iFrame, void *pBuf, unsigned int nBuf) { + int rc = SQLITE_OK; + + if (!pagerUseWal(pPager)) { + return SQLITE_ERROR; + } + unsigned int mxFrame; + rc = pPager->wal->methods.xFrameCount(pPager->wal->pData, 1, &mxFrame); + if (rc != SQLITE_OK) { + return rc; + } + if (iFrame <= mxFrame) { + return SQLITE_OK; + } + u8 *aFrame = (u8*)pBuf; + u32 pgno = sqlite3Get4byte(&aFrame[0]); + u32 nTruncate = sqlite3Get4byte(&aFrame[4]); + u8 *pData = aFrame + 24; + + PgHdr pghdr; + memset(&pghdr, 0, sizeof(PgHdr)); + pghdr.pPage = NULL; + pghdr.pData = pData; + pghdr.pExtra = NULL; + pghdr.pgno = pgno; + pghdr.flags = 0; + + int isCommit = (nTruncate != 0); + + int nFrames = 0; + rc = pPager->wal->methods.xFrames(pPager->wal->pData, + pPager->pageSize, + &pghdr, + nTruncate, + isCommit, + pPager->walSyncFlags, + &nFrames); + assert( nFrames == 1 ); + return rc; +} + #ifdef SQLITE_ENABLE_SETLK_TIMEOUT /* ** If pager pPager is a wal-mode database not in exclusive locking mode, diff --git a/libsql-sqlite3/src/pager.h b/libsql-sqlite3/src/pager.h index 875b1fc17d..abe7cd6e27 100644 --- a/libsql-sqlite3/src/pager.h +++ b/libsql-sqlite3/src/pager.h @@ -133,6 +133,12 @@ int sqlite3PagerReadFileheader(Pager*, int, unsigned char*); void sqlite3PagerSetBusyHandler(Pager*, int(*)(void *), void *); int sqlite3PagerSetPagesize(Pager*, u32*, int); Pgno sqlite3PagerMaxPageCount(Pager*, Pgno); +int sqlite3PagerWalFrameCount(Pager *, unsigned int *); +int sqlite3PagerWalReadFrame(Pager *, unsigned int, void *, unsigned int); +int sqlite3PagerWalBeginCommit(Pager*); +int sqlite3PagerWalEndCommit(Pager*); +int sqlite3PagerWalInsert(Pager*, unsigned int, void *, unsigned int); + void sqlite3PagerSetCachesize(Pager*, int); int sqlite3PagerSetSpillsize(Pager*, int); void sqlite3PagerSetMmapLimit(Pager *, sqlite3_int64); diff --git a/libsql-sqlite3/src/sqlite.h.in b/libsql-sqlite3/src/sqlite.h.in index 7df3afc632..eb96b55254 100644 --- a/libsql-sqlite3/src/sqlite.h.in +++ b/libsql-sqlite3/src/sqlite.h.in @@ -10549,6 +10549,59 @@ int sqlite3_preupdate_blobwrite(sqlite3 *); */ void *libsql_close_hook(sqlite3 *db, void (*xClose)(void *pCtx, sqlite3 *db), void *arg); +/* +** CAPI3REF: Disable WAL checkpointing +** METHOD: sqlite3 +** +** ^The [libsql_wal_disable_checkpoint(D)] interface disables automatic +** checkpointing of the WAL file for [database connection] D. +** +** Note: This function disables WAL checkpointing entirely, including when +** the last database connection is closed. This is different from +** sqlite3_wal_autocheckpoint() which only disables automatic checkpoints +** for the current connection, but still allows checkpointing when the +** connection is closed. +*/ +int libsql_wal_disable_checkpoint(sqlite3 *db); + +/* +** CAPI3REF: Get the number of frames in the WAL file +** METHOD: sqlite3 +** +** ^The [libsql_wal_frame_count(D,P)] interface returns the number of frames +** in the WAL file for [database connection] D into *P. +*/ +int libsql_wal_frame_count(sqlite3*, unsigned int*); + +/* +** CAPI3REF: Get a frame from the WAL file +** METHOD: sqlite3 +** +** ^The [libsql_wal_get_frame(D,I,P,S)] interface extracts frame I from +** the WAL file for [database connection] D into memory obtained from +** [sqlite3_malloc64()] and returns a pointer to that memory. The size of +** the memory allocated is given by S. +*/ +int libsql_wal_get_frame(sqlite3*, unsigned int, void*, unsigned int); + +/* +** CAPI3REF: Begin frame insertion into the WAL +** METHOD: sqlite3 +*/ +int libsql_wal_insert_begin(sqlite3*); + +/* +** CAPI3REF: End frame insertion into the WAL +** METHOD: sqlite3 +*/ +int libsql_wal_insert_end(sqlite3*); + +/* +** CAPI3REF: Insert a frame into the WAL +** METHOD: sqlite3 +*/ +int libsql_wal_insert_frame(sqlite3*, unsigned int, void *, unsigned int); + /* ** CAPI3REF: Low-level system error code ** METHOD: sqlite3 diff --git a/libsql-sqlite3/src/sqliteInt.h b/libsql-sqlite3/src/sqliteInt.h index 0a9dd98d66..cd9bf41dfa 100644 --- a/libsql-sqlite3/src/sqliteInt.h +++ b/libsql-sqlite3/src/sqliteInt.h @@ -1755,6 +1755,7 @@ struct sqlite3 { PreUpdate *pPreUpdate; /* Context for active pre-update callback */ #endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ #ifndef SQLITE_OMIT_WAL + int walCheckPointDisabled; int (*xWalCallback)(void *, sqlite3 *, const char *, int); void *pWalArg; #endif diff --git a/libsql-sqlite3/src/test_walapi.c b/libsql-sqlite3/src/test_walapi.c new file mode 100644 index 0000000000..f7bdd54628 --- /dev/null +++ b/libsql-sqlite3/src/test_walapi.c @@ -0,0 +1,154 @@ +#include +#include +#include + +#if 0 +static void dump_frame(unsigned char *frame, size_t size){ + for(int addr=0; addrexclusiveMode==WAL_NORMAL_MODE ){ pWal->exclusiveMode = WAL_EXCLUSIVE_MODE; } - rc = sqlite3WalCheckpoint(pWal, db, - SQLITE_CHECKPOINT_PASSIVE, 0, 0, sync_flags, nBuf, zBuf, 0, 0, NULL, NULL - ); + /* Don't checkpoint on close if automatic WAL checkpointing is disabled. */ + if( !db->walCheckPointDisabled ){ + rc = sqlite3WalCheckpoint(pWal, db, + SQLITE_CHECKPOINT_PASSIVE, 0, 0, sync_flags, nBuf, zBuf, 0, 0, NULL, NULL + ); + } else { + rc = SQLITE_ERROR; + } if( rc==SQLITE_OK ){ int bPersist = -1; sqlite3OsFileControlHint( @@ -3386,6 +3391,29 @@ static int sqlite3WalReadFrame( return sqlite3OsRead(pWal->pWalFd, pOut, (nOut>sz ? sz : nOut), iOffset); } +/* +** Read the contents of frame iRead from the wal file into buffer pOut +** (which is nOut bytes in size). Return SQLITE_OK if successful, or an +** error code otherwise. +*/ +static int sqlite3WalReadFrameRaw( + Wal *pWal, /* WAL handle */ + u32 iRead, /* Frame to read */ + int nOut, /* Size of buffer pOut in bytes */ + u8 *pOut /* Buffer to write page data to */ +){ + int sz; + i64 iOffset; + sz = pWal->hdr.szPage; + sz = (sz&0xfe00) + ((sz&0x0001)<<16); + testcase( sz<=32768 ); + testcase( sz>=65536 ); + iOffset = walFrameOffset(iRead, sz); + /* testcase( IS_BIG_INT(iOffset) ); // requires a 4GiB WAL */ + sz += WAL_FRAME_HDRSIZE; + return sqlite3OsRead(pWal->pWalFd, pOut, (nOut>sz ? sz : nOut), iOffset); +} + /* ** Return the size of the database in pages (or zero, if unknown). */ @@ -3996,6 +4024,19 @@ static int walFrames( return rc; } +int sqlite3WalFrameCount(Wal *pWal, int locked, unsigned int *pnFrames){ + int rc = SQLITE_OK; + if( locked==0 ) { + rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1); + if (rc != SQLITE_OK) return rc; + } + *pnFrames = pWal->hdr.mxFrame; + if( locked==0 ) { + walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1); + } + return SQLITE_OK; +} + /* ** Write a set of frames to the log. The caller must hold the write-lock ** on the log file (obtained using sqlite3WalBeginWriteTransaction()). @@ -4495,12 +4536,14 @@ static int sqlite3WalOpen( out->methods.xEndReadTransaction = (void (*)(wal_impl *))sqlite3WalEndReadTransaction; out->methods.xFindFrame = (int (*)(wal_impl *, unsigned int, unsigned int *))sqlite3WalFindFrame; out->methods.xReadFrame = (int (*)(wal_impl *, unsigned int, int, unsigned char *))sqlite3WalReadFrame; + out->methods.xReadFrameRaw = (int (*)(wal_impl *, unsigned int, int, unsigned char *))sqlite3WalReadFrameRaw; out->methods.xDbsize = (unsigned int (*)(wal_impl *))sqlite3WalDbsize; out->methods.xBeginWriteTransaction = (int (*)(wal_impl *))sqlite3WalBeginWriteTransaction; out->methods.xEndWriteTransaction = (int (*)(wal_impl *))sqlite3WalEndWriteTransaction; out->methods.xUndo = (int (*)(wal_impl *, int (*)(void *, unsigned int), void *))sqlite3WalUndo; out->methods.xSavepoint = (void (*)(wal_impl *, unsigned int *))sqlite3WalSavepoint; out->methods.xSavepointUndo = (int (*)(wal_impl *, unsigned int *))sqlite3WalSavepointUndo; + out->methods.xFrameCount = (int (*)(wal_impl *, int, unsigned int *))sqlite3WalFrameCount; out->methods.xFrames = (int (*)(wal_impl *, int, libsql_pghdr *, unsigned int, int, int, int *))sqlite3WalFrames; out->methods.xCheckpoint = (int (*)(wal_impl *, sqlite3 *, int, int (*)(void *), void *, int, int, unsigned char *, int *, int *, int (*)(void*, int, const unsigned char*, int, int, int), void*))sqlite3WalCheckpoint; out->methods.xCallback = (int (*)(wal_impl *))sqlite3WalCallback; diff --git a/libsql-sqlite3/src/wal.h b/libsql-sqlite3/src/wal.h index 1939485002..69df6716d8 100644 --- a/libsql-sqlite3/src/wal.h +++ b/libsql-sqlite3/src/wal.h @@ -56,6 +56,7 @@ typedef struct libsql_wal_methods { /* Read a page from the write-ahead log, if it is present. */ int (*xFindFrame)(wal_impl* pWal, unsigned int, unsigned int *); int (*xReadFrame)(wal_impl* pWal, unsigned int, int, unsigned char *); + int (*xReadFrameRaw)(wal_impl* pWal, unsigned int, int, unsigned char *); /* If the WAL is not empty, return the size of the database. */ unsigned int (*xDbsize)(wal_impl* pWal); @@ -75,6 +76,9 @@ typedef struct libsql_wal_methods { ** response to a ROLLBACK TO command. */ int (*xSavepointUndo)(wal_impl* pWal, unsigned int *aWalData); + /* Return the number of frames in the WAL */ + int (*xFrameCount)(wal_impl* pWal, int, unsigned int *); + /* Write a frame or frames to the log. */ int (*xFrames)(wal_impl* pWal, int, libsql_pghdr *, unsigned int, int, int, int*); diff --git a/libsql-sqlite3/test/rust_suite/src/virtual_wal.rs b/libsql-sqlite3/test/rust_suite/src/virtual_wal.rs index bf847dee27..2395a34b76 100644 --- a/libsql-sqlite3/test/rust_suite/src/virtual_wal.rs +++ b/libsql-sqlite3/test/rust_suite/src/virtual_wal.rs @@ -98,6 +98,18 @@ mod tests { self.0.read_frame(frame_no, buffer) } + fn read_frame_raw( + &mut self, + page_no: NonZeroU32, + buffer: &mut [u8], + ) -> libsql_sys::wal::Result<()> { + self.0.read_frame_raw(page_no, buffer) + } + + fn frame_count(&self, locked: i32) -> libsql_sys::wal::Result { + self.0.frame_count(locked) + } + fn db_size(&self) -> u32 { self.0.db_size() } diff --git a/libsql-storage/src/lib.rs b/libsql-storage/src/lib.rs index 0de68d3117..ce65fe1307 100644 --- a/libsql-storage/src/lib.rs +++ b/libsql-storage/src/lib.rs @@ -266,6 +266,14 @@ impl Wal for DurableWal { Ok(()) } + fn read_frame_raw(&mut self, _page_no: std::num::NonZeroU32, _buffer: &mut [u8]) -> Result<()> { + todo!() + } + + fn frame_count(&self, _locked: i32) -> Result { + todo!() + } + fn db_size(&self) -> u32 { let size = self.local_cache.get_max_frame_num().unwrap(); trace!("DurableWal::db_size() => {}", size); diff --git a/libsql-sys/src/wal/either.rs b/libsql-sys/src/wal/either.rs index 1f9a67609e..dbad73f861 100644 --- a/libsql-sys/src/wal/either.rs +++ b/libsql-sys/src/wal/either.rs @@ -39,6 +39,12 @@ macro_rules! create_either { } } + fn read_frame_raw(&mut self, frame_no: std::num::NonZeroU32, buffer: &mut [u8]) -> super::Result<()> { + match self { + $( $name::$t(inner) => inner.read_frame_raw(frame_no, buffer) ),* + } + } + fn db_size(&self) -> u32 { match self { $( $name::$t(inner) => inner.db_size() ),* @@ -75,6 +81,12 @@ macro_rules! create_either { } } + fn frame_count(&self, locked: i32) -> super::Result { + match self { + $( $name::$t(inner) => inner.frame_count(locked) ),* + } + } + fn insert_frames( &mut self, page_size: std::ffi::c_int, diff --git a/libsql-sys/src/wal/ffi.rs b/libsql-sys/src/wal/ffi.rs index 2dedcd2567..6b1d303d8f 100644 --- a/libsql-sys/src/wal/ffi.rs +++ b/libsql-sys/src/wal/ffi.rs @@ -1,4 +1,4 @@ -use std::ffi::{c_char, c_int, c_longlong, c_void, CStr}; +use std::ffi::{c_char, c_int, c_longlong, c_uint, c_void, CStr}; use std::num::NonZeroU32; use std::ptr::null; @@ -23,12 +23,14 @@ pub(crate) fn construct_libsql_wal(wal: *mut W) -> libsql_wal { xEndReadTransaction: Some(end_read_transaction::), xFindFrame: Some(find_frame::), xReadFrame: Some(read_frame::), + xReadFrameRaw: Some(read_frame_raw::), xDbsize: Some(db_size::), xBeginWriteTransaction: Some(begin_write_transaction::), xEndWriteTransaction: Some(end_write_transaction::), xUndo: Some(undo::), xSavepoint: Some(savepoint::), xSavepointUndo: Some(savepoint_undo::), + xFrameCount: Some(frame_count::), xFrames: Some(frames::), xCheckpoint: Some(checkpoint::), xCallback: Some(callback::), @@ -210,6 +212,23 @@ pub unsafe extern "C" fn read_frame( } } +pub unsafe extern "C" fn read_frame_raw( + wal: *mut wal_impl, + frame: u32, + n_out: c_int, + p_out: *mut u8, +) -> i32 { + let this = &mut (*(wal as *mut T)); + let buffer = std::slice::from_raw_parts_mut(p_out, n_out as usize); + match this.read_frame_raw( + NonZeroU32::new(frame).expect("invalid frame number"), + buffer, + ) { + Ok(_) => SQLITE_OK, + Err(code) => code.extended_code, + } +} + pub unsafe extern "C" fn db_size(wal: *mut wal_impl) -> u32 { let this = &mut (*(wal as *mut T)); this.db_size() @@ -276,6 +295,25 @@ pub unsafe extern "C" fn savepoint_undo(wal: *mut wal_impl, wal_data: *m } } +pub unsafe extern "C" fn frame_count( + wal: *mut wal_impl, + locked: i32, + out: *mut c_uint, +) -> c_int { + let this = &mut (*(wal as *mut T)); + match this.frame_count(locked) { + Ok(n) => { + if !out.is_null() { + unsafe { + *out = n as _; + } + } + SQLITE_OK + } + Err(code) => code.extended_code, + } +} + pub unsafe extern "C" fn frames( wal: *mut wal_impl, page_size: c_int, diff --git a/libsql-sys/src/wal/mod.rs b/libsql-sys/src/wal/mod.rs index 3577cb44e2..71e0c21ee3 100644 --- a/libsql-sys/src/wal/mod.rs +++ b/libsql-sys/src/wal/mod.rs @@ -186,6 +186,8 @@ pub trait Wal { fn find_frame(&mut self, page_no: NonZeroU32) -> Result>; /// reads frame `frame_no` into buffer. fn read_frame(&mut self, frame_no: NonZeroU32, buffer: &mut [u8]) -> Result<()>; + /// reads frame `frame_no` including its frame header into buffer. + fn read_frame_raw(&mut self, frame_no: NonZeroU32, buffer: &mut [u8]) -> Result<()>; fn db_size(&self) -> u32; @@ -197,6 +199,8 @@ pub trait Wal { fn savepoint(&mut self, rollback_data: &mut [u32]); fn savepoint_undo(&mut self, rollback_data: &mut [u32]) -> Result<()>; + fn frame_count(&self, locked: i32) -> Result; + /// Insert frames in the wal. On commit, returns the number of inserted frames for that /// transaction, or 0 for non-commit calls. fn insert_frames( diff --git a/libsql-sys/src/wal/sqlite3_wal.rs b/libsql-sys/src/wal/sqlite3_wal.rs index 548434a2a2..0764a79cd9 100644 --- a/libsql-sys/src/wal/sqlite3_wal.rs +++ b/libsql-sys/src/wal/sqlite3_wal.rs @@ -203,6 +203,22 @@ impl Wal for Sqlite3Wal { } } + fn read_frame_raw(&mut self, frame_no: NonZeroU32, buffer: &mut [u8]) -> Result<()> { + let rc = unsafe { + (self.inner.methods.xReadFrameRaw.unwrap())( + self.inner.pData, + frame_no.into(), + buffer.len() as _, + buffer.as_mut_ptr(), + ) + }; + if rc != 0 { + Err(Error::new(rc)) + } else { + Ok(()) + } + } + fn db_size(&self) -> u32 { unsafe { (self.inner.methods.xDbsize.unwrap())(self.inner.pData) } } @@ -272,6 +288,18 @@ impl Wal for Sqlite3Wal { } } + fn frame_count(&self, locked: i32) -> Result { + let mut out: u32 = 0; + let rc = unsafe { + (self.inner.methods.xFrameCount.unwrap())(self.inner.pData, locked, &mut out) + }; + if rc != 0 { + Err(Error::new(rc)) + } else { + Ok(out) + } + } + fn insert_frames( &mut self, page_size: c_int, diff --git a/libsql-sys/src/wal/wrapper.rs b/libsql-sys/src/wal/wrapper.rs index 713ba0347b..4f3fdb7159 100644 --- a/libsql-sys/src/wal/wrapper.rs +++ b/libsql-sys/src/wal/wrapper.rs @@ -61,6 +61,10 @@ impl, W: Wal> Wal for WalRef { unsafe { (*self.wrapper).read_frame(&mut *self.wrapped, frame_no, buffer) } } + fn read_frame_raw(&mut self, frame_no: NonZeroU32, buffer: &mut [u8]) -> super::Result<()> { + unsafe { (*self.wrapper).read_frame_raw(&mut *self.wrapped, frame_no, buffer) } + } + fn db_size(&self) -> u32 { unsafe { (*self.wrapper).db_size(&*self.wrapped) } } @@ -85,6 +89,10 @@ impl, W: Wal> Wal for WalRef { unsafe { (*self.wrapper).savepoint_undo(&mut *self.wrapped, rollback_data) } } + fn frame_count(&self, locked: i32) -> super::Result { + unsafe { (*self.wrapper).frame_count(&*self.wrapped, locked) } + } + fn insert_frames( &mut self, page_size: c_int, @@ -234,6 +242,11 @@ where self.wrapper.read_frame(&mut self.wrapped, frame_no, buffer) } + fn read_frame_raw(&mut self, frame_no: NonZeroU32, buffer: &mut [u8]) -> super::Result<()> { + self.wrapper + .read_frame_raw(&mut self.wrapped, frame_no, buffer) + } + fn db_size(&self) -> u32 { self.wrapper.db_size(&self.wrapped) } @@ -259,6 +272,10 @@ where .savepoint_undo(&mut self.wrapped, rollback_data) } + fn frame_count(&self, locked: i32) -> super::Result { + self.wrapper.frame_count(&self.wrapped, locked) + } + fn insert_frames( &mut self, page_size: std::ffi::c_int, @@ -355,6 +372,15 @@ pub trait WrapWal { wrapped.read_frame(frame_no, buffer) } + fn read_frame_raw( + &mut self, + wrapped: &mut W, + frame_no: NonZeroU32, + buffer: &mut [u8], + ) -> super::Result<()> { + wrapped.read_frame_raw(frame_no, buffer) + } + fn db_size(&self, wrapped: &W) -> u32 { wrapped.db_size() } @@ -383,6 +409,10 @@ pub trait WrapWal { wrapped.savepoint_undo(rollback_data) } + fn frame_count(&self, wrapped: &W, locked: i32) -> super::Result { + wrapped.frame_count(locked) + } + fn insert_frames( &mut self, wrapped: &mut W, @@ -562,6 +592,19 @@ where self.0.read_frame(&mut r, frame_no, buffer) } + fn read_frame_raw( + &mut self, + wrapped: &mut W, + frame_no: NonZeroU32, + buffer: &mut [u8], + ) -> super::Result<()> { + let mut r = WalRef { + wrapper: &mut self.1, + wrapped, + }; + self.0.read_frame_raw(&mut r, frame_no, buffer) + } + fn db_size(&self, wrapped: &W) -> u32 { let r = WalRef { wrapper: &self.1 as *const B as *mut B, @@ -778,6 +821,18 @@ impl, W: Wal> WrapWal for Option { } } + fn read_frame_raw( + &mut self, + wrapped: &mut W, + frame_no: NonZeroU32, + buffer: &mut [u8], + ) -> super::Result<()> { + match self { + Some(t) => t.read_frame_raw(wrapped, frame_no, buffer), + None => wrapped.read_frame_raw(frame_no, buffer), + } + } + fn db_size(&self, wrapped: &W) -> u32 { match self { Some(t) => t.db_size(wrapped), diff --git a/libsql-wal/src/wal.rs b/libsql-wal/src/wal.rs index 8b057dfc06..6c89ce848f 100644 --- a/libsql-wal/src/wal.rs +++ b/libsql-wal/src/wal.rs @@ -170,6 +170,15 @@ where Ok(()) } + #[tracing::instrument(skip_all, fields(id = self.conn_id))] + fn read_frame_raw( + &mut self, + _page_no: std::num::NonZeroU32, + _buffer: &mut [u8], + ) -> libsql_sys::wal::Result<()> { + Err(libsql_sys::wal::Error::new(10)) // SQLITE_IOERR + } + #[tracing::instrument(skip_all, fields(id = self.conn_id))] fn db_size(&self) -> u32 { let db_size = match self.tx.as_ref() { @@ -266,6 +275,11 @@ where } } + #[tracing::instrument(skip_all, fields(id = self.conn_id))] + fn frame_count(&self, _locked: i32) -> libsql_sys::wal::Result { + Err(libsql_sys::wal::Error::new(10)) // SQLITE_IOERR + } + #[tracing::instrument(skip_all, fields(id = self.conn_id))] fn insert_frames( &mut self, diff --git a/libsql/src/connection.rs b/libsql/src/connection.rs index 9cac629743..08966f5aad 100644 --- a/libsql/src/connection.rs +++ b/libsql/src/connection.rs @@ -245,3 +245,9 @@ impl Connection { self.conn.load_extension(dylib_path.as_ref(), entry_point) } } + +impl fmt::Debug for Connection { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Connection").finish() + } +}