Fix sessions module handling of sqlite_stat1 rows with (idx IS NULL).
FossilOrigin-Name: 0e916416331d7948b312a5dd58ac0c145030bb3b47a37dab2636564397249a86
This commit is contained in:
commit
b8a0fb75db
@ -121,5 +121,191 @@ do_execsql_test -db db2 2.4 {
|
|||||||
|
|
||||||
do_execsql_test -db db2 2.5 { SELECT count(*) FROM t1 } 32
|
do_execsql_test -db db2 2.5 { SELECT count(*) FROM t1 } 32
|
||||||
|
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
db2 close
|
||||||
|
forcedelete test.db2
|
||||||
|
reset_db
|
||||||
|
sqlite3 db2 test.db2
|
||||||
|
|
||||||
|
do_test 3.0 {
|
||||||
|
do_common_sql {
|
||||||
|
CREATE TABLE t1(a, b, c);
|
||||||
|
ANALYZE;
|
||||||
|
DELETE FROM sqlite_stat1;
|
||||||
|
}
|
||||||
|
execsql {
|
||||||
|
INSERT INTO t1 VALUES(1, 1, 1);
|
||||||
|
INSERT INTO t1 VALUES(2, 2, 2);
|
||||||
|
INSERT INTO t1 VALUES(3, 3, 3);
|
||||||
|
INSERT INTO t1 VALUES(4, 4, 4);
|
||||||
|
}
|
||||||
|
} {}
|
||||||
|
|
||||||
|
do_iterator_test 3.1 {} {
|
||||||
|
ANALYZE
|
||||||
|
} {
|
||||||
|
{INSERT sqlite_stat1 0 XX. {} {t t1 b {} t 4}}
|
||||||
|
}
|
||||||
|
db null null
|
||||||
|
db2 null null
|
||||||
|
do_execsql_test 3.2 {
|
||||||
|
SELECT * FROM sqlite_stat1;
|
||||||
|
} {t1 null 4}
|
||||||
|
do_test 3.3 {
|
||||||
|
execsql { DELETE FROM sqlite_stat1 }
|
||||||
|
do_then_apply_sql { ANALYZE }
|
||||||
|
execsql { SELECT * FROM sqlite_stat1 } db2
|
||||||
|
} {t1 null 4}
|
||||||
|
do_test 3.4 {
|
||||||
|
execsql { INSERT INTO t1 VALUES(5,5,5) }
|
||||||
|
do_then_apply_sql { ANALYZE }
|
||||||
|
execsql { SELECT * FROM sqlite_stat1 } db2
|
||||||
|
} {t1 null 5}
|
||||||
|
do_test 3.5 {
|
||||||
|
do_then_apply_sql { DROP TABLE t1 }
|
||||||
|
execsql { SELECT * FROM sqlite_stat1 } db2
|
||||||
|
} {}
|
||||||
|
|
||||||
|
do_test 3.6.1 {
|
||||||
|
execsql {
|
||||||
|
CREATE TABLE t1(a, b, c);
|
||||||
|
CREATE TABLE t2(x, y, z);
|
||||||
|
INSERT INTO t1 VALUES(1,1,1), (2,2,2), (3,3,3), (4,4,4), (5,5,5);
|
||||||
|
INSERT INTO t2 SELECT * FROM t1;
|
||||||
|
DELETE FROM sqlite_stat1;
|
||||||
|
}
|
||||||
|
sqlite3session S db main
|
||||||
|
S attach sqlite_stat1
|
||||||
|
execsql { ANALYZE }
|
||||||
|
} {}
|
||||||
|
do_changeset_test 3.6.2 S {
|
||||||
|
{INSERT sqlite_stat1 0 XX. {} {t t2 b {} t 5}}
|
||||||
|
{INSERT sqlite_stat1 0 XX. {} {t t1 b {} t 5}}
|
||||||
|
}
|
||||||
|
do_changeset_invert_test 3.6.3 S {
|
||||||
|
{DELETE sqlite_stat1 0 XX. {t t2 b {} t 5} {}}
|
||||||
|
{DELETE sqlite_stat1 0 XX. {t t1 b {} t 5} {}}
|
||||||
|
}
|
||||||
|
do_test 3.6.4 { S delete } {}
|
||||||
|
|
||||||
|
proc sql_changeset_concat {args} {
|
||||||
|
foreach sql $args {
|
||||||
|
sqlite3session S db main
|
||||||
|
S attach sqlite_stat1
|
||||||
|
execsql $sql
|
||||||
|
set change [S changeset]
|
||||||
|
S delete
|
||||||
|
|
||||||
|
if {[info vars ret]!=""} {
|
||||||
|
set ret [sqlite3changeset_concat $ret $change]
|
||||||
|
} else {
|
||||||
|
set ret $change
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
changeset_to_list $ret
|
||||||
|
}
|
||||||
|
|
||||||
|
proc do_scc_test {tn args} {
|
||||||
|
uplevel [list \
|
||||||
|
do_test $tn [concat sql_changeset_concat [lrange $args 0 end-1]] \
|
||||||
|
[list {*}[ lindex $args end ]]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
do_execsql_test 3.7.0 {
|
||||||
|
DELETE FROM sqlite_stat1;
|
||||||
|
}
|
||||||
|
do_scc_test 3.7.1 {
|
||||||
|
ANALYZE;
|
||||||
|
} {
|
||||||
|
INSERT INTO t2 VALUES(6,6,6);
|
||||||
|
ANALYZE;
|
||||||
|
} {
|
||||||
|
{INSERT sqlite_stat1 0 XX. {} {t t1 b {} t 5}}
|
||||||
|
{INSERT sqlite_stat1 0 XX. {} {t t2 b {} t 6}}
|
||||||
|
}
|
||||||
|
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
catch { db2 close }
|
||||||
|
reset_db
|
||||||
|
forcedelete test.db2
|
||||||
|
sqlite3 db2 test.db2
|
||||||
|
|
||||||
|
do_test 4.1.0 {
|
||||||
|
do_common_sql {
|
||||||
|
CREATE TABLE t1(a, b);
|
||||||
|
CREATE INDEX i1 ON t1(a);
|
||||||
|
CREATE INDEX i2 ON t1(b);
|
||||||
|
INSERT INTO t1 VALUES(1,1), (2,2);
|
||||||
|
ANALYZE;
|
||||||
|
}
|
||||||
|
execsql { DELETE FROM sqlite_stat1 }
|
||||||
|
} {}
|
||||||
|
|
||||||
|
do_test 4.1.1 {
|
||||||
|
execsql { INSERT INTO t1 VALUES(3,3); }
|
||||||
|
set C [changeset_from_sql {ANALYZE}]
|
||||||
|
set ::c [list]
|
||||||
|
proc xConflict {args} {
|
||||||
|
lappend ::c $args
|
||||||
|
return "OMIT"
|
||||||
|
}
|
||||||
|
sqlite3changeset_apply db2 $C xConflict
|
||||||
|
set ::c
|
||||||
|
} [list {*}{
|
||||||
|
{INSERT sqlite_stat1 CONFLICT {t t1 t i1 t {3 1}} {t t1 t i1 t {2 1}}}
|
||||||
|
{INSERT sqlite_stat1 CONFLICT {t t1 t i2 t {3 1}} {t t1 t i2 t {2 1}}}
|
||||||
|
}]
|
||||||
|
|
||||||
|
do_execsql_test -db db2 4.1.2 {
|
||||||
|
SELECT * FROM sqlite_stat1 ORDER BY 1,2;
|
||||||
|
} {t1 i1 {2 1} t1 i2 {2 1}}
|
||||||
|
|
||||||
|
do_test 4.1.3 {
|
||||||
|
proc xConflict {args} {
|
||||||
|
return "REPLACE"
|
||||||
|
}
|
||||||
|
sqlite3changeset_apply db2 $C xConflict
|
||||||
|
execsql { SELECT * FROM sqlite_stat1 ORDER BY 1,2 } db2
|
||||||
|
} {t1 i1 {3 1} t1 i2 {3 1}}
|
||||||
|
|
||||||
|
do_test 4.2.0 {
|
||||||
|
do_common_sql {
|
||||||
|
DROP TABLE t1;
|
||||||
|
CREATE TABLE t3(x,y);
|
||||||
|
INSERT INTO t3 VALUES('a','a');
|
||||||
|
INSERT INTO t3 VALUES('b','b');
|
||||||
|
ANALYZE;
|
||||||
|
}
|
||||||
|
execsql { DELETE FROM sqlite_stat1 }
|
||||||
|
} {}
|
||||||
|
do_test 4.2.1 {
|
||||||
|
execsql { INSERT INTO t3 VALUES('c','c'); }
|
||||||
|
set C [changeset_from_sql {ANALYZE}]
|
||||||
|
set ::c [list]
|
||||||
|
proc xConflict {args} {
|
||||||
|
lappend ::c $args
|
||||||
|
return "OMIT"
|
||||||
|
}
|
||||||
|
sqlite3changeset_apply db2 $C xConflict
|
||||||
|
set ::c
|
||||||
|
} [list {*}{
|
||||||
|
{INSERT sqlite_stat1 CONFLICT {t t3 b {} t 3} {t t3 b {} t 2}}
|
||||||
|
}]
|
||||||
|
|
||||||
|
db2 null null
|
||||||
|
do_execsql_test -db db2 4.2.2 {
|
||||||
|
SELECT * FROM sqlite_stat1 ORDER BY 1,2;
|
||||||
|
} {t3 null 2}
|
||||||
|
|
||||||
|
do_test 4.2.3 {
|
||||||
|
proc xConflict {args} {
|
||||||
|
return "REPLACE"
|
||||||
|
}
|
||||||
|
sqlite3changeset_apply db2 $C xConflict
|
||||||
|
execsql { SELECT * FROM sqlite_stat1 ORDER BY 1,2 } db2
|
||||||
|
} {t3 null 3}
|
||||||
|
|
||||||
finish_test
|
finish_test
|
||||||
|
|
||||||
|
@ -46,6 +46,7 @@ struct sqlite3_session {
|
|||||||
int rc; /* Non-zero if an error has occurred */
|
int rc; /* Non-zero if an error has occurred */
|
||||||
void *pFilterCtx; /* First argument to pass to xTableFilter */
|
void *pFilterCtx; /* First argument to pass to xTableFilter */
|
||||||
int (*xTableFilter)(void *pCtx, const char *zTab);
|
int (*xTableFilter)(void *pCtx, const char *zTab);
|
||||||
|
sqlite3_value *pZeroBlob; /* Value containing X'' */
|
||||||
sqlite3_session *pNext; /* Next session object on same db. */
|
sqlite3_session *pNext; /* Next session object on same db. */
|
||||||
SessionTable *pTable; /* List of attached tables */
|
SessionTable *pTable; /* List of attached tables */
|
||||||
SessionHook hook; /* APIs to grab new and old data with */
|
SessionHook hook; /* APIs to grab new and old data with */
|
||||||
@ -113,6 +114,7 @@ struct SessionTable {
|
|||||||
SessionTable *pNext;
|
SessionTable *pNext;
|
||||||
char *zName; /* Local name of table */
|
char *zName; /* Local name of table */
|
||||||
int nCol; /* Number of columns in table zName */
|
int nCol; /* Number of columns in table zName */
|
||||||
|
int bStat1; /* True if this is sqlite_stat1 */
|
||||||
const char **azCol; /* Column names */
|
const char **azCol; /* Column names */
|
||||||
u8 *abPK; /* Array of primary key flags */
|
u8 *abPK; /* Array of primary key flags */
|
||||||
int nEntry; /* Total number of entries in hash table */
|
int nEntry; /* Total number of entries in hash table */
|
||||||
@ -496,6 +498,7 @@ static int sessionPreupdateHash(
|
|||||||
h = sessionHashAppendBlob(h, n, z);
|
h = sessionHashAppendBlob(h, n, z);
|
||||||
}else{
|
}else{
|
||||||
assert( eType==SQLITE_NULL );
|
assert( eType==SQLITE_NULL );
|
||||||
|
assert( pTab->bStat1==0 || i!=1 );
|
||||||
*pbNullPK = 1;
|
*pbNullPK = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1047,11 +1050,55 @@ static int sessionInitTable(sqlite3_session *pSession, SessionTable *pTab){
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if( 0==sqlite3_stricmp("sqlite_stat1", pTab->zName) ){
|
||||||
|
pTab->bStat1 = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (pSession->rc || pTab->abPK==0);
|
return (pSession->rc || pTab->abPK==0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Versions of the four methods in object SessionHook for use with the
|
||||||
|
** sqlite_stat1 table. The purpose of this is to substitute a zero-length
|
||||||
|
** blob each time a NULL value is read from the "idx" column of the
|
||||||
|
** sqlite_stat1 table.
|
||||||
|
*/
|
||||||
|
typedef struct SessionStat1Ctx SessionStat1Ctx;
|
||||||
|
struct SessionStat1Ctx {
|
||||||
|
SessionHook hook;
|
||||||
|
sqlite3_session *pSession;
|
||||||
|
};
|
||||||
|
static int sessionStat1Old(void *pCtx, int iCol, sqlite3_value **ppVal){
|
||||||
|
SessionStat1Ctx *p = (SessionStat1Ctx*)pCtx;
|
||||||
|
sqlite3_value *pVal = 0;
|
||||||
|
int rc = p->hook.xOld(p->hook.pCtx, iCol, &pVal);
|
||||||
|
if( rc==SQLITE_OK && iCol==1 && sqlite3_value_type(pVal)==SQLITE_NULL ){
|
||||||
|
pVal = p->pSession->pZeroBlob;
|
||||||
|
}
|
||||||
|
*ppVal = pVal;
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
static int sessionStat1New(void *pCtx, int iCol, sqlite3_value **ppVal){
|
||||||
|
SessionStat1Ctx *p = (SessionStat1Ctx*)pCtx;
|
||||||
|
sqlite3_value *pVal = 0;
|
||||||
|
int rc = p->hook.xNew(p->hook.pCtx, iCol, &pVal);
|
||||||
|
if( rc==SQLITE_OK && iCol==1 && sqlite3_value_type(pVal)==SQLITE_NULL ){
|
||||||
|
pVal = p->pSession->pZeroBlob;
|
||||||
|
}
|
||||||
|
*ppVal = pVal;
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
static int sessionStat1Count(void *pCtx){
|
||||||
|
SessionStat1Ctx *p = (SessionStat1Ctx*)pCtx;
|
||||||
|
return p->hook.xCount(p->hook.pCtx);
|
||||||
|
}
|
||||||
|
static int sessionStat1Depth(void *pCtx){
|
||||||
|
SessionStat1Ctx *p = (SessionStat1Ctx*)pCtx;
|
||||||
|
return p->hook.xDepth(p->hook.pCtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** This function is only called from with a pre-update-hook reporting a
|
** This function is only called from with a pre-update-hook reporting a
|
||||||
** change on table pTab (attached to session pSession). The type of change
|
** change on table pTab (attached to session pSession). The type of change
|
||||||
@ -1068,6 +1115,7 @@ static void sessionPreupdateOneChange(
|
|||||||
int iHash;
|
int iHash;
|
||||||
int bNull = 0;
|
int bNull = 0;
|
||||||
int rc = SQLITE_OK;
|
int rc = SQLITE_OK;
|
||||||
|
SessionStat1Ctx stat1;
|
||||||
|
|
||||||
if( pSession->rc ) return;
|
if( pSession->rc ) return;
|
||||||
|
|
||||||
@ -1087,6 +1135,25 @@ static void sessionPreupdateOneChange(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( pTab->bStat1 ){
|
||||||
|
stat1.hook = pSession->hook;
|
||||||
|
stat1.pSession = pSession;
|
||||||
|
pSession->hook.pCtx = (void*)&stat1;
|
||||||
|
pSession->hook.xNew = sessionStat1New;
|
||||||
|
pSession->hook.xOld = sessionStat1Old;
|
||||||
|
pSession->hook.xCount = sessionStat1Count;
|
||||||
|
pSession->hook.xDepth = sessionStat1Depth;
|
||||||
|
if( pSession->pZeroBlob==0 ){
|
||||||
|
sqlite3_value *p = sqlite3ValueNew(0);
|
||||||
|
if( p==0 ){
|
||||||
|
rc = SQLITE_NOMEM;
|
||||||
|
goto error_out;
|
||||||
|
}
|
||||||
|
sqlite3ValueSetStr(p, 0, "", 0, SQLITE_STATIC);
|
||||||
|
pSession->pZeroBlob = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Calculate the hash-key for this change. If the primary key of the row
|
/* 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
|
** includes a NULL value, exit early. Such changes are ignored by the
|
||||||
** session module. */
|
** session module. */
|
||||||
@ -1176,6 +1243,9 @@ static void sessionPreupdateOneChange(
|
|||||||
|
|
||||||
/* If an error has occurred, mark the session object as failed. */
|
/* If an error has occurred, mark the session object as failed. */
|
||||||
error_out:
|
error_out:
|
||||||
|
if( pTab->bStat1 ){
|
||||||
|
pSession->hook = stat1.hook;
|
||||||
|
}
|
||||||
if( rc!=SQLITE_OK ){
|
if( rc!=SQLITE_OK ){
|
||||||
pSession->rc = rc;
|
pSession->rc = rc;
|
||||||
}
|
}
|
||||||
@ -1637,6 +1707,7 @@ void sqlite3session_delete(sqlite3_session *pSession){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
sqlite3_mutex_leave(sqlite3_db_mutex(db));
|
sqlite3_mutex_leave(sqlite3_db_mutex(db));
|
||||||
|
sqlite3ValueFree(pSession->pZeroBlob);
|
||||||
|
|
||||||
/* Delete all attached table objects. And the contents of their
|
/* Delete all attached table objects. And the contents of their
|
||||||
** associated hash-tables. */
|
** associated hash-tables. */
|
||||||
@ -2104,28 +2175,41 @@ static int sessionSelectStmt(
|
|||||||
sqlite3_stmt **ppStmt /* OUT: Prepared SELECT statement */
|
sqlite3_stmt **ppStmt /* OUT: Prepared SELECT statement */
|
||||||
){
|
){
|
||||||
int rc = SQLITE_OK;
|
int rc = SQLITE_OK;
|
||||||
int i;
|
char *zSql = 0;
|
||||||
const char *zSep = "";
|
int nSql = -1;
|
||||||
SessionBuffer buf = {0, 0, 0};
|
|
||||||
|
|
||||||
sessionAppendStr(&buf, "SELECT * FROM ", &rc);
|
if( 0==sqlite3_stricmp("sqlite_stat1", zTab) ){
|
||||||
sessionAppendIdent(&buf, zDb, &rc);
|
zSql = sqlite3_mprintf(
|
||||||
sessionAppendStr(&buf, ".", &rc);
|
"SELECT tbl, ?2, stat FROM %Q.sqlite_stat1 WHERE tbl IS ?1 AND "
|
||||||
sessionAppendIdent(&buf, zTab, &rc);
|
"idx IS (CASE WHEN ?2=X'' THEN NULL ELSE ?2 END)", zDb
|
||||||
sessionAppendStr(&buf, " WHERE ", &rc);
|
);
|
||||||
for(i=0; i<nCol; i++){
|
}else{
|
||||||
if( abPK[i] ){
|
int i;
|
||||||
sessionAppendStr(&buf, zSep, &rc);
|
const char *zSep = "";
|
||||||
sessionAppendIdent(&buf, azCol[i], &rc);
|
SessionBuffer buf = {0, 0, 0};
|
||||||
sessionAppendStr(&buf, " = ?", &rc);
|
|
||||||
sessionAppendInteger(&buf, i+1, &rc);
|
sessionAppendStr(&buf, "SELECT * FROM ", &rc);
|
||||||
zSep = " AND ";
|
sessionAppendIdent(&buf, zDb, &rc);
|
||||||
|
sessionAppendStr(&buf, ".", &rc);
|
||||||
|
sessionAppendIdent(&buf, zTab, &rc);
|
||||||
|
sessionAppendStr(&buf, " WHERE ", &rc);
|
||||||
|
for(i=0; i<nCol; i++){
|
||||||
|
if( abPK[i] ){
|
||||||
|
sessionAppendStr(&buf, zSep, &rc);
|
||||||
|
sessionAppendIdent(&buf, azCol[i], &rc);
|
||||||
|
sessionAppendStr(&buf, " IS ?", &rc);
|
||||||
|
sessionAppendInteger(&buf, i+1, &rc);
|
||||||
|
zSep = " AND ";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
zSql = (char*)buf.aBuf;
|
||||||
|
nSql = buf.nBuf;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( rc==SQLITE_OK ){
|
if( rc==SQLITE_OK ){
|
||||||
rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, ppStmt, 0);
|
rc = sqlite3_prepare_v2(db, zSql, nSql, ppStmt, 0);
|
||||||
}
|
}
|
||||||
sqlite3_free(buf.aBuf);
|
sqlite3_free(zSql);
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3294,7 +3378,7 @@ struct SessionApplyCtx {
|
|||||||
int nCol; /* Size of azCol[] and abPK[] arrays */
|
int nCol; /* Size of azCol[] and abPK[] arrays */
|
||||||
const char **azCol; /* Array of column names */
|
const char **azCol; /* Array of column names */
|
||||||
u8 *abPK; /* Boolean array - true if column is in PK */
|
u8 *abPK; /* Boolean array - true if column is in PK */
|
||||||
|
int bStat1; /* True if table is sqlite_stat1 */
|
||||||
int bDeferConstraints; /* True to defer constraints */
|
int bDeferConstraints; /* True to defer constraints */
|
||||||
SessionBuffer constraints; /* Deferred constraints are stored here */
|
SessionBuffer constraints; /* Deferred constraints are stored here */
|
||||||
};
|
};
|
||||||
@ -3464,6 +3548,7 @@ static int sessionUpdateRow(
|
|||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** Formulate and prepare an SQL statement to query table zTab by primary
|
** Formulate and prepare an SQL statement to query table zTab by primary
|
||||||
** key. Assuming the following table structure:
|
** key. Assuming the following table structure:
|
||||||
@ -3525,6 +3610,47 @@ static int sessionInsertRow(
|
|||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int sessionPrepare(sqlite3 *db, sqlite3_stmt **pp, const char *zSql){
|
||||||
|
return sqlite3_prepare_v2(db, zSql, -1, pp, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Prepare statements for applying changes to the sqlite_stat1 table.
|
||||||
|
** These are similar to those created by sessionSelectRow(),
|
||||||
|
** sessionInsertRow(), sessionUpdateRow() and sessionDeleteRow() for
|
||||||
|
** other tables.
|
||||||
|
*/
|
||||||
|
static int sessionStat1Sql(sqlite3 *db, SessionApplyCtx *p){
|
||||||
|
int rc = sessionSelectRow(db, "sqlite_stat1", p);
|
||||||
|
if( rc==SQLITE_OK ){
|
||||||
|
rc = sessionPrepare(db, &p->pInsert,
|
||||||
|
"INSERT INTO main.sqlite_stat1 VALUES(?1, "
|
||||||
|
"CASE WHEN length(?2)=0 AND typeof(?2)='blob' THEN NULL ELSE ?2 END, "
|
||||||
|
"?3)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if( rc==SQLITE_OK ){
|
||||||
|
rc = sessionPrepare(db, &p->pUpdate,
|
||||||
|
"UPDATE main.sqlite_stat1 SET "
|
||||||
|
"tbl = CASE WHEN ?2 THEN ?3 ELSE tbl END, "
|
||||||
|
"idx = CASE WHEN ?5 THEN ?6 ELSE idx END, "
|
||||||
|
"stat = CASE WHEN ?8 THEN ?9 ELSE stat END "
|
||||||
|
"WHERE tbl=?1 AND idx IS "
|
||||||
|
"CASE WHEN length(?4)=0 AND typeof(?4)='blob' THEN NULL ELSE ?4 END "
|
||||||
|
"AND (?10 OR ?8=0 OR stat IS ?7)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if( rc==SQLITE_OK ){
|
||||||
|
rc = sessionPrepare(db, &p->pDelete,
|
||||||
|
"DELETE FROM main.sqlite_stat1 WHERE tbl=?1 AND idx IS "
|
||||||
|
"CASE WHEN length(?2)=0 AND typeof(?2)='blob' THEN NULL ELSE ?2 END "
|
||||||
|
"AND (?4 OR stat IS ?3)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert( rc==SQLITE_OK );
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** A wrapper around sqlite3_bind_value() that detects an extra problem.
|
** A wrapper around sqlite3_bind_value() that detects an extra problem.
|
||||||
** See comments in the body of this function for details.
|
** See comments in the body of this function for details.
|
||||||
@ -3855,11 +3981,25 @@ static int sessionApplyOneOp(
|
|||||||
|
|
||||||
}else{
|
}else{
|
||||||
assert( op==SQLITE_INSERT );
|
assert( op==SQLITE_INSERT );
|
||||||
rc = sessionBindRow(pIter, sqlite3changeset_new, nCol, 0, p->pInsert);
|
if( p->bStat1 ){
|
||||||
if( rc!=SQLITE_OK ) return rc;
|
/* Check if there is a conflicting row. For sqlite_stat1, this needs
|
||||||
|
** to be done using a SELECT, as there is no PRIMARY KEY in the
|
||||||
|
** database schema to throw an exception if a duplicate is inserted. */
|
||||||
|
rc = sessionSeekToRow(p->db, pIter, p->abPK, p->pSelect);
|
||||||
|
if( rc==SQLITE_ROW ){
|
||||||
|
rc = SQLITE_CONSTRAINT;
|
||||||
|
sqlite3_reset(p->pSelect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( rc==SQLITE_OK ){
|
||||||
|
rc = sessionBindRow(pIter, sqlite3changeset_new, nCol, 0, p->pInsert);
|
||||||
|
if( rc!=SQLITE_OK ) return rc;
|
||||||
|
|
||||||
|
sqlite3_step(p->pInsert);
|
||||||
|
rc = sqlite3_reset(p->pInsert);
|
||||||
|
}
|
||||||
|
|
||||||
sqlite3_step(p->pInsert);
|
|
||||||
rc = sqlite3_reset(p->pInsert);
|
|
||||||
if( (rc&0xff)==SQLITE_CONSTRAINT ){
|
if( (rc&0xff)==SQLITE_CONSTRAINT ){
|
||||||
rc = sessionConflictHandler(
|
rc = sessionConflictHandler(
|
||||||
SQLITE_CHANGESET_CONFLICT, p, pIter, xConflict, pCtx, pbReplace
|
SQLITE_CHANGESET_CONFLICT, p, pIter, xConflict, pCtx, pbReplace
|
||||||
@ -4092,12 +4232,20 @@ static int sessionChangesetApply(
|
|||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
sApply.nCol = nCol;
|
sApply.nCol = nCol;
|
||||||
if((rc = sessionSelectRow(db, zTab, &sApply))
|
if( 0==sqlite3_stricmp(zTab, "sqlite_stat1") ){
|
||||||
|| (rc = sessionUpdateRow(db, zTab, &sApply))
|
if( (rc = sessionStat1Sql(db, &sApply) ) ){
|
||||||
|| (rc = sessionDeleteRow(db, zTab, &sApply))
|
break;
|
||||||
|| (rc = sessionInsertRow(db, zTab, &sApply))
|
}
|
||||||
){
|
sApply.bStat1 = 1;
|
||||||
break;
|
}else{
|
||||||
|
if((rc = sessionSelectRow(db, zTab, &sApply))
|
||||||
|
|| (rc = sessionUpdateRow(db, zTab, &sApply))
|
||||||
|
|| (rc = sessionDeleteRow(db, zTab, &sApply))
|
||||||
|
|| (rc = sessionInsertRow(db, zTab, &sApply))
|
||||||
|
){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sApply.bStat1 = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nTab = sqlite3Strlen30(zTab);
|
nTab = sqlite3Strlen30(zTab);
|
||||||
|
@ -147,6 +147,35 @@ int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect);
|
|||||||
**
|
**
|
||||||
** SQLITE_OK is returned if the call completes without error. Or, if an error
|
** SQLITE_OK is returned if the call completes without error. Or, if an error
|
||||||
** occurs, an SQLite error code (e.g. SQLITE_NOMEM) is returned.
|
** occurs, an SQLite error code (e.g. SQLITE_NOMEM) is returned.
|
||||||
|
**
|
||||||
|
** <h3>Special sqlite_stat1 Handling</h3>
|
||||||
|
**
|
||||||
|
** As of SQLite version 3.22.0, the "sqlite_stat1" table is an exception to
|
||||||
|
** some of the rules above. In SQLite, the schema of sqlite_stat1 is:
|
||||||
|
** <pre>
|
||||||
|
** CREATE TABLE sqlite_stat1(tbl,idx,stat)
|
||||||
|
** </pre>
|
||||||
|
**
|
||||||
|
** Even though sqlite_stat1 does not have a PRIMARY KEY, changes are
|
||||||
|
** recorded for it as if the PRIMARY KEY is (tbl,idx). Additionally, changes
|
||||||
|
** are recorded for rows for which (idx IS NULL) is true. However, for such
|
||||||
|
** rows a zero-length blob (SQL value X'') is stored in the changeset or
|
||||||
|
** patchset instead of a NULL value. This allows such changesets to be
|
||||||
|
** manipulated by legacy implementations of sqlite3changeset_invert(),
|
||||||
|
** concat() and similar.
|
||||||
|
**
|
||||||
|
** The sqlite3changeset_apply() function automatically converts the
|
||||||
|
** zero-length blob back to a NULL value when updating the sqlite_stat1
|
||||||
|
** table. However, if the application calls sqlite3changeset_new(),
|
||||||
|
** sqlite3changeset_old() or sqlite3changeset_conflict on a changeset
|
||||||
|
** iterator directly (including on a changeset iterator passed to a
|
||||||
|
** conflict-handler callback) then the X'' value is returned. The application
|
||||||
|
** must translate X'' to NULL itself if required.
|
||||||
|
**
|
||||||
|
** Legacy (older than 3.22.0) versions of the sessions module cannot capture
|
||||||
|
** changes made to the sqlite_stat1 table. Legacy versions of the
|
||||||
|
** sqlite3changeset_apply() function silently ignore any modifications to the
|
||||||
|
** sqlite_stat1 table that are part of a changeset or patchset.
|
||||||
*/
|
*/
|
||||||
int sqlite3session_attach(
|
int sqlite3session_attach(
|
||||||
sqlite3_session *pSession, /* Session object */
|
sqlite3_session *pSession, /* Session object */
|
||||||
|
21
manifest
21
manifest
@ -1,5 +1,5 @@
|
|||||||
C Fix\sto\sthe\sdocumentation\sfor\ssqlite3_trace_v2().\s\sNo\schanges\sto\scode.
|
C Fix\ssessions\smodule\shandling\sof\ssqlite_stat1\srows\swith\s(idx\sIS\sNULL).
|
||||||
D 2018-01-18T16:52:35.238
|
D 2018-01-18T16:59:52.652
|
||||||
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
|
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
|
||||||
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
|
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
|
||||||
F Makefile.in 38f84f301cbef443b2d269f67a74b8cc536469831f70df7c3e912acc04932cc2
|
F Makefile.in 38f84f301cbef443b2d269f67a74b8cc536469831f70df7c3e912acc04932cc2
|
||||||
@ -398,10 +398,10 @@ F ext/session/sessionat.test efe88965e74ff1bc2af9c310b28358c02d420c1fb2705cc7a28
|
|||||||
F ext/session/sessiondiff.test ad13dd65664bae26744e1f18eb3cbd5588349b7e9118851d8f9364248d67bcec
|
F ext/session/sessiondiff.test ad13dd65664bae26744e1f18eb3cbd5588349b7e9118851d8f9364248d67bcec
|
||||||
F ext/session/sessionfault.test da273f2712b6411e85e71465a1733b8501dbf6f7
|
F ext/session/sessionfault.test da273f2712b6411e85e71465a1733b8501dbf6f7
|
||||||
F ext/session/sessionfault2.test 04aa0bc9aa70ea43d8de82c4f648db4de1e990b0
|
F ext/session/sessionfault2.test 04aa0bc9aa70ea43d8de82c4f648db4de1e990b0
|
||||||
F ext/session/sessionstat1.test e3a3f5876ce1526b48f6f447ee0b18960ac683e3fc891791e1ca0c08e823d498
|
F ext/session/sessionstat1.test 41cd97c2e48619a41cdf8ae749e1b25f34719de638689221aa43971be693bf4e
|
||||||
F ext/session/sessionwor.test 2f3744236dc8b170a695b7d8ddc8c743c7e79fdc
|
F ext/session/sessionwor.test 2f3744236dc8b170a695b7d8ddc8c743c7e79fdc
|
||||||
F ext/session/sqlite3session.c c9d813ffa8db0670257da0a0c931e7188dfbb0010bf1a38274775c9b6300dd5c
|
F ext/session/sqlite3session.c 989466bba4dff0ede8d4c450b1fc65ca222b87e31193eddbf3931b88bf898a57
|
||||||
F ext/session/sqlite3session.h cb4d860101ba6d3ac810f18684539b766d24d668fa2436cdde90d711af9464fb
|
F ext/session/sqlite3session.h 01774161cbd328fe3d496323655b9cc142317ff1fb1ae15c1232075ea240e3a4
|
||||||
F ext/session/test_session.c eb0bd6c1ea791c1d66ee4ef94c16500dad936386
|
F ext/session/test_session.c eb0bd6c1ea791c1d66ee4ef94c16500dad936386
|
||||||
F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3
|
F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3
|
||||||
F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04
|
F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04
|
||||||
@ -422,7 +422,7 @@ F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b
|
|||||||
F sqlite3.1 fc7ad8990fc8409983309bb80de8c811a7506786
|
F sqlite3.1 fc7ad8990fc8409983309bb80de8c811a7506786
|
||||||
F sqlite3.pc.in 48fed132e7cb71ab676105d2a4dc77127d8c1f3a
|
F sqlite3.pc.in 48fed132e7cb71ab676105d2a4dc77127d8c1f3a
|
||||||
F src/alter.c cf7a8af45cb0ace672f47a1b29ab24092a9e8cd8d945a9974e3b5d925f548594
|
F src/alter.c cf7a8af45cb0ace672f47a1b29ab24092a9e8cd8d945a9974e3b5d925f548594
|
||||||
F src/analyze.c f9bfffd0416c547a916cb96793b94684bdb0d26a71542ea31819c6757741c19d
|
F src/analyze.c 6b42e36a5dcc2703a771f2411bd5e99524bd62c7ecde209bb88dfb04c72f046e
|
||||||
F src/attach.c 84c477e856b24c2b9a0983b438a707c0cf4d616cee7a425401d418e58afec24c
|
F src/attach.c 84c477e856b24c2b9a0983b438a707c0cf4d616cee7a425401d418e58afec24c
|
||||||
F src/auth.c 6277d63837357549fe14e723490d6dc1a38768d71c795c5eb5c0f8a99f918f73
|
F src/auth.c 6277d63837357549fe14e723490d6dc1a38768d71c795c5eb5c0f8a99f918f73
|
||||||
F src/backup.c faf17e60b43233c214aae6a8179d24503a61e83b
|
F src/backup.c faf17e60b43233c214aae6a8179d24503a61e83b
|
||||||
@ -1700,7 +1700,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
|
|||||||
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
|
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
|
||||||
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
|
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
|
||||||
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
|
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
|
||||||
P 469b96be5350ba2291518280ffe179b87aa7fbe701e2813ef63843922771517a
|
P 6fbd0a11a66f8eb4d7820cb49c23bdcb917db98a22c29d76edea1eea6dab0a4e dc7c48cb4126db9e25c73512cc743155293fe1c4c2516f8c84102228695b6e70
|
||||||
R 2b93eecc1bfa73dda9a58b538de6c0af
|
R e340b654f2343cebf57c71c1eef74e4f
|
||||||
U drh
|
T +closed dc7c48cb4126db9e25c73512cc743155293fe1c4c2516f8c84102228695b6e70
|
||||||
Z f64363038b04686cc1ed3a7aa13703ce
|
U dan
|
||||||
|
Z 94f64c633d9eca20229572c5d0ac1d82
|
||||||
|
@ -1 +1 @@
|
|||||||
6fbd0a11a66f8eb4d7820cb49c23bdcb917db98a22c29d76edea1eea6dab0a4e
|
0e916416331d7948b312a5dd58ac0c145030bb3b47a37dab2636564397249a86
|
@ -1309,6 +1309,9 @@ static void analyzeOneTable(
|
|||||||
sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur, regNewRowid);
|
sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur, regNewRowid);
|
||||||
sqlite3VdbeAddOp3(v, OP_Insert, iStatCur, regTemp, regNewRowid);
|
sqlite3VdbeAddOp3(v, OP_Insert, iStatCur, regTemp, regNewRowid);
|
||||||
sqlite3VdbeChangeP5(v, OPFLAG_APPEND);
|
sqlite3VdbeChangeP5(v, OPFLAG_APPEND);
|
||||||
|
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
|
||||||
|
sqlite3VdbeChangeP4(v, -1, (char*)pStat1, P4_TABLE);
|
||||||
|
#endif
|
||||||
sqlite3VdbeJumpHere(v, jZeroRows);
|
sqlite3VdbeJumpHere(v, jZeroRows);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user