diff --git a/libsql-ffi/bundled/src/sqlite3.c b/libsql-ffi/bundled/src/sqlite3.c index 82db050d36..91907325e0 100644 --- a/libsql-ffi/bundled/src/sqlite3.c +++ b/libsql-ffi/bundled/src/sqlite3.c @@ -10938,6 +10938,44 @@ SQLITE_API int sqlite3_preupdate_blobwrite(sqlite3 *); */ SQLITE_API void *libsql_close_hook(sqlite3 *db, void (*xClose)(void *pCtx, sqlite3 *db), void *arg); +/* +** 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 a WAL commit +** METHOD: sqlite3 +*/ +SQLITE_API int libsql_wal_insert_begin(sqlite3*); + +/* +** CAPI3REF: End a WAL commit +** METHOD: sqlite3 +*/ +SQLITE_API int libsql_wal_insert_end(sqlite3*); + +/* +** CAPI3REF: Insert a frame into the WAL file +** 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 +14001,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); @@ -16378,6 +16417,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 unsigned int sqlite3PagerWalFrameCount(Pager *); +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); @@ -57270,6 +57315,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); @@ -65214,6 +65260,103 @@ SQLITE_PRIVATE int sqlite3PagerCloseWal(Pager *pPager, sqlite3 *db){ return rc; } +SQLITE_PRIVATE unsigned int sqlite3PagerWalFrameCount(Pager *pPager){ + if( pagerUseWal(pPager) ){ + // TODO: We are under sqlite3 mutex, but do we need something else? + struct sqlite3_wal* pWal = (void*) pPager->wal->pData; + return pWal->hdr.mxFrame; + }else{ + return 0; + } +} + +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; + } +} + +static void frame_to_pghdr(PgHdr *pPghdr, unsigned int pgno, void *pData) { + pPghdr->pPage = NULL; /* Pcache object page handle */ + pPghdr->pData = pData; /* Page data */ + pPghdr->pExtra = NULL; /* Extra content */ + pPghdr->pCache = NULL; /* PRIVATE: Cache that owns this page */ + pPghdr->pDirty = NULL; /* Transient list of dirty sorted by pgno */ + pPghdr->pPager = NULL; /* The pager this page is part of */ + pPghdr->pgno = pgno; /* Page number for this page */ + pPghdr->pageHash = 0; /* Hash of page content */ + pPghdr->flags = 0; /* PGHDR flags defined below */ + pPghdr->nRef = 1; /* Number of users of this page */ + pPghdr->pDirtyNext = NULL; /* Next element in list of dirty pages */ + pPghdr->pDirtyPrev = NULL; /* Previous element in list of dirty pages */ +} + +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; + + // Check if WAL mode is enabled + if (!pagerUseWal(pPager)) { + return SQLITE_ERROR; + } + + // Extract information from the WAL frame + u8 *aFrame = (u8*)pBuf; + u32 pgno = sqlite3Get4byte(&aFrame[0]); + u32 nTruncate = sqlite3Get4byte(&aFrame[4]); + u8 *pData = aFrame + 24; + + // Prepare the PgHdr structure + PgHdr pghdr; + memset(&pghdr, 0, sizeof(PgHdr)); + pghdr.pPage = NULL; + pghdr.pData = pData; + pghdr.pExtra = NULL; + pghdr.pgno = pgno; + pghdr.flags = 0; + + // Determine if this is a commit frame + int isCommit = (nTruncate != 0); + + // Call xFrames + int nFrames = 0; + rc = pPager->wal->methods.xFrames(pPager->wal->pData, + pPager->pageSize, + &pghdr, + nTruncate, + isCommit, + pPager->walSyncFlags, + &nFrames); + return rc; +} + #ifdef SQLITE_ENABLE_SETLK_TIMEOUT /* ** If pager pPager is a wal-mode database not in exclusive locking mode, @@ -67601,9 +67744,10 @@ 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 - ); + rc = SQLITE_ERROR; + //rc = sqlite3WalCheckpoint(pWal, db, + // SQLITE_CHECKPOINT_PASSIVE, 0, 0, sync_flags, nBuf, zBuf, 0, 0, NULL, NULL + //); if( rc==SQLITE_OK ){ int bPersist = -1; sqlite3OsFileControlHint( @@ -68731,6 +68875,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). */ @@ -69840,6 +70007,7 @@ 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; @@ -182972,6 +183140,137 @@ void *libsql_close_hook( return pRet; } +/* +** 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); + *pnFrame = sqlite3PagerWalFrameCount(pPager); + 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. diff --git a/libsql-ffi/bundled/src/sqlite3.h b/libsql-ffi/bundled/src/sqlite3.h index d526834332..4f866dc3c2 100644 --- a/libsql-ffi/bundled/src/sqlite3.h +++ b/libsql-ffi/bundled/src/sqlite3.h @@ -10549,6 +10549,44 @@ SQLITE_API int sqlite3_preupdate_blobwrite(sqlite3 *); */ SQLITE_API void *libsql_close_hook(sqlite3 *db, void (*xClose)(void *pCtx, sqlite3 *db), void *arg); +/* +** 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 a WAL commit +** METHOD: sqlite3 +*/ +SQLITE_API int libsql_wal_insert_begin(sqlite3*); + +/* +** CAPI3REF: End a WAL commit +** METHOD: sqlite3 +*/ +SQLITE_API int libsql_wal_insert_end(sqlite3*); + +/* +** CAPI3REF: Insert a frame into the WAL file +** 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 +13612,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);