From 266fef0b19accb15bcde0bedb73d254cb9965651 Mon Sep 17 00:00:00 2001
From: Liu Hongyang <liuhongyang4@huawei.com>
Date: Thu, 11 Sep 2025 14:27:25 +0800
[PATCH 06/12] Support-Binlog
src/sqlite3.c | 1587 ++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 1578 insertions(+), 9 deletions(-)
@@ -2938,7 +2938,9 @@ SQLITE_API sqlite3_int64 sqlite3_last_insert_rowid(sqlite3*);
#define SQLITE_DBCONFIG_SET_SHAREDBLOCK 2004
#define SQLITE_DBCONFIG_USE_SHAREDBLOCK 2005
#endif /* SQLITE_SHARED_BLOCK_OPTIMIZATION */
-
+#ifdef SQLITE_ENABLE_BINLOG
+#define SQLITE_DBCONFIG_ENABLE_BINLOG 2006 /* Sqlite3BinlogConfig */
+#endif
/*
** CAPI3REF: Set the Last Insert Rowid value.
** METHOD: sqlite3
@@ -5351,6 +5353,19 @@ 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 */
+#ifdef SQLITE_ENABLE_BINLOG
+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);
+#endif
/*
** CAPI3REF: Number of columns in a result set
** METHOD: sqlite3_stmt
@@ -17617,6 +17632,202 @@ typedef struct CodecParameter {
} CodecParameter;
#endif /* defined(SQLITE_HAS_CODEC) && defined(SQLITE_CODEC_ATTACH_CHANGED) */
+#ifdef SQLITE_ENABLE_BINLOG
+/************** 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, const char *dbPath);
+typedef void (*BinlogOnLogFullFuncT)(void *pCtx, u16 currentCount, const char *dbPath);
+typedef int (*BinlogOnErrnoTransFuncT)(BinlogErrno errNo);
+
+typedef struct BinlogConfig {
+ Sqlite3BinlogMode logMode;
+ u16 fullCallbackThreshold;
+ u32 maxFileSize;
+ char *filePath;
+ BinlogOnErrorFuncT onError;
+ BinlogOnLogFullFuncT onLogFull;
+ void *callbackCtx;
+ u32 readHwmdelay;
+ BinlogOnErrnoTransFuncT onErrnoTrans;
+} 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_ROW_START = 69,
+ BINLOG_EVENT_TYPE_ROW_TABLE = 70,
+ BINLOG_EVENT_TYPE_ROW_FULL_DATA = 71,
+ BINLOG_EVENT_TYPE_ROW_COMMIT = 72,
+ BINLOG_EVENT_TYPE_ROW_ROLLBACK = 73,
+ 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, const char *dbPath);
+ void (*xLogFullCallback)(void *pCtx, u16 currentCount, const char *dbPath);
+ 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
+ int nZero; // extra zero bytes at the end 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, const char *dbPath);
+ void (*xLogFullCallback)(void *pCtx, u16 currentCount, const char *dbPath);
+ BinlogInstanceT *binlogConn;
+ BinlogApi binlogApi;
+ u8 isSkipTrigger;
+} 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,
+ STMT_TYPE_TEMP_DB_MODIFY,
+} 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(sqlite3 *db, Sqlite3BinlogStmt *bStmt, char **sql, Table **pOutTable);
+SQLITE_PRIVATE void sqlite3BinlogStmtFinalize(sqlite3 *db, Sqlite3BinlogStmt *bStmt);
+
+SQLITE_PRIVATE int sqlite3BinlogInitApi(sqlite3 *db);
+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, const BinlogRow *pRow);
+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 ************************************/
+#endif
+
/*
** Each database connection is an instance of the following structure.
*/
@@ -17770,6 +17981,9 @@ struct sqlite3 {
#if defined(SQLITE_HAS_CODEC) && defined(SQLITE_CODEC_ATTACH_CHANGED)
CodecParameter codecParm;
#endif /* defined(SQLITE_HAS_CODEC) && defined(SQLITE_CODEC_ATTACH_CHANGED) */
+#ifdef SQLITE_ENABLE_BINLOG
+ Sqlite3BinlogHandle xBinlogHandle;
+#endif
};
/*
@@ -23666,6 +23880,14 @@ struct Vdbe {
int startPos;
int addedRows;
#endif /* SQLITE_SHARED_BLOCK_OPTIMIZATION */
+#ifdef SQLITE_ENABLE_BINLOG
+/* begin of binlog related field */
+ StmtType stmtType; /* Type of binlog statement */
+ BinlogDMLData *pBinlogDMLData; /* Data for binlog row-based DML statement */
+ const char *pDataTmp; /* Temporary binlog data for op_notfound */
+ sqlite3_uint64 nDataTmp; /* Size of pDataTmp */
+/* end of binlog related field */
+#endif
};
/*
@@ -88435,7 +88657,11 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){
rc = sqlite3BtreeCommitPhaseOne(pBt, 0);
}
}
-
+#ifdef SQLITE_ENABLE_BINLOG
+ if ( rc==SQLITE_OK ){
+ sqlite3BinlogWrite(p);
+ }
+#endif
/* 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,
@@ -88583,6 +88809,9 @@ 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.
*/
+#ifdef SQLITE_ENABLE_BINLOG
+ sqlite3BinlogWrite(p);
+#endif
disable_simulated_io_errors();
sqlite3BeginBenignMalloc();
for(i=0; i<db->nDb; i++){
@@ -88868,6 +89097,9 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){
}else if( eStatementOp==0 ){
if( p->rc==SQLITE_OK || p->errorAction==OE_Fail ){
eStatementOp = SAVEPOINT_RELEASE;
+#ifdef SQLITE_ENABLE_BINLOG
+ sqlite3BinlogWrite(p);
+#endif
}else if( p->errorAction==OE_Abort ){
eStatementOp = SAVEPOINT_ROLLBACK;
}else{
@@ -89090,6 +89322,9 @@ SQLITE_PRIVATE int sqlite3VdbeReset(Vdbe *p){
fclose(out);
}
}
+#endif
+#ifdef SQLITE_ENABLE_BINLOG
+ sqlite3FreeBinlogRowData(p);
#endif
return p->rc & db->errMask;
}
@@ -89194,6 +89429,9 @@ static void sqlite3VdbeClearObject(sqlite3 *db, Vdbe *p){
sqlite3DbFree(db, p->aScan);
}
#endif
+#ifdef SQLITE_ENABLE_BINLOG
+ sqlite3FreeBinlogRowData(p);
+#endif
}
/*
@@ -91886,6 +92124,52 @@ SQLITE_API int sqlite3_set_droptable_handle(sqlite3 *db, void (*xFunc)(sqlite3*,
}
#endif /* SQLITE_ENABLE_DROPTABLE_CALLBACK */
+#ifdef SQLITE_ENABLE_BINLOG
+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;
+ }
+ if(!sqlite3SafetyCheckOk(srcDb) || !sqlite3SafetyCheckOk(destDb)) {
+ sqlite3_log(SQLITE_WARNING, "replay binlog parameter is not valid db");
+ return SQLITE_MISUSE_BKPT;
+ }
+ 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;
+ }
+ if(!sqlite3SafetyCheckOk(db)) {
+ sqlite3_log(SQLITE_WARNING, "clean binlog parameter is not valid db");
+ return SQLITE_MISUSE_BKPT;
+ }
+ 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;
+}
+#endif
/*
** This is the top-level implementation of sqlite3_step(). Call
** sqlite3Step() to do most of the work. If a schema error occurs,
@@ -99189,6 +99473,12 @@ case OP_Found: { /* jump, in3, ncycle */
pC->nullRow = 1-alreadyExists;
pC->deferredMoveto = 0;
pC->cacheStatus = CACHE_STALE;
+#ifdef SQLITE_ENABLE_BINLOG
+ if ( pOp->opcode==OP_NotFound && r.nField==0 && !sqlite3IsSkipWriteBinlog(p)){
+ p->nDataTmp = r.aMem->n;
+ p->pDataTmp = r.aMem->z;
+ }
+#endif
if( pOp->opcode==OP_Found ){
VdbeBranchTaken(alreadyExists!=0,2);
if( alreadyExists ) goto jump_to_p2;
@@ -99584,6 +99874,16 @@ case OP_Insert: {
}
x.pKey = 0;
assert( BTREE_PREFORMAT==OPFLAG_PREFORMAT );
+#ifdef SQLITE_ENABLE_BINLOG
+ if ( pOp->p4.pTab
+ && !sqlite3IsSkipWriteBinlog(p)
+ && sqlite3IsRowBasedBinlog(p)
+ && sqlite3TransferLogEventType(p->stmtType) == BINLOG_EVENT_TYPE_DML ) {
+ BinlogRow rowData = {
+ (pOp->p5 & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_INSERT, x.nKey, x.nData, x.nZero, pData->z};
+ sqlite3StoreBinlogRowData(p, pC->uc.pCursor, &rowData);
+ }
+#endif
rc = sqlite3BtreeInsert(pC->uc.pCursor, &x,
(pOp->p5 & (OPFLAG_APPEND|OPFLAG_SAVEPOSITION|OPFLAG_PREFORMAT)),
seekResult
@@ -99699,6 +99999,29 @@ case OP_Delete: {
assert( CORRUPT_DB || pC->movetoTarget==iKey );
}
#endif
+#ifdef SQLITE_ENABLE_BINLOG
+ if( !sqlite3IsSkipWriteBinlog(p)
+ && sqlite3IsRowBasedBinlog(p)
+ && sqlite3TransferLogEventType(p->stmtType) == BINLOG_EVENT_TYPE_DML ){
+ if (pC->isTable) {
+ BinlogRow rowData = {SQLITE_DELETE, sqlite3BtreeIntegerKey(pC->uc.pCursor), 0, 0, NULL};
+ sqlite3StoreBinlogRowData(p, pC->uc.pCursor, &rowData);
+ } 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
+ ** pC may have no row data or invalid row data when deletion has a subquery
+ ** so we need to get row data from op_notfound, which is stored in p->pDataTmp
+ */
+ int isUseTmpData = pOp->p4type == P4_TABLE && p->nDataTmp != 0 && p->pDataTmp != NULL;
+ sqlite3_uint64 nData = isUseTmpData ? p->nDataTmp : pC->szRow;
+ const char *pData = isUseTmpData ? p->pDataTmp : (const char *)pC->aRow;
+ BinlogRow rowData = {SQLITE_DELETE, -1, nData, 0, pData};
+ sqlite3StoreBinlogRowData(p, pC->uc.pCursor, &rowData);
+ }
+ }
+ }
+#endif
/* 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
@@ -100322,6 +100645,15 @@ case OP_IdxInsert: { /* in2 */
x.pKey = pIn2->z;
x.aMem = aMem + pOp->p3;
x.nMem = (u16)pOp->p4.i;
+#ifdef SQLITE_ENABLE_BINLOG
+ if( (pOp->p5 & OPFLAG_NCHANGE)
+ && !sqlite3IsSkipWriteBinlog(p)
+ && sqlite3IsRowBasedBinlog(p)
+ && sqlite3TransferLogEventType(p->stmtType) == BINLOG_EVENT_TYPE_DML ){
+ BinlogRow rowData = {SQLITE_INSERT, -1, x.nKey, 0, pIn2->z};
+ sqlite3StoreBinlogRowData(p, pC->uc.pCursor, &rowData);
+ }
+#endif
rc = sqlite3BtreeInsert(pC->uc.pCursor, &x,
(pOp->p5 & (OPFLAG_APPEND|OPFLAG_SAVEPOSITION|OPFLAG_PREFORMAT)),
((pOp->p5 & OPFLAG_USESEEKRESULT) ? pC->seekResult : 0)
@@ -116953,7 +117285,11 @@ SQLITE_PRIVATE void sqlite3AlterRenameTable(
renameReloadSchema(pParse, iDb, INITFLAG_AlterRename);
renameTestSchema(pParse, zDb, iDb==1, "after rename", 0);
-
+#ifdef SQLITE_ENABLE_BINLOG
+ if( v ){
+ v->stmtType = iDb==1 ? STMT_TYPE_TEMP_DB_MODIFY : STMT_TYPE_ALTER_RENAME_TABLE;
+ }
+#endif
exit_rename_table:
sqlite3SrcListDelete(db, pSrc);
sqlite3DbFree(db, zName);
@@ -117135,6 +117471,9 @@ SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){
zTab, zDb
);
}
+#ifdef SQLITE_ENABLE_BINLOG
+ v->stmtType = iDb==1 ? STMT_TYPE_TEMP_DB_MODIFY : STMT_TYPE_ALTER_ADD_COL;
+#endif
}
}
@@ -117346,7 +117685,13 @@ SQLITE_PRIVATE void sqlite3AlterRenameColumn(
/* Drop and reload the database schema. */
renameReloadSchema(pParse, iSchema, INITFLAG_AlterRename);
renameTestSchema(pParse, zDb, iSchema==1, "after rename", 1);
-
+#ifdef SQLITE_ENABLE_BINLOG
+ Vdbe *v = NULL;
+ v = sqlite3GetVdbe(pParse);
+ if( v ){
+ v->stmtType = iSchema==1 ? STMT_TYPE_TEMP_DB_MODIFY : STMT_TYPE_ALTER_RENAME_COL;
+ }
+#endif
exit_rename_column:
sqlite3SrcListDelete(db, pSrc);
sqlite3DbFree(db, zOld);
@@ -118957,7 +119302,13 @@ SQLITE_PRIVATE void sqlite3AlterDropColumn(Parse *pParse, SrcList *pSrc, const T
sqlite3VdbeAddOp2(v, OP_Next, iCur, addr+1); VdbeCoverage(v);
sqlite3VdbeJumpHere(v, addr);
}
-
+#ifdef SQLITE_ENABLE_BINLOG
+ Vdbe *v = NULL;
+ v = sqlite3GetVdbe(pParse);
+ if( v ){
+ v->stmtType = iDb==1 ? STMT_TYPE_TEMP_DB_MODIFY : STMT_TYPE_ALTER_DROP_COL;
+ }
+#endif
exit_drop_column:
sqlite3DbFree(db, zCol);
sqlite3SrcListDelete(db, pSrc);
@@ -123295,6 +123646,9 @@ SQLITE_PRIVATE void sqlite3StartTable(
sqlite3VdbeAddOp3(v, OP_Insert, 0, reg3, reg1);
sqlite3VdbeChangeP5(v, OPFLAG_APPEND);
sqlite3VdbeAddOp0(v, OP_Close);
+#ifdef SQLITE_ENABLE_BINLOG
+ v->stmtType = iDb==1 ? STMT_TYPE_TEMP_DB_MODIFY : STMT_TYPE_CREATE_TABLE;
+#endif
}
/* Normal (non-error) return. */
@@ -124963,7 +125317,13 @@ SQLITE_PRIVATE void sqlite3CreateView(
/* Use sqlite3EndTable() to add the view to the schema table */
sqlite3EndTable(pParse, 0, &sEnd, 0, 0);
-
+#ifdef SQLITE_ENABLE_BINLOG
+ Vdbe *v = NULL;
+ v = sqlite3GetVdbe(pParse);
+ if( v ){
+ v->stmtType = iDb==1 ? STMT_TYPE_TEMP_DB_MODIFY : STMT_TYPE_CREATE_VIEW;
+ }
+#endif
create_view_fail:
sqlite3SelectDelete(db, pSelect);
if( IN_RENAME_OBJECT ){
@@ -125486,6 +125846,13 @@ SQLITE_PRIVATE void sqlite3DropTable(Parse *pParse, SrcList *pName, int isView,
sqlite3FkDropTable(pParse, pName, pTab);
}
sqlite3CodeDropTable(pParse, pTab, iDb, isView);
+#ifdef SQLITE_ENABLE_BINLOG
+ if( iDb == 1) {
+ v->stmtType = STMT_TYPE_TEMP_DB_MODIFY;
+ } else {
+ v->stmtType = (isView > 0) ? STMT_TYPE_DROP_VIEW : STMT_TYPE_DROP_TABLE;
+ }
+#endif
}
exit_drop_table:
@@ -126344,6 +126711,9 @@ 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);
+#ifdef SQLITE_ENABLE_BINLOG
+ v->stmtType = iDb==1 ? STMT_TYPE_TEMP_DB_MODIFY : STMT_TYPE_CREATE_INDEX;
+#endif
}else{
/* An automatic index created by a PRIMARY KEY or UNIQUE constraint */
/* zStmt = sqlite3MPrintf(""); */
@@ -126546,6 +126916,9 @@ 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);
+#ifdef SQLITE_ENABLE_BINLOG
+ v->stmtType = iDb==1 ? STMT_TYPE_TEMP_DB_MODIFY : STMT_TYPE_DROP_INDEX;
+#endif
}
exit_drop_index:
@@ -127070,6 +127443,9 @@ SQLITE_PRIVATE void sqlite3BeginTransaction(Parse *pParse, int type){
}
}
sqlite3VdbeAddOp0(v, OP_AutoCommit);
+#ifdef SQLITE_ENABLE_BINLOG
+ v->stmtType = STMT_TYPE_BEGIN_TRANSACTION;
+#endif
}
/*
@@ -127092,6 +127468,9 @@ SQLITE_PRIVATE void sqlite3EndTransaction(Parse *pParse, int eType){
v = sqlite3GetVdbe(pParse);
if( v ){
sqlite3VdbeAddOp2(v, OP_AutoCommit, 1, isRollback);
+#ifdef SQLITE_ENABLE_BINLOG
+ v->stmtType = (isRollback > 0) ? STMT_TYPE_ROLLBACK_TRANSACTION : STMT_TYPE_COMMIT_TRANSACTION;
+#endif
}
}
@@ -127112,6 +127491,9 @@ SQLITE_PRIVATE void sqlite3Savepoint(Parse *pParse, int op, Token *pName){
return;
}
sqlite3VdbeAddOp4(v, OP_Savepoint, op, 0, 0, zName, P4_DYNAMIC);
+#ifdef SQLITE_ENABLE_BINLOG
+ v->stmtType = STMT_TYPE_SAVEPOINT;
+#endif
}
}
@@ -137480,6 +137862,13 @@ 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 */
+#ifdef SQLITE_ENABLE_BINLOG
+#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 /* SQLITE_ENABLE_BINLOG */
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
@@ -139533,6 +139922,9 @@ SQLITE_PRIVATE void sqlite3Pragma(
if( v==0 ) return;
sqlite3VdbeRunOnlyOnce(v);
pParse->nMem = 2;
+#ifdef SQLITE_ENABLE_BINLOG
+ v->stmtType = STMT_TYPE_PRAGMA;
+#endif
/* Interpret the [schema.] part of the pragma statement. iDb is the
** index of the database this pragma is being applied to in db.aDb[]. */
@@ -152286,7 +152678,13 @@ SQLITE_PRIVATE void sqlite3BeginTrigger(
if (tr_tm == TK_INSTEAD){
tr_tm = TK_BEFORE;
}
-
+#ifdef SQLITE_ENABLE_BINLOG
+ Vdbe *v = NULL;
+ v = sqlite3GetVdbe(pParse);
+ if( v ){
+ v->stmtType = iDb==1 ? STMT_TYPE_TEMP_DB_MODIFY : STMT_TYPE_CREATE_TRIGGER;
+ }
+#endif
/* Build the Trigger object */
pTrigger = (Trigger*)sqlite3DbMallocZero(db, sizeof(Trigger));
if( pTrigger==0 ) goto trigger_cleanup;
@@ -152681,6 +153079,13 @@ SQLITE_PRIVATE void sqlite3DropTrigger(Parse *pParse, SrcList *pName, int noErr)
goto drop_trigger_cleanup;
}
sqlite3DropTriggerPtr(pParse, pTrigger);
+#ifdef SQLITE_ENABLE_BINLOG
+ Vdbe *v = NULL;
+ v = sqlite3GetVdbe(pParse);
+ if (v) {
+ v->stmtType = i==0 ? STMT_TYPE_TEMP_DB_MODIFY : STMT_TYPE_DROP_TRIGGER;
+ }
+#endif
drop_trigger_cleanup:
sqlite3SrcListDelete(db, pName);
@@ -153427,6 +153832,15 @@ SQLITE_PRIVATE void sqlite3CodeRowTrigger(
assert( tr_tm==TRIGGER_BEFORE || tr_tm==TRIGGER_AFTER );
assert( (op==TK_UPDATE)==(pChanges!=0) );
+#ifdef SQLITE_ENABLE_BINLOG
+ /* If this db is a replica for a row-based binlog, the trigger operations
+ ** will be recorded by the row statements. Therefore, we need to skip the
+ ** trigger operations on replica database.
+ */
+ if (pParse->db->xBinlogHandle.isSkipTrigger) {
+ return;
+ }
+#endif
for(p=pTrigger; p; p=p->pNext){
/* Sanity checking: The schema for the trigger and for the table are
@@ -180656,6 +181070,13 @@ SQLITE_API int sqlite3_db_config(sqlite3 *db, int op, ...){
break;
}
#endif /* SQLITE_SHARED_BLOCK_OPTIMIZATION */
+#ifdef SQLITE_ENABLE_BINLOG
+ case SQLITE_DBCONFIG_ENABLE_BINLOG: {
+ Sqlite3BinlogConfig *pBinlogConfig = va_arg(ap, Sqlite3BinlogConfig*);
+ rc = sqlite3SetBinLogConfig(db, pBinlogConfig);
+ break;
+ }
+#endif
default: {
static const struct {
int op; /* The opcode */
@@ -181083,6 +181504,9 @@ SQLITE_PRIVATE void sqlite3LeaveMutexAndCloseZombie(sqlite3 *db){
sqlite3CollapseDatabaseArray(db);
assert( db->nDb<=2 );
assert( db->aDb==db->aDbStatic );
+#ifdef SQLITE_ENABLE_BINLOG
+ (void)sqlite3BinlogClose(db);
+#endif
/* Tell the code in notify.c that the connection no longer holds any
** locks and does not require any further unlock-notify callbacks.
@@ -183308,6 +183732,9 @@ opendb_out:
db->mDropSchemaName = NULL;
db->xDropTableHandle = NULL;
#endif /* SQLITE_ENABLE_DROPTABLE_CALLBACK */
+#ifdef SQLITE_ENABLE_BINLOG
+ sqlite3BinlogReset(db);
+#endif
*ppDb = db;
#ifdef SQLITE_ENABLE_SQLLOG
if( sqlite3GlobalConfig.xSqllog ){
@@ -256913,6 +257340,1136 @@ static void walLogCheckpointInfo(Wal *pWal, sqlite3 *db, sqlite3_int64 startTime
}
#endif
+#ifdef SQLITE_ENABLE_BINLOG
+/************** 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 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 = bConfig->xErrorCallback;
+ conf.onLogFull = bConfig->xLogFullCallback;
+ conf.onErrnoTrans = sqlite3TransferBinlogErrno;
+ conf.callbackCtx = bConfig->callbackCtx;
+
+ 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;
+ db->xBinlogHandle.isSkipTrigger = 0;
+ 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, u32 nSql, 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 = nSql;
+ 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 && p->pBinlogDMLData->pSavePointName != NULL) {
+ u32 len = strlen(p->pBinlogDMLData->pSavePointName) + 1;
+ sqlite3DirectWriteBinlog(p, BINLOG_EVENT_TYPE_ROW_ROLLBACK, p->pBinlogDMLData->pSavePointName, len, 0);
+ sqlite3DirectWriteBinlog(p, BINLOG_EVENT_TYPE_ROW_COMMIT, p->pBinlogDMLData->pSavePointName, len, 1);
+ p->db->xBinlogHandle.flags |= BINLOG_FLAG_UPDATE_TID;
+ } else if (!isReleased && p->pBinlogDMLData->pSavePointName != NULL) {
+ /* 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
+ */
+ u32 len = strlen(p->pBinlogDMLData->pSavePointName) + 1;
+ sqlite3DirectWriteBinlog(p, BINLOG_EVENT_TYPE_ROW_ROLLBACK, p->pBinlogDMLData->pSavePointName, len, 0);
+ }
+
+ 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; i<pIdx->nKeyCol; 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, i);
+ 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; i<pIdx->nKeyCol; 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; k<pTab->nCol; 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; i<pTab->nCol; 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(sqlite3 *db, const BinlogRow *pRow, Table *pTab){
+ assert( db!=NULL );
+ assert( pRow!=NULL );
+ assert( pTab!=NULL );
+
+ 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;
+}
+
+SQLITE_PRIVATE int sqlite3BinlogWriteTable(Vdbe *p, Table *pTable){
+ assert( p!=NULL );
+ assert( pTable!=NULL );
+
+ char *tableName = pTable->zName;
+ u32 len = strlen(tableName) + 1;
+ return sqlite3DirectWriteBinlog(p, BINLOG_EVENT_TYPE_ROW_TABLE, tableName, len, 0);
+}
+
+SQLITE_PRIVATE int sqlite3BinlogDecodeRowBuffer(char *buffer, u32 nBuffer, u8 *op,
+ sqlite3_int64 *rowid, sqlite3_uint64 *nData, int *nZero, char **pData)
+{
+ if (nBuffer < sizeof(u8) + sizeof(sqlite3_int64) + sizeof(sqlite3_uint64)) {
+ sqlite3_log(SQLITE_WARNING, "Failed to decode binlog row, %u", nBuffer);
+ return SQLITE_ERROR;
+ }
+
+ u64 offset = 0;
+ *op = *((u8*)(buffer + offset));
+ offset += sizeof(u8);
+
+ errno_t errNo = memcpy_s(rowid, sizeof(sqlite3_int64), buffer + offset, sizeof(sqlite3_int64));
+ if (errNo != EOK) {
+ sqlite3_log(SQLITE_WARNING, "Failed to decode binlog row rowid");
+ return SQLITE_ERROR;
+ }
+ offset += sizeof(sqlite3_int64);
+
+ errNo = memcpy_s(nData, sizeof(sqlite3_uint64), buffer + offset, sizeof(sqlite3_uint64));
+ if (errNo != EOK) {
+ sqlite3_log(SQLITE_WARNING, "Failed to decode binlog row nData");
+ return SQLITE_ERROR;
+ }
+ offset += sizeof(sqlite3_uint64);
+
+ if (nBuffer == (sizeof(u8) + sizeof(sqlite3_int64) + sizeof(sqlite3_uint64) + *nData + sizeof(int))) {
+ errNo = memcpy_s(nZero, sizeof(int), buffer + offset, sizeof(int));
+ if (errNo != EOK) {
+ sqlite3_log(SQLITE_WARNING, "Failed to decode binlog row nZero");
+ return SQLITE_ERROR;
+ }
+ offset += sizeof(int);
+ }
+
+ *pData = buffer + offset;
+ return SQLITE_OK;
+}
+
+SQLITE_PRIVATE int sqlite3BinlogGetRowBuffer(const BinlogRow *pRow, char **pOutBuffer, u64 *nOutBuffer){
+ assert( pRow!=NULL );
+ assert( pOutBuffer!=NULL );
+ assert( nOutBuffer!=NULL );
+
+ u8 op = pRow->op == SQLITE_DELETE ? SQLITE_DELETE : SQLITE_INSERT;
+ sqlite3_int64 rowid = pRow->rowid;
+ sqlite3_uint64 nData = pRow->nData;
+ int nZero = pRow->nZero;
+ const char* pData = pRow->pData;
+
+ u64 nRowBuffer = sizeof(op) + sizeof(rowid) + sizeof(nData) + +sizeof(nZero) + nData;
+ char *pRowBuffer = sqlite3MallocZero(nRowBuffer);
+ if (pRowBuffer == NULL) {
+ sqlite3_log(SQLITE_ERROR, "Failed to malloc binlog row, %lu", nRowBuffer);
+ return SQLITE_ERROR;
+ }
+
+ errno_t errNo = memcpy_s(pRowBuffer, nRowBuffer, &op, sizeof(op));
+ if (errNo != EOK) {
+ goto error_when_memcpy_binlog_row;
+ }
+
+ u64 offset = sizeof(op);
+ errNo = memcpy_s(pRowBuffer + offset, nRowBuffer, &rowid, sizeof(rowid));
+ offset += sizeof(rowid);
+ if (errNo != EOK) {
+ goto error_when_memcpy_binlog_row;
+ }
+
+ errNo = memcpy_s(pRowBuffer + offset, nRowBuffer, &nData, sizeof(nData));
+ offset += sizeof(nData);
+ if (errNo != EOK) {
+ goto error_when_memcpy_binlog_row;
+ }
+
+ errNo = memcpy_s(pRowBuffer + offset, nRowBuffer, &nZero, sizeof(nZero));
+ offset += sizeof(nZero);
+ if (errNo != EOK) {
+ goto error_when_memcpy_binlog_row;
+ }
+
+ if (nData > 0) {
+ errNo = memcpy_s(pRowBuffer + offset, nRowBuffer, pData, nData);
+ if (errNo != EOK) {
+ goto error_when_memcpy_binlog_row;
+ }
+ }
+
+ *pOutBuffer = pRowBuffer;
+ *nOutBuffer = nRowBuffer;
+ return SQLITE_OK;
+
+error_when_memcpy_binlog_row:
+ sqlite3_free(pRowBuffer);
+ sqlite3_log(SQLITE_ERROR, "Failed to copy binlog row, %lu", nRowBuffer);
+ return SQLITE_ERROR;
+}
+
+SQLITE_PRIVATE int sqlite3BinlogWriteRowData(Vdbe *p, const BinlogRow *pRow){
+ assert( p!=NULL && pRow!=NULL );
+ if ((pRow->nData == 0 || pRow->pData == NULL) && pRow->op != SQLITE_DELETE) {
+ sqlite3_log(SQLITE_ERROR, "binlog row data empty, %llu", pRow->nData);
+ return SQLITE_ERROR;
+ }
+
+ u64 nRowBuffer = 0;
+ char *pRowBuffer = NULL;
+ int rc = sqlite3BinlogGetRowBuffer(pRow, &pRowBuffer, &nRowBuffer);
+ if (rc != SQLITE_OK) {
+ return rc;
+ }
+
+ rc = sqlite3DirectWriteBinlog(p, BINLOG_EVENT_TYPE_ROW_FULL_DATA, pRowBuffer, nRowBuffer, 0);
+ sqlite3_free(pRowBuffer);
+ return rc;
+}
+
+/**
+** 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, const BinlogRow *pRow){
+ assert( p!=NULL && pCursor!=NULL && pRow!=NULL );
+ assert( p->db!=NULL );
+ assert( sqlite3IsRowBasedBinlog(p) );
+ sqlite3 *db = p->db;
+ BinlogDMLData *dataContainer = NULL;
+
+ if (pCursor->pBtree == p->db->aDb[1].pBt) {
+ p->stmtType = STMT_TYPE_TEMP_DB_MODIFY;
+ }
+
+ 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; i<SQLITE_UUID_BLOB_LENGTH; i++) {
+ u8 byte = db->xBinlogHandle.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;
+ int rc = sqlite3DirectWriteBinlog(p, BINLOG_EVENT_TYPE_ROW_START, savePointName, strlen(savePointName)+1, 0);
+ if (rc != SQLITE_OK) {
+ goto error_when_store_binlog;
+ }
+ } else {
+ dataContainer = p->pBinlogDMLData;
+ }
+
+ if (dataContainer->pTable != pTab) {
+ int rc = sqlite3BinlogWriteTable(p, pTab);
+ if (rc != SQLITE_OK) {
+ goto error_when_store_binlog;
+ }
+ dataContainer->pTable = pTab;
+ }
+
+ if (sqlite3BinlogWriteRowData(p, pRow) == SQLITE_OK) {
+ 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 *sqlite3BinlogGetHexFromBlobOrStr(sqlite3 *db, sqlite3_value *value) {
+ assert( db!=NULL && value!=NULL );
+ size_t strSize = (value->n) * 2 + 1;
+ char *zHex, *z;
+ z = zHex =sqlite3DbMallocZero(db, strSize);
+ if (z == NULL) {
+ return NULL;
+ }
+ for(int i=0; i<value->n; i++) {
+ unsigned char c = (value->z)[i];
+ *(z++) = hexdigits[(c>>4)&0xf];
+ *(z++) = hexdigits[c&0xf];
+ }
+ *z = 0;
+ return zHex;
+}
+
+SQLITE_PRIVATE char *sqlite3BinlogGetEscapedText(sqlite3 *db, const char *originalStr) {
+ sqlite3_str *pStr = sqlite3_str_new(db);
+ size_t nOriginal = strlen(originalStr);
+ sqlite3_str_appendchar(pStr, 1, '\'');
+ for (size_t i=0; i<nOriginal; i++) {
+ if (originalStr[i] == '\'') {
+ sqlite3_str_appendchar(pStr, 1, '\'');
+ }
+ sqlite3_str_appendchar(pStr, 1, originalStr[i]);
+ }
+ sqlite3_str_appendchar(pStr, 1, '\'');
+ return sqlite3_str_finish(pStr);
+}
+
+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;
+ sqlite3_uint64 nData = pRow->nData + (sqlite3_uint64)pRow->nZero;
+ char *pData = sqlite3MallocZero(nData);
+ if (pData == NULL) {
+ sqlite3_log(SQLITE_ERROR, "binlog failed to allocate memory");
+ return NULL;
+ }
+ errno_t errNo = memcpy_s(pData, nData, pRow->pData, pRow->nData);
+ if (errNo != EOK) {
+ sqlite3_log(SQLITE_ERROR, "binlog failed to copy data, rc:%d, errno:%d", errNo, errno);
+ sqlite3_free(pData);
+ return NULL;
+ }
+ sqlite3BinlogStat4Column(db, pData, nData, iCol, &tempVal);
+ if (tempVal == NULL) {
+ sqlite3_log(SQLITE_ERROR, "decode data failed");
+ sqlite3_free(pData);
+ return NULL;
+ }
+ int type = sqlite3_value_type(tempVal);
+ const unsigned char *temp = sqlite3_value_text(tempVal);
+
+ char *res = NULL;
+ if (type == SQLITE_BLOB) {
+ char *hex = sqlite3BinlogGetHexFromBlobOrStr(db, tempVal);
+ if (hex != NULL) {
+ res = sqlite3_mprintf("x'%s'", hex);
+ sqlite3DbFree(db, hex);
+ } else {
+ res = sqlite3_mprintf("'%s'", temp);
+ }
+ } else if (type == SQLITE_TEXT) {
+ res = sqlite3BinlogGetEscapedText(db, (const char *)temp);
+ } else if (type == SQLITE_NULL) {
+ res = sqlite3_mprintf("NULL");
+ } else {
+ res = sqlite3_mprintf("%s", temp);
+ }
+ sqlite3ValueFree(tempVal);
+ sqlite3_free(pData);
+ 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;
+ BinlogWriteDataT logData;
+ logData.type = sqlite3TransferLogEventType(p->stmtType);
+
+ char *zSql = NULL;
+ if (logData.type == BINLOG_EVENT_TYPE_DML && sqlite3IsRowBasedBinlog(p) && p->pBinlogDMLData != NULL) {
+ // release savepoint
+ zSql = sqlite3_mprintf("%s", p->pBinlogDMLData->pSavePointName);
+ logData.type = BINLOG_EVENT_TYPE_ROW_COMMIT;
+ p->pBinlogDMLData->isSavePointReleased = 1;
+ } else if (p->stmtType == STMT_TYPE_TEMP_DB_MODIFY) {
+ sqlite3_log(SQLITE_WARNING, "binlog skip temp, t=%d", p->stmtType);
+ sqlite3FreeBinlogRowData(p);
+ return;
+ } else {
+ zSql = sqlite3_expanded_sql((sqlite3_stmt *)p);
+ }
+ if (zSql == NULL) {
+ sqlite3_log(SQLITE_ERROR, "binlog get sql failed");
+ sqlite3FreeBinlogRowData(p);
+ return;
+ }
+
+ 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;
+ }
+ db->xBinlogHandle.isSkipTrigger = 0;
+}
+
+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 char *sqlite3BinlogGetRowSql(sqlite3 *db, Table* pTable, char *buffer, u32 nBuffer, int *rc)
+{
+ if (buffer == NULL) {
+ *rc = SQLITE_ERROR;
+ sqlite3_log(SQLITE_ERROR, "binlog row has no data");
+ return NULL;
+ }
+
+ u8 op = 0;
+ sqlite3_int64 rowid = 0;
+ sqlite3_uint64 nData = 0;
+ int nZero = 0;
+ char *pData = NULL;
+ *rc = sqlite3BinlogDecodeRowBuffer(buffer, nBuffer, &op, &rowid, &nData, &nZero, &pData);
+ if (*rc != SQLITE_OK) {
+ return NULL;
+ }
+
+ BinlogRow pRow;
+ pRow.op = op;
+ pRow.nData = nData;
+ pRow.nZero = nZero;
+ pRow.pData = pData;
+ pRow.rowid = rowid;
+ char *sql = sqlite3GetBinlogRowStmt(db, &pRow, pTable);
+ if (sql == NULL) {
+ *rc = SQLITE_ERROR;
+ }
+ return sql;
+}
+
+SQLITE_PRIVATE Table *sqliteBinlogGetTable(sqlite3 *db, const char *pTableName, int *rc)
+{
+ assert( db!=NULL );
+ assert( pTableName!=NULL );
+ assert( rc!=NULL );
+ sqlite3_stmt *stmt = NULL;
+ *rc = sqlite3_prepare_v2(db, "SELECT COUNT(*) FROM sqlite_master WHERE type = 'table';", -1, &stmt, NULL);
+ if (*rc != SQLITE_OK) {
+ return NULL;
+ }
+ *rc = sqlite3_step(stmt);
+ Table *pTable = sqlite3FindTable(db, pTableName, NULL);
+ sqlite3_finalize(stmt);
+ return pTable;
+}
+
+SQLITE_PRIVATE char *sqlite3BinlogGetEventSql(sqlite3 *db, BinlogEventT *event, Table **pOutTable, int *rc)
+{
+ assert( event!=NULL );
+ switch (event->head.eventType) {
+ case BINLOG_EVENT_TYPE_ROW_START:
+ return sqlite3_mprintf("savepoint %s;", (char *)event->body);
+ case BINLOG_EVENT_TYPE_ROW_TABLE: {
+ char *pTableName = (char *)event->body;
+ *pOutTable = sqliteBinlogGetTable(db, pTableName, rc);
+ if (*pOutTable == NULL) {
+ *rc = SQLITE_ERROR;
+ sqlite3_log(SQLITE_WARNING, "binlog find no table, rc=%d", *rc);
+ } else {
+ *rc = SQLITE_OK;
+ }
+ return NULL;
+ }
+ case BINLOG_EVENT_TYPE_ROW_FULL_DATA:
+ if (pOutTable == NULL || *pOutTable == NULL) {
+ *rc = SQLITE_ERROR;
+ sqlite3_log(SQLITE_ERROR, "binlog row has no table");
+ return NULL;
+ }
+ return sqlite3BinlogGetRowSql(db, *pOutTable, (char *)event->body, event->head.eventLength, rc);
+ case BINLOG_EVENT_TYPE_ROW_COMMIT:
+ return sqlite3_mprintf("release %s;", (char *)event->body);
+ case BINLOG_EVENT_TYPE_ROW_ROLLBACK:
+ return sqlite3_mprintf("rollback to %s;", (char *)event->body);
+ default:
+ return sqlite3_mprintf("%s", (char *)event->body);
+ }
+}
+
+SQLITE_PRIVATE int sqlite3BinlogStmtStep(sqlite3 *db, Sqlite3BinlogStmt *bStmt, char **sql, Table **pOutTable)
+{
+ if (bStmt == NULL || bStmt->cursor == NULL || sql == NULL) {
+ sqlite3_log(SQLITE_ERROR, "binlog stmt step 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 step event is null");
+ return SQLITE_ERROR;
+ }
+ int rc = SQLITE_OK;
+ *sql = sqlite3BinlogGetEventSql(db, event, pOutTable, &rc);
+ if (rc != SQLITE_OK){
+ return rc;
+ }
+ 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;
+ }
+ 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_BUSY) {
+ return res;
+ }
+ if (res != SQLITE_OK) {
+ sqlite3BinlogClose(srcDb);
+ return res;
+ }
+ u8 oldIsSkipTrigger = destDb->xBinlogHandle.isSkipTrigger;
+ destDb->xBinlogHandle.isSkipTrigger = 1;
+ 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;
+ Table *pTab = NULL;
+ while ((res = sqlite3BinlogStmtStep(destDb, &bStmt, &bSql, &pTab)) == SQLITE_ROW) {
+ if (bSql == NULL) {
+ continue;
+ }
+ int errCode = sqlite3BinlogExecuteReplaySql(srcDb, destDb, bSql);
+ sqlite3_free(bSql);
+ bSql = NULL;
+ if (errCode != SQLITE_OK) {
+ res = errCode;
+ break;
+ }
+ }
+ sqlite3BinlogStmtFinalize(srcDb, &bStmt);
+ if (res != SQLITE_DONE && res != SQLITE_OK) {
+ sqlite3BinlogErrorCallback(srcDb, res, "exec replay sql failed");
+ }
+ if (res != SQLITE_DONE) {
+ break;
+ }
+ } while (1);
+ destDb->xBinlogHandle.isSkipTrigger = oldIsSkipTrigger;
+ if (res != SQLITE_OK) {
+ sqlite3_exec(destDb, "rollback;", NULL, NULL, NULL);
+ }
+ (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;
+ }
+
+ const char *zFile = sqlite3_db_filename(db, 0);
+ if (zFile == NULL || (db->xBinlogHandle.flags & BINLOG_FLAG_ENABLE) == 0) {
+ sqlite3_log(SQLITE_ERROR, "binlog db has no file path");
+ return;
+ }
+ db->xBinlogHandle.xErrorCallback(db->xBinlogHandle.callbackCtx, errNo, errMsg, zFile);
+}
+/************** End of binlog implement ************************************/
+#endif /* SQLITE_ENABLE_BINLOG */
+
// export the symbols
#ifdef SQLITE_EXPORT_SYMBOLS
#ifndef SQLITE_CKSUMVFS_STATIC
@@ -256942,6 +258499,9 @@ struct sqlite3_api_routines_extra {
int (*key_v2)(sqlite3*,const char*,const void*,int);
int (*rekey)(sqlite3*,const void*,int);
int (*rekey_v2)(sqlite3*,const char*,const void*,int);
+ int (*is_support_binlog)(void);
+ int (*replay_binlog)(sqlite3*, sqlite3*);
+ int (*clean_binlog)(sqlite3*, BinlogFileCleanModeE);
};
typedef struct sqlite3_api_routines_extra sqlite3_api_routines_extra;
@@ -256952,13 +258512,22 @@ static const sqlite3_api_routines_extra sqlite3ExtraApis = {
sqlite3_key,
sqlite3_key_v2,
sqlite3_rekey,
- sqlite3_rekey_v2
+ sqlite3_rekey_v2,
#else
0,
0,
0,
- 0
+ 0,
#endif /* SQLITE_HAS_CODEC */
+#ifdef SQLITE_ENABLE_BINLOG
+ sqlite3_is_support_binlog,
+ sqlite3_replay_binlog,
+ sqlite3_clean_binlog,
+#else
+ 0,
+ 0,
+ 0,
+#endif/* SQLITE_ENABLE_BINLOG */
};
EXPORT_SYMBOLS const sqlite3_api_routines *sqlite3_export_symbols = &sqlite3Apis;
--
2.25.1