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
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
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
|
||||
|
||||
|
@ -46,6 +46,7 @@ struct sqlite3_session {
|
||||
int rc; /* Non-zero if an error has occurred */
|
||||
void *pFilterCtx; /* First argument to pass to xTableFilter */
|
||||
int (*xTableFilter)(void *pCtx, const char *zTab);
|
||||
sqlite3_value *pZeroBlob; /* Value containing X'' */
|
||||
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 */
|
||||
@ -113,6 +114,7 @@ struct SessionTable {
|
||||
SessionTable *pNext;
|
||||
char *zName; /* Local name of table */
|
||||
int nCol; /* Number of columns in table zName */
|
||||
int bStat1; /* True if this is sqlite_stat1 */
|
||||
const char **azCol; /* Column names */
|
||||
u8 *abPK; /* Array of primary key flags */
|
||||
int nEntry; /* Total number of entries in hash table */
|
||||
@ -496,6 +498,7 @@ static int sessionPreupdateHash(
|
||||
h = sessionHashAppendBlob(h, n, z);
|
||||
}else{
|
||||
assert( eType==SQLITE_NULL );
|
||||
assert( pTab->bStat1==0 || i!=1 );
|
||||
*pbNullPK = 1;
|
||||
}
|
||||
}
|
||||
@ -1047,11 +1050,55 @@ static int sessionInitTable(sqlite3_session *pSession, SessionTable *pTab){
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( 0==sqlite3_stricmp("sqlite_stat1", pTab->zName) ){
|
||||
pTab->bStat1 = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
** change on table pTab (attached to session pSession). The type of change
|
||||
@ -1068,6 +1115,7 @@ static void sessionPreupdateOneChange(
|
||||
int iHash;
|
||||
int bNull = 0;
|
||||
int rc = SQLITE_OK;
|
||||
SessionStat1Ctx stat1;
|
||||
|
||||
if( pSession->rc ) return;
|
||||
|
||||
@ -1087,6 +1135,25 @@ static void sessionPreupdateOneChange(
|
||||
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
|
||||
** includes a NULL value, exit early. Such changes are ignored by the
|
||||
** session module. */
|
||||
@ -1176,6 +1243,9 @@ static void sessionPreupdateOneChange(
|
||||
|
||||
/* If an error has occurred, mark the session object as failed. */
|
||||
error_out:
|
||||
if( pTab->bStat1 ){
|
||||
pSession->hook = stat1.hook;
|
||||
}
|
||||
if( rc!=SQLITE_OK ){
|
||||
pSession->rc = rc;
|
||||
}
|
||||
@ -1637,6 +1707,7 @@ void sqlite3session_delete(sqlite3_session *pSession){
|
||||
}
|
||||
}
|
||||
sqlite3_mutex_leave(sqlite3_db_mutex(db));
|
||||
sqlite3ValueFree(pSession->pZeroBlob);
|
||||
|
||||
/* Delete all attached table objects. And the contents of their
|
||||
** associated hash-tables. */
|
||||
@ -2104,28 +2175,41 @@ static int sessionSelectStmt(
|
||||
sqlite3_stmt **ppStmt /* OUT: Prepared SELECT statement */
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
int i;
|
||||
const char *zSep = "";
|
||||
SessionBuffer buf = {0, 0, 0};
|
||||
char *zSql = 0;
|
||||
int nSql = -1;
|
||||
|
||||
sessionAppendStr(&buf, "SELECT * FROM ", &rc);
|
||||
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, " = ?", &rc);
|
||||
sessionAppendInteger(&buf, i+1, &rc);
|
||||
zSep = " AND ";
|
||||
if( 0==sqlite3_stricmp("sqlite_stat1", zTab) ){
|
||||
zSql = sqlite3_mprintf(
|
||||
"SELECT tbl, ?2, stat FROM %Q.sqlite_stat1 WHERE tbl IS ?1 AND "
|
||||
"idx IS (CASE WHEN ?2=X'' THEN NULL ELSE ?2 END)", zDb
|
||||
);
|
||||
}else{
|
||||
int i;
|
||||
const char *zSep = "";
|
||||
SessionBuffer buf = {0, 0, 0};
|
||||
|
||||
sessionAppendStr(&buf, "SELECT * FROM ", &rc);
|
||||
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 ){
|
||||
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;
|
||||
}
|
||||
|
||||
@ -3294,7 +3378,7 @@ struct SessionApplyCtx {
|
||||
int nCol; /* Size of azCol[] and abPK[] arrays */
|
||||
const char **azCol; /* Array of column names */
|
||||
u8 *abPK; /* Boolean array - true if column is in PK */
|
||||
|
||||
int bStat1; /* True if table is sqlite_stat1 */
|
||||
int bDeferConstraints; /* True to defer constraints */
|
||||
SessionBuffer constraints; /* Deferred constraints are stored here */
|
||||
};
|
||||
@ -3464,6 +3548,7 @@ static int sessionUpdateRow(
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Formulate and prepare an SQL statement to query table zTab by primary
|
||||
** key. Assuming the following table structure:
|
||||
@ -3525,6 +3610,47 @@ static int sessionInsertRow(
|
||||
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.
|
||||
** See comments in the body of this function for details.
|
||||
@ -3855,11 +3981,25 @@ static int sessionApplyOneOp(
|
||||
|
||||
}else{
|
||||
assert( op==SQLITE_INSERT );
|
||||
rc = sessionBindRow(pIter, sqlite3changeset_new, nCol, 0, p->pInsert);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
if( p->bStat1 ){
|
||||
/* 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 ){
|
||||
rc = sessionConflictHandler(
|
||||
SQLITE_CHANGESET_CONFLICT, p, pIter, xConflict, pCtx, pbReplace
|
||||
@ -4092,12 +4232,20 @@ static int sessionChangesetApply(
|
||||
}
|
||||
else{
|
||||
sApply.nCol = nCol;
|
||||
if((rc = sessionSelectRow(db, zTab, &sApply))
|
||||
|| (rc = sessionUpdateRow(db, zTab, &sApply))
|
||||
|| (rc = sessionDeleteRow(db, zTab, &sApply))
|
||||
|| (rc = sessionInsertRow(db, zTab, &sApply))
|
||||
){
|
||||
break;
|
||||
if( 0==sqlite3_stricmp(zTab, "sqlite_stat1") ){
|
||||
if( (rc = sessionStat1Sql(db, &sApply) ) ){
|
||||
break;
|
||||
}
|
||||
sApply.bStat1 = 1;
|
||||
}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);
|
||||
|
@ -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
|
||||
** 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(
|
||||
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.
|
||||
D 2018-01-18T16:52:35.238
|
||||
C Fix\ssessions\smodule\shandling\sof\ssqlite_stat1\srows\swith\s(idx\sIS\sNULL).
|
||||
D 2018-01-18T16:59:52.652
|
||||
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
|
||||
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
|
||||
F Makefile.in 38f84f301cbef443b2d269f67a74b8cc536469831f70df7c3e912acc04932cc2
|
||||
@ -398,10 +398,10 @@ F ext/session/sessionat.test efe88965e74ff1bc2af9c310b28358c02d420c1fb2705cc7a28
|
||||
F ext/session/sessiondiff.test ad13dd65664bae26744e1f18eb3cbd5588349b7e9118851d8f9364248d67bcec
|
||||
F ext/session/sessionfault.test da273f2712b6411e85e71465a1733b8501dbf6f7
|
||||
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/sqlite3session.c c9d813ffa8db0670257da0a0c931e7188dfbb0010bf1a38274775c9b6300dd5c
|
||||
F ext/session/sqlite3session.h cb4d860101ba6d3ac810f18684539b766d24d668fa2436cdde90d711af9464fb
|
||||
F ext/session/sqlite3session.c 989466bba4dff0ede8d4c450b1fc65ca222b87e31193eddbf3931b88bf898a57
|
||||
F ext/session/sqlite3session.h 01774161cbd328fe3d496323655b9cc142317ff1fb1ae15c1232075ea240e3a4
|
||||
F ext/session/test_session.c eb0bd6c1ea791c1d66ee4ef94c16500dad936386
|
||||
F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3
|
||||
F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04
|
||||
@ -422,7 +422,7 @@ F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b
|
||||
F sqlite3.1 fc7ad8990fc8409983309bb80de8c811a7506786
|
||||
F sqlite3.pc.in 48fed132e7cb71ab676105d2a4dc77127d8c1f3a
|
||||
F src/alter.c cf7a8af45cb0ace672f47a1b29ab24092a9e8cd8d945a9974e3b5d925f548594
|
||||
F src/analyze.c f9bfffd0416c547a916cb96793b94684bdb0d26a71542ea31819c6757741c19d
|
||||
F src/analyze.c 6b42e36a5dcc2703a771f2411bd5e99524bd62c7ecde209bb88dfb04c72f046e
|
||||
F src/attach.c 84c477e856b24c2b9a0983b438a707c0cf4d616cee7a425401d418e58afec24c
|
||||
F src/auth.c 6277d63837357549fe14e723490d6dc1a38768d71c795c5eb5c0f8a99f918f73
|
||||
F src/backup.c faf17e60b43233c214aae6a8179d24503a61e83b
|
||||
@ -1700,7 +1700,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
|
||||
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
|
||||
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
|
||||
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
|
||||
P 469b96be5350ba2291518280ffe179b87aa7fbe701e2813ef63843922771517a
|
||||
R 2b93eecc1bfa73dda9a58b538de6c0af
|
||||
U drh
|
||||
Z f64363038b04686cc1ed3a7aa13703ce
|
||||
P 6fbd0a11a66f8eb4d7820cb49c23bdcb917db98a22c29d76edea1eea6dab0a4e dc7c48cb4126db9e25c73512cc743155293fe1c4c2516f8c84102228695b6e70
|
||||
R e340b654f2343cebf57c71c1eef74e4f
|
||||
T +closed dc7c48cb4126db9e25c73512cc743155293fe1c4c2516f8c84102228695b6e70
|
||||
U dan
|
||||
Z 94f64c633d9eca20229572c5d0ac1d82
|
||||
|
@ -1 +1 @@
|
||||
6fbd0a11a66f8eb4d7820cb49c23bdcb917db98a22c29d76edea1eea6dab0a4e
|
||||
0e916416331d7948b312a5dd58ac0c145030bb3b47a37dab2636564397249a86
|
@ -1309,6 +1309,9 @@ static void analyzeOneTable(
|
||||
sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur, regNewRowid);
|
||||
sqlite3VdbeAddOp3(v, OP_Insert, iStatCur, regTemp, regNewRowid);
|
||||
sqlite3VdbeChangeP5(v, OPFLAG_APPEND);
|
||||
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
sqlite3VdbeChangeP4(v, -1, (char*)pStat1, P4_TABLE);
|
||||
#endif
|
||||
sqlite3VdbeJumpHere(v, jZeroRows);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user