--- /dev/null
+# 2011 Mar 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.
+#
+#***********************************************************************
+#
+# The focus of this file is testing the session module.
+#
+
+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 sessionrowid
+
+do_execsql_test 0.0 {
+ CREATE TABLE t1(a, b);
+}
+
+foreach {tn rowid bEmpty} {
+ 1 0 1
+ 2 1 0
+ 3 -1 1
+} {
+ do_test 0.$tn {
+ sqlite3session S db main
+ if {$rowid>=0} { S object_config rowid $rowid }
+ S attach t1
+ execsql { INSERT INTO t1 VALUES(1, 2); }
+ expr [string length [S changeset]]==0
+ } $bEmpty
+ S delete
+}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 1.0 {
+ CREATE TABLE t1(a, b);
+}
+
+do_iterator_test 1.1 t1 {
+ INSERT INTO t1 VALUES('i', 'one');
+} {
+ {INSERT t1 0 X.. {} {i 1 t i t one}}
+}
+
+do_execsql_test 1.2 {
+ SELECT rowid, * FROM t1
+} {1 i one}
+
+do_iterator_test 1.3 t1 {
+ UPDATE t1 SET b='two'
+} {
+ {UPDATE t1 0 X.. {i 1 {} {} t one} {{} {} {} {} t two}}
+}
+
+do_iterator_test 1.4 t1 {
+ DELETE FROM t1;
+} {
+ {DELETE t1 0 X.. {i 1 t i t two} {}}
+}
+
+do_iterator_test 1.5 t1 {
+ INSERT INTO t1(rowid, a, b) VALUES(14, 'hello', 'world');
+ INSERT INTO t1(rowid, a, b) VALUES(NULL, 'yes', 'no');
+ INSERT INTO t1(rowid, a, b) VALUES(-123, 'ii', 'iii');
+} {
+ {INSERT t1 0 X.. {} {i -123 t ii t iii}}
+ {INSERT t1 0 X.. {} {i 15 t yes t no}}
+ {INSERT t1 0 X.. {} {i 14 t hello t world}}
+}
+
+do_iterator_test 1.6 t1 {
+ UPDATE t1 SET a='deluxe' WHERE rowid=14;
+ DELETE FROM t1 WHERE rowid=-123;
+ INSERT INTO t1 VALUES('x', 'xi');
+} {
+ {DELETE t1 0 X.. {i -123 t ii t iii} {}}
+ {UPDATE t1 0 X.. {i 14 t hello {} {}} {{} {} t deluxe {} {}}}
+ {INSERT t1 0 X.. {} {i 16 t x t xi}}
+}
+
+#-------------------------------------------------------------------------
+reset_db
+forcedelete test.db2
+sqlite3 db2 test.db2
+
+do_execsql_test 2.0 {
+ CREATE TABLE t1(a, b);
+}
+do_execsql_test -db db2 2.0.1 {
+ CREATE TABLE t1(a, b);
+}
+
+proc xConflict {args} {
+ puts "CONFLICT!"
+ return "OMIT"
+}
+
+do_test 2.1 {
+ set C [changeset_from_sql {
+ INSERT INTO t1 VALUES('abc', 'def');
+ }]
+ sqlite3changeset_apply db2 $C xConflict
+ execsql { SELECT * FROM t1 } db2
+} {abc def}
+do_test 2.2 {
+ set C [changeset_from_sql {
+ UPDATE t1 SET b='hello'
+ }]
+ sqlite3changeset_apply db2 $C xConflict
+ execsql { SELECT * FROM t1 } db2
+} {abc hello}
+do_test 2.3 {
+ set C [changeset_from_sql {
+ DELETE FROM t1 WHERE b='hello'
+ }]
+ sqlite3changeset_apply db2 $C xConflict
+ execsql { SELECT * FROM t1 } db2
+} {}
+
+do_test 2.4 {
+ do_then_apply_sql {
+ INSERT INTO t1 VALUES('i', 'one');
+ INSERT INTO t1 VALUES('ii', 'two');
+ INSERT INTO t1 VALUES('iii', 'three');
+ INSERT INTO t1 VALUES('iv', 'four');
+ }
+ compare_db db db2
+} {}
+
+do_test 2.5 {
+ do_then_apply_sql {
+ DELETE FROM t1 WHERE a='ii';
+ UPDATE t1 SET b='THREE' WHERE a='iii';
+ UPDATE t1 SET a='III' WHERE a='iii';
+ INSERT INTO t1 VALUES('v', 'five');
+ }
+ compare_db db db2
+} {}
+
+do_execsql_test 2.6 {SELECT * FROM t1} {i one III THREE iv four v five}
+do_execsql_test -db db2 2.7 {SELECT * FROM t1} {i one III THREE iv four v five}
+
+#-------------------------------------------------------------------------
+db2 close
+reset_db
+forcedelete test.db2
+sqlite3 db2 test.db2
+
+set init_sql {
+ CREATE TABlE t4(a, b);
+ CREATE INDEX t4a ON t4(a);
+ CREATE UNIQUE INDEX t4b ON t4(b);
+}
+
+do_execsql_test 3.0 $init_sql
+do_execsql_test -db db2 3.0a $init_sql
+
+do_execsql_test -db db2 3.1 {
+ INSERT INTO t4(rowid, a, b) VALUES(43, 'hello', 'world');
+}
+do_conflict_test 3.2 -sql {
+ INSERT INTO t4(rowid, a, b) VALUES(43, 'abc', 'def');
+} -tables t4 -conflicts {
+ {INSERT t4 CONFLICT {i 43 t abc t def} {i 43 t hello t world}}
+}
+do_execsql_test -db db2 3.3 {
+ SELECT * FROM t4
+} {hello world}
+
+do_execsql_test 3.4 { DELETE FROM t4 }
+do_conflict_test 3.5 -sql {
+ INSERT INTO t4(rowid, a, b) VALUES(43, 'abc', 'def');
+} -tables t4 -conflicts {
+ {INSERT t4 CONFLICT {i 43 t abc t def} {i 43 t hello t world}}
+} -policy REPLACE
+do_execsql_test -db db2 3.6 {
+ SELECT * FROM t4
+} {abc def}
+
+do_execsql_test 3.7 { DELETE FROM t4 }
+do_conflict_test 3.8 -sql {
+ INSERT INTO t4(rowid, a, b) VALUES(45, 'xyz', 'def');
+} -tables t4 -conflicts {
+ {INSERT t4 CONSTRAINT {i 45 t xyz t def}}
+}
+do_execsql_test -db db2 3.9 {
+ SELECT * FROM t4
+} {abc def}
+
+
+do_execsql_test -db db 3.10a { DELETE FROM t4 }
+do_execsql_test -db db2 3.10b { DELETE FROM t4 }
+
+do_execsql_test -db db 3.11a {
+ INSERT INTO t4(rowid, a, b) VALUES(111, 'one', 'one');
+ INSERT INTO t4(rowid, a, b) VALUES(222, 'two', 'two');
+}
+do_execsql_test -db db2 3.11b {
+ INSERT INTO t4(rowid, a, b) VALUES(111, 'one', 'blip');
+}
+
+do_conflict_test 3.12 -sql {
+ DELETE FROM t4 WHERE a='one';
+} -tables t4 -conflicts {
+ {DELETE t4 DATA {i 111 t one t one} {i 111 t one t blip}}
+}
+do_execsql_test -db db2 3.13 {
+ SELECT * FROM t4
+} {one blip}
+
+do_conflict_test 3.14 -sql {
+ DELETE FROM t4 WHERE a='two';
+} -tables t4 -conflicts {
+ {DELETE t4 NOTFOUND {i 222 t two t two}}
+}
+do_execsql_test -db db2 3.15 {
+ SELECT * FROM t4
+} {one blip}
+
+do_execsql_test -db db 3.16a { DELETE FROM t4 }
+do_execsql_test -db db2 3.16b { DELETE FROM t4 }
+
+do_execsql_test -db db 3.17a {
+ INSERT INTO t4(rowid, a, b) VALUES(111, 'one', 'one');
+ INSERT INTO t4(rowid, a, b) VALUES(222, 'two', 'two');
+}
+do_execsql_test -db db2 3.17b {
+ INSERT INTO t4(rowid, a, b) VALUES(111, 'one', 'blip');
+}
+
+do_conflict_test 3.18 -sql {
+ UPDATE t4 SET b='xyz' WHERE a='one'
+} -tables t4 -conflicts {
+ {UPDATE t4 DATA {i 111 {} {} t one} {{} {} {} {} t xyz} {i 111 t one t blip}}
+}
+do_execsql_test -db db2 3.19 {
+ SELECT * FROM t4
+} {one blip}
+
+do_conflict_test 3.20 -sql {
+ UPDATE t4 SET b='123' WHERE a='two'
+} -tables t4 -conflicts {
+ {UPDATE t4 NOTFOUND {i 222 {} {} t two} {{} {} {} {} t 123}}
+}
+do_execsql_test -db db2 3.21 {
+ SELECT * FROM t4
+} {one blip}
+
+#--------------------------------------------------------------------------
+breakpoint
+do_diff_test 4.0 {
+ CREATE TABLE t1(x, y);
+ CREATE TABLE aux.t1(x, y);
+ INSERT INTO t1 VALUES(1, 2);
+}
+
+do_diff_test 4.1 {
+ CREATE TABLE t1(x, y);
+ CREATE TABLE aux.t1(x, y);
+ INSERT INTO aux.t1 VALUES(1, 2);
+}
+
+do_diff_test 4.2 {
+ CREATE TABLE t1(x, y);
+ CREATE TABLE aux.t1(x, y);
+ INSERT INTO t1(rowid, x, y) VALUES(413, 'hello', 'there');
+ INSERT INTO aux.t1(rowid, x, y) VALUES(413, 'hello', 'world');
+}
+
+finish_test
+
# endif
#endif
+#define SESSIONS_ROWID "_rowid_"
+
static int sessions_strm_chunk_size = SESSIONS_STRM_CHUNK_SIZE;
typedef struct SessionHook SessionHook;
int bEnable; /* True if currently recording */
int bIndirect; /* True if all changes are indirect */
int bAutoAttach; /* True to auto-attach tables */
+ int bImplicitPK; /* True to handle tables with implicit PK */
int rc; /* Non-zero if an error has occurred */
void *pFilterCtx; /* First argument to pass to xTableFilter */
int (*xTableFilter)(void *pCtx, const char *zTab);
char *zName; /* Local name of table */
int nCol; /* Number of columns in table zName */
int bStat1; /* True if this is sqlite_stat1 */
+ int bRowid; /* True if this table uses rowid for PK */
const char **azCol; /* Column names */
u8 *abPK; /* Array of primary key flags */
int nEntry; /* Total number of entries in hash table */
*/
static int sessionPreupdateHash(
sqlite3_session *pSession, /* Session object that owns pTab */
+ i64 iRowid,
SessionTable *pTab, /* Session table handle */
int bNew, /* True to hash the new.* PK */
int *piHash, /* OUT: Hash value */
unsigned int h = 0; /* Hash value to return */
int i; /* Used to iterate through columns */
- assert( *pbNullPK==0 );
- assert( pTab->nCol==pSession->hook.xCount(pSession->hook.pCtx) );
- for(i=0; i<pTab->nCol; i++){
- if( pTab->abPK[i] ){
- int rc;
- int eType;
- sqlite3_value *pVal;
-
- if( bNew ){
- rc = pSession->hook.xNew(pSession->hook.pCtx, i, &pVal);
- }else{
- rc = pSession->hook.xOld(pSession->hook.pCtx, i, &pVal);
- }
- if( rc!=SQLITE_OK ) return rc;
+ if( pTab->bRowid ){
+ assert( pTab->nCol-1==pSession->hook.xCount(pSession->hook.pCtx) );
+ h = sessionHashAppendI64(h, iRowid);
+ }else{
+ assert( *pbNullPK==0 );
+ assert( pTab->nCol==pSession->hook.xCount(pSession->hook.pCtx) );
+ for(i=0; i<pTab->nCol; i++){
+ if( pTab->abPK[i] ){
+ int rc;
+ int eType;
+ sqlite3_value *pVal;
- eType = sqlite3_value_type(pVal);
- h = sessionHashAppendType(h, eType);
- if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
- i64 iVal;
- if( eType==SQLITE_INTEGER ){
- iVal = sqlite3_value_int64(pVal);
+ if( bNew ){
+ rc = pSession->hook.xNew(pSession->hook.pCtx, i, &pVal);
}else{
- double rVal = sqlite3_value_double(pVal);
- assert( sizeof(iVal)==8 && sizeof(rVal)==8 );
- memcpy(&iVal, &rVal, 8);
+ rc = pSession->hook.xOld(pSession->hook.pCtx, i, &pVal);
}
- h = sessionHashAppendI64(h, iVal);
- }else if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){
- const u8 *z;
- int n;
- if( eType==SQLITE_TEXT ){
- z = (const u8 *)sqlite3_value_text(pVal);
+ if( rc!=SQLITE_OK ) return rc;
+
+ eType = sqlite3_value_type(pVal);
+ h = sessionHashAppendType(h, eType);
+ if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
+ i64 iVal;
+ if( eType==SQLITE_INTEGER ){
+ iVal = sqlite3_value_int64(pVal);
+ }else{
+ double rVal = sqlite3_value_double(pVal);
+ assert( sizeof(iVal)==8 && sizeof(rVal)==8 );
+ memcpy(&iVal, &rVal, 8);
+ }
+ h = sessionHashAppendI64(h, iVal);
+ }else if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){
+ const u8 *z;
+ int n;
+ if( eType==SQLITE_TEXT ){
+ z = (const u8 *)sqlite3_value_text(pVal);
+ }else{
+ z = (const u8 *)sqlite3_value_blob(pVal);
+ }
+ n = sqlite3_value_bytes(pVal);
+ if( !z && (eType!=SQLITE_BLOB || n>0) ) return SQLITE_NOMEM;
+ h = sessionHashAppendBlob(h, n, z);
}else{
- z = (const u8 *)sqlite3_value_blob(pVal);
+ assert( eType==SQLITE_NULL );
+ assert( pTab->bStat1==0 || i!=1 );
+ *pbNullPK = 1;
}
- n = sqlite3_value_bytes(pVal);
- if( !z && (eType!=SQLITE_BLOB || n>0) ) return SQLITE_NOMEM;
- h = sessionHashAppendBlob(h, n, z);
- }else{
- assert( eType==SQLITE_NULL );
- assert( pTab->bStat1==0 || i!=1 );
- *pbNullPK = 1;
}
}
}
*/
static int sessionPreupdateEqual(
sqlite3_session *pSession, /* Session object that owns SessionTable */
+ i64 iRowid, /* Rowid value if pTab->bRowid */
SessionTable *pTab, /* Table associated with change */
SessionChange *pChange, /* Change to compare to */
int op /* Current pre-update operation */
int iCol; /* Used to iterate through columns */
u8 *a = pChange->aRecord; /* Cursor used to scan change record */
+ if( pTab->bRowid ){
+ if( a[0]!=SQLITE_INTEGER ) return 0;
+ return sessionGetI64(&a[1])==iRowid;
+ }
+
assert( op==SQLITE_INSERT || op==SQLITE_UPDATE || op==SQLITE_DELETE );
for(iCol=0; iCol<pTab->nCol; iCol++){
if( !pTab->abPK[iCol] ){
int *pnCol, /* OUT: number of columns */
const char **pzTab, /* OUT: Copy of zThis */
const char ***pazCol, /* OUT: Array of column names for table */
- u8 **pabPK /* OUT: Array of booleans - true for PK col */
+ u8 **pabPK, /* OUT: Array of booleans - true for PK col */
+ int *pbRowid /* OUT: True if only PK is a rowid */
){
char *zPragma;
sqlite3_stmt *pStmt;
u8 *pAlloc = 0;
char **azCol = 0;
u8 *abPK = 0;
+ int bRowid = 0; /* Set to true to use rowid as PK */
assert( pazCol && pabPK );
}
nByte = nThis + 1;
+ bRowid = (pbRowid!=0);
while( SQLITE_ROW==sqlite3_step(pStmt) ){
nByte += sqlite3_column_bytes(pStmt, 1);
nDbCol++;
+ if( sqlite3_column_int(pStmt, 5) ) bRowid = 0;
}
+ if( nDbCol==0 ) bRowid = 0;
+ nDbCol += bRowid;
+ nByte += strlen(SESSIONS_ROWID);
rc = sqlite3_reset(pStmt);
if( rc==SQLITE_OK ){
}
i = 0;
+ if( bRowid ){
+ int nName = strlen(SESSIONS_ROWID);
+ memcpy(pAlloc, SESSIONS_ROWID, nName+1);
+ azCol[i] = (char*)pAlloc;
+ pAlloc += nName+1;
+ abPK[i] = 1;
+ i++;
+ }
while( SQLITE_ROW==sqlite3_step(pStmt) ){
int nName = sqlite3_column_bytes(pStmt, 1);
const unsigned char *zName = sqlite3_column_text(pStmt, 1);
i++;
}
rc = sqlite3_reset(pStmt);
-
}
/* If successful, populate the output variables. Otherwise, zero them and
if( pzTab ) *pzTab = 0;
sessionFree(pSession, azCol);
}
+ if( pbRowid ) *pbRowid = bRowid;
sqlite3_finalize(pStmt);
return rc;
}
u8 *abPK;
assert( pTab->azCol==0 || pTab->abPK==0 );
pSession->rc = sessionTableInfo(pSession, pSession->db, pSession->zDb,
- pTab->zName, &pTab->nCol, 0, &pTab->azCol, &abPK
+ pTab->zName, &pTab->nCol, 0, &pTab->azCol, &abPK,
+ (pSession->bImplicitPK ? &pTab->bRowid : 0)
);
if( pSession->rc==SQLITE_OK ){
int i;
){
i64 nNew = 2;
if( pC->op==SQLITE_INSERT ){
+ if( pTab->bRowid ) nNew += 9;
if( op!=SQLITE_DELETE ){
int ii;
for(ii=0; ii<pTab->nCol; ii++){
}else{
int ii;
u8 *pCsr = pC->aRecord;
- for(ii=0; ii<pTab->nCol; ii++){
+ if( pTab->bRowid ){
+ nNew += 9;
+ pCsr += 9;
+ }
+ for(ii=0; ii<(pTab->nCol-pTab->bRowid); ii++){
int bChanged = 1;
int nOld = 0;
int eType;
*/
static void sessionPreupdateOneChange(
int op, /* One of SQLITE_UPDATE, INSERT, DELETE */
+ i64 iRowid,
sqlite3_session *pSession, /* Session object pTab is attached to */
SessionTable *pTab /* Table that change applies to */
){
/* Check the number of columns in this xPreUpdate call matches the
** number of columns in the table. */
- if( pTab->nCol!=pSession->hook.xCount(pSession->hook.pCtx) ){
+ if( (pTab->nCol-pTab->bRowid)!=pSession->hook.xCount(pSession->hook.pCtx) ){
pSession->rc = SQLITE_SCHEMA;
return;
}
/* 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(pSession, pTab, op==SQLITE_INSERT, &iHash, &bNull);
+ rc = sessionPreupdateHash(
+ pSession, iRowid, pTab, op==SQLITE_INSERT, &iHash, &bNull
+ );
if( rc!=SQLITE_OK ) goto error_out;
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(pSession, pTab, pC, op) ) break;
+ if( sessionPreupdateEqual(pSession, iRowid, pTab, pC, op) ) break;
}
if( pC==0 ){
/* Figure out how large an allocation is required */
nByte = sizeof(SessionChange);
- for(i=0; i<pTab->nCol; i++){
+ for(i=0; i<(pTab->nCol-pTab->bRowid); i++){
sqlite3_value *p = 0;
if( op!=SQLITE_INSERT ){
TESTONLY(int trc = ) pSession->hook.xOld(pSession->hook.pCtx, i, &p);
rc = sessionSerializeValue(0, p, &nByte);
if( rc!=SQLITE_OK ) goto error_out;
}
+ if( pTab->bRowid ){
+ nByte += 9; /* Size of rowid field - an integer */
+ }
/* Allocate the change object */
pC = (SessionChange *)sessionMalloc64(pSession, nByte);
** required values and encodings have already been cached in memory.
** It is not possible for an OOM to occur in this block. */
nByte = 0;
- for(i=0; i<pTab->nCol; i++){
+ if( pTab->bRowid ){
+ pC->aRecord[0] = SQLITE_INTEGER;
+ sessionPutI64(&pC->aRecord[1], iRowid);
+ nByte = 9;
+ }
+ for(i=0; i<(pTab->nCol-pTab->bRowid); i++){
sqlite3_value *p = 0;
if( op!=SQLITE_INSERT ){
pSession->hook.xOld(pSession->hook.pCtx, i, &p);
pSession->rc = sessionFindTable(pSession, zName, &pTab);
if( pTab ){
assert( pSession->rc==SQLITE_OK );
- sessionPreupdateOneChange(op, pSession, pTab);
+ assert( op==SQLITE_UPDATE || iKey1==iKey2 );
+ sessionPreupdateOneChange(op, iKey1, pSession, pTab);
if( op==SQLITE_UPDATE ){
- sessionPreupdateOneChange(SQLITE_INSERT, pSession, pTab);
+ sessionPreupdateOneChange(SQLITE_INSERT, iKey2, pSession, pTab);
}
}
}
typedef struct SessionDiffCtx SessionDiffCtx;
struct SessionDiffCtx {
sqlite3_stmt *pStmt;
+ int bRowid;
int nOldOff;
};
*/
static int sessionDiffOld(void *pCtx, int iVal, sqlite3_value **ppVal){
SessionDiffCtx *p = (SessionDiffCtx*)pCtx;
- *ppVal = sqlite3_column_value(p->pStmt, iVal+p->nOldOff);
+ *ppVal = sqlite3_column_value(p->pStmt, iVal+p->nOldOff+p->bRowid);
return SQLITE_OK;
}
static int sessionDiffNew(void *pCtx, int iVal, sqlite3_value **ppVal){
SessionDiffCtx *p = (SessionDiffCtx*)pCtx;
- *ppVal = sqlite3_column_value(p->pStmt, iVal);
+ *ppVal = sqlite3_column_value(p->pStmt, iVal+p->bRowid);
return SQLITE_OK;
}
static int sessionDiffCount(void *pCtx){
SessionDiffCtx *p = (SessionDiffCtx*)pCtx;
- return p->nOldOff ? p->nOldOff : sqlite3_column_count(p->pStmt);
+ return (p->nOldOff ? p->nOldOff : sqlite3_column_count(p->pStmt)) - p->bRowid;
}
static int sessionDiffDepth(void *pCtx){
(void)pCtx;
static char *sessionSelectFindNew(
const char *zDb1, /* Pick rows in this db only */
const char *zDb2, /* But not in this one */
+ int bRowid,
const char *zTbl, /* Table name */
const char *zExpr
){
+ const char *zSel = (bRowid ? SESSIONS_ROWID ", *" : "*");
char *zRet = sqlite3_mprintf(
- "SELECT * FROM \"%w\".\"%w\" WHERE NOT EXISTS ("
+ "SELECT %s FROM \"%w\".\"%w\" WHERE NOT EXISTS ("
" SELECT 1 FROM \"%w\".\"%w\" WHERE %s"
")",
- zDb1, zTbl, zDb2, zTbl, zExpr
+ zSel, zDb1, zTbl, zDb2, zTbl, zExpr
);
return zRet;
}
char *zExpr
){
int rc = SQLITE_OK;
- char *zStmt = sessionSelectFindNew(zDb1, zDb2, pTab->zName,zExpr);
+ char *zStmt = sessionSelectFindNew(
+ zDb1, zDb2, pTab->bRowid, pTab->zName, zExpr
+ );
if( zStmt==0 ){
rc = SQLITE_NOMEM;
SessionDiffCtx *pDiffCtx = (SessionDiffCtx*)pSession->hook.pCtx;
pDiffCtx->pStmt = pStmt;
pDiffCtx->nOldOff = 0;
+ pDiffCtx->bRowid = pTab->bRowid;
while( SQLITE_ROW==sqlite3_step(pStmt) ){
- sessionPreupdateOneChange(op, pSession, pTab);
+ i64 iRowid = (pTab->bRowid ? sqlite3_column_int64(pStmt, 0) : 0);
+ sessionPreupdateOneChange(op, iRowid, pSession, pTab);
}
rc = sqlite3_finalize(pStmt);
}
return rc;
}
+/*
+** Return a comma-separated list of the fully-qualified (with both database
+** and table name) column names from table pTab. e.g.
+**
+** "main"."t1"."a", "main"."t1"."b", "main"."t1"."c"
+*/
+static char *sessionAllCols(
+ const char *zDb,
+ SessionTable *pTab
+){
+ int ii;
+ char *zRet = 0;
+ for(ii=0; ii<pTab->nCol; ii++){
+ zRet = sqlite3_mprintf("%z%s\"%w\".\"%w\".\"%w\"%s",
+ zRet, (zRet ? ", " : ""), zDb, pTab->zName, pTab->azCol[ii]
+ );
+ if( !zRet ) break;
+ }
+ return zRet;
+}
+
static int sessionDiffFindModified(
sqlite3_session *pSession,
SessionTable *pTab,
if( zExpr2==0 ){
rc = SQLITE_NOMEM;
}else{
+ char *z1 = sessionAllCols(pSession->zDb, pTab);
+ char *z2 = sessionAllCols(zFrom, pTab);
char *zStmt = sqlite3_mprintf(
- "SELECT * FROM \"%w\".\"%w\", \"%w\".\"%w\" WHERE %s AND (%z)",
- pSession->zDb, pTab->zName, zFrom, pTab->zName, zExpr, zExpr2
+ "SELECT %s,%s FROM \"%w\".\"%w\", \"%w\".\"%w\" WHERE %s AND (%z)",
+ z1, z2, pSession->zDb, pTab->zName, zFrom, pTab->zName, zExpr, zExpr2
);
- if( zStmt==0 ){
+ if( zStmt==0 || z1==0 || z2==0 ){
rc = SQLITE_NOMEM;
}else{
sqlite3_stmt *pStmt;
pDiffCtx->pStmt = pStmt;
pDiffCtx->nOldOff = pTab->nCol;
while( SQLITE_ROW==sqlite3_step(pStmt) ){
- sessionPreupdateOneChange(SQLITE_UPDATE, pSession, pTab);
+ i64 iRowid = (pTab->bRowid ? sqlite3_column_int64(pStmt, 0) : 0);
+ sessionPreupdateOneChange(SQLITE_UPDATE, iRowid, pSession, pTab);
}
rc = sqlite3_finalize(pStmt);
}
- sqlite3_free(zStmt);
}
+ sqlite3_free(zStmt);
+ sqlite3_free(z1);
+ sqlite3_free(z2);
}
return rc;
int bHasPk = 0;
int bMismatch = 0;
int nCol; /* Columns in zFrom.zTbl */
+ int bRowid = 0;
u8 *abPK;
const char **azCol = 0;
- rc = sessionTableInfo(0, db, zFrom, zTbl, &nCol, 0, &azCol, &abPK);
+ rc = sessionTableInfo(0, db, zFrom, zTbl, &nCol, 0, &azCol, &abPK,
+ pSession->bImplicitPK ? &bRowid : 0
+ );
if( rc==SQLITE_OK ){
if( pTo->nCol!=nCol ){
bMismatch = 1;
int bIgnoreNoop,
const char *zDb, /* Database name */
const char *zTab, /* Table name */
+ int bRowid,
int nCol, /* Number of columns in table */
const char **azCol, /* Names of table columns */
u8 *abPK, /* PRIMARY KEY array */
int rc = SQLITE_OK;
char *zSql = 0;
const char *zSep = "";
- const char *zCols = "*";
+ const char *zCols = bRowid ? SESSIONS_ROWID ", *" : "*";
int nSql = -1;
int i;
zCols = "tbl, ?2, stat";
}else{
for(i=0; i<nCol; i++){
-
if( abPK[i] ){
sessionAppendStr(&pkfield, zSep, &rc);
sessionAppendStr(&pkvar, zSep, &rc);
sqlite3_stmt *pSel = 0; /* SELECT statement to query table pTab */
int nRewind = buf.nBuf; /* Initial size of write buffer */
int nNoop; /* Size of buffer after writing tbl header */
+ int bRowid = 0;
/* Check the table schema is still Ok. */
- rc = sessionTableInfo(0, db, pSession->zDb, zName, &nCol, 0,&azCol,&abPK);
- if( !rc && (pTab->nCol!=nCol || memcmp(abPK, pTab->abPK, nCol)) ){
+ rc = sessionTableInfo(
+ 0, db, pSession->zDb, zName, &nCol, 0, &azCol, &abPK,
+ (pSession->bImplicitPK ? &bRowid : 0)
+ );
+ if( rc==SQLITE_OK && (
+ pTab->nCol!=nCol
+ || pTab->bRowid!=bRowid
+ || memcmp(abPK, pTab->abPK, nCol)
+ )){
rc = SQLITE_SCHEMA;
}
/* Build and compile a statement to execute: */
if( rc==SQLITE_OK ){
rc = sessionSelectStmt(
- db, 0, pSession->zDb, zName, nCol, azCol, abPK, &pSel
+ db, 0, pSession->zDb, zName, bRowid, nCol, azCol, abPK, &pSel
);
}
int rc;
if( pnChangeset==0 || ppChangeset==0 ) return SQLITE_MISUSE;
- rc = sessionGenerateChangeset(pSession, 0, 0, 0, pnChangeset,ppChangeset);
- assert( rc || pnChangeset==0
+ rc = sessionGenerateChangeset(pSession, 0, 0, 0, pnChangeset, ppChangeset);
+ assert( 1 || rc || pnChangeset==0
|| pSession->bEnableSize==0 || *pnChangeset<=pSession->nMaxChangesetSize
);
return rc;
break;
}
+ case SQLITE_SESSION_OBJCONFIG_ROWID: {
+ int iArg = *(int*)pArg;
+ if( iArg>=0 ){
+ if( pSession->pTable ){
+ rc = SQLITE_MISUSE;
+ }else{
+ pSession->bImplicitPK = (iArg!=0);
+ }
+ }
+ *(int*)pArg = pSession->bImplicitPK;
+ break;
+ }
+
default:
rc = SQLITE_MISUSE;
}
u8 bRebaseStarted; /* If table header is already in rebase */
u8 bRebase; /* True to collect rebase information */
u8 bIgnoreNoop; /* True to ignore no-op conflicts */
+ int bRowid;
};
/* Number of prepared UPDATE statements to cache. */
const char *zTab, /* Table name */
SessionApplyCtx *p /* Session changeset-apply context */
){
+ /* TODO */
return sessionSelectStmt(db, p->bIgnoreNoop,
- "main", zTab, p->nCol, p->azCol, p->abPK, &p->pSelect
+ "main", zTab, p->bRowid, p->nCol, p->azCol, p->abPK, &p->pSelect
);
}
sApply.bStat1 = 0;
sApply.bDeferConstraints = 1;
sApply.bRebaseStarted = 0;
+ sApply.bRowid = 0;
memset(&sApply.constraints, 0, sizeof(SessionBuffer));
/* If an xFilter() callback was specified, invoke it now. If the
int i;
sqlite3changeset_pk(pIter, &abPK, 0);
- rc = sessionTableInfo(0,
- db, "main", zNew, &sApply.nCol, &zTab, &sApply.azCol, &sApply.abPK
+ rc = sessionTableInfo(0, db, "main", zNew,
+ &sApply.nCol, &zTab, &sApply.azCol, &sApply.abPK, &sApply.bRowid
);
if( rc!=SQLITE_OK ) break;
for(i=0; i<sApply.nCol; i++){