]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Fix a problem in the sessions module with logging sqlite_stat1 rows for which
authordan <dan@noemail.net>
Wed, 17 Jan 2018 20:57:20 +0000 (20:57 +0000)
committerdan <dan@noemail.net>
Wed, 17 Jan 2018 20:57:20 +0000 (20:57 +0000)
(idx IS NULL) is true.

FossilOrigin-Name: 25bf734be1b3883fccf12ac4d93d50289aa307fb60a52e0e32df12f7ee4edc7a

ext/session/sessionstat1.test
ext/session/sqlite3session.c
manifest
manifest.uuid
src/analyze.c

index 59de1cf1b4f5810a726c3d35f5691e8cb8227887..22b2de8dbcd3fe19ed36b474268fcb97258ca390 100644 (file)
@@ -121,5 +121,114 @@ 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}}
+}
+
+
+
+
+
 finish_test
 
index 6ef90037de80b21ece8116279e4a3ae894ea70c1..cbe75d5667cf032dcfcfebe2eabe870dbecc758a 100644 (file)
@@ -113,6 +113,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 */
@@ -472,31 +473,35 @@ static int sessionPreupdateHash(
       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);
+      if( pTab->bStat1 && eType==SQLITE_NULL ){
+        h = sessionHashAppendType(h, SQLITE_BLOB);
+      }else{
+        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 );
+          *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 );
-        *pbNullPK = 1;
       }
     }
   }
@@ -550,7 +555,7 @@ static unsigned int sessionChangeHash(
          || eType==SQLITE_TEXT || eType==SQLITE_BLOB 
          || eType==SQLITE_NULL || eType==0 
     );
-    assert( !isPK || (eType!=0 && eType!=SQLITE_NULL) );
+    assert( !isPK || (eType!=0 && (pTab->bStat1 || eType!=SQLITE_NULL)) );
 
     if( isPK ){
       a++;
@@ -558,7 +563,7 @@ static unsigned int sessionChangeHash(
       if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
         h = sessionHashAppendI64(h, sessionGetI64(a));
         a += 8;
-      }else{
+      }else if( eType!=SQLITE_NULL ){
         int n; 
         a += sessionVarintGet(a, &n);
         h = sessionHashAppendBlob(h, n, a);
@@ -794,6 +799,7 @@ static int sessionPreupdateEqual(
       sqlite3_value *pVal;        /* Value returned by preupdate_new/old */
       int rc;                     /* Error code from preupdate_new/old */
       int eType = *a++;           /* Type of value from change record */
+      int eValType;
 
       /* The following calls to preupdate_new() and preupdate_old() can not
       ** fail. This is because they cache their return values, and by the
@@ -808,7 +814,14 @@ static int sessionPreupdateEqual(
         rc = pSession->hook.xOld(pSession->hook.pCtx, iCol, &pVal);
       }
       assert( rc==SQLITE_OK );
-      if( sqlite3_value_type(pVal)!=eType ) return 0;
+      eValType = sqlite3_value_type(pVal);
+      if( eType==SQLITE_BLOB && eValType==SQLITE_NULL && pTab->bStat1 ){
+        int n;
+        a += sessionVarintGet(a, &n);
+        if( n!=0 ) return 0;
+        continue;
+      }
+      if( eValType!=eType ) return 0;
 
       /* A SessionChange object never has a NULL value in a PK column */
       assert( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT
@@ -1047,6 +1060,9 @@ 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);
@@ -1093,7 +1109,7 @@ static void sessionPreupdateOneChange(
   rc = sessionPreupdateHash(pSession, pTab, op==SQLITE_INSERT, &iHash, &bNull);
   if( rc!=SQLITE_OK ) goto error_out;
 
-  if( bNull==0 ){
+  if( bNull==0 || pTab->bStat1 ){
     /* Search the hash table for an existing record for this row. */
     SessionChange *pC;
     for(pC=pTab->apChange[iHash]; pC; pC=pC->pNext){
@@ -1128,6 +1144,7 @@ static void sessionPreupdateOneChange(
         rc = sessionSerializeValue(0, p, &nByte);
         if( rc!=SQLITE_OK ) goto error_out;
       }
+      if( pTab->bStat1 ) nByte += 30;
   
       /* Allocate the change object */
       pChange = (SessionChange *)sqlite3_malloc(nByte);
@@ -1151,7 +1168,12 @@ static void sessionPreupdateOneChange(
         }else if( pTab->abPK[i] ){
           pSession->hook.xNew(pSession->hook.pCtx, i, &p);
         }
-        sessionSerializeValue(&pChange->aRecord[nByte], p, &nByte);
+        if( p && pTab->bStat1 && sqlite3_value_type(p)==SQLITE_NULL ){
+          pChange->aRecord[nByte++] = SQLITE_BLOB;
+          nByte += sessionVarintPut(&pChange->aRecord[nByte], 0);
+        }else{
+          sessionSerializeValue(&pChange->aRecord[nByte], p, &nByte);
+        }
       }
 
       /* Add the change to the hash-table */
@@ -2104,28 +2126,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;
 }
 
@@ -2153,7 +2188,7 @@ static int sessionSelectBind(
     switch( eType ){
       case 0:
       case SQLITE_NULL:
-        assert( abPK[i]==0 );
+        /* assert( abPK[i]==0 ); */
         break;
 
       case SQLITE_INTEGER: {
@@ -3464,6 +3499,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 +3561,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.
@@ -4092,6 +4169,11 @@ static int sessionChangesetApply(
         }
         else{
           sApply.nCol = nCol;
+          if( 0==sqlite3_stricmp(zTab, "sqlite_stat1") ){
+            if( (rc = sessionStat1Sql(db, &sApply) ) ){
+              break;
+            }
+          }else
           if((rc = sessionSelectRow(db, zTab, &sApply))
           || (rc = sessionUpdateRow(db, zTab, &sApply))
           || (rc = sessionDeleteRow(db, zTab, &sApply))
index 5e365cf00f15bbd9fd1637511f538ae83b527343..ce17059a628c52edd499f0ae18dbe722f87e9f81 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Fix\sa\sproblem\scausing\sthe\ssessions\smodule\sto\soccasionally\slose\strack\sof\srows\nwith\scomposite\sprimary\skeys\swhen\sthere\sare\stwo\srows\swith\sthe\ssame\stext\svalue\nin\sthe\sleftmost\scolumn\sof\sthe\sPK.
-D 2018-01-17T17:38:18.448
+C Fix\sa\sproblem\sin\sthe\ssessions\smodule\swith\slogging\ssqlite_stat1\srows\sfor\swhich\n(idx\sIS\sNULL)\sis\strue.
+D 2018-01-17T20:57:20.602
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F Makefile.in 38f84f301cbef443b2d269f67a74b8cc536469831f70df7c3e912acc04932cc2
@@ -398,9 +398,9 @@ 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 16268a9bf62ab19c9bc9e41404bf7a13b5fd37c9cb6cf278a472f0c6c50d7ac1
 F ext/session/sessionwor.test 2f3744236dc8b170a695b7d8ddc8c743c7e79fdc
-F ext/session/sqlite3session.c c9d813ffa8db0670257da0a0c931e7188dfbb0010bf1a38274775c9b6300dd5c
+F ext/session/sqlite3session.c 870d142ec13c0e053139e7f3427aa550e59faf1775ae2bca051766089d9faf7b
 F ext/session/sqlite3session.h cb4d860101ba6d3ac810f18684539b766d24d668fa2436cdde90d711af9464fb
 F ext/session/test_session.c eb0bd6c1ea791c1d66ee4ef94c16500dad936386
 F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3
@@ -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,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P a8aea925f8fde8f2dc5ff4b744d54aa2bf8916f3ee57f22d77fd1ddb5a35a9cc
-R fffe7fea955e558a2fcc7877bcdc39db
+P 09aed13678374bf22087cd808808b711dc703b7c18bc8aaf704850611e17f5cd
+R 99a2e08bb84b46a9c74125d0aa1dbedc
+T *branch * sessions-stat1
+T *sym-sessions-stat1 *
+T -sym-trunk *
 U dan
-Z 8da05c1c6303c10ad33f099cfa382df7
+Z 913b883400e9793131764656e068c1a3
index 41ac115dac35e1eb61f53d31dea45404460d4842..a3bd20f4c8b03541159c6341b51392b31d9a6168 100644 (file)
@@ -1 +1 @@
-09aed13678374bf22087cd808808b711dc703b7c18bc8aaf704850611e17f5cd
\ No newline at end of file
+25bf734be1b3883fccf12ac4d93d50289aa307fb60a52e0e32df12f7ee4edc7a
\ No newline at end of file
index 85c603ffdf50fd3bd3fdffb071f3537d2dea93b7..0d13d77790dfcbb15bb4db37e787a0e5ba322958 100644 (file)
@@ -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);
   }
 }