Merge the sessions-diff branch with this one.
FossilOrigin-Name: 805baa57e5d2e97dccddc08eb72f2564df4802e8
This commit is contained in:
commit
58bd114a6f
150
ext/session/sessionD.test
Normal file
150
ext/session/sessionD.test
Normal file
@ -0,0 +1,150 @@
|
||||
# 2014 August 16
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
# This file focuses on the sqlite3session_diff() function.
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source [file join [file dirname [info script]] session_common.tcl]
|
||||
source $testdir/tester.tcl
|
||||
ifcapable !session {finish_test; return}
|
||||
|
||||
set testprefix sessionD
|
||||
|
||||
proc scksum {db dbname} {
|
||||
|
||||
if {$dbname=="temp"} {
|
||||
set master sqlite_temp_master
|
||||
} else {
|
||||
set master $dbname.sqlite_master
|
||||
}
|
||||
|
||||
set alltab [$db eval "SELECT name FROM $master WHERE type='table'"]
|
||||
set txt [$db eval "SELECT * FROM $master ORDER BY type,name,sql"]
|
||||
foreach tab $alltab {
|
||||
set cols [list]
|
||||
db eval "PRAGMA $dbname.table_info = $tab" x {
|
||||
lappend cols "quote($x(name))"
|
||||
}
|
||||
set cols [join $cols ,]
|
||||
append txt [db eval "SELECT $cols FROM $tab ORDER BY $cols"]
|
||||
}
|
||||
return [md5 $txt]
|
||||
}
|
||||
|
||||
proc do_diff_test {tn setup} {
|
||||
reset_db
|
||||
forcedelete test.db2
|
||||
execsql { ATTACH 'test.db2' AS aux }
|
||||
execsql $setup
|
||||
|
||||
sqlite3session S db main
|
||||
foreach tbl [db eval {SELECT name FROM sqlite_master WHERE type='table'}] {
|
||||
S attach $tbl
|
||||
S diff aux $tbl
|
||||
}
|
||||
|
||||
set C [S changeset]
|
||||
S delete
|
||||
|
||||
sqlite3 db2 test.db2
|
||||
sqlite3changeset_apply db2 $C ""
|
||||
uplevel do_test $tn.1 [list {execsql { PRAGMA integrity_check } db2}] ok
|
||||
db2 close
|
||||
|
||||
set cksum [scksum db main]
|
||||
uplevel do_test $tn.2 [list {scksum db aux}] [list $cksum]
|
||||
}
|
||||
|
||||
|
||||
forcedelete test.db2
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE t2(a PRIMARY KEY, b);
|
||||
INSERT INTO t2 VALUES(1, 'one');
|
||||
INSERT INTO t2 VALUES(2, 'two');
|
||||
|
||||
ATTACH 'test.db2' AS aux;
|
||||
CREATE TABLE aux.t2(a PRIMARY KEY, b);
|
||||
}
|
||||
|
||||
do_test 1.1 {
|
||||
sqlite3session S db main
|
||||
S attach t2
|
||||
S diff aux t2
|
||||
set C [S changeset]
|
||||
S delete
|
||||
} {}
|
||||
|
||||
do_test 1.2 {
|
||||
sqlite3 db2 test.db2
|
||||
sqlite3changeset_apply db2 $C ""
|
||||
db2 close
|
||||
db eval { SELECT * FROM aux.t2 }
|
||||
} {1 one 2 two}
|
||||
|
||||
do_diff_test 2.1 {
|
||||
CREATE TABLE aux.t1(x, y, PRIMARY KEY(y));
|
||||
CREATE TABLE t1(x, y, PRIMARY KEY(y));
|
||||
|
||||
INSERT INTO t1 VALUES(1, 2);
|
||||
INSERT INTO t1 VALUES(NULL, 'xyz');
|
||||
INSERT INTO t1 VALUES(4.5, 5.5);
|
||||
}
|
||||
|
||||
do_diff_test 2.2 {
|
||||
CREATE TABLE aux.t1(x, y, PRIMARY KEY(y));
|
||||
CREATE TABLE t1(x, y, PRIMARY KEY(y));
|
||||
|
||||
INSERT INTO aux.t1 VALUES(1, 2);
|
||||
INSERT INTO aux.t1 VALUES(NULL, 'xyz');
|
||||
INSERT INTO aux.t1 VALUES(4.5, 5.5);
|
||||
}
|
||||
|
||||
do_diff_test 2.3 {
|
||||
CREATE TABLE aux.t1(a PRIMARY KEY, b TEXT);
|
||||
CREATE TABLE t1(a PRIMARY KEY, b TEXT);
|
||||
|
||||
INSERT INTO aux.t1 VALUES(1, 'one');
|
||||
INSERT INTO aux.t1 VALUES(2, 'two');
|
||||
INSERT INTO aux.t1 VALUES(3, 'three');
|
||||
|
||||
INSERT INTO t1 VALUES(1, 'I');
|
||||
INSERT INTO t1 VALUES(2, 'two');
|
||||
INSERT INTO t1 VALUES(3, 'III');
|
||||
}
|
||||
|
||||
do_diff_test 2.4 {
|
||||
CREATE TABLE aux.t1(a, b, c, d, PRIMARY KEY(c, b, a));
|
||||
CREATE TABLE t1(a, b, c, d, PRIMARY KEY(c, b, a));
|
||||
|
||||
INSERT INTO t1 VALUES('hvkzyipambwdqlvwv','',-458331.50,X'DA51ED5E84');
|
||||
INSERT INTO t1 VALUES(X'C5C6B5DD','jjxrath',40917,830244);
|
||||
INSERT INTO t1 VALUES(-204877.54,X'1704C253D5F3AFA8',155120.88,NULL);
|
||||
INSERT INTO t1
|
||||
VALUES('ckmqmzoeuvxisxqy',X'EB5A5D3A1DD22FD1','tidhjcbvbppdt',-642987.37);
|
||||
INSERT INTO t1 VALUES(-851726,-161992,-469943,-159541);
|
||||
INSERT INTO t1 VALUES(X'4A6A667F858938',185083,X'7A',NULL);
|
||||
|
||||
INSERT INTO aux.t1 VALUES(415075.74,'auawczkb',X'',X'57B4FAAF2595');
|
||||
INSERT INTO aux.t1 VALUES(727637,711560,-181340,'hphuo');
|
||||
INSERT INTO aux.t1
|
||||
VALUES(-921322.81,662959,'lvlgwdgxaurr','ajjrzrbhqflsutnymgc');
|
||||
INSERT INTO aux.t1 VALUES(-146061,-377892,X'4E','gepvpvvuhszpxabbb');
|
||||
INSERT INTO aux.t1 VALUES(-851726,-161992,-469943,-159541);
|
||||
INSERT INTO aux.t1 VALUES(X'4A6A667F858938',185083,X'7A',NULL);
|
||||
INSERT INTO aux.t1 VALUES(-204877.54,X'1704C253D5F3AFA8',155120.88, 4);
|
||||
INSERT INTO aux.t1
|
||||
VALUES('ckmqmzoeuvxisxqy',X'EB5A5D3A1DD22FD1','tidgtsplhjcbvbppdt',-642987.3);
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
@ -25,6 +25,15 @@ typedef struct SessionInput SessionInput;
|
||||
# endif
|
||||
#endif
|
||||
|
||||
typedef struct SessionHook SessionHook;
|
||||
struct SessionHook {
|
||||
void *pCtx;
|
||||
int (*xOld)(void*,int,sqlite3_value**);
|
||||
int (*xNew)(void*,int,sqlite3_value**);
|
||||
int (*xCount)(void*);
|
||||
int (*xDepth)(void*);
|
||||
};
|
||||
|
||||
/*
|
||||
** Session handle structure.
|
||||
*/
|
||||
@ -39,6 +48,7 @@ struct sqlite3_session {
|
||||
int (*xTableFilter)(void *pCtx, const char *zTab);
|
||||
sqlite3_session *pNext; /* Next session object on same db. */
|
||||
SessionTable *pTable; /* List of attached tables */
|
||||
SessionHook hook; /* APIs to grab new and old data with */
|
||||
};
|
||||
|
||||
/*
|
||||
@ -438,7 +448,7 @@ static unsigned int sessionHashAppendType(unsigned int h, int eType){
|
||||
** and the output variables are set as described above.
|
||||
*/
|
||||
static int sessionPreupdateHash(
|
||||
sqlite3 *db, /* Database handle */
|
||||
sqlite3_session *pSession, /* Session object that owns pTab */
|
||||
SessionTable *pTab, /* Session table handle */
|
||||
int bNew, /* True to hash the new.* PK */
|
||||
int *piHash, /* OUT: Hash value */
|
||||
@ -448,7 +458,7 @@ static int sessionPreupdateHash(
|
||||
int i; /* Used to iterate through columns */
|
||||
|
||||
assert( *pbNullPK==0 );
|
||||
assert( pTab->nCol==sqlite3_preupdate_count(db) );
|
||||
assert( pTab->nCol==pSession->hook.xCount(pSession->hook.pCtx) );
|
||||
for(i=0; i<pTab->nCol; i++){
|
||||
if( pTab->abPK[i] ){
|
||||
int rc;
|
||||
@ -456,9 +466,9 @@ static int sessionPreupdateHash(
|
||||
sqlite3_value *pVal;
|
||||
|
||||
if( bNew ){
|
||||
rc = sqlite3_preupdate_new(db, i, &pVal);
|
||||
rc = pSession->hook.xNew(pSession->hook.pCtx, i, &pVal);
|
||||
}else{
|
||||
rc = sqlite3_preupdate_old(db, i, &pVal);
|
||||
rc = pSession->hook.xOld(pSession->hook.pCtx, i, &pVal);
|
||||
}
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
|
||||
@ -764,11 +774,12 @@ static int sessionMergeUpdate(
|
||||
** false.
|
||||
*/
|
||||
static int sessionPreupdateEqual(
|
||||
sqlite3 *db, /* Database handle */
|
||||
sqlite3_session *pSession, /* Session object that owns SessionTable */
|
||||
SessionTable *pTab, /* Table associated with change */
|
||||
SessionChange *pChange, /* Change to compare to */
|
||||
int op /* Current pre-update operation */
|
||||
){
|
||||
sqlite3 *db = pSession->db;
|
||||
int iCol; /* Used to iterate through columns */
|
||||
u8 *a = pChange->aRecord; /* Cursor used to scan change record */
|
||||
|
||||
@ -787,11 +798,11 @@ static int sessionPreupdateEqual(
|
||||
** within sessionPreupdateHash(). The first two asserts below verify
|
||||
** this (that the method has already been called). */
|
||||
if( op==SQLITE_INSERT ){
|
||||
assert( db->pPreUpdate->pNewUnpacked || db->pPreUpdate->aNew );
|
||||
rc = sqlite3_preupdate_new(db, iCol, &pVal);
|
||||
/* assert( db->pPreUpdate->pNewUnpacked || db->pPreUpdate->aNew ); */
|
||||
rc = pSession->hook.xNew(pSession->hook.pCtx, iCol, &pVal);
|
||||
}else{
|
||||
assert( db->pPreUpdate->pUnpacked );
|
||||
rc = sqlite3_preupdate_old(db, iCol, &pVal);
|
||||
/* assert( db->pPreUpdate->pUnpacked ); */
|
||||
rc = pSession->hook.xOld(pSession->hook.pCtx, iCol, &pVal);
|
||||
}
|
||||
assert( rc==SQLITE_OK );
|
||||
if( sqlite3_value_type(pVal)!=eType ) return 0;
|
||||
@ -1019,7 +1030,7 @@ static int sessionInitTable(sqlite3_session *pSession, SessionTable *pTab){
|
||||
);
|
||||
}
|
||||
if( pSession->rc==SQLITE_OK
|
||||
&& pTab->nCol!=sqlite3_preupdate_count(pSession->db)
|
||||
&& pTab->nCol!=pSession->hook.xCount(pSession->hook.pCtx)
|
||||
){
|
||||
pSession->rc = SQLITE_SCHEMA;
|
||||
}
|
||||
@ -1039,9 +1050,8 @@ static void sessionPreupdateOneChange(
|
||||
sqlite3_session *pSession, /* Session object pTab is attached to */
|
||||
SessionTable *pTab /* Table that change applies to */
|
||||
){
|
||||
sqlite3 *db = pSession->db;
|
||||
int iHash;
|
||||
int bNullPk = 0;
|
||||
int bNull = 0;
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
if( pSession->rc ) return;
|
||||
@ -1058,14 +1068,14 @@ static void sessionPreupdateOneChange(
|
||||
/* Calculate the hash-key for this change. If the primary key of the row
|
||||
** includes a NULL value, exit early. Such changes are ignored by the
|
||||
** session module. */
|
||||
rc = sessionPreupdateHash(db, pTab, op==SQLITE_INSERT, &iHash, &bNullPk);
|
||||
rc = sessionPreupdateHash(pSession, pTab, op==SQLITE_INSERT, &iHash, &bNull);
|
||||
if( rc!=SQLITE_OK ) goto error_out;
|
||||
|
||||
if( bNullPk==0 ){
|
||||
if( bNull==0 ){
|
||||
/* Search the hash table for an existing record for this row. */
|
||||
SessionChange *pC;
|
||||
for(pC=pTab->apChange[iHash]; pC; pC=pC->pNext){
|
||||
if( sessionPreupdateEqual(db, pTab, pC, op) ) break;
|
||||
if( sessionPreupdateEqual(pSession, pTab, pC, op) ) break;
|
||||
}
|
||||
|
||||
if( pC==0 ){
|
||||
@ -1084,10 +1094,10 @@ static void sessionPreupdateOneChange(
|
||||
for(i=0; i<pTab->nCol; i++){
|
||||
sqlite3_value *p = 0;
|
||||
if( op!=SQLITE_INSERT ){
|
||||
TESTONLY(int trc = ) sqlite3_preupdate_old(pSession->db, i, &p);
|
||||
TESTONLY(int trc = ) pSession->hook.xOld(pSession->hook.pCtx, i, &p);
|
||||
assert( trc==SQLITE_OK );
|
||||
}else if( pTab->abPK[i] ){
|
||||
TESTONLY(int trc = ) sqlite3_preupdate_new(pSession->db, i, &p);
|
||||
TESTONLY(int trc = ) pSession->hook.xNew(pSession->hook.pCtx, i, &p);
|
||||
assert( trc==SQLITE_OK );
|
||||
}
|
||||
|
||||
@ -1115,15 +1125,15 @@ static void sessionPreupdateOneChange(
|
||||
for(i=0; i<pTab->nCol; i++){
|
||||
sqlite3_value *p = 0;
|
||||
if( op!=SQLITE_INSERT ){
|
||||
sqlite3_preupdate_old(pSession->db, i, &p);
|
||||
pSession->hook.xOld(pSession->hook.pCtx, i, &p);
|
||||
}else if( pTab->abPK[i] ){
|
||||
sqlite3_preupdate_new(pSession->db, i, &p);
|
||||
pSession->hook.xNew(pSession->hook.pCtx, i, &p);
|
||||
}
|
||||
sessionSerializeValue(&pChange->aRecord[nByte], p, &nByte);
|
||||
}
|
||||
|
||||
/* Add the change to the hash-table */
|
||||
if( pSession->bIndirect || sqlite3_preupdate_depth(pSession->db) ){
|
||||
if( pSession->bIndirect || pSession->hook.xDepth(pSession->hook.pCtx) ){
|
||||
pChange->bIndirect = 1;
|
||||
}
|
||||
pChange->nRecord = nByte;
|
||||
@ -1134,7 +1144,9 @@ static void sessionPreupdateOneChange(
|
||||
}else if( pC->bIndirect ){
|
||||
/* If the existing change is considered "indirect", but this current
|
||||
** change is "direct", mark the change object as direct. */
|
||||
if( sqlite3_preupdate_depth(pSession->db)==0 && pSession->bIndirect==0 ){
|
||||
if( pSession->hook.xDepth(pSession->hook.pCtx)==0
|
||||
&& pSession->bIndirect==0
|
||||
){
|
||||
pC->bIndirect = 0;
|
||||
}
|
||||
}
|
||||
@ -1147,6 +1159,39 @@ static void sessionPreupdateOneChange(
|
||||
}
|
||||
}
|
||||
|
||||
static int sessionFindTable(
|
||||
sqlite3_session *pSession,
|
||||
const char *zName,
|
||||
SessionTable **ppTab
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
int nName = sqlite3Strlen30(zName);
|
||||
SessionTable *pRet;
|
||||
|
||||
/* Search for an existing table */
|
||||
for(pRet=pSession->pTable; pRet; pRet=pRet->pNext){
|
||||
if( 0==sqlite3_strnicmp(pRet->zName, zName, nName+1) ) break;
|
||||
}
|
||||
|
||||
if( pRet==0 && pSession->bAutoAttach ){
|
||||
/* If there is a table-filter configured, invoke it. If it returns 0,
|
||||
** do not automatically add the new table. */
|
||||
if( pSession->xTableFilter==0
|
||||
|| pSession->xTableFilter(pSession->pFilterCtx, zName)
|
||||
){
|
||||
rc = sqlite3session_attach(pSession, zName);
|
||||
if( rc==SQLITE_OK ){
|
||||
pRet = pSession->pTable;
|
||||
assert( 0==sqlite3_strnicmp(pRet->zName, zName, nName+1) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert( rc==SQLITE_OK || pRet==0 );
|
||||
*ppTab = pRet;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** The 'pre-update' hook registered by this module with SQLite databases.
|
||||
*/
|
||||
@ -1161,7 +1206,6 @@ static void xPreUpdate(
|
||||
){
|
||||
sqlite3_session *pSession;
|
||||
int nDb = sqlite3Strlen30(zDb);
|
||||
int nName = sqlite3Strlen30(zName);
|
||||
|
||||
assert( sqlite3_mutex_held(db->mutex) );
|
||||
|
||||
@ -1175,37 +1219,312 @@ static void xPreUpdate(
|
||||
if( pSession->rc ) continue;
|
||||
if( sqlite3_strnicmp(zDb, pSession->zDb, nDb+1) ) continue;
|
||||
|
||||
for(pTab=pSession->pTable; pTab || pSession->bAutoAttach; pTab=pTab->pNext){
|
||||
if( !pTab ){
|
||||
/* This branch is taken if table zName has not yet been attached to
|
||||
** this session and the auto-attach flag is set. */
|
||||
|
||||
/* If there is a table-filter configured, invoke it. If it returns 0,
|
||||
** this change will not be recorded. Break out of the loop early in
|
||||
** this case. */
|
||||
if( pSession->xTableFilter
|
||||
&& pSession->xTableFilter(pSession->pFilterCtx, zName)==0
|
||||
){
|
||||
break;
|
||||
}
|
||||
|
||||
pSession->rc = sqlite3session_attach(pSession,zName);
|
||||
if( pSession->rc ) break;
|
||||
pTab = pSession->pTable;
|
||||
assert( 0==sqlite3_strnicmp(pTab->zName, zName, nName+1) );
|
||||
}
|
||||
|
||||
if( 0==sqlite3_strnicmp(pTab->zName, zName, nName+1) ){
|
||||
sessionPreupdateOneChange(op, pSession, pTab);
|
||||
if( op==SQLITE_UPDATE ){
|
||||
sessionPreupdateOneChange(SQLITE_INSERT, pSession, pTab);
|
||||
}
|
||||
break;
|
||||
pSession->rc = sessionFindTable(pSession, zName, &pTab);
|
||||
if( pTab ){
|
||||
assert( pSession->rc==SQLITE_OK );
|
||||
sessionPreupdateOneChange(op, pSession, pTab);
|
||||
if( op==SQLITE_UPDATE ){
|
||||
sessionPreupdateOneChange(SQLITE_INSERT, pSession, pTab);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** The pre-update hook implementations.
|
||||
*/
|
||||
static int sessionPreupdateOld(void *pCtx, int iVal, sqlite3_value **ppVal){
|
||||
return sqlite3_preupdate_old((sqlite3*)pCtx, iVal, ppVal);
|
||||
}
|
||||
static int sessionPreupdateNew(void *pCtx, int iVal, sqlite3_value **ppVal){
|
||||
return sqlite3_preupdate_new((sqlite3*)pCtx, iVal, ppVal);
|
||||
}
|
||||
static int sessionPreupdateCount(void *pCtx){
|
||||
return sqlite3_preupdate_count((sqlite3*)pCtx);
|
||||
}
|
||||
static int sessionPreupdateDepth(void *pCtx){
|
||||
return sqlite3_preupdate_depth((sqlite3*)pCtx);
|
||||
}
|
||||
|
||||
/*
|
||||
** Install the pre-update hooks on the session object passed as the only
|
||||
** argument.
|
||||
*/
|
||||
static void sessionPreupdateHooks(
|
||||
sqlite3_session *pSession
|
||||
){
|
||||
pSession->hook.pCtx = (void*)pSession->db;
|
||||
pSession->hook.xOld = sessionPreupdateOld;
|
||||
pSession->hook.xNew = sessionPreupdateNew;
|
||||
pSession->hook.xCount = sessionPreupdateCount;
|
||||
pSession->hook.xDepth = sessionPreupdateDepth;
|
||||
}
|
||||
|
||||
typedef struct SessionDiffCtx SessionDiffCtx;
|
||||
struct SessionDiffCtx {
|
||||
sqlite3_stmt *pStmt;
|
||||
int nOldOff;
|
||||
};
|
||||
|
||||
/*
|
||||
** The diff hook implementations.
|
||||
*/
|
||||
static int sessionDiffOld(void *pCtx, int iVal, sqlite3_value **ppVal){
|
||||
SessionDiffCtx *p = (SessionDiffCtx*)pCtx;
|
||||
*ppVal = sqlite3_column_value(p->pStmt, iVal+p->nOldOff);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
static int sessionDiffNew(void *pCtx, int iVal, sqlite3_value **ppVal){
|
||||
SessionDiffCtx *p = (SessionDiffCtx*)pCtx;
|
||||
*ppVal = sqlite3_column_value(p->pStmt, iVal);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
static int sessionDiffCount(void *pCtx){
|
||||
SessionDiffCtx *p = (SessionDiffCtx*)pCtx;
|
||||
return p->nOldOff ? p->nOldOff : sqlite3_column_count(p->pStmt);
|
||||
}
|
||||
static int sessionDiffDepth(void *pCtx){
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Install the diff hooks on the session object passed as the only
|
||||
** argument.
|
||||
*/
|
||||
static void sessionDiffHooks(
|
||||
sqlite3_session *pSession,
|
||||
SessionDiffCtx *pDiffCtx
|
||||
){
|
||||
pSession->hook.pCtx = (void*)pDiffCtx;
|
||||
pSession->hook.xOld = sessionDiffOld;
|
||||
pSession->hook.xNew = sessionDiffNew;
|
||||
pSession->hook.xCount = sessionDiffCount;
|
||||
pSession->hook.xDepth = sessionDiffDepth;
|
||||
}
|
||||
|
||||
static char *sessionExprComparePK(
|
||||
int nCol,
|
||||
const char *zDb1, const char *zDb2,
|
||||
const char *zTab,
|
||||
const char **azCol, u8 *abPK
|
||||
){
|
||||
int i;
|
||||
const char *zSep = "";
|
||||
char *zRet = 0;
|
||||
|
||||
for(i=0; i<nCol; i++){
|
||||
if( abPK[i] ){
|
||||
zRet = sqlite3_mprintf("%z%s\"%w\".\"%w\".\"%w\"=\"%w\".\"%w\".\"%w\"",
|
||||
zRet, zSep, zDb1, zTab, azCol[i], zDb2, zTab, azCol[i]
|
||||
);
|
||||
zSep = " AND ";
|
||||
if( zRet==0 ) break;
|
||||
}
|
||||
}
|
||||
|
||||
return zRet;
|
||||
}
|
||||
|
||||
static char *sessionExprCompareOther(
|
||||
int nCol,
|
||||
const char *zDb1, const char *zDb2,
|
||||
const char *zTab,
|
||||
const char **azCol, u8 *abPK
|
||||
){
|
||||
int i;
|
||||
const char *zSep = "";
|
||||
char *zRet = 0;
|
||||
int bHave = 0;
|
||||
|
||||
for(i=0; i<nCol; i++){
|
||||
if( abPK[i]==0 ){
|
||||
bHave = 1;
|
||||
zRet = sqlite3_mprintf(
|
||||
"%z%s\"%w\".\"%w\".\"%w\" IS NOT \"%w\".\"%w\".\"%w\"",
|
||||
zRet, zSep, zDb1, zTab, azCol[i], zDb2, zTab, azCol[i]
|
||||
);
|
||||
zSep = " OR ";
|
||||
if( zRet==0 ) break;
|
||||
}
|
||||
}
|
||||
|
||||
if( bHave==0 ){
|
||||
assert( zRet==0 );
|
||||
zRet = sqlite3_mprintf("0");
|
||||
}
|
||||
|
||||
return zRet;
|
||||
}
|
||||
|
||||
static char *sessionSelectFindNew(
|
||||
int nCol,
|
||||
const char *zDb1, /* Pick rows in this db only */
|
||||
const char *zDb2, /* But not in this one */
|
||||
const char *zTbl, /* Table name */
|
||||
const char *zExpr
|
||||
){
|
||||
char *zRet = sqlite3_mprintf(
|
||||
"SELECT * FROM \"%w\".\"%w\" WHERE NOT EXISTS ("
|
||||
" SELECT 1 FROM \"%w\".\"%w\" WHERE %s"
|
||||
")",
|
||||
zDb1, zTbl, zDb2, zTbl, zExpr
|
||||
);
|
||||
return zRet;
|
||||
}
|
||||
|
||||
static int sessionDiffFindNew(
|
||||
int op,
|
||||
sqlite3_session *pSession,
|
||||
SessionTable *pTab,
|
||||
const char *zDb1,
|
||||
const char *zDb2,
|
||||
char *zExpr
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
char *zStmt = sessionSelectFindNew(pTab->nCol, zDb1, zDb2, pTab->zName,zExpr);
|
||||
|
||||
if( zStmt==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
sqlite3_stmt *pStmt;
|
||||
rc = sqlite3_prepare(pSession->db, zStmt, -1, &pStmt, 0);
|
||||
if( rc==SQLITE_OK ){
|
||||
SessionDiffCtx *pDiffCtx = (SessionDiffCtx*)pSession->hook.pCtx;
|
||||
pDiffCtx->pStmt = pStmt;
|
||||
pDiffCtx->nOldOff = 0;
|
||||
while( SQLITE_ROW==sqlite3_step(pStmt) ){
|
||||
sessionPreupdateOneChange(op, pSession, pTab);
|
||||
}
|
||||
rc = sqlite3_finalize(pStmt);
|
||||
}
|
||||
sqlite3_free(zStmt);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int sessionDiffFindModified(
|
||||
sqlite3_session *pSession,
|
||||
SessionTable *pTab,
|
||||
const char *zFrom,
|
||||
const char *zExpr
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
char *zExpr2 = sessionExprCompareOther(pTab->nCol,
|
||||
pSession->zDb, zFrom, pTab->zName, pTab->azCol, pTab->abPK
|
||||
);
|
||||
if( zExpr2==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
char *zStmt = sqlite3_mprintf(
|
||||
"SELECT * FROM \"%w\".\"%w\", \"%w\".\"\%w\" WHERE %s AND %z",
|
||||
pSession->zDb, pTab->zName, zFrom, pTab->zName, zExpr, zExpr2
|
||||
);
|
||||
if( zStmt==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
sqlite3_stmt *pStmt;
|
||||
rc = sqlite3_prepare(pSession->db, zStmt, -1, &pStmt, 0);
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
SessionDiffCtx *pDiffCtx = (SessionDiffCtx*)pSession->hook.pCtx;
|
||||
pDiffCtx->pStmt = pStmt;
|
||||
pDiffCtx->nOldOff = pTab->nCol;
|
||||
while( SQLITE_ROW==sqlite3_step(pStmt) ){
|
||||
sessionPreupdateOneChange(SQLITE_UPDATE, pSession, pTab);
|
||||
}
|
||||
rc = sqlite3_finalize(pStmt);
|
||||
}
|
||||
sqlite3_free(zStmt);
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int sqlite3session_diff(
|
||||
sqlite3_session *pSession,
|
||||
const char *zFrom,
|
||||
const char *zTbl,
|
||||
char **pzErrMsg
|
||||
){
|
||||
const char *zDb = pSession->zDb;
|
||||
int rc = pSession->rc;
|
||||
SessionDiffCtx d;
|
||||
|
||||
memset(&d, 0, sizeof(d));
|
||||
sessionDiffHooks(pSession, &d);
|
||||
|
||||
if( pzErrMsg ) *pzErrMsg = 0;
|
||||
if( rc==SQLITE_OK ){
|
||||
char *zExpr = 0;
|
||||
sqlite3 *db = pSession->db;
|
||||
SessionTable *pTo; /* Table zTbl */
|
||||
|
||||
/* Locate and if necessary initialize the target table object */
|
||||
rc = sessionFindTable(pSession, zTbl, &pTo);
|
||||
if( pTo==0 ) goto diff_out;
|
||||
if( pTo->nCol==0 ){
|
||||
rc = pSession->rc = sessionTableInfo(db, zDb,
|
||||
pTo->zName, &pTo->nCol, 0, &pTo->azCol, &pTo->abPK
|
||||
);
|
||||
}
|
||||
|
||||
/* Check the table schemas match */
|
||||
if( rc==SQLITE_OK ){
|
||||
int nCol; /* Columns in zFrom.zTbl */
|
||||
u8 *abPK;
|
||||
const char **azCol = 0;
|
||||
rc = sessionTableInfo(db, zFrom, zTbl, &nCol, 0, &azCol, &abPK);
|
||||
if( rc==SQLITE_OK ){
|
||||
int bMismatch = 0;
|
||||
if( pTo->nCol!=nCol || memcmp(pTo->abPK, abPK, nCol) ){
|
||||
bMismatch = 1;
|
||||
}else{
|
||||
int i;
|
||||
for(i=0; i<nCol; i++){
|
||||
if( sqlite3_stricmp(azCol[i], pTo->azCol[i]) ) bMismatch = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if( bMismatch ){
|
||||
*pzErrMsg = sqlite3_mprintf("table schemas do not match");
|
||||
rc = SQLITE_ERROR;
|
||||
}
|
||||
}
|
||||
sqlite3_free(azCol);
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
zExpr = sessionExprComparePK(pTo->nCol,
|
||||
zDb, zFrom, pTo->zName, pTo->azCol, pTo->abPK
|
||||
);
|
||||
}
|
||||
|
||||
/* Find new rows */
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sessionDiffFindNew(SQLITE_INSERT, pSession, pTo, zDb, zFrom, zExpr);
|
||||
}
|
||||
|
||||
/* Find old rows */
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sessionDiffFindNew(SQLITE_DELETE, pSession, pTo, zFrom, zDb, zExpr);
|
||||
}
|
||||
|
||||
/* Find modified rows */
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sessionDiffFindModified(pSession, pTo, zFrom, zExpr);
|
||||
}
|
||||
|
||||
sqlite3_free(zExpr);
|
||||
}
|
||||
|
||||
diff_out:
|
||||
sessionPreupdateHooks(pSession);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Create a session object. This session object will record changes to
|
||||
** database zDb attached to connection db.
|
||||
@ -1230,6 +1549,7 @@ int sqlite3session_create(
|
||||
pNew->zDb = (char *)&pNew[1];
|
||||
pNew->bEnable = 1;
|
||||
memcpy(pNew->zDb, zDb, nDb+1);
|
||||
sessionPreupdateHooks(pNew);
|
||||
|
||||
/* Add the new session object to the linked list of session objects
|
||||
** attached to database handle $db. Do this under the cover of the db
|
||||
|
@ -273,6 +273,63 @@ int sqlite3session_changeset(
|
||||
void **ppChangeset /* OUT: Buffer containing changeset */
|
||||
);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Load The Difference Between Tables Into A Session
|
||||
**
|
||||
** If it is not already attached to the session object passed as the first
|
||||
** argument, this function attaches table zTbl in the same manner as the
|
||||
** [sqlite3session_attach()] function. If zTbl does not exist, or if it
|
||||
** does not have a primary key, this function is a no-op (but does not return
|
||||
** an error).
|
||||
**
|
||||
** Argument zFromDb must be the name of a database ("main", "temp" etc.)
|
||||
** attached to the same database handle as the session object that contains
|
||||
** a table compatible with the table attached to the session by this function.
|
||||
** A table is considered compatible if it:
|
||||
**
|
||||
** <ul>
|
||||
** <li> Has the same name,
|
||||
** <li> Has the same set of columns declared in the same order, and
|
||||
** <li> Has the same PRIMARY KEY definition.
|
||||
** </ul>
|
||||
**
|
||||
** This function adds a set of changes to the session object that could be
|
||||
** used to update the table in database zFrom (call this the "from-table")
|
||||
** so that its content is the same as the table attached to the session
|
||||
** object (call this the "to-table"). Specifically:
|
||||
**
|
||||
** <ul>
|
||||
** <li> For each row (primary key) that exists in the to-table but not in
|
||||
** the from-table, an INSERT record is added to the session object.
|
||||
**
|
||||
** <li> For each row (primary key) that exists in the to-table but not in
|
||||
** the from-table, a DELETE record is added to the session object.
|
||||
**
|
||||
** <li> For each row (primary key) that exists in both tables, but features
|
||||
** different in each, an UPDATE record is added to the session.
|
||||
** </ul>
|
||||
**
|
||||
** To clarify, if this function is called and then a changeset constructed
|
||||
** using [sqlite3session_changeset()], then after applying that changeset to
|
||||
** database zFrom the contents of the two compatible tables would be
|
||||
** identical.
|
||||
**
|
||||
** It an error if database zFrom does not exist or does not contain the
|
||||
** required compatible table.
|
||||
**
|
||||
** If the operation successful, SQLITE_OK is returned. Otherwise, an SQLite
|
||||
** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg
|
||||
** may be set to point to a buffer containing an English language error
|
||||
** message. It is the responsibility of the caller to free this buffer using
|
||||
** sqlite3_free().
|
||||
*/
|
||||
int sqlite3session_diff(
|
||||
sqlite3_session *pSession,
|
||||
const char *zFromDb,
|
||||
const char *zTbl,
|
||||
char **pzErrMsg
|
||||
);
|
||||
|
||||
|
||||
/*
|
||||
** CAPI3REF: Generate A Patchset From A Session Object
|
||||
|
@ -117,14 +117,15 @@ static int test_session_cmd(
|
||||
const char *zMsg;
|
||||
int iSub;
|
||||
} aSub[] = {
|
||||
{ "attach", 1, "TABLE", }, /* 0 */
|
||||
{ "changeset", 0, "", }, /* 1 */
|
||||
{ "delete", 0, "", }, /* 2 */
|
||||
{ "enable", 1, "BOOL", }, /* 3 */
|
||||
{ "indirect", 1, "BOOL", }, /* 4 */
|
||||
{ "isempty", 0, "", }, /* 5 */
|
||||
{ "table_filter", 1, "SCRIPT", }, /* 6 */
|
||||
{ "patchset", 0, "", }, /* 7 */
|
||||
{ "attach", 1, "TABLE", }, /* 0 */
|
||||
{ "changeset", 0, "", }, /* 1 */
|
||||
{ "delete", 0, "", }, /* 2 */
|
||||
{ "enable", 1, "BOOL", }, /* 3 */
|
||||
{ "indirect", 1, "BOOL", }, /* 4 */
|
||||
{ "isempty", 0, "", }, /* 5 */
|
||||
{ "table_filter", 1, "SCRIPT", }, /* 6 */
|
||||
{ "patchset", 0, "", }, /* 7 */
|
||||
{ "diff", 2, "FROMDB TBL", }, /* 8 */
|
||||
{ 0 }
|
||||
};
|
||||
int iSub;
|
||||
@ -216,6 +217,24 @@ static int test_session_cmd(
|
||||
sqlite3session_table_filter(pSession, test_table_filter, clientData);
|
||||
break;
|
||||
}
|
||||
|
||||
case 8: { /* diff */
|
||||
char *zErr = 0;
|
||||
rc = sqlite3session_diff(pSession,
|
||||
Tcl_GetString(objv[2]),
|
||||
Tcl_GetString(objv[3]),
|
||||
&zErr
|
||||
);
|
||||
assert( rc!=SQLITE_OK || zErr==0 );
|
||||
if( zErr ){
|
||||
Tcl_AppendResult(interp, zErr, 0);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
if( rc ){
|
||||
return test_session_error(interp, rc);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return TCL_OK;
|
||||
|
17
manifest
17
manifest
@ -1,5 +1,5 @@
|
||||
C Updates\sto\scomments\sdescribing\sthe\schangeset\sand\spatchset\sformats.
|
||||
D 2015-04-09T19:19:22.574
|
||||
C Merge\sthe\ssessions-diff\sbranch\swith\sthis\sone.
|
||||
D 2015-04-11T17:09:36.697
|
||||
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
|
||||
F Makefile.in 3083cf0c2bc6618e532b9478ce735bb512322985
|
||||
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
|
||||
@ -158,11 +158,12 @@ F ext/session/session9.test 5409d90d8141881d08285ed1c2c0d8d10fb92069
|
||||
F ext/session/sessionA.test 1feeab0b8e03527f08f2f1defb442da25480138f
|
||||
F ext/session/sessionB.test 06961b7c3641151f5d23088250ecad132501113c
|
||||
F ext/session/sessionC.test 3982f8577b0744c5ce3aaef7cfeb5bd903f17fe4
|
||||
F ext/session/sessionD.test 95903bd6a23dadbda7e192f730dd7dedd2d369ef
|
||||
F ext/session/session_common.tcl 9de0451b6a47218fc16b9ed8876b6238a0a3d88d
|
||||
F ext/session/sessionfault.test bef044d0952c0d62c31c8d2400be72c8684545cc
|
||||
F ext/session/sqlite3session.c 27eb71e164a8e05d344c6254e8f906f093dfd4d1
|
||||
F ext/session/sqlite3session.h 16608d29879a0ed3c6be6b7fb18dcdb5c707aaef
|
||||
F ext/session/test_session.c a28352e99bc6a83b94e4cce99a7bf25c73d6d489
|
||||
F ext/session/sqlite3session.c 7876b6e614f089158722b66bb05e269ab6bf0f61
|
||||
F ext/session/sqlite3session.h d9ebd8d4c5791aafdf18165575c7f2223c09279b
|
||||
F ext/session/test_session.c 037fc25340a918eb2195972fed439d7adf7b5db9
|
||||
F ext/userauth/sqlite3userauth.h 19cb6f0e31316d0ee4afdfb7a85ef9da3333a220
|
||||
F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04
|
||||
F ext/userauth/userauth.c 5fa3bdb492f481bbc1709fc83c91ebd13460c69e
|
||||
@ -1267,7 +1268,7 @@ F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1
|
||||
F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
|
||||
F tool/warnings.sh 0abfd78ceb09b7f7c27c688c8e3fe93268a13b32
|
||||
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
|
||||
P b9459d5980c6249a5c1bc5ea72cb4f3b1ba0e433
|
||||
R a602d0c91e77a7e6222a205b1b755ccf
|
||||
P 5342f721e878b52f207731ea57411f3669ae3f3f c4340b2ea2115ad97dcac036f9034e132ab789e5
|
||||
R 93733f2c3fd75be901f1056d40d1b2f2
|
||||
U dan
|
||||
Z 08fe7407f1e8070b595e48c16318a0a6
|
||||
Z 4fa2ba77648737a16f5365f796a8dd77
|
||||
|
@ -1 +1 @@
|
||||
5342f721e878b52f207731ea57411f3669ae3f3f
|
||||
805baa57e5d2e97dccddc08eb72f2564df4802e8
|
Loading…
x
Reference in New Issue
Block a user