diff --git a/include/sqlite3.h b/include/sqlite3.h index 3b1a37a5f9f9dd316545f5714549933f223b70b8..34f181bf8e8d71f4a630f4eac4b7452b4cb56160 100644 --- a/include/sqlite3.h +++ b/include/sqlite3.h @@ -2548,6 +2548,7 @@ struct sqlite3_mem_methods { #define SQLITE_DBCONFIG_STMT_SCANSTATUS 1018 /* int int* */ #define SQLITE_DBCONFIG_REVERSE_SCANORDER 1019 /* int int* */ #define SQLITE_DBCONFIG_MAX 1019 /* Largest DBCONFIG */ +#define SQLITE_DBCONFIG_ENABLE_BINLOG 2006 /* Sqlite3BinlogConfig */ /* ** CAPI3REF: Enable Or Disable Extended Result Codes @@ -3258,6 +3259,24 @@ SQLITE_API int sqlite3_set_droptable_handle( ); #endif /* SQLITE_ENABLE_DROPTABLE_CALLBACK */ +SQLITE_API int sqlite3_is_support_binlog(void); + +SQLITE_API int sqlite3_replay_binlog( + sqlite3 *srcDb, + sqlite3 *destDb +); + +typedef enum BinlogFileCleanMode { + BINLOG_FILE_CLEAN_ALL_MODE = 0, + BINLOG_FILE_CLEAN_READ_MODE = 1, + BINLOG_FILE_CLEAN_MODE_MAX, +} BinlogFileCleanModeE; + +SQLITE_API int sqlite3_clean_binlog( + sqlite3 *db, + BinlogFileCleanModeE mode +); + /* ** CAPI3REF: Authorizer Return Codes ** @@ -13478,6 +13497,26 @@ struct fts5_api { ** END OF REGISTRATION API *************************************************************************/ +/************************************************************************* +** BINLOG CONFIG +*/ +typedef enum { + ROW = 0, +} Sqlite3BinlogMode; + +typedef struct Sqlite3BinlogConfig { + Sqlite3BinlogMode mode; + unsigned short fullCallbackThreshold; + unsigned int maxFileSize; + void (*xErrorCallback)(void *pCtx, int errNo, char *errMsg); + void (*xLogFullCallback)(void *pCtx, unsigned short currentCount); + void *callbackCtx; +} Sqlite3BinlogConfig; + +/* +** END OF BINLOG CONFIG +*************************************************************************/ + #ifdef __cplusplus } /* end of the 'extern "C"' block */ #endif diff --git a/include/sqlite3ext.h b/include/sqlite3ext.h index 437cd3ebab7d3ca3f696b0fb9629abdecd42ed94..4071bad7b960f4695c429d5e77bf7e0666cb4602 100644 --- a/include/sqlite3ext.h +++ b/include/sqlite3ext.h @@ -368,6 +368,11 @@ struct sqlite3_api_routines { int (*set_clientdata)(sqlite3*, const char*, void*, void(*)(void*)); /* handle after drop table done */ int (*set_droptable_handle)(sqlite3*,void(*)(sqlite3*,const char*,const char*)); + int (*is_support_binlog)(void); + /* replay binlog from src db to the dest db */ + int (*replay_binlog)(sqlite3*, sqlite3*); + /* clean the binlog of the db */ + int (*clean_binlog)(sqlite3*, BinlogFileCleanModeE); }; /* @@ -707,6 +712,11 @@ extern const sqlite3_api_routines *sqlite3_export_symbols; #define sqlite3_set_clientdata sqlite3_api->set_clientdata /* handle after drop table done */ #define sqlite3_set_droptable_handle sqlite3_api->set_droptable_handle +#define sqlite3_is_support_binlog sqlite3_api->is_support_binlog +/* replay binlog from src db to the dest db */ +#define sqlite3_replay_binlog sqlite3_api->replay_binlog +/* clean the binlog of the db */ +#define sqlite3_clean_binlog sqlite3_api->clean_binlog #endif /* !defined(SQLITE_CORE) && (!defined(SQLITE_OMIT_LOAD_EXTENSION) || defined(SQLITE3_EXPORT_SYMBOLS)) */ #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) diff --git a/patch/0007-SupportBinlog.patch b/patch/0007-SupportBinlog.patch new file mode 100644 index 0000000000000000000000000000000000000000..a1c83bc4bf5c828654a59d628108f4447bcfd000 --- /dev/null +++ b/patch/0007-SupportBinlog.patch @@ -0,0 +1,1487 @@ +From 121b11aef446208388e302612051619deb9087c7 Mon Sep 17 00:00:00 2001 +From: Liu Hongyang +Date: Mon, 28 Apr 2025 18:59:21 +0800 +Subject: [PATCH] binlog + +Signed-off-by: Liu Hongyang +--- + src/sqlite3.c | 1214 ++++++++++++++++++++++++++++++++++++++++++++++++- + 1 file changed, 1210 insertions(+), 4 deletions(-) + +diff --git a/src/sqlite3.c b/src/sqlite3.c +index 7e9dcbf..3a6a372 100644 +--- a/src/sqlite3.c ++++ b/src/sqlite3.c +@@ -2868,6 +2868,7 @@ struct sqlite3_mem_methods { + #define SQLITE_DBCONFIG_SET_SHAREDBLOCK 2004 + #define SQLITE_DBCONFIG_USE_SHAREDBLOCK 2005 + #endif /* SQLITE_SHARED_BLOCK_OPTIMIZATION */ ++#define SQLITE_DBCONFIG_ENABLE_BINLOG 2006 /* Sqlite3BinlogConfig */ + + /* + ** CAPI3REF: Enable Or Disable Extended Result Codes +@@ -5355,6 +5356,18 @@ SQLITE_API int sqlite3_step(sqlite3_stmt*); + SQLITE_API int sqlite3_set_droptable_handle(sqlite3*, void (*xFunc)(sqlite3*,const char*,const char*)); + #endif /* SQLITE_ENABLE_DROPTABLE_CALLBACK */ + ++SQLITE_API int sqlite3_is_support_binlog(void); ++ ++SQLITE_API int sqlite3_replay_binlog(sqlite3 *srcDb, sqlite3 *destDb); ++ ++typedef enum BinlogFileCleanMode { ++ BINLOG_FILE_CLEAN_ALL_MODE = 0, ++ BINLOG_FILE_CLEAN_READ_MODE = 1, ++ BINLOG_FILE_CLEAN_MODE_MAX, ++} BinlogFileCleanModeE; ++ ++SQLITE_API int sqlite3_clean_binlog(sqlite3 *db, BinlogFileCleanModeE mode); ++ + /* + ** CAPI3REF: Number of columns in a result set + ** METHOD: sqlite3_stmt +@@ -17751,6 +17764,193 @@ typedef struct CodecParameter { + } CodecParameter; + #endif /* defined(SQLITE_HAS_CODEC) && defined(SQLITE_CODEC_ATTACH_CHANGED) */ + ++/************** Begin of the header file of binlog ************************************/ ++#define SQLITE_UUID_BLOB_LENGTH 16 ++ ++typedef enum { ++ ROW = 0, ++} Sqlite3BinlogMode; ++ ++typedef enum { ++ GMERR_OK = 0, ++ GMERR_BASE = 1000000, ++ GMERR_LOCK_NOT_AVAILABLE = GMERR_BASE + 12002, ++ GMERR_BINLOG_READ_FINISH = GMERR_BASE + 29200, ++} BinlogErrnoE; ++ ++typedef int BinlogErrno; ++typedef void (*BinlogOnErrorFuncT)(void *pCtx, BinlogErrno errNo, char *errMsg); ++typedef void (*BinlogOnLogFullFuncT)(void *pCtx, u16 currentCount); ++ ++typedef struct BinlogConfig { ++ Sqlite3BinlogMode logMode; ++ u16 fullCallbackThreshold; ++ u32 maxFileSize; ++ char *filePath; ++ BinlogOnErrorFuncT onError; ++ BinlogOnLogFullFuncT onLogFull; ++ void *callbackCtx; ++ u32 readHwmdelay; ++} BinlogConfigT; ++ ++typedef enum BinlogEventType { ++ BINLOG_EVENT_TYPE_UNDEFINED = 0, ++ BINLOG_EVENT_TYPE_DDL = 64, ++ BINLOG_EVENT_TYPE_PRAGMA = 65, ++ BINLOG_EVENT_TYPE_DML = 66, ++ BINLOG_EVENT_TYPE_ROLLBACK = 67, ++ BINLOG_EVENT_TYPE_BUTT, ++} BinlogEventTypeE; ++ ++typedef u8 BinlogXidT[SQLITE_UUID_BLOB_LENGTH]; ++ ++typedef struct BinlogWriteData { ++ BinlogEventTypeE type; ++ BinlogXidT xid; ++ char *data; ++ u32 dataLength; ++ int isFinishTrx; ++} BinlogWriteDataT; ++ ++typedef struct BinlogEventHead { ++ u32 checksum; ++ u64 timestamp; ++ u8 eventType; ++ u32 eventLength; ++ BinlogXidT xid; ++ u64 nextEventPos; ++} BinlogEventHeadT; ++ ++typedef struct BinlogEvent { ++ BinlogEventHeadT head; ++ u8 *body; ++} BinlogEventT; ++ ++typedef struct BinlogReadResult { ++ u32 eventNum; ++ BinlogEventT **sqlEvent; ++} BinlogReadResultT; ++ ++typedef struct BinlogInstanceT BinlogInstanceT; ++typedef void (*BinlogFreeReadResult)(BinlogInstanceT *instance, BinlogReadResultT *readRes); ++typedef BinlogErrno (*BinlogOpen)(const BinlogConfigT *, BinlogInstanceT **); ++typedef BinlogErrno (*BinlogClose)(BinlogInstanceT *instance); ++typedef BinlogErrno (*BinlogWrite)(BinlogInstanceT *instance, const BinlogWriteDataT *data); ++typedef BinlogErrno (*BinlogRead)(BinlogInstanceT *instance, BinlogReadResultT **readRes); ++typedef BinlogErrno (*BinlogFileClean)(BinlogInstanceT *instance, BinlogFileCleanModeE cleanMode); ++typedef BinlogErrno (*BinlogLockRead)(BinlogInstanceT *instance); ++typedef BinlogErrno (*BinlogUnlockRead)(BinlogInstanceT *instance); ++/************** End of the header file of binlog ************************************/ ++ ++/************** Binlog typedef in sqlite3 BEGIN ************************************/ ++#define BINLOG_FLAG_ENABLE 0x00000001 ++#define BINLOG_FLAG_UPDATE_TID 0x00000002 ++ ++typedef struct Sqlite3BinlogConfig { ++ Sqlite3BinlogMode mode; ++ u16 fullCallbackThreshold; ++ u32 maxFileSize; ++ void (*xErrorCallback)(void *pCtx, int errNo, char *errMsg); ++ void (*xLogFullCallback)(void *pCtx, u16 currentCount); ++ void *callbackCtx; ++} Sqlite3BinlogConfig; ++ ++typedef struct BinlogRow { ++ int op; // one of the SQLITE_INSERT, SQLITE_UPDATE, SQLITE_DELETE ++ sqlite3_int64 rowid; // rowid of changed data, -1 for without rowid table ++ sqlite3_uint64 nData; // size of pData ++ const char *pData; // pointer to the row data in record format ++} BinlogRow; ++ ++/* stores the affected rows by one DML statement*/ ++typedef struct BinlogDMLData { ++ Table *pTable; // pointer to the table being modified ++ char *pSavePointName; // savepoint name for all the rows modified by current statment ++ int isSavePointReleased; // if the savepoint is released, used when free BinlogDMLData ++} BinlogDMLData; ++ ++typedef void* BinlogLib; ++ ++typedef struct BinlogApi { ++ BinlogLib binlogLib; ++ BinlogOpen binlogOpenApi; ++ BinlogClose binlogCloseApi; ++ BinlogWrite binlogWriteApi; ++ BinlogRead binlogReadApi; ++ BinlogFreeReadResult binlogFreeReadResultApi; ++ BinlogFileClean binlogFileCleanApi; ++ BinlogLockRead binlogLockReadApi; ++ BinlogUnlockRead binlogUnlockReadApi; ++} BinlogApi; ++ ++typedef struct Sqlite3BinlogHandle { ++ Sqlite3BinlogMode mode; ++ void *callbackCtx; ++ u64 flags; ++ u8 xTid[SQLITE_UUID_BLOB_LENGTH]; ++ void (*xErrorCallback)(void *pCtx, int errNo, char *errMsg); ++ void (*xLogFullCallback)(void *pCtx, u16 currentCount); ++ BinlogInstanceT *binlogConn; ++ BinlogApi binlogApi; ++} Sqlite3BinlogHandle; ++ ++typedef enum { ++ STMT_TYPE_DML = 0, ++ STMT_TYPE_CREATE_TABLE, ++ STMT_TYPE_CREATE_INDEX, ++ STMT_TYPE_CREATE_TRIGGER, ++ STMT_TYPE_CREATE_VIEW, ++ STMT_TYPE_DROP_TABLE, ++ STMT_TYPE_DROP_INDEX, ++ STMT_TYPE_DROP_TRIGGER, ++ STMT_TYPE_DROP_VIEW, ++ STMT_TYPE_ALTER_ADD_COL, ++ STMT_TYPE_ALTER_RENAME_COL, ++ STMT_TYPE_ALTER_RENAME_TABLE, ++ STMT_TYPE_ALTER_DROP_COL, ++ STMT_TYPE_BEGIN_TRANSACTION, ++ STMT_TYPE_COMMIT_TRANSACTION, ++ STMT_TYPE_ROLLBACK_TRANSACTION, ++ STMT_TYPE_PRAGMA, ++ STMT_TYPE_SAVEPOINT, ++} StmtType; ++ ++typedef struct Sqlite3BinlogStmt { ++ BinlogReadResultT *cursor; ++ u32 curIdx; ++} Sqlite3BinlogStmt; ++ ++typedef struct Sqlite3BinlogApiInfo { ++ void **funcP; ++ const char *funcN; ++} Sqlite3BinlogApiInfo; ++ ++SQLITE_PRIVATE int sqlite3BinlogStmtPrepare(sqlite3 *db, Sqlite3BinlogStmt *bStmt); ++SQLITE_PRIVATE int sqlite3BinlogStmtStep(Sqlite3BinlogStmt *bStmt, char **sql); ++SQLITE_PRIVATE void sqlite3BinlogStmtFinalize(sqlite3 *db, Sqlite3BinlogStmt *bStmt); ++ ++SQLITE_PRIVATE int sqlite3BinlogInitApi(sqlite3 *db); ++SQLITE_PRIVATE void sqlite3SetBinlogErrorCallback(void *pCtx, int errNo, char *errMsg); ++SQLITE_PRIVATE void sqlite3SetBinlogFullCallback(void *pCtx, u16 currentCount); ++SQLITE_PRIVATE int sqlite3SetBinLogConfig(sqlite3 *db, Sqlite3BinlogConfig *bConfig); ++SQLITE_PRIVATE int sqlite3TransferBinlogErrno(BinlogErrno err); ++ ++SQLITE_PRIVATE void sqlite3BinlogWrite(Vdbe *p); ++SQLITE_PRIVATE int sqlite3BinlogClose(sqlite3 *db); ++SQLITE_PRIVATE void sqlite3BinlogReset(sqlite3 *db); ++SQLITE_PRIVATE int sqlite3BinlogReplay(sqlite3 *srcDb, sqlite3 *destDb); ++SQLITE_PRIVATE int sqlite3BinlogClean(sqlite3 *db, BinlogFileCleanModeE mode); ++SQLITE_PRIVATE void sqlite3BinlogErrorCallback(sqlite3 *db, int errNo, char *errMsg); ++SQLITE_PRIVATE BinlogEventTypeE sqlite3TransferLogEventType(StmtType stmtType); ++SQLITE_PRIVATE int sqlite3IsSkipWriteBinlog(Vdbe *p); ++SQLITE_PRIVATE int sqlite3IsRowBasedBinlog(Vdbe *p); ++SQLITE_PRIVATE Table *sqlite3BinlogFindTable(BtCursor *pC); ++SQLITE_PRIVATE void sqlite3StoreBinlogRowData(Vdbe *p, BtCursor *pCursor, sqlite3_uint64 nData, ++ const char *pData, int op, sqlite3_int64 rowid); ++SQLITE_PRIVATE void sqlite3FreeBinlogRowData(Vdbe *p); ++SQLITE_PRIVATE char *sqlite3BinlogGetNthCol(sqlite3 *db, const Table *pTab, const BinlogRow *pRow, int iCol); ++/************** Binlog typedef in sqlite3 END ************************************/ ++ + /* + ** Each database connection is an instance of the following structure. + */ +@@ -17904,6 +18104,7 @@ struct sqlite3 { + #if defined(SQLITE_HAS_CODEC) && defined(SQLITE_CODEC_ATTACH_CHANGED) + CodecParameter codecParm; + #endif /* defined(SQLITE_HAS_CODEC) && defined(SQLITE_CODEC_ATTACH_CHANGED) */ ++ Sqlite3BinlogHandle xBinlogHandle; + }; + + /* +@@ -23831,6 +24032,11 @@ struct Vdbe { + int startPos; + int addedRows; + #endif /* SQLITE_SHARED_BLOCK_OPTIMIZATION */ ++ ++/* begin of binlog related field */ ++ StmtType stmtType; /* Type of binlog statement */ ++ BinlogDMLData *pBinlogDMLData; /* Data for binlog row-based DML statement */ ++/* end of binlog related field */ + }; + + /* +@@ -89228,6 +89434,10 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){ + } + } + ++ if ( rc==SQLITE_OK ){ ++ sqlite3BinlogWrite(p); ++ } ++ + /* Do the commit only if all databases successfully complete phase 1. + ** If one of the BtreeCommitPhaseOne() calls fails, this indicates an + ** IO error while deleting or truncating a journal file. It is unlikely, +@@ -89375,6 +89585,7 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){ + ** transaction is already guaranteed, but some stray 'cold' journals + ** may be lying around. Returning an error code won't help matters. + */ ++ sqlite3BinlogWrite(p); + disable_simulated_io_errors(); + sqlite3BeginBenignMalloc(); + for(i=0; inDb; i++){ +@@ -89660,6 +89871,7 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){ + }else if( eStatementOp==0 ){ + if( p->rc==SQLITE_OK || p->errorAction==OE_Fail ){ + eStatementOp = SAVEPOINT_RELEASE; ++ sqlite3BinlogWrite(p); + }else if( p->errorAction==OE_Abort ){ + eStatementOp = SAVEPOINT_ROLLBACK; + }else{ +@@ -89883,6 +90095,7 @@ SQLITE_PRIVATE int sqlite3VdbeReset(Vdbe *p){ + } + } + #endif ++ sqlite3FreeBinlogRowData(p); + return p->rc & db->errMask; + } + +@@ -89986,6 +90199,7 @@ static void sqlite3VdbeClearObject(sqlite3 *db, Vdbe *p){ + sqlite3DbFree(db, p->aScan); + } + #endif ++ sqlite3FreeBinlogRowData(p); + } + + /* +@@ -92685,6 +92899,43 @@ SQLITE_API int sqlite3_set_droptable_handle(sqlite3 *db, void (*xFunc)(sqlite3*, + return 0; + } + #endif /* SQLITE_ENABLE_DROPTABLE_CALLBACK */ ++ ++SQLITE_API int sqlite3_is_support_binlog(void) ++{ ++ return SQLITE_ERROR; ++} ++ ++SQLITE_API int sqlite3_replay_binlog(sqlite3 *srcDb, sqlite3 *destDb) ++{ ++ if (srcDb == NULL || destDb == NULL) { ++ sqlite3_log(SQLITE_ERROR, "replay binlog parameter is null"); ++ return SQLITE_ERROR; ++ } ++ int rc = SQLITE_OK; ++ sqlite3_mutex_enter(srcDb->mutex); ++ rc = sqlite3BinlogReplay(srcDb, destDb); ++ if (rc != SQLITE_OK) { ++ sqlite3_log(rc, "replay binlog err:%d", rc); ++ } ++ sqlite3_mutex_leave(srcDb->mutex); ++ return rc; ++} ++ ++SQLITE_API int sqlite3_clean_binlog(sqlite3 *db, BinlogFileCleanModeE mode) ++{ ++ if (db == NULL) { ++ sqlite3_log(SQLITE_ERROR, "clean binlog parameter is null"); ++ return SQLITE_ERROR; ++ } ++ int rc = SQLITE_OK; ++ sqlite3_mutex_enter(db->mutex); ++ rc = sqlite3BinlogClean(db, mode); ++ if (rc != SQLITE_OK) { ++ sqlite3_log(rc, "clean binlog err:%d", rc); ++ } ++ sqlite3_mutex_leave(db->mutex); ++ return rc; ++} + + /* + ** This is the top-level implementation of sqlite3_step(). Call +@@ -100393,6 +100644,13 @@ case OP_Insert: { + } + x.pKey = 0; + assert( BTREE_PREFORMAT==OPFLAG_PREFORMAT ); ++ if ( pOp->p4.pTab ++ && !sqlite3IsSkipWriteBinlog(p) ++ && sqlite3IsRowBasedBinlog(p) ++ && sqlite3TransferLogEventType(p->stmtType) == BINLOG_EVENT_TYPE_DML ) { ++ sqlite3StoreBinlogRowData(p, pC->uc.pCursor, x.nData, pData->z, ++ (pOp->p5 & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_INSERT, x.nKey); ++ } + rc = sqlite3BtreeInsert(pC->uc.pCursor, &x, + (pOp->p5 & (OPFLAG_APPEND|OPFLAG_SAVEPOSITION|OPFLAG_PREFORMAT)), + seekResult +@@ -100509,6 +100767,20 @@ case OP_Delete: { + } + #endif + ++ if( !sqlite3IsSkipWriteBinlog(p) ++ && sqlite3IsRowBasedBinlog(p) ++ && sqlite3TransferLogEventType(p->stmtType) == BINLOG_EVENT_TYPE_DML ){ ++ if (pC->isTable) { ++ sqlite3StoreBinlogRowData(p, pC->uc.pCursor, 0, NULL, SQLITE_DELETE, sqlite3BtreeIntegerKey(pC->uc.pCursor)); ++ } else { ++ Table *pBinlogTab = sqlite3BinlogFindTable(pC->uc.pCursor); ++ if (pBinlogTab != NULL && !HasRowid(pBinlogTab)) { ++ /* If pC is not a table, then this is a delete operation in a without rowid table */ ++ sqlite3StoreBinlogRowData(p, pC->uc.pCursor, pC->szRow, (const char *)pC->aRow, SQLITE_DELETE, -1); ++ } ++ } ++ } ++ + /* If the update-hook or pre-update-hook will be invoked, set zDb to + ** the name of the db to pass as to it. Also set local pTab to a copy + ** of p4.pTab. Finally, if p5 is true, indicating that this cursor was +@@ -101141,6 +101413,12 @@ case OP_IdxInsert: { /* in2 */ + x.pKey = pIn2->z; + x.aMem = aMem + pOp->p3; + x.nMem = (u16)pOp->p4.i; ++ if( (pOp->p5 & OPFLAG_NCHANGE) ++ && !sqlite3IsSkipWriteBinlog(p) ++ && sqlite3IsRowBasedBinlog(p) ++ && sqlite3TransferLogEventType(p->stmtType) == BINLOG_EVENT_TYPE_DML ){ ++ sqlite3StoreBinlogRowData(p, pC->uc.pCursor, x.nKey, pIn2->z, SQLITE_INSERT, -1); ++ } + rc = sqlite3BtreeInsert(pC->uc.pCursor, &x, + (pOp->p5 & (OPFLAG_APPEND|OPFLAG_SAVEPOSITION|OPFLAG_PREFORMAT)), + ((pOp->p5 & OPFLAG_USESEEKRESULT) ? pC->seekResult : 0) +@@ -117998,6 +118276,9 @@ SQLITE_PRIVATE void sqlite3AlterRenameTable( + renameReloadSchema(pParse, iDb, INITFLAG_AlterRename); + renameTestSchema(pParse, zDb, iDb==1, "after rename", 0); + ++ if( v ){ ++ v->stmtType = STMT_TYPE_ALTER_RENAME_TABLE; ++ } + exit_rename_table: + sqlite3SrcListDelete(db, pSrc); + sqlite3DbFree(db, zName); +@@ -118179,6 +118460,7 @@ SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){ + zTab, zDb + ); + } ++ v->stmtType = STMT_TYPE_ALTER_ADD_COL; + } + } + +@@ -118391,6 +118673,12 @@ SQLITE_PRIVATE void sqlite3AlterRenameColumn( + renameReloadSchema(pParse, iSchema, INITFLAG_AlterRename); + renameTestSchema(pParse, zDb, iSchema==1, "after rename", 1); + ++ Vdbe *v = NULL; ++ v = sqlite3GetVdbe(pParse); ++ if( v ){ ++ v->stmtType = STMT_TYPE_ALTER_RENAME_COL; ++ } ++ + exit_rename_column: + sqlite3SrcListDelete(db, pSrc); + sqlite3DbFree(db, zOld); +@@ -120007,6 +120295,12 @@ SQLITE_PRIVATE void sqlite3AlterDropColumn(Parse *pParse, SrcList *pSrc, const T + sqlite3VdbeJumpHere(v, addr); + } + ++ Vdbe *v = NULL; ++ v = sqlite3GetVdbe(pParse); ++ if( v ){ ++ v->stmtType = STMT_TYPE_ALTER_DROP_COL; ++ } ++ + exit_drop_column: + sqlite3DbFree(db, zCol); + sqlite3SrcListDelete(db, pSrc); +@@ -124376,6 +124670,7 @@ SQLITE_PRIVATE void sqlite3StartTable( + sqlite3VdbeAddOp3(v, OP_Insert, 0, reg3, reg1); + sqlite3VdbeChangeP5(v, OPFLAG_APPEND); + sqlite3VdbeAddOp0(v, OP_Close); ++ v->stmtType = STMT_TYPE_CREATE_TABLE; + } + + /* Normal (non-error) return. */ +@@ -126043,6 +126338,12 @@ SQLITE_PRIVATE void sqlite3CreateView( + /* Use sqlite3EndTable() to add the view to the schema table */ + sqlite3EndTable(pParse, 0, &sEnd, 0, 0); + ++ Vdbe *v = NULL; ++ v = sqlite3GetVdbe(pParse); ++ if( v ){ ++ v->stmtType = STMT_TYPE_CREATE_VIEW; ++ } ++ + create_view_fail: + sqlite3SelectDelete(db, pSelect); + if( IN_RENAME_OBJECT ){ +@@ -126566,6 +126867,7 @@ SQLITE_PRIVATE void sqlite3DropTable(Parse *pParse, SrcList *pName, int isView, + sqlite3FkDropTable(pParse, pName, pTab); + } + sqlite3CodeDropTable(pParse, pTab, iDb, isView); ++ v->stmtType = (isView > 0) ? STMT_TYPE_DROP_VIEW : STMT_TYPE_DROP_TABLE; + } + + exit_drop_table: +@@ -127424,6 +127726,7 @@ SQLITE_PRIVATE void sqlite3CreateIndex( + /* A named index with an explicit CREATE INDEX statement */ + zStmt = sqlite3MPrintf(db, "CREATE%s INDEX %.*s", + onError==OE_None ? "" : " UNIQUE", n, pName->z); ++ v->stmtType = STMT_TYPE_CREATE_INDEX; + }else{ + /* An automatic index created by a PRIMARY KEY or UNIQUE constraint */ + /* zStmt = sqlite3MPrintf(""); */ +@@ -127626,6 +127929,7 @@ SQLITE_PRIVATE void sqlite3DropIndex(Parse *pParse, SrcList *pName, int ifExists + sqlite3ChangeCookie(pParse, iDb); + destroyRootPage(pParse, pIndex->tnum, iDb); + sqlite3VdbeAddOp4(v, OP_DropIndex, iDb, 0, 0, pIndex->zName, 0); ++ v->stmtType = STMT_TYPE_DROP_INDEX; + } + + exit_drop_index: +@@ -128150,6 +128454,7 @@ SQLITE_PRIVATE void sqlite3BeginTransaction(Parse *pParse, int type){ + } + } + sqlite3VdbeAddOp0(v, OP_AutoCommit); ++ v->stmtType = STMT_TYPE_BEGIN_TRANSACTION; + } + + /* +@@ -128172,6 +128477,7 @@ SQLITE_PRIVATE void sqlite3EndTransaction(Parse *pParse, int eType){ + v = sqlite3GetVdbe(pParse); + if( v ){ + sqlite3VdbeAddOp2(v, OP_AutoCommit, 1, isRollback); ++ v->stmtType = (isRollback > 0) ? STMT_TYPE_ROLLBACK_TRANSACTION : STMT_TYPE_COMMIT_TRANSACTION; + } + } + +@@ -128192,6 +128498,7 @@ SQLITE_PRIVATE void sqlite3Savepoint(Parse *pParse, int op, Token *pName){ + return; + } + sqlite3VdbeAddOp4(v, OP_Savepoint, op, 0, 0, zName, P4_DYNAMIC); ++ v->stmtType = STMT_TYPE_SAVEPOINT; + } + } + +@@ -138430,10 +138737,15 @@ struct sqlite3_api_routines { + /* Version 3.44.0 and later */ + void *(*get_clientdata)(sqlite3*,const char*); + int (*set_clientdata)(sqlite3*, const char*, void*, void(*)(void*)); +-#ifdef SQLITE_ENABLE_DROPTABLE_CALLBACK ++ + /* handle after drop table done */ + int (*set_droptable_handle)(sqlite3*,void(*)(sqlite3*,const char*,const char*)); +-#endif /* SQLITE_ENABLE_DROPTABLE_CALLBACK */ ++ ++ int (*is_support_binlog)(void); ++ /* replay binlog from src db to the dest db */ ++ int (*replay_binlog)(sqlite3*, sqlite3*); ++ /* clean the binlog of the db */ ++ int (*clean_binlog)(sqlite3*, BinlogFileCleanModeE); + }; + + /* +@@ -138771,6 +139083,12 @@ typedef int (*sqlite3_loadext_entry)( + /* handle after drop table done */ + #define sqlite3_set_droptable_handle sqlite3_api->set_droptable_handle + #endif /* SQLITE_ENABLE_DROPTABLE_CALLBACK */ ++ ++#define sqlite3_is_support_binlog sqlite3_api->is_support_binlog ++/* replay binlog from src db to the dest db */ ++#define sqlite3_replay_binlog sqlite3_api->replay_binlog ++/* clean the binlog of the db */ ++#define sqlite3_clean_binlog sqlite3_api->clean_binlog + #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ + + #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) +@@ -139302,10 +139620,13 @@ static const sqlite3_api_routines sqlite3Apis = { + sqlite3_get_clientdata, + sqlite3_set_clientdata, + #ifdef SQLITE_ENABLE_DROPTABLE_CALLBACK +- sqlite3_set_droptable_handle ++ sqlite3_set_droptable_handle, + #else +- 0 ++ 0, + #endif /* SQLITE_ENABLE_DROPTABLE_CALLBACK */ ++ sqlite3_is_support_binlog, ++ sqlite3_replay_binlog, ++ sqlite3_clean_binlog, + }; + + /* True if x is the directory separator character +@@ -140854,6 +141175,7 @@ SQLITE_PRIVATE void sqlite3Pragma( + if( v==0 ) return; + sqlite3VdbeRunOnlyOnce(v); + pParse->nMem = 2; ++ v->stmtType = STMT_TYPE_PRAGMA; + + /* Interpret the [schema.] part of the pragma statement. iDb is the + ** index of the database this pragma is being applied to in db.aDb[]. */ +@@ -153764,6 +154086,12 @@ SQLITE_PRIVATE void sqlite3BeginTrigger( + tr_tm = TK_BEFORE; + } + ++ Vdbe *v = NULL; ++ v = sqlite3GetVdbe(pParse); ++ if( v ){ ++ v->stmtType = STMT_TYPE_CREATE_TRIGGER; ++ } ++ + /* Build the Trigger object */ + pTrigger = (Trigger*)sqlite3DbMallocZero(db, sizeof(Trigger)); + if( pTrigger==0 ) goto trigger_cleanup; +@@ -154158,6 +154486,11 @@ SQLITE_PRIVATE void sqlite3DropTrigger(Parse *pParse, SrcList *pName, int noErr) + goto drop_trigger_cleanup; + } + sqlite3DropTriggerPtr(pParse, pTrigger); ++ Vdbe *v = NULL; ++ v = sqlite3GetVdbe(pParse); ++ if (v) { ++ v->stmtType = STMT_TYPE_DROP_TRIGGER; ++ } + + drop_trigger_cleanup: + sqlite3SrcListDelete(db, pName); +@@ -182631,6 +182964,11 @@ SQLITE_API int sqlite3_db_config(sqlite3 *db, int op, ...){ + rc = SQLITE_OK; + break; + } ++ case SQLITE_DBCONFIG_ENABLE_BINLOG: { ++ Sqlite3BinlogConfig *pBinlogConfig = va_arg(ap, Sqlite3BinlogConfig*); ++ rc = sqlite3SetBinLogConfig(db, pBinlogConfig); ++ break; ++ } + #endif /* SQLITE_SHARED_BLOCK_OPTIMIZATION */ + default: { + static const struct { +@@ -183059,6 +183397,7 @@ SQLITE_PRIVATE void sqlite3LeaveMutexAndCloseZombie(sqlite3 *db){ + sqlite3CollapseDatabaseArray(db); + assert( db->nDb<=2 ); + assert( db->aDb==db->aDbStatic ); ++ (void)sqlite3BinlogClose(db); + + /* Tell the code in notify.c that the connection no longer holds any + ** locks and does not require any further unlock-notify callbacks. +@@ -185284,6 +185623,7 @@ opendb_out: + db->mDropSchemaName = NULL; + db->xDropTableHandle = NULL; + #endif /* SQLITE_ENABLE_DROPTABLE_CALLBACK */ ++ sqlite3BinlogReset(db); + *ppDb = db; + #ifdef SQLITE_ENABLE_SQLLOG + if( sqlite3GlobalConfig.xSqllog ){ +@@ -261583,6 +261923,872 @@ static void DumpLocksByPager(Pager *pPager) + } + #endif /* SQLITE_OS_UNIX */ + ++/************** Begin of binlog implement ************************************/ ++SQLITE_PRIVATE int sqlite3BinlogInitApi(sqlite3 *db) ++{ ++ if (db == NULL || db->xBinlogHandle.binlogApi.binlogLib == NULL) { ++ sqlite3_log(SQLITE_ERROR, "binlog init api parameter is null"); ++ return SQLITE_ERROR; ++ } ++ Sqlite3BinlogApiInfo apiInfo[] = { ++ {(void **)&db->xBinlogHandle.binlogApi.binlogOpenApi, "BinlogOpen"}, ++ {(void **)&db->xBinlogHandle.binlogApi.binlogCloseApi, "BinlogClose"}, ++ {(void **)&db->xBinlogHandle.binlogApi.binlogWriteApi, "BinlogWrite"}, ++ {(void **)&db->xBinlogHandle.binlogApi.binlogReadApi, "BinlogRead"}, ++ {(void **)&db->xBinlogHandle.binlogApi.binlogFreeReadResultApi, "BinlogFreeReadResult"}, ++ {(void **)&db->xBinlogHandle.binlogApi.binlogFileCleanApi, "BinlogFileClean"}, ++ {(void **)&db->xBinlogHandle.binlogApi.binlogLockReadApi, "BinlogLockRead"}, ++ {(void **)&db->xBinlogHandle.binlogApi.binlogUnlockReadApi, "BinlogUnlockRead"}, ++ }; ++ int apiCount = sizeof(apiInfo) / sizeof(apiInfo[0]); ++ for (int i = 0; i < apiCount; i++) { ++ *apiInfo[i].funcP = sqlite3OsDlSym(db->pVfs, db->xBinlogHandle.binlogApi.binlogLib, apiInfo[i].funcN); ++ if (*apiInfo[i].funcP == NULL) { ++ sqlite3_log(SQLITE_ERROR, "dlsym binlog err: %s", apiInfo[i].funcN); ++ return SQLITE_ERROR; ++ } ++ } ++ return SQLITE_OK; ++} ++ ++SQLITE_PRIVATE void sqlite3SetBinlogErrorCallback(void *pCtx, int errNo, char *errMsg) ++{ ++ sqlite3 *db = (sqlite3 *)pCtx; ++ if (db == NULL || db->xBinlogHandle.xErrorCallback == NULL) { ++ return; ++ } ++ db->xBinlogHandle.xErrorCallback(db->xBinlogHandle.callbackCtx, sqlite3TransferBinlogErrno(errNo), errMsg); ++} ++ ++SQLITE_PRIVATE void sqlite3SetBinlogFullCallback(void *pCtx, u16 currentCount) ++{ ++ sqlite3 *db = (sqlite3 *)pCtx; ++ if (db == NULL || db->xBinlogHandle.xLogFullCallback == NULL) { ++ return; ++ } ++ db->xBinlogHandle.xLogFullCallback(db->xBinlogHandle.callbackCtx, currentCount); ++} ++ ++SQLITE_PRIVATE int sqlite3SetBinLogConfig(sqlite3 *db, Sqlite3BinlogConfig *bConfig) ++{ ++ if (db == NULL) { ++ sqlite3_log(SQLITE_ERROR, "set binlog config parameter is null"); ++ return SQLITE_ERROR; ++ } ++ if (bConfig == NULL) { ++ return sqlite3BinlogClose(db); ++ } ++ ++ const char *zFile = sqlite3_db_filename(db, 0); ++ if (zFile == NULL || (db->xBinlogHandle.flags & BINLOG_FLAG_ENABLE)) { ++ sqlite3_log(SQLITE_ERROR, "set binlog config zFile is null or binlog is already enabled"); ++ return SQLITE_ERROR; ++ } ++ ++ void *handle = sqlite3OsDlOpen(db->pVfs, "libarkdata_db_core.z.so"); ++ if (handle == NULL) { ++ sqlite3_log(SQLITE_ERROR, "dlopen binlog err:%d", errno); ++ return SQLITE_ERROR; ++ } ++ db->xBinlogHandle.binlogApi.binlogLib = handle; ++ int rc = sqlite3BinlogInitApi(db); ++ if (rc != SQLITE_OK) { ++ sqlite3_log(rc, "set binlog config init api err:%d", rc); ++ sqlite3BinlogReset(db); ++ return rc; ++ } ++ ++ BinlogConfigT conf; ++ conf.logMode = bConfig->mode; ++ conf.fullCallbackThreshold = bConfig->fullCallbackThreshold; ++ conf.maxFileSize = bConfig->maxFileSize; ++ conf.filePath = (char *)zFile; ++ conf.onError = sqlite3SetBinlogErrorCallback; ++ conf.onLogFull = sqlite3SetBinlogFullCallback; ++ conf.callbackCtx = db; ++ ++ BinlogInstanceT *inst = NULL; ++ rc = sqlite3TransferBinlogErrno(db->xBinlogHandle.binlogApi.binlogOpenApi(&conf, &inst)); ++ if (rc != SQLITE_OK) { ++ sqlite3_log(SQLITE_ERROR, "binlog open err:%d %d", rc, errno); ++ sqlite3BinlogReset(db); ++ return SQLITE_ERROR; ++ } ++ ++ db->xBinlogHandle.mode = bConfig->mode; ++ db->xBinlogHandle.flags |= (BINLOG_FLAG_ENABLE | BINLOG_FLAG_UPDATE_TID); ++ db->xBinlogHandle.xErrorCallback = bConfig->xErrorCallback; ++ db->xBinlogHandle.xLogFullCallback = bConfig->xLogFullCallback; ++ db->xBinlogHandle.callbackCtx = bConfig->callbackCtx; ++ db->xBinlogHandle.binlogConn = inst; ++ return SQLITE_OK; ++} ++ ++SQLITE_PRIVATE int sqlite3GenerateUuid(u8 *aBlobOut) ++ { ++ if (aBlobOut == NULL) { ++ return SQLITE_ERROR; ++ } ++ u8 aBlob[SQLITE_UUID_BLOB_LENGTH]; ++ sqlite3_randomness(SQLITE_UUID_BLOB_LENGTH, aBlob); ++ aBlob[6] = (aBlob[6] & 0x0f) + 0x40; ++ aBlob[8] = (aBlob[8] & 0x3f) + 0x80; ++ ++ errno_t errNo = EOK; ++ errNo = memcpy_s(aBlobOut, SQLITE_UUID_BLOB_LENGTH, aBlob, SQLITE_UUID_BLOB_LENGTH); ++ if (errNo != EOK) { ++ return SQLITE_ERROR; ++ } ++ return SQLITE_OK; ++} ++ ++SQLITE_PRIVATE BinlogEventTypeE sqlite3TransferLogEventType(StmtType stmtType) ++{ ++ switch(stmtType) { ++ case STMT_TYPE_CREATE_TABLE: ++ case STMT_TYPE_CREATE_INDEX: ++ case STMT_TYPE_CREATE_TRIGGER: ++ case STMT_TYPE_CREATE_VIEW: ++ case STMT_TYPE_DROP_TABLE: ++ case STMT_TYPE_DROP_INDEX: ++ case STMT_TYPE_DROP_TRIGGER: ++ case STMT_TYPE_DROP_VIEW: ++ case STMT_TYPE_ALTER_ADD_COL: ++ case STMT_TYPE_ALTER_RENAME_COL: ++ case STMT_TYPE_ALTER_RENAME_TABLE: ++ case STMT_TYPE_ALTER_DROP_COL: ++ return BINLOG_EVENT_TYPE_DDL; ++ case STMT_TYPE_PRAGMA: ++ return BINLOG_EVENT_TYPE_PRAGMA; ++ case STMT_TYPE_ROLLBACK_TRANSACTION: ++ return BINLOG_EVENT_TYPE_ROLLBACK; ++ default: ++ return BINLOG_EVENT_TYPE_DML; ++ } ++} ++ ++/* Write an sql into binlog with given type and isFinish flag */ ++SQLITE_PRIVATE int sqlite3DirectWriteBinlog(Vdbe *p, BinlogEventTypeE type, char *zSql, int isFinishTrx) ++{ ++ BinlogWriteDataT logData; ++ errno_t errNo = memcpy_s(&logData.xid, SQLITE_UUID_BLOB_LENGTH, p->db->xBinlogHandle.xTid, SQLITE_UUID_BLOB_LENGTH); ++ if (errNo != EOK) { ++ sqlite3_log(SQLITE_WARNING, "binlog memcpy xid failed"); ++ return SQLITE_ERROR; ++ } ++ ++ logData.type = type; ++ logData.data = zSql; ++ logData.dataLength = strlen(zSql); ++ logData.isFinishTrx = isFinishTrx; ++ sqlite3 *db = p->db; ++ int rc = sqlite3TransferBinlogErrno(db->xBinlogHandle.binlogApi.binlogWriteApi(db->xBinlogHandle.binlogConn, ++ &logData)); ++ if (rc != SQLITE_OK) { ++ sqlite3_log(SQLITE_WARNING, "binlog write err:%d", rc); ++ return SQLITE_ERROR; ++ } ++ return SQLITE_OK; ++} ++ ++/* Free the pBinlogDMLData saved on the Vdbe pointer */ ++SQLITE_PRIVATE void sqlite3FreeBinlogRowData(Vdbe *p) { ++ if (p == NULL || p->db == NULL || p->pBinlogDMLData == NULL) { ++ return; ++ } ++ int isReleased = p->pBinlogDMLData->isSavePointReleased; ++ if (!isReleased && p->db->autoCommit > 0) { ++ sqlite3DirectWriteBinlog(p, BINLOG_EVENT_TYPE_ROLLBACK, (char *)"rollback;", 1); ++ p->db->xBinlogHandle.flags |= BINLOG_FLAG_UPDATE_TID; ++ } else if (!isReleased) { ++ /* If not in auto commit mode and savepoint not released, ++ ** it means this statement failed to execute within a transaction. ++ ** In this case, we need to rollback to the point before this statement is executed. ++ ** If the rollback statement failed to write into binlog, then there may be an memory leak ++ */ ++ char *rollbackSavePoint = sqlite3_mprintf("rollback to %s;", p->pBinlogDMLData->pSavePointName); ++ sqlite3DirectWriteBinlog(p, BINLOG_EVENT_TYPE_DML, rollbackSavePoint, 0); ++ sqlite3DbFree(p->db, rollbackSavePoint); ++ } ++ ++ if (p->pBinlogDMLData->pSavePointName != NULL) { ++ sqlite3DbFree(p->db, p->pBinlogDMLData->pSavePointName); ++ p->pBinlogDMLData->pSavePointName = NULL; ++ } ++ ++ sqlite3DbFree(p->db, p->pBinlogDMLData); ++ p->pBinlogDMLData = NULL; ++} ++ ++/* Check and create a new binlog xid if needed */ ++SQLITE_PRIVATE int sqlite3UpdateBinlogXidIfNeeded(sqlite3 *db){ ++ assert(db!=NULL); ++ if ((db->xBinlogHandle.flags & BINLOG_FLAG_UPDATE_TID) != 0) { ++ if (sqlite3GenerateUuid(db->xBinlogHandle.xTid) != SQLITE_OK) { ++ sqlite3_log(SQLITE_ERROR, "binlog generate uuid failed"); ++ return SQLITE_ERROR; ++ } ++ db->xBinlogHandle.flags &= ~BINLOG_FLAG_UPDATE_TID; ++ } ++ return SQLITE_OK; ++} ++ ++/* Return a delete sql for a regular table, the returned value needs free by caller */ ++SQLITE_PRIVATE char *sqlite3BinlogRowDelete(Table *pTab, sqlite3_int64 rowid){ ++ return sqlite3_mprintf("delete from \"%s\" where rowid=%lld;", pTab->zName, rowid); ++} ++ ++/* Return a delete sql for a without rowid table, the returned value needs free by caller */ ++SQLITE_PRIVATE char *sqlite3BinlogRowDeleteNoRowid(sqlite3 *db, Table *pTab, const BinlogRow *pRow){ ++ char *whereClause = NULL;; ++ Index *pIdx = sqlite3PrimaryKeyIndex(pTab); ++ ++ for (int i=0; inKeyCol; i++) { ++ i16 iCol = pIdx->aiColumn[i]; ++ Column *pCol = &(pTab->aCol[iCol]); ++ if (whereClause == NULL) { ++ char *colValue = sqlite3BinlogGetNthCol(db, pTab, pRow, i); ++ if (colValue == NULL) { ++ return NULL; ++ } ++ whereClause = sqlite3_mprintf("%s=%s", pCol->zCnName, colValue); ++ sqlite3DbFree(db, colValue); ++ } else { ++ char *colValue = sqlite3BinlogGetNthCol(db, pTab, pRow, iCol); ++ if (colValue == NULL) { ++ sqlite3DbFree(db, whereClause); ++ return NULL; ++ } ++ char *temp = sqlite3_mprintf("%s and %s=%s", whereClause, pCol->zCnName, colValue); ++ sqlite3DbFree(db, whereClause); ++ sqlite3DbFree(db, colValue); ++ whereClause = temp; ++ } ++ if (whereClause == NULL) { ++ return NULL; ++ } ++ } ++ ++ char *result = sqlite3_mprintf("delete from \"%s\" where %s;", pTab->zName, whereClause); ++ sqlite3DbFree(db, whereClause); ++ return result; ++} ++ ++/* Get the field name and field values for a without rowid table, in two comma seperated strings */ ++SQLITE_PRIVATE int sqlite3BinlogGetFieldDataNoRowid( ++ sqlite3 *db, /* dB handle */ ++ Table *pTab, /* Table to which the row belongs */ ++ const BinlogRow *pRow, /* Row data to be written into binlog */ ++ char **outNames, /* field names to be returned */ ++ char **outValues /* field values to be returned */ ++){ ++ char *fieldNames = NULL; ++ char *fieldValues = NULL; ++ Index *pIdx = sqlite3PrimaryKeyIndex(pTab); ++ /* Without rowid table fields are rearranged with primary keys at front */ ++ for (int i=0; inKeyCol; i++) { ++ i16 iCol = pIdx->aiColumn[i]; ++ if (i==0) { ++ fieldNames = sqlite3_mprintf("%s", pTab->aCol[iCol].zCnName); ++ } else { ++ char *temp = sqlite3_mprintf("%s, %s", fieldNames, pTab->aCol[iCol].zCnName); ++ sqlite3DbFree(db, fieldNames); ++ fieldNames = temp; ++ } ++ if (fieldNames==NULL) goto no_rowid_no_mem; ++ } ++ ++ for (int k=0; knCol; k++) { ++ if ( (pTab->aCol[k].colFlags & COLFLAG_PRIMKEY) == 0 ) { ++ char *temp = sqlite3_mprintf("%s, %s", fieldNames, pTab->aCol[k].zCnName); ++ sqlite3DbFree(db, fieldNames); ++ fieldNames = temp; ++ } ++ if (k==0) { ++ fieldValues = sqlite3BinlogGetNthCol(db, pTab, pRow, k); ++ } else { ++ char *newVal = sqlite3BinlogGetNthCol(db, pTab, pRow, k); ++ if (newVal==NULL) { ++ goto no_rowid_no_mem; ++ } ++ char *temp = sqlite3_mprintf("%s, %s", fieldValues, newVal); ++ sqlite3DbFree(db, newVal); ++ sqlite3DbFree(db, fieldValues); ++ fieldValues = temp; ++ } ++ if (fieldValues == NULL || fieldValues == NULL) { ++ goto no_rowid_no_mem; ++ } ++ } ++ *outNames = fieldNames; ++ *outValues = fieldValues; ++ return SQLITE_OK; ++ ++no_rowid_no_mem: ++ sqlite3DbFree(db, fieldNames); ++ sqlite3DbFree(db, fieldValues); ++ return SQLITE_NOMEM; ++} ++ ++/* Get the field name and field values for a regular rowid table, in two comma seperated strings */ ++SQLITE_PRIVATE int sqlite3BinlogGetFieldData(sqlite3 *db, Table *pTab, const BinlogRow *pRow, char **outNames, char **outValues){ ++ char *fieldNames = NULL; ++ char *fieldValues = NULL; ++ for (int i=0; inCol; i++) { ++ if (i==0) { ++ fieldNames = sqlite3_mprintf("%s", pTab->aCol[i].zCnName); ++ fieldValues = sqlite3BinlogGetNthCol(db, pTab, pRow, i); ++ } else { ++ char *temp = sqlite3_mprintf("%s, %s", fieldNames, pTab->aCol[i].zCnName); ++ sqlite3DbFree(db, fieldNames); ++ fieldNames = temp; ++ ++ char *newVal = sqlite3BinlogGetNthCol(db, pTab, pRow, i); ++ if (newVal==NULL) goto no_mem; ++ temp = sqlite3_mprintf("%s, %s", fieldValues, newVal); ++ sqlite3DbFree(db, newVal); ++ sqlite3DbFree(db, fieldValues); ++ fieldValues = temp; ++ ++ } ++ if (fieldValues == NULL || fieldValues == NULL) { ++ goto no_mem; ++ } ++ } ++ *outNames = fieldNames; ++ *outValues = fieldValues; ++ return SQLITE_OK; ++ ++no_mem: ++ sqlite3DbFree(db, fieldNames); ++ sqlite3DbFree(db, fieldValues); ++ return SQLITE_NOMEM; ++} ++ ++/* Return an insert sql based on the binlog row data, the returned value needs free by caller */ ++SQLITE_PRIVATE char *sqlite3BinlogRowInsert(sqlite3 *db, Table *pTab, const BinlogRow *pRow){ ++ char *fieldNames = NULL; ++ char *fieldValues = NULL; ++ ++ int rc = SQLITE_OK; ++ if (HasRowid(pTab)) { ++ rc = sqlite3BinlogGetFieldData(db, pTab, pRow, &fieldNames, &fieldValues); ++ } else { ++ rc = sqlite3BinlogGetFieldDataNoRowid(db, pTab, pRow, &fieldNames, &fieldValues); ++ } ++ ++ if (rc != SQLITE_OK) { ++ sqlite3_log(rc, "binlog get field data err:%d", rc); ++ return NULL; ++ } ++ char *result = NULL; ++ if (HasRowid(pTab)) { ++ result = sqlite3_mprintf( ++ "insert into \"%s\" (%s, rowid) values (%s, %lld) on conflict do update set (%s) = (%s);", ++ pTab->zName, fieldNames, fieldValues, pRow->rowid, fieldNames, fieldValues ++ ); ++ } else { ++ result = sqlite3_mprintf( ++ "insert into \"%s\" (%s) values (%s) on conflict do update set (%s) = (%s);", ++ pTab->zName, fieldNames, fieldValues, fieldNames, fieldValues ++ ); ++ } ++ ++ sqlite3DbFree(db, fieldNames); ++ sqlite3DbFree(db, fieldValues); ++ return result; ++} ++ ++/* Return an update sql for a regular table. Without rowid table has no update operation */ ++SQLITE_PRIVATE char *sqlite3BinlogRowUpdate(sqlite3 *db, Table *pTab, const BinlogRow *pRow){ ++ char *fieldNames = NULL; ++ char *fieldValues = NULL; ++ if (sqlite3BinlogGetFieldData(db, pTab, pRow, &fieldNames, &fieldValues) != SQLITE_OK) { ++ return NULL; ++ } ++ char *result = sqlite3_mprintf( ++ "insert into \"%s\" (%s, rowid) values (%s, %lld) on conflict(rowid) do update set (%s) = (%s);", ++ pTab->zName, fieldNames, ++ fieldValues, pRow->rowid, ++ fieldNames, fieldValues ++ ); ++ sqlite3DbFree(db, fieldNames); ++ sqlite3DbFree(db, fieldValues); ++ return result; ++} ++ ++/* Return the sql statement corresponds to a binlog row data, NULL is returned if error occurs */ ++SQLITE_PRIVATE char *sqlite3GetBinlogRowStmt(Vdbe *p, const BinlogRow *pRow){ ++ assert(p!=NULL); ++ assert(p->db!=NULL); ++ assert(p->pBinlogDMLData!=NULL); ++ sqlite3 *db = p->db; ++ Table *pTab = p->pBinlogDMLData->pTable; ++ ++ switch (pRow->op) { ++ case SQLITE_DELETE: ++ return HasRowid(pTab) ? sqlite3BinlogRowDelete(pTab, pRow->rowid) : sqlite3BinlogRowDeleteNoRowid(db, pTab, pRow); ++ case SQLITE_INSERT: ++ return sqlite3BinlogRowInsert(db, pTab, pRow); ++ case SQLITE_UPDATE: ++ return sqlite3BinlogRowUpdate(db, pTab, pRow); ++ default: ++ return NULL; ++ } ++} ++ ++/* Find the table that is pointed by pC, NULL is returned if a valid table can not be found */ ++SQLITE_PRIVATE Table *sqlite3BinlogFindTable(BtCursor *pC){ ++ if (pC==NULL || pC->pBtree == NULL || pC->pBtree->pBt == NULL || pC->pBtree->pBt->pSchema == NULL) { ++ return NULL; ++ } ++ Schema *pSchema = (Schema *)pC->pBtree->pBt->pSchema; ++ Table *pTab = NULL; ++ HashElem *k; ++ for(k=sqliteHashFirst(&pSchema->tblHash); k; k=sqliteHashNext(k)){ ++ Table *tempTab = (Table*)sqliteHashData(k); ++ if (tempTab->tnum==pC->pgnoRoot) { ++ pTab = tempTab; ++ break; ++ } ++ } ++ if (pTab == NULL || sqlite3_stricmp(pTab->zName, LEGACY_SCHEMA_TABLE) == 0) { ++ return NULL; ++ } ++ return pTab; ++} ++ ++/** ++** This is the function called in sqlite3VdbeExec to write a row-based binlog statement. ++** If an error occured, then the row data is cleared and the state will be logged in statement mode ++**/ ++SQLITE_PRIVATE void sqlite3StoreBinlogRowData(Vdbe *p, BtCursor *pCursor, ++ sqlite3_uint64 nData, const char *pData, int op, sqlite3_int64 rowid){ ++ assert(p!=NULL && pCursor!=NULL); ++ assert(p->db!=NULL); ++ assert(sqlite3IsRowBasedBinlog(p)); ++ assert(op == SQLITE_INSERT || op == SQLITE_UPDATE || op == SQLITE_DELETE); ++ sqlite3 *db = p->db; ++ BinlogDMLData *dataContainer = NULL; ++ ++ Table *pTab = sqlite3BinlogFindTable(pCursor); ++ if (pTab == NULL) { ++ goto error_when_store_binlog; ++ } ++ ++ if (p->pBinlogDMLData == NULL) { ++ /* If pBinlogDMLData is empty, we need to initialize the data by the following steps: ++ ** 1. Change the xid if needed ++ ** 2. Start a savepoint so that all the rows are in the same transaction ++ */ ++ dataContainer = (BinlogDMLData *)sqlite3DbMallocZero(db, sizeof(BinlogDMLData)); ++ if (dataContainer == NULL) { ++ goto error_when_store_binlog; ++ } ++ ++ if (sqlite3UpdateBinlogXidIfNeeded(p->db) != SQLITE_OK) { ++ goto error_when_store_binlog; ++ } ++ ++ char xTidStr[SQLITE_UUID_BLOB_LENGTH*2+1] = {0}; ++ for (int i=0; ixBinlogHandle.xTid[i]; ++ sqlite3_snprintf(3, xTidStr+i*2, "%02x", byte); ++ } ++ char *savePointName = sqlite3_mprintf("%s%s", "BinlogRow", xTidStr); ++ if (savePointName == NULL) { ++ goto error_when_store_binlog; ++ } ++ dataContainer->pSavePointName = savePointName; ++ p->pBinlogDMLData = dataContainer; ++ char *zSavePointSql = sqlite3_mprintf("savepoint %s;", savePointName); ++ if (zSavePointSql==NULL) { ++ goto error_when_store_binlog; ++ } ++ int rc = sqlite3DirectWriteBinlog(p, sqlite3TransferLogEventType(p->stmtType), zSavePointSql, 0); ++ sqlite3DbFree(db, zSavePointSql); ++ if (rc != SQLITE_OK) { ++ goto error_when_store_binlog; ++ } ++ } else { ++ dataContainer = p->pBinlogDMLData; ++ } ++ ++ dataContainer->pTable = pTab; ++ BinlogRow pRow; ++ pRow.op = op; ++ pRow.nData = nData; ++ pRow.pData = pData; ++ pRow.rowid = rowid; ++ char *zSql = sqlite3GetBinlogRowStmt(p, &pRow); ++ if (zSql == NULL) { ++ goto error_when_store_binlog; ++ } ++ ++ if (sqlite3DirectWriteBinlog(p, sqlite3TransferLogEventType(p->stmtType), zSql, 0) != SQLITE_OK) { ++ sqlite3DbFree(db, zSql); ++ goto error_when_store_binlog; ++ } ++ sqlite3DbFree(db, zSql); ++ return; ++ ++error_when_store_binlog: ++ sqlite3_log(SQLITE_WARNING, "Failed to store row-based binlog data, use statement mode instead."); ++ if (p->pBinlogDMLData == NULL && dataContainer != NULL) { ++ sqlite3DbFree(db, dataContainer); ++ } ++ sqlite3FreeBinlogRowData(p); ++ return; ++} ++ ++SQLITE_PRIVATE int sqlite3IsRowBasedBinlog(Vdbe *p) { ++ return p ++ && p->db ++ && (p->db->xBinlogHandle.flags & BINLOG_FLAG_ENABLE) ++ && (p->db->xBinlogHandle.mode == ROW); ++} ++ ++/* Get i-th column value from a row formated data. i starts from 0 */ ++SQLITE_PRIVATE int sqlite3BinlogStat4Column( ++ sqlite3 *db, /* Database handle */ ++ const void *pRec, /* Pointer to buffer containing record */ ++ int nRec, /* Size of buffer pRec in bytes */ ++ int iCol, /* Column to extract */ ++ sqlite3_value **ppVal /* OUT: Extracted value */ ++){ ++ u32 t = 0; /* a column type code */ ++ u32 nHdr; /* Size of the header in the record */ ++ u32 iHdr; /* Next unread header byte */ ++ i64 iField; /* Next unread data byte */ ++ u32 szField = 0; /* Size of the current data field */ ++ int i; /* Column index */ ++ u8 *a = (u8*)pRec; /* Typecast byte array */ ++ Mem *pMem = *ppVal; /* Write result into this Mem object */ ++ ++ iHdr = getVarint32(a, nHdr); ++ if( nHdr>(u32)nRec || iHdr>=nHdr ) return SQLITE_CORRUPT_BKPT; ++ iField = nHdr; ++ for(i=0; i<=iCol; i++){ ++ iHdr += getVarint32(&a[iHdr], t); ++ ++ if( iHdr>nHdr ) return SQLITE_CORRUPT_BKPT; ++ szField = sqlite3VdbeSerialTypeLen(t); ++ iField += szField; ++ } ++ ++ if( iField>nRec ) return SQLITE_CORRUPT_BKPT; ++ if( pMem==0 ){ ++ pMem = *ppVal = sqlite3ValueNew(db); ++ if( pMem==0 ) return SQLITE_NOMEM_BKPT; ++ } ++ sqlite3VdbeSerialGet(&a[iField-szField], t, pMem); ++ pMem->enc = ENC(db); ++ return SQLITE_OK; ++} ++ ++SQLITE_PRIVATE char *sqlite3BinlogGetNthCol(sqlite3 *db, const Table *pTab, const BinlogRow *pRow, int iCol) { ++ assert(db != NULL && pTab != NULL && pRow != NULL); ++ assert(iCol >= 0); ++ if (HasRowid(pTab) && pTab->iPKey == iCol) { ++ return sqlite3_mprintf("%lld", pRow->rowid); ++ } ++ ++ sqlite3_value *tempVal = NULL; ++ sqlite3BinlogStat4Column(db, pRow->pData, pRow->nData, iCol, &tempVal); ++ if (tempVal == NULL) { ++ sqlite3_log(SQLITE_ERROR, "decode data failed"); ++ return NULL; ++ } ++ int type = sqlite3_value_type(tempVal); ++ const unsigned char *temp = sqlite3_value_text(tempVal); ++ ++ char *res = NULL; ++ if (type == SQLITE_BLOB || type == SQLITE_TEXT) { ++ res = sqlite3_mprintf("'%s'", temp); ++ } else if (type == SQLITE_NULL) { ++ res = sqlite3_mprintf("NULL"); ++ } else { ++ res = sqlite3_mprintf("%s", temp); ++ } ++ sqlite3ValueFree(tempVal); ++ ++ return res; ++} ++ ++/* Check if binlog is enabled and the stmtType needs being logged */ ++SQLITE_PRIVATE int sqlite3IsSkipWriteBinlog(Vdbe *p) { ++ int isBinlogEnabled = (p != NULL) && (p->db != NULL) && ++ ((p->db->xBinlogHandle.flags & BINLOG_FLAG_ENABLE) != 0) && ++ p->db->xBinlogHandle.binlogApi.binlogWriteApi; ++ ++ return p == NULL ++ || !isBinlogEnabled ++ || p->db->nVdbeExec > 1 ++ || (p->readOnly && p->stmtType != STMT_TYPE_BEGIN_TRANSACTION && p->stmtType != STMT_TYPE_SAVEPOINT && ++ p->stmtType != STMT_TYPE_COMMIT_TRANSACTION && p->stmtType != STMT_TYPE_ROLLBACK_TRANSACTION); ++} ++ ++SQLITE_PRIVATE void sqlite3BinlogWrite(Vdbe *p) ++{ ++ if (sqlite3IsSkipWriteBinlog(p)) { ++ sqlite3FreeBinlogRowData(p); ++ return; ++ } ++ ++ sqlite3 *db = p->db; ++ char *zSql = sqlite3_expanded_sql((sqlite3_stmt *)p); ++ if (zSql == NULL) { ++ sqlite3_log(SQLITE_ERROR, "binlog get sql failed"); ++ sqlite3FreeBinlogRowData(p); ++ return; ++ } ++ BinlogWriteDataT logData; ++ logData.type = sqlite3TransferLogEventType(p->stmtType); ++ ++ if (logData.type == BINLOG_EVENT_TYPE_DML && sqlite3IsRowBasedBinlog(p) && p->pBinlogDMLData != NULL) { ++ // release savepoint ++ sqlite3_free(zSql); ++ zSql = sqlite3_mprintf("release savepoint %s;", p->pBinlogDMLData->pSavePointName); ++ p->pBinlogDMLData->isSavePointReleased = 1; ++ } ++ ++ if (sqlite3UpdateBinlogXidIfNeeded(db) != SQLITE_OK) { ++ sqlite3FreeBinlogRowData(p); ++ sqlite3BinlogErrorCallback(db, SQLITE_ERROR, "generate tid failed"); ++ sqlite3_free(zSql); ++ sqlite3BinlogClose(db); ++ return; ++ } ++ errno_t errNo = EOK; ++ errNo = memcpy_s(&logData.xid, SQLITE_UUID_BLOB_LENGTH, db->xBinlogHandle.xTid, SQLITE_UUID_BLOB_LENGTH); ++ if (errNo != EOK) { ++ sqlite3FreeBinlogRowData(p); ++ sqlite3BinlogErrorCallback(db, SQLITE_ERROR, "copy tid failed"); ++ sqlite3_free(zSql); ++ sqlite3BinlogClose(db); ++ return; ++ } ++ logData.data = zSql; ++ logData.dataLength = strlen(zSql); ++ logData.isFinishTrx = db->autoCommit; ++ int rc = sqlite3TransferBinlogErrno(db->xBinlogHandle.binlogApi.binlogWriteApi(db->xBinlogHandle.binlogConn, ++ &logData)); ++ sqlite3_free(zSql); ++ if (rc != SQLITE_OK) { ++ sqlite3FreeBinlogRowData(p); ++ sqlite3_log(SQLITE_ERROR, "binlog write err:%d len:%u", rc, logData.dataLength); ++ sqlite3BinlogClose(db); ++ return; ++ } ++ if (db->autoCommit > 0) { ++ db->xBinlogHandle.flags |= BINLOG_FLAG_UPDATE_TID; ++ } ++ sqlite3FreeBinlogRowData(p); ++} ++ ++SQLITE_PRIVATE int sqlite3TransferBinlogErrno(BinlogErrno err) ++{ ++ switch (err) { ++ case GMERR_OK: ++ return SQLITE_OK; ++ case GMERR_LOCK_NOT_AVAILABLE: ++ return SQLITE_BUSY; ++ case GMERR_BINLOG_READ_FINISH: ++ return SQLITE_DONE; ++ default: ++ return SQLITE_ERROR; ++ } ++} ++ ++SQLITE_PRIVATE int sqlite3BinlogClose(sqlite3 *db) ++{ ++ if (db == NULL) { ++ sqlite3_log(SQLITE_ERROR, "binlog close parameter is null"); ++ return SQLITE_ERROR; ++ } ++ if ((db->xBinlogHandle.flags & BINLOG_FLAG_ENABLE) == 0) { ++ return SQLITE_OK; ++ } ++ if (db->xBinlogHandle.binlogApi.binlogCloseApi) { ++ int rc = sqlite3TransferBinlogErrno(db->xBinlogHandle.binlogApi.binlogCloseApi(db->xBinlogHandle.binlogConn)); ++ if (rc != SQLITE_OK) { ++ sqlite3_log(rc, "binlog close err:%d", rc); ++ return rc; ++ } ++ } ++ sqlite3BinlogReset(db); ++ return SQLITE_OK; ++} ++ ++SQLITE_PRIVATE void sqlite3BinlogReset(sqlite3 *db) ++{ ++ if (db == NULL) { ++ sqlite3_log(SQLITE_ERROR, "binlog reset parameter is null"); ++ return; ++ } ++ db->xBinlogHandle.flags = 0; ++ (void)memset_s(db->xBinlogHandle.xTid, SQLITE_UUID_BLOB_LENGTH, 0, SQLITE_UUID_BLOB_LENGTH); ++ db->xBinlogHandle.xErrorCallback = NULL; ++ db->xBinlogHandle.binlogApi.binlogOpenApi = NULL; ++ db->xBinlogHandle.binlogApi.binlogCloseApi = NULL; ++ db->xBinlogHandle.binlogApi.binlogWriteApi = NULL; ++ db->xBinlogHandle.binlogApi.binlogReadApi = NULL; ++ db->xBinlogHandle.binlogApi.binlogFileCleanApi = NULL; ++ db->xBinlogHandle.binlogApi.binlogFreeReadResultApi = NULL; ++ db->xBinlogHandle.binlogApi.binlogLockReadApi = NULL; ++ db->xBinlogHandle.binlogApi.binlogUnlockReadApi = NULL; ++ db->xBinlogHandle.callbackCtx = NULL; ++ db->xBinlogHandle.binlogConn = NULL; ++ if (db->xBinlogHandle.binlogApi.binlogLib) { ++ sqlite3OsDlClose(db->pVfs, db->xBinlogHandle.binlogApi.binlogLib); ++ db->xBinlogHandle.binlogApi.binlogLib = NULL; ++ } ++} ++ ++SQLITE_PRIVATE int sqlite3BinlogStmtPrepare(sqlite3 *db, Sqlite3BinlogStmt *bStmt) ++{ ++ if (db == NULL || db->xBinlogHandle.binlogApi.binlogReadApi == NULL || bStmt == NULL) { ++ sqlite3_log(SQLITE_ERROR, "binlog stmt prepare parameter is null"); ++ return SQLITE_ERROR; ++ } ++ BinlogReadResultT *result = NULL; ++ int rc = sqlite3TransferBinlogErrno(db->xBinlogHandle.binlogApi.binlogReadApi(db->xBinlogHandle.binlogConn, ++ &result)); ++ if (rc != SQLITE_OK) { ++ if (rc != SQLITE_DONE) { ++ sqlite3_log(rc, "binlog stmt prepare err:%d", rc); ++ } ++ return rc; ++ } ++ bStmt->cursor = result; ++ bStmt->curIdx = 0; ++ return SQLITE_OK; ++} ++ ++SQLITE_PRIVATE int sqlite3BinlogStmtStep(Sqlite3BinlogStmt *bStmt, char **sql) ++{ ++ if (bStmt == NULL || bStmt->cursor == NULL || sql == NULL) { ++ sqlite3_log(SQLITE_ERROR, "binlog stmt stmp parameter is null"); ++ return SQLITE_ERROR; ++ } ++ if (bStmt->curIdx >= bStmt->cursor->eventNum) { ++ return SQLITE_DONE; ++ } ++ BinlogEventT *event = bStmt->cursor->sqlEvent[bStmt->curIdx]; ++ if (event == NULL) { ++ sqlite3_log(SQLITE_ERROR, "binlog stmt stmp event is null"); ++ return SQLITE_ERROR; ++ } ++ *sql = (char *)event->body; ++ bStmt->curIdx++; ++ return SQLITE_ROW; ++} ++ ++SQLITE_PRIVATE void sqlite3BinlogStmtFinalize(sqlite3 *db, Sqlite3BinlogStmt *bStmt) ++{ ++ if (db == NULL || bStmt == NULL || bStmt->cursor == NULL || ++ db->xBinlogHandle.binlogApi.binlogFreeReadResultApi == NULL) { ++ sqlite3_log(SQLITE_WARNING, "binlog stmt finalize parameter is null"); ++ return; ++ } ++ db->xBinlogHandle.binlogApi.binlogFreeReadResultApi(db->xBinlogHandle.binlogConn, bStmt->cursor); ++ bStmt->cursor = NULL; ++ bStmt->curIdx = 0; ++} ++ ++SQLITE_PRIVATE int sqlite3BinlogExecuteReplaySql(sqlite3 *srcDb, sqlite3 *destDb, const char *sql) ++{ ++ if (srcDb == NULL || destDb == NULL || sql == NULL) { ++ sqlite3_log(SQLITE_ERROR, "binlog execute replay sql parameter is null"); ++ return SQLITE_ERROR; ++ } ++ int rc = sqlite3_exec(destDb, sql, NULL, NULL, NULL); ++ if (rc == SQLITE_OK || rc == SQLITE_CONSTRAINT) { ++ return SQLITE_OK; ++ } ++ sqlite3BinlogErrorCallback(srcDb, rc, "exec replay sql failed"); ++ return rc; ++} ++ ++SQLITE_PRIVATE int sqlite3BinlogReplay(sqlite3 *srcDb, sqlite3 *destDb) ++{ ++ if (srcDb == NULL || destDb == NULL) { ++ sqlite3_log(SQLITE_ERROR, "binlog replay parameter is null"); ++ return SQLITE_ERROR; ++ } ++ if ((srcDb->xBinlogHandle.flags & BINLOG_FLAG_ENABLE) == 0) { ++ sqlite3_log(SQLITE_ERROR, "binlog replay srcDb not enable binlog"); ++ return SQLITE_ERROR; ++ } ++ const char *srcFile = sqlite3_db_filename(srcDb, 0); ++ const char *destFile = sqlite3_db_filename(destDb, 0); ++ if (srcFile == NULL || destFile == NULL || strcmp(srcFile, destFile) == 0) { ++ return SQLITE_ERROR; ++ } ++ int res = ++ sqlite3TransferBinlogErrno(srcDb->xBinlogHandle.binlogApi.binlogLockReadApi(srcDb->xBinlogHandle.binlogConn)); ++ if (res != SQLITE_OK) { ++ return res; ++ } ++ do { ++ Sqlite3BinlogStmt bStmt; ++ bStmt.curIdx = 0; ++ bStmt.cursor = NULL; ++ res = sqlite3BinlogStmtPrepare(srcDb, &bStmt); ++ if (res != SQLITE_OK) { ++ if (res == SQLITE_DONE) { ++ res = SQLITE_OK; ++ } else { ++ sqlite3_log(res, "read binlog failed"); ++ } ++ break; ++ } ++ char *bSql = NULL; ++ while ((res = sqlite3BinlogStmtStep(&bStmt, &bSql)) == SQLITE_ROW) { ++ if (bSql == NULL) { ++ sqlite3_log(SQLITE_ERROR, "binlog event read invalid"); ++ continue; ++ } ++ int errCode = sqlite3BinlogExecuteReplaySql(srcDb, destDb, bSql); ++ if (errCode != SQLITE_OK) { ++ res = errCode; ++ break; ++ } ++ bSql = NULL; ++ } ++ sqlite3BinlogStmtFinalize(srcDb, &bStmt); ++ if (res != SQLITE_DONE) { ++ break; ++ } ++ } while (1); ++ (void)srcDb->xBinlogHandle.binlogApi.binlogUnlockReadApi(srcDb->xBinlogHandle.binlogConn); ++ if (res != SQLITE_OK) { ++ sqlite3BinlogClose(srcDb); ++ } ++ return res; ++} ++ ++SQLITE_PRIVATE int sqlite3BinlogClean(sqlite3 *db, BinlogFileCleanModeE mode) ++{ ++ if (db == NULL || mode < BINLOG_FILE_CLEAN_ALL_MODE || ++ mode >= BINLOG_FILE_CLEAN_MODE_MAX || (db->xBinlogHandle.flags & BINLOG_FLAG_ENABLE) == 0 || ++ db->xBinlogHandle.binlogApi.binlogFileCleanApi == NULL) { ++ sqlite3_log(SQLITE_ERROR, "binlog clean parameter is invalid"); ++ return SQLITE_ERROR; ++ } ++ ++ return sqlite3TransferBinlogErrno(db->xBinlogHandle.binlogApi.binlogFileCleanApi(db->xBinlogHandle.binlogConn, mode)); ++} ++ ++SQLITE_PRIVATE void sqlite3BinlogErrorCallback(sqlite3 *db, int errNo, char *errMsg) ++{ ++ if (db == NULL || db->xBinlogHandle.xErrorCallback == NULL) { ++ return; ++ } ++ db->xBinlogHandle.xErrorCallback(db->xBinlogHandle.callbackCtx, errNo, errMsg); ++} ++/************** End of binlog implement ************************************/ ++ + // hw export the symbols + #ifdef SQLITE_EXPORT_SYMBOLS + #ifndef SQLITE_CKSUMVFS_STATIC +-- +2.25.1 + diff --git a/patch/0007-BugFix-CurrVersion.patch b/patch/0008-BugFix-CurrVersion.patch similarity index 86% rename from patch/0007-BugFix-CurrVersion.patch rename to patch/0008-BugFix-CurrVersion.patch index 36d5eac8bfb289677e2bf1055a315f764e5c1464..d63cab113d17b29ddbe8c34bf8742e67f08a2053 100644 --- a/patch/0007-BugFix-CurrVersion.patch +++ b/patch/0008-BugFix-CurrVersion.patch @@ -1,17 +1,18 @@ -From 4cb905b575c2103caada8386cb5fe35562059d4a Mon Sep 17 00:00:00 2001 -From: ryne3366 -Date: Mon, 24 Mar 2025 20:30:45 +0800 -Subject: [PATCH] check permission for meta file +From 0cb4346c98013b6276d1fda601871627bbd9e9c4 Mon Sep 17 00:00:00 2001 +From: Liu Hongyang +Date: Mon, 28 Apr 2025 19:02:48 +0800 +Subject: [PATCH] bugfix +Signed-off-by: Liu Hongyang --- src/sqlite3.c | 120 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 96 insertions(+), 24 deletions(-) diff --git a/src/sqlite3.c b/src/sqlite3.c -index 7e9dcbf..d350cfc 100644 +index 3a6a372..6394950 100644 --- a/src/sqlite3.c +++ b/src/sqlite3.c -@@ -45151,7 +45151,7 @@ static int unixOpen( +@@ -45357,7 +45357,7 @@ static int unixOpen( flags |= SQLITE_OPEN_READONLY; openFlags |= O_RDONLY; isReadonly = 1; @@ -20,7 +21,7 @@ index 7e9dcbf..d350cfc 100644 pReadonly = findReusableFd(zName, flags); if( pReadonly ){ fd = pReadonly->fd; -@@ -70082,6 +70082,7 @@ SQLITE_PRIVATE int sqlite3WalFrames( +@@ -70288,6 +70288,7 @@ SQLITE_PRIVATE int sqlite3WalFrames( ** If parameter xBusy is not NULL, it is a pointer to a busy-handler ** callback. In this case this function runs a blocking checkpoint. */ @@ -28,7 +29,7 @@ index 7e9dcbf..d350cfc 100644 SQLITE_PRIVATE int sqlite3WalCheckpoint( Wal *pWal, /* Wal connection */ sqlite3 *db, /* Check this handle's interrupt flag */ -@@ -70177,7 +70178,12 @@ SQLITE_PRIVATE int sqlite3WalCheckpoint( +@@ -70383,7 +70384,12 @@ SQLITE_PRIVATE int sqlite3WalCheckpoint( -1, 0, zMsg, NULL); rc = SQLITE_CORRUPT_REPORT(&context); }else{ @@ -42,7 +43,7 @@ index 7e9dcbf..d350cfc 100644 } /* If no error occurred, set the output variables. */ -@@ -205751,6 +205757,39 @@ static int fts3ExprTermOffsetInit(Fts3Expr *pExpr, int iPhrase, void *ctx){ +@@ -206091,6 +206097,39 @@ static int fts3ExprTermOffsetInit(Fts3Expr *pExpr, int iPhrase, void *ctx){ return rc; } @@ -82,7 +83,7 @@ index 7e9dcbf..d350cfc 100644 /* ** Implementation of offsets() function. */ -@@ -205787,6 +205826,12 @@ SQLITE_PRIVATE void sqlite3Fts3Offsets( +@@ -206127,6 +206166,12 @@ SQLITE_PRIVATE void sqlite3Fts3Offsets( sCtx.iDocid = pCsr->iPrevId; sCtx.pCsr = pCsr; @@ -95,7 +96,7 @@ index 7e9dcbf..d350cfc 100644 /* Loop through the table columns, appending offset information to ** string-buffer res for each column. */ -@@ -260390,7 +260435,7 @@ static int PragmaMetaDoubleWrie(sqlite3 *db, int iDb, Parse *parse, const char * +@@ -260730,7 +260775,7 @@ static int PragmaMetaDoubleWrie(sqlite3 *db, int iDb, Parse *parse, const char * sqlite3_mutex_enter(db->mutex); // only support enabled meta double write int rc = MetaDwrOpenAndCheck(pBt); @@ -104,7 +105,7 @@ index 7e9dcbf..d350cfc 100644 parse->nErr++; parse->rc = rc; } -@@ -260842,6 +260887,29 @@ static inline const char *GetMetaFilePath(Pager *pPager) +@@ -261182,6 +261227,29 @@ static inline const char *GetMetaFilePath(Pager *pPager) return pPager->metaFd == NULL ? NULL : ((const char *)pPager->metaFd + ROUND8(pPager->pVfs->szOsFile)); } @@ -134,7 +135,7 @@ index 7e9dcbf..d350cfc 100644 static int MetaDwrOpenFile(Pager *pPager, u8 openCreate) { if (pPager->metaFd || pPager->zFilename == NULL) { return SQLITE_OK; -@@ -260858,15 +260926,10 @@ static int MetaDwrOpenFile(Pager *pPager, u8 openCreate) { +@@ -261198,15 +261266,10 @@ static int MetaDwrOpenFile(Pager *pPager, u8 openCreate) { return SQLITE_NOMEM_BKPT; } sqlite3_snprintf(size, metaPath, "%s-dwr", pPager->zFilename); @@ -151,7 +152,7 @@ index 7e9dcbf..d350cfc 100644 u32 flags = (SQLITE_OPEN_READWRITE | SQLITE_OPEN_SUPER_JOURNAL); if (openCreate) { flags |= SQLITE_OPEN_CREATE; -@@ -261425,16 +261488,6 @@ static inline void MarkLockStatusByRc(int rc, u32 lockIdx, u32 lockLen, u8 lockT +@@ -261765,16 +261828,6 @@ static inline void MarkLockStatusByRc(int rc, u32 lockIdx, u32 lockLen, u8 lockT } } @@ -168,7 +169,7 @@ index 7e9dcbf..d350cfc 100644 static inline const char *IdxToLockName(u32 idx) { const char *lockName[MAX_LOCK_NUM] = {"write", "ckpt", "recover", "read0", -@@ -261451,7 +261504,7 @@ static void DumpHandleLock(char *dumpBuf, int dumpBufLen) +@@ -261791,7 +261844,7 @@ static void DumpHandleLock(char *dumpBuf, int dumpBufLen) for (int i = 0; i < MAX_LOCK_NUM && availLen > DUMP_MAX_STR_LEN; i++) { if (lockStatus[i] != NO_LOCK) { tmp[0] = '\0'; @@ -177,7 +178,7 @@ index 7e9dcbf..d350cfc 100644 int len = strlen(tmp); tmp += len; availLen -= len; -@@ -261490,8 +261543,8 @@ static void DumpTrxProcessLocks(unixFile *file, char *dumpBuf, int dumpBufLen) +@@ -261830,8 +261883,8 @@ static void DumpTrxProcessLocks(unixFile *file, char *dumpBuf, int dumpBufLen) sqlite3_log(SQLITE_ERROR, "[SQLite]Inode is null!"); return; } @@ -188,7 +189,7 @@ index 7e9dcbf..d350cfc 100644 const char *lockName[DB_LOCK_NUM] = {"pending", "reserved", "shared_first"}; char *tmp = dumpBuf; int availLen = dumpBufLen - 1; -@@ -261511,10 +261564,13 @@ static void DumpTrxProcessLocks(unixFile *file, char *dumpBuf, int dumpBufLen) +@@ -261851,10 +261904,13 @@ static void DumpTrxProcessLocks(unixFile *file, char *dumpBuf, int dumpBufLen) static void DumpWalLocks(unixFile *file, u8 walEnabled, char *dumpBuf, int dumpBufLen) { @@ -204,9 +205,9 @@ index 7e9dcbf..d350cfc 100644 unixShmNode *pShmNode = file->pShm->pShmNode; char *tmp = dumpBuf; int availLen = dumpBufLen - 1; -@@ -261583,6 +261639,22 @@ static void DumpLocksByPager(Pager *pPager) +@@ -262789,6 +262845,22 @@ SQLITE_PRIVATE void sqlite3BinlogErrorCallback(sqlite3 *db, int errNo, char *err } - #endif /* SQLITE_OS_UNIX */ + /************** End of binlog implement ************************************/ +static void walLogCheckpointInfo(Wal *pWal, sqlite3 *db, sqlite3_int64 startTime) { + sqlite3_int64 endTime; @@ -228,5 +229,5 @@ index 7e9dcbf..d350cfc 100644 #ifdef SQLITE_EXPORT_SYMBOLS #ifndef SQLITE_CKSUMVFS_STATIC -- -2.34.1 +2.25.1