/ Check-in [0e916416]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Fix sessions module handling of sqlite_stat1 rows with (idx IS NULL).
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256:0e916416331d7948b312a5dd58ac0c145030bb3b47a37dab2636564397249a86
User & Date: dan 2018-01-18 16:59:52
Context
2018-01-18
17:09
Update the autoconf configure.ac script and Makefile.am templates so that ZLIB is automatically detected and used. check-in: 41bfb6b8 user: drh tags: trunk
16:59
Fix sessions module handling of sqlite_stat1 rows with (idx IS NULL). check-in: 0e916416 user: dan tags: trunk
16:56
Clarify the handling of the sqlite_stat1 table by legacy versions of the sessions module. Closed-Leaf check-in: dc7c48cb user: dan tags: sessions-stat1
16:52
Fix to the documentation for sqlite3_trace_v2(). No changes to code. check-in: 6fbd0a11 user: drh tags: trunk
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to ext/session/sessionstat1.test.

   116    116   
   117    117   do_execsql_test -db db2 2.4 {
   118    118     SELECT * FROM sqlite_stat1
   119    119   } {
   120    120   }
   121    121   
   122    122   do_execsql_test -db db2 2.5 { SELECT count(*) FROM t1 } 32
          123  +
          124  +#-------------------------------------------------------------------------
          125  +db2 close
          126  +forcedelete test.db2
          127  +reset_db
          128  +sqlite3 db2 test.db2
          129  +
          130  +do_test 3.0 {
          131  +  do_common_sql {
          132  +    CREATE TABLE t1(a, b, c);
          133  +    ANALYZE;
          134  +    DELETE FROM sqlite_stat1;
          135  +  }
          136  +  execsql {
          137  +    INSERT INTO t1 VALUES(1, 1, 1);
          138  +    INSERT INTO t1 VALUES(2, 2, 2);
          139  +    INSERT INTO t1 VALUES(3, 3, 3);
          140  +    INSERT INTO t1 VALUES(4, 4, 4);
          141  +  }
          142  +} {} 
          143  +
          144  +do_iterator_test 3.1 {} {
          145  +  ANALYZE
          146  +} {
          147  +  {INSERT sqlite_stat1 0 XX. {} {t t1 b {} t 4}}
          148  +}
          149  +db null null
          150  +db2 null null
          151  +do_execsql_test 3.2 {
          152  +  SELECT * FROM sqlite_stat1;
          153  +} {t1 null 4}
          154  +do_test 3.3 {
          155  +  execsql { DELETE FROM sqlite_stat1 }
          156  +  do_then_apply_sql { ANALYZE }
          157  +  execsql { SELECT * FROM sqlite_stat1 } db2
          158  +} {t1 null 4}
          159  +do_test 3.4 {
          160  +  execsql { INSERT INTO t1 VALUES(5,5,5) }
          161  +  do_then_apply_sql { ANALYZE }
          162  +  execsql { SELECT * FROM sqlite_stat1 } db2
          163  +} {t1 null 5}
          164  +do_test 3.5 {
          165  +  do_then_apply_sql { DROP TABLE t1 }
          166  +  execsql { SELECT * FROM sqlite_stat1 } db2
          167  +} {}
          168  +
          169  +do_test 3.6.1 {
          170  +  execsql { 
          171  +    CREATE TABLE t1(a, b, c);
          172  +    CREATE TABLE t2(x, y, z);
          173  +    INSERT INTO t1 VALUES(1,1,1), (2,2,2), (3,3,3), (4,4,4), (5,5,5);
          174  +    INSERT INTO t2 SELECT * FROM t1;
          175  +    DELETE FROM sqlite_stat1;
          176  +  }
          177  +  sqlite3session S db main
          178  +  S attach sqlite_stat1
          179  +  execsql { ANALYZE }
          180  +} {}
          181  +do_changeset_test 3.6.2 S {
          182  +  {INSERT sqlite_stat1 0 XX. {} {t t2 b {} t 5}}
          183  +  {INSERT sqlite_stat1 0 XX. {} {t t1 b {} t 5}}
          184  +}
          185  +do_changeset_invert_test 3.6.3 S {
          186  +  {DELETE sqlite_stat1 0 XX. {t t2 b {} t 5} {}}
          187  +  {DELETE sqlite_stat1 0 XX. {t t1 b {} t 5} {}}
          188  +}
          189  +do_test 3.6.4 { S delete } {}
          190  +
          191  +proc sql_changeset_concat {args} {
          192  +  foreach sql $args {
          193  +    sqlite3session S db main
          194  +    S attach sqlite_stat1
          195  +    execsql $sql
          196  +    set change [S changeset]
          197  +    S delete
          198  +
          199  +    if {[info vars ret]!=""} {
          200  +      set ret [sqlite3changeset_concat $ret $change]
          201  +    } else {
          202  +      set ret $change
          203  +    }
          204  +  }
          205  +
          206  +  changeset_to_list $ret
          207  +}
          208  +
          209  +proc do_scc_test {tn args} {
          210  +  uplevel [list \
          211  +    do_test $tn [concat sql_changeset_concat [lrange $args 0 end-1]] \
          212  +    [list {*}[ lindex $args end ]]
          213  +  ]
          214  +}
          215  +
          216  +do_execsql_test 3.7.0 {
          217  +  DELETE FROM sqlite_stat1;
          218  +}
          219  +do_scc_test 3.7.1 {
          220  +  ANALYZE;
          221  +} {
          222  +  INSERT INTO t2 VALUES(6,6,6);
          223  +  ANALYZE;
          224  +} {
          225  +  {INSERT sqlite_stat1 0 XX. {} {t t1 b {} t 5}}
          226  +  {INSERT sqlite_stat1 0 XX. {} {t t2 b {} t 6}}
          227  +}
          228  +
          229  +#-------------------------------------------------------------------------
          230  +catch { db2 close }
          231  +reset_db
          232  +forcedelete test.db2
          233  +sqlite3 db2 test.db2
          234  +
          235  +do_test 4.1.0 {
          236  +  do_common_sql {
          237  +    CREATE TABLE t1(a, b);
          238  +    CREATE INDEX i1 ON t1(a);
          239  +    CREATE INDEX i2 ON t1(b);
          240  +    INSERT INTO t1 VALUES(1,1), (2,2);
          241  +    ANALYZE;
          242  +  }
          243  +  execsql { DELETE FROM sqlite_stat1 }
          244  +} {}
          245  +
          246  +do_test 4.1.1 {
          247  +  execsql { INSERT INTO t1 VALUES(3,3); }
          248  +  set C [changeset_from_sql {ANALYZE}]
          249  +  set ::c [list]
          250  +  proc xConflict {args} {
          251  +    lappend ::c $args
          252  +    return "OMIT"
          253  +  }
          254  +  sqlite3changeset_apply db2 $C xConflict
          255  +  set ::c
          256  +} [list {*}{
          257  +  {INSERT sqlite_stat1 CONFLICT {t t1 t i1 t {3 1}} {t t1 t i1 t {2 1}}}
          258  +  {INSERT sqlite_stat1 CONFLICT {t t1 t i2 t {3 1}} {t t1 t i2 t {2 1}}}
          259  +}]
          260  +
          261  +do_execsql_test -db db2 4.1.2 {
          262  +  SELECT * FROM sqlite_stat1 ORDER BY 1,2;
          263  +} {t1 i1 {2 1} t1 i2 {2 1}}
          264  +
          265  +do_test 4.1.3 {
          266  +  proc xConflict {args} {
          267  +    return "REPLACE"
          268  +  }
          269  +  sqlite3changeset_apply db2 $C xConflict
          270  +  execsql { SELECT * FROM sqlite_stat1 ORDER BY 1,2 } db2
          271  +} {t1 i1 {3 1} t1 i2 {3 1}}
          272  +
          273  +do_test 4.2.0 {
          274  +  do_common_sql { 
          275  +    DROP TABLE t1;
          276  +    CREATE TABLE t3(x,y);
          277  +    INSERT INTO t3 VALUES('a','a');
          278  +    INSERT INTO t3 VALUES('b','b');
          279  +    ANALYZE;
          280  +  }
          281  +  execsql { DELETE FROM sqlite_stat1 }
          282  +} {}
          283  +do_test 4.2.1 {
          284  +  execsql { INSERT INTO t3 VALUES('c','c'); }
          285  +  set C [changeset_from_sql {ANALYZE}]
          286  +  set ::c [list]
          287  +  proc xConflict {args} {
          288  +    lappend ::c $args
          289  +    return "OMIT"
          290  +  }
          291  +  sqlite3changeset_apply db2 $C xConflict
          292  +  set ::c
          293  +} [list {*}{
          294  +  {INSERT sqlite_stat1 CONFLICT {t t3 b {} t 3} {t t3 b {} t 2}}
          295  +}]
          296  +
          297  +db2 null null
          298  +do_execsql_test -db db2 4.2.2 {
          299  +  SELECT * FROM sqlite_stat1 ORDER BY 1,2;
          300  +} {t3 null 2}
          301  +
          302  +do_test 4.2.3 {
          303  +  proc xConflict {args} {
          304  +    return "REPLACE"
          305  +  }
          306  +  sqlite3changeset_apply db2 $C xConflict
          307  +  execsql { SELECT * FROM sqlite_stat1 ORDER BY 1,2 } db2
          308  +} {t3 null 3}
   123    309   
   124    310   finish_test
   125    311   

Changes to ext/session/sqlite3session.c.

    42     42     char *zDb;                      /* Name of database session is attached to */
    43     43     int bEnable;                    /* True if currently recording */
    44     44     int bIndirect;                  /* True if all changes are indirect */
    45     45     int bAutoAttach;                /* True to auto-attach tables */
    46     46     int rc;                         /* Non-zero if an error has occurred */
    47     47     void *pFilterCtx;               /* First argument to pass to xTableFilter */
    48     48     int (*xTableFilter)(void *pCtx, const char *zTab);
           49  +  sqlite3_value *pZeroBlob;       /* Value containing X'' */
    49     50     sqlite3_session *pNext;         /* Next session object on same db. */
    50     51     SessionTable *pTable;           /* List of attached tables */
    51     52     SessionHook hook;               /* APIs to grab new and old data with */
    52     53   };
    53     54   
    54     55   /*
    55     56   ** Instances of this structure are used to build strings or binary records.
................................................................................
   109    110   ** a subset of the initial values that the modified row contained at the
   110    111   ** start of the session. Or no initial values if the row was inserted.
   111    112   */
   112    113   struct SessionTable {
   113    114     SessionTable *pNext;
   114    115     char *zName;                    /* Local name of table */
   115    116     int nCol;                       /* Number of columns in table zName */
          117  +  int bStat1;                     /* True if this is sqlite_stat1 */
   116    118     const char **azCol;             /* Column names */
   117    119     u8 *abPK;                       /* Array of primary key flags */
   118    120     int nEntry;                     /* Total number of entries in hash table */
   119    121     int nChange;                    /* Size of apChange[] array */
   120    122     SessionChange **apChange;       /* Hash table buckets */
   121    123   };
   122    124   
................................................................................
   492    494             z = (const u8 *)sqlite3_value_blob(pVal);
   493    495           }
   494    496           n = sqlite3_value_bytes(pVal);
   495    497           if( !z && (eType!=SQLITE_BLOB || n>0) ) return SQLITE_NOMEM;
   496    498           h = sessionHashAppendBlob(h, n, z);
   497    499         }else{
   498    500           assert( eType==SQLITE_NULL );
          501  +        assert( pTab->bStat1==0 || i!=1 );
   499    502           *pbNullPK = 1;
   500    503         }
   501    504       }
   502    505     }
   503    506   
   504    507     *piHash = (h % pTab->nChange);
   505    508     return SQLITE_OK;
................................................................................
  1043   1046         int i;
  1044   1047         for(i=0; i<pTab->nCol; i++){
  1045   1048           if( abPK[i] ){
  1046   1049             pTab->abPK = abPK;
  1047   1050             break;
  1048   1051           }
  1049   1052         }
         1053  +      if( 0==sqlite3_stricmp("sqlite_stat1", pTab->zName) ){
         1054  +        pTab->bStat1 = 1;
         1055  +      }
  1050   1056       }
  1051   1057     }
  1052   1058     return (pSession->rc || pTab->abPK==0);
  1053   1059   }
         1060  +
         1061  +/*
         1062  +** Versions of the four methods in object SessionHook for use with the
         1063  +** sqlite_stat1 table. The purpose of this is to substitute a zero-length
         1064  +** blob each time a NULL value is read from the "idx" column of the
         1065  +** sqlite_stat1 table.
         1066  +*/
         1067  +typedef struct SessionStat1Ctx SessionStat1Ctx;
         1068  +struct SessionStat1Ctx {
         1069  +  SessionHook hook;
         1070  +  sqlite3_session *pSession;
         1071  +};
         1072  +static int sessionStat1Old(void *pCtx, int iCol, sqlite3_value **ppVal){
         1073  +  SessionStat1Ctx *p = (SessionStat1Ctx*)pCtx;
         1074  +  sqlite3_value *pVal = 0;
         1075  +  int rc = p->hook.xOld(p->hook.pCtx, iCol, &pVal);
         1076  +  if( rc==SQLITE_OK && iCol==1 && sqlite3_value_type(pVal)==SQLITE_NULL ){
         1077  +    pVal = p->pSession->pZeroBlob;
         1078  +  }
         1079  +  *ppVal = pVal;
         1080  +  return rc;
         1081  +}
         1082  +static int sessionStat1New(void *pCtx, int iCol, sqlite3_value **ppVal){
         1083  +  SessionStat1Ctx *p = (SessionStat1Ctx*)pCtx;
         1084  +  sqlite3_value *pVal = 0;
         1085  +  int rc = p->hook.xNew(p->hook.pCtx, iCol, &pVal);
         1086  +  if( rc==SQLITE_OK && iCol==1 && sqlite3_value_type(pVal)==SQLITE_NULL ){
         1087  +    pVal = p->pSession->pZeroBlob;
         1088  +  }
         1089  +  *ppVal = pVal;
         1090  +  return rc;
         1091  +}
         1092  +static int sessionStat1Count(void *pCtx){
         1093  +  SessionStat1Ctx *p = (SessionStat1Ctx*)pCtx;
         1094  +  return p->hook.xCount(p->hook.pCtx);
         1095  +}
         1096  +static int sessionStat1Depth(void *pCtx){
         1097  +  SessionStat1Ctx *p = (SessionStat1Ctx*)pCtx;
         1098  +  return p->hook.xDepth(p->hook.pCtx);
         1099  +}
         1100  +
  1054   1101   
  1055   1102   /*
  1056   1103   ** This function is only called from with a pre-update-hook reporting a 
  1057   1104   ** change on table pTab (attached to session pSession). The type of change
  1058   1105   ** (UPDATE, INSERT, DELETE) is specified by the first argument.
  1059   1106   **
  1060   1107   ** Unless one is already present or an error occurs, an entry is added
................................................................................
  1064   1111     int op,                         /* One of SQLITE_UPDATE, INSERT, DELETE */
  1065   1112     sqlite3_session *pSession,      /* Session object pTab is attached to */
  1066   1113     SessionTable *pTab              /* Table that change applies to */
  1067   1114   ){
  1068   1115     int iHash; 
  1069   1116     int bNull = 0; 
  1070   1117     int rc = SQLITE_OK;
         1118  +  SessionStat1Ctx stat1;
  1071   1119   
  1072   1120     if( pSession->rc ) return;
  1073   1121   
  1074   1122     /* Load table details if required */
  1075   1123     if( sessionInitTable(pSession, pTab) ) return;
  1076   1124   
  1077   1125     /* Check the number of columns in this xPreUpdate call matches the 
................................................................................
  1082   1130     }
  1083   1131   
  1084   1132     /* Grow the hash table if required */
  1085   1133     if( sessionGrowHash(0, pTab) ){
  1086   1134       pSession->rc = SQLITE_NOMEM;
  1087   1135       return;
  1088   1136     }
         1137  +
         1138  +  if( pTab->bStat1 ){
         1139  +    stat1.hook = pSession->hook;
         1140  +    stat1.pSession = pSession;
         1141  +    pSession->hook.pCtx = (void*)&stat1;
         1142  +    pSession->hook.xNew = sessionStat1New;
         1143  +    pSession->hook.xOld = sessionStat1Old;
         1144  +    pSession->hook.xCount = sessionStat1Count;
         1145  +    pSession->hook.xDepth = sessionStat1Depth;
         1146  +    if( pSession->pZeroBlob==0 ){
         1147  +      sqlite3_value *p = sqlite3ValueNew(0);
         1148  +      if( p==0 ){
         1149  +        rc = SQLITE_NOMEM;
         1150  +        goto error_out;
         1151  +      }
         1152  +      sqlite3ValueSetStr(p, 0, "", 0, SQLITE_STATIC);
         1153  +      pSession->pZeroBlob = p;
         1154  +    }
         1155  +  }
  1089   1156   
  1090   1157     /* Calculate the hash-key for this change. If the primary key of the row
  1091   1158     ** includes a NULL value, exit early. Such changes are ignored by the
  1092   1159     ** session module. */
  1093   1160     rc = sessionPreupdateHash(pSession, pTab, op==SQLITE_INSERT, &iHash, &bNull);
  1094   1161     if( rc!=SQLITE_OK ) goto error_out;
  1095   1162   
................................................................................
  1172   1239           pC->bIndirect = 0;
  1173   1240         }
  1174   1241       }
  1175   1242     }
  1176   1243   
  1177   1244     /* If an error has occurred, mark the session object as failed. */
  1178   1245    error_out:
         1246  +  if( pTab->bStat1 ){
         1247  +    pSession->hook = stat1.hook;
         1248  +  }
  1179   1249     if( rc!=SQLITE_OK ){
  1180   1250       pSession->rc = rc;
  1181   1251     }
  1182   1252   }
  1183   1253   
  1184   1254   static int sessionFindTable(
  1185   1255     sqlite3_session *pSession, 
................................................................................
  1633   1703       if( (*pp)==pSession ){
  1634   1704         *pp = (*pp)->pNext;
  1635   1705         if( pHead ) sqlite3_preupdate_hook(db, xPreUpdate, (void*)pHead);
  1636   1706         break;
  1637   1707       }
  1638   1708     }
  1639   1709     sqlite3_mutex_leave(sqlite3_db_mutex(db));
         1710  +  sqlite3ValueFree(pSession->pZeroBlob);
  1640   1711   
  1641   1712     /* Delete all attached table objects. And the contents of their 
  1642   1713     ** associated hash-tables. */
  1643   1714     sessionDeleteTable(pSession->pTable);
  1644   1715   
  1645   1716     /* Free the session object itself. */
  1646   1717     sqlite3_free(pSession);
................................................................................
  2100   2171     const char *zTab,               /* Table name */
  2101   2172     int nCol,                       /* Number of columns in table */
  2102   2173     const char **azCol,             /* Names of table columns */
  2103   2174     u8 *abPK,                       /* PRIMARY KEY  array */
  2104   2175     sqlite3_stmt **ppStmt           /* OUT: Prepared SELECT statement */
  2105   2176   ){
  2106   2177     int rc = SQLITE_OK;
  2107         -  int i;
  2108         -  const char *zSep = "";
  2109         -  SessionBuffer buf = {0, 0, 0};
  2110         -
  2111         -  sessionAppendStr(&buf, "SELECT * FROM ", &rc);
  2112         -  sessionAppendIdent(&buf, zDb, &rc);
  2113         -  sessionAppendStr(&buf, ".", &rc);
  2114         -  sessionAppendIdent(&buf, zTab, &rc);
  2115         -  sessionAppendStr(&buf, " WHERE ", &rc);
  2116         -  for(i=0; i<nCol; i++){
  2117         -    if( abPK[i] ){
  2118         -      sessionAppendStr(&buf, zSep, &rc);
  2119         -      sessionAppendIdent(&buf, azCol[i], &rc);
  2120         -      sessionAppendStr(&buf, " = ?", &rc);
  2121         -      sessionAppendInteger(&buf, i+1, &rc);
  2122         -      zSep = " AND ";
  2123         -    }
  2124         -  }
  2125         -  if( rc==SQLITE_OK ){
  2126         -    rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, ppStmt, 0);
  2127         -  }
  2128         -  sqlite3_free(buf.aBuf);
         2178  +  char *zSql = 0;
         2179  +  int nSql = -1;
         2180  +
         2181  +  if( 0==sqlite3_stricmp("sqlite_stat1", zTab) ){
         2182  +    zSql = sqlite3_mprintf(
         2183  +        "SELECT tbl, ?2, stat FROM %Q.sqlite_stat1 WHERE tbl IS ?1 AND "
         2184  +        "idx IS (CASE WHEN ?2=X'' THEN NULL ELSE ?2 END)", zDb
         2185  +    );
         2186  +  }else{
         2187  +    int i;
         2188  +    const char *zSep = "";
         2189  +    SessionBuffer buf = {0, 0, 0};
         2190  +
         2191  +    sessionAppendStr(&buf, "SELECT * FROM ", &rc);
         2192  +    sessionAppendIdent(&buf, zDb, &rc);
         2193  +    sessionAppendStr(&buf, ".", &rc);
         2194  +    sessionAppendIdent(&buf, zTab, &rc);
         2195  +    sessionAppendStr(&buf, " WHERE ", &rc);
         2196  +    for(i=0; i<nCol; i++){
         2197  +      if( abPK[i] ){
         2198  +        sessionAppendStr(&buf, zSep, &rc);
         2199  +        sessionAppendIdent(&buf, azCol[i], &rc);
         2200  +        sessionAppendStr(&buf, " IS ?", &rc);
         2201  +        sessionAppendInteger(&buf, i+1, &rc);
         2202  +        zSep = " AND ";
         2203  +      }
         2204  +    }
         2205  +    zSql = (char*)buf.aBuf;
         2206  +    nSql = buf.nBuf;
         2207  +  }
         2208  +
         2209  +  if( rc==SQLITE_OK ){
         2210  +    rc = sqlite3_prepare_v2(db, zSql, nSql, ppStmt, 0);
         2211  +  }
         2212  +  sqlite3_free(zSql);
  2129   2213     return rc;
  2130   2214   }
  2131   2215   
  2132   2216   /*
  2133   2217   ** Bind the PRIMARY KEY values from the change passed in argument pChange
  2134   2218   ** to the SELECT statement passed as the first argument. The SELECT statement
  2135   2219   ** is as prepared by function sessionSelectStmt().
................................................................................
  3290   3374     sqlite3_stmt *pDelete;          /* DELETE statement */
  3291   3375     sqlite3_stmt *pUpdate;          /* UPDATE statement */
  3292   3376     sqlite3_stmt *pInsert;          /* INSERT statement */
  3293   3377     sqlite3_stmt *pSelect;          /* SELECT statement */
  3294   3378     int nCol;                       /* Size of azCol[] and abPK[] arrays */
  3295   3379     const char **azCol;             /* Array of column names */
  3296   3380     u8 *abPK;                       /* Boolean array - true if column is in PK */
  3297         -
         3381  +  int bStat1;                     /* True if table is sqlite_stat1 */
  3298   3382     int bDeferConstraints;          /* True to defer constraints */
  3299   3383     SessionBuffer constraints;      /* Deferred constraints are stored here */
  3300   3384   };
  3301   3385   
  3302   3386   /*
  3303   3387   ** Formulate a statement to DELETE a row from database db. Assuming a table
  3304   3388   ** structure like this:
................................................................................
  3459   3543     if( rc==SQLITE_OK ){
  3460   3544       rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pUpdate, 0);
  3461   3545     }
  3462   3546     sqlite3_free(buf.aBuf);
  3463   3547   
  3464   3548     return rc;
  3465   3549   }
         3550  +
  3466   3551   
  3467   3552   /*
  3468   3553   ** Formulate and prepare an SQL statement to query table zTab by primary
  3469   3554   ** key. Assuming the following table structure:
  3470   3555   **
  3471   3556   **     CREATE TABLE x(a, b, c, d, PRIMARY KEY(a, c));
  3472   3557   **
................................................................................
  3520   3605   
  3521   3606     if( rc==SQLITE_OK ){
  3522   3607       rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pInsert, 0);
  3523   3608     }
  3524   3609     sqlite3_free(buf.aBuf);
  3525   3610     return rc;
  3526   3611   }
         3612  +
         3613  +static int sessionPrepare(sqlite3 *db, sqlite3_stmt **pp, const char *zSql){
         3614  +  return sqlite3_prepare_v2(db, zSql, -1, pp, 0);
         3615  +}
         3616  +
         3617  +/*
         3618  +** Prepare statements for applying changes to the sqlite_stat1 table.
         3619  +** These are similar to those created by sessionSelectRow(),
         3620  +** sessionInsertRow(), sessionUpdateRow() and sessionDeleteRow() for 
         3621  +** other tables.
         3622  +*/
         3623  +static int sessionStat1Sql(sqlite3 *db, SessionApplyCtx *p){
         3624  +  int rc = sessionSelectRow(db, "sqlite_stat1", p);
         3625  +  if( rc==SQLITE_OK ){
         3626  +    rc = sessionPrepare(db, &p->pInsert,
         3627  +        "INSERT INTO main.sqlite_stat1 VALUES(?1, "
         3628  +        "CASE WHEN length(?2)=0 AND typeof(?2)='blob' THEN NULL ELSE ?2 END, "
         3629  +        "?3)"
         3630  +    );
         3631  +  }
         3632  +  if( rc==SQLITE_OK ){
         3633  +    rc = sessionPrepare(db, &p->pUpdate,
         3634  +        "UPDATE main.sqlite_stat1 SET "
         3635  +        "tbl = CASE WHEN ?2 THEN ?3 ELSE tbl END, "
         3636  +        "idx = CASE WHEN ?5 THEN ?6 ELSE idx END, "
         3637  +        "stat = CASE WHEN ?8 THEN ?9 ELSE stat END  "
         3638  +        "WHERE tbl=?1 AND idx IS "
         3639  +        "CASE WHEN length(?4)=0 AND typeof(?4)='blob' THEN NULL ELSE ?4 END "
         3640  +        "AND (?10 OR ?8=0 OR stat IS ?7)"
         3641  +    );
         3642  +  }
         3643  +  if( rc==SQLITE_OK ){
         3644  +    rc = sessionPrepare(db, &p->pDelete,
         3645  +        "DELETE FROM main.sqlite_stat1 WHERE tbl=?1 AND idx IS "
         3646  +        "CASE WHEN length(?2)=0 AND typeof(?2)='blob' THEN NULL ELSE ?2 END "
         3647  +        "AND (?4 OR stat IS ?3)"
         3648  +    );
         3649  +  }
         3650  +  assert( rc==SQLITE_OK );
         3651  +  return rc;
         3652  +}
  3527   3653   
  3528   3654   /*
  3529   3655   ** A wrapper around sqlite3_bind_value() that detects an extra problem. 
  3530   3656   ** See comments in the body of this function for details.
  3531   3657   */
  3532   3658   static int sessionBindValue(
  3533   3659     sqlite3_stmt *pStmt,            /* Statement to bind value to */
................................................................................
  3851   3977         rc = sessionConflictHandler(
  3852   3978             SQLITE_CHANGESET_CONFLICT, p, pIter, xConflict, pCtx, 0
  3853   3979         );
  3854   3980       }
  3855   3981   
  3856   3982     }else{
  3857   3983       assert( op==SQLITE_INSERT );
  3858         -    rc = sessionBindRow(pIter, sqlite3changeset_new, nCol, 0, p->pInsert);
  3859         -    if( rc!=SQLITE_OK ) return rc;
         3984  +    if( p->bStat1 ){
         3985  +      /* Check if there is a conflicting row. For sqlite_stat1, this needs
         3986  +      ** to be done using a SELECT, as there is no PRIMARY KEY in the 
         3987  +      ** database schema to throw an exception if a duplicate is inserted.  */
         3988  +      rc = sessionSeekToRow(p->db, pIter, p->abPK, p->pSelect);
         3989  +      if( rc==SQLITE_ROW ){
         3990  +        rc = SQLITE_CONSTRAINT;
         3991  +        sqlite3_reset(p->pSelect);
         3992  +      }
         3993  +    }
  3860   3994   
  3861         -    sqlite3_step(p->pInsert);
  3862         -    rc = sqlite3_reset(p->pInsert);
         3995  +    if( rc==SQLITE_OK ){
         3996  +      rc = sessionBindRow(pIter, sqlite3changeset_new, nCol, 0, p->pInsert);
         3997  +      if( rc!=SQLITE_OK ) return rc;
         3998  +
         3999  +      sqlite3_step(p->pInsert);
         4000  +      rc = sqlite3_reset(p->pInsert);
         4001  +    }
         4002  +
  3863   4003       if( (rc&0xff)==SQLITE_CONSTRAINT ){
  3864   4004         rc = sessionConflictHandler(
  3865   4005             SQLITE_CHANGESET_CONFLICT, p, pIter, xConflict, pCtx, pbReplace
  3866   4006         );
  3867   4007       }
  3868   4008     }
  3869   4009   
................................................................................
  4088   4228             schemaMismatch = 1;
  4089   4229             sqlite3_log(SQLITE_SCHEMA, "sqlite3changeset_apply(): "
  4090   4230                 "primary key mismatch for table %s", zTab
  4091   4231             );
  4092   4232           }
  4093   4233           else{
  4094   4234             sApply.nCol = nCol;
  4095         -          if((rc = sessionSelectRow(db, zTab, &sApply))
  4096         -          || (rc = sessionUpdateRow(db, zTab, &sApply))
  4097         -          || (rc = sessionDeleteRow(db, zTab, &sApply))
  4098         -          || (rc = sessionInsertRow(db, zTab, &sApply))
  4099         -          ){
  4100         -            break;
         4235  +          if( 0==sqlite3_stricmp(zTab, "sqlite_stat1") ){
         4236  +            if( (rc = sessionStat1Sql(db, &sApply) ) ){
         4237  +              break;
         4238  +            }
         4239  +            sApply.bStat1 = 1;
         4240  +          }else{
         4241  +            if((rc = sessionSelectRow(db, zTab, &sApply))
         4242  +                || (rc = sessionUpdateRow(db, zTab, &sApply))
         4243  +                || (rc = sessionDeleteRow(db, zTab, &sApply))
         4244  +                || (rc = sessionInsertRow(db, zTab, &sApply))
         4245  +              ){
         4246  +              break;
         4247  +            }
         4248  +            sApply.bStat1 = 0;
  4101   4249             }
  4102   4250           }
  4103   4251           nTab = sqlite3Strlen30(zTab);
  4104   4252         }
  4105   4253       }
  4106   4254   
  4107   4255       /* If there is a schema mismatch on the current table, proceed to the

Changes to ext/session/sqlite3session.h.

   143    143   ** no changes will be recorded in either of these scenarios.
   144    144   **
   145    145   ** Changes are not recorded for individual rows that have NULL values stored
   146    146   ** in one or more of their PRIMARY KEY columns.
   147    147   **
   148    148   ** SQLITE_OK is returned if the call completes without error. Or, if an error 
   149    149   ** occurs, an SQLite error code (e.g. SQLITE_NOMEM) is returned.
          150  +**
          151  +** <h3>Special sqlite_stat1 Handling</h3>
          152  +**
          153  +** As of SQLite version 3.22.0, the "sqlite_stat1" table is an exception to 
          154  +** some of the rules above. In SQLite, the schema of sqlite_stat1 is:
          155  +**  <pre>
          156  +**  &nbsp;     CREATE TABLE sqlite_stat1(tbl,idx,stat)  
          157  +**  </pre>
          158  +**
          159  +** Even though sqlite_stat1 does not have a PRIMARY KEY, changes are 
          160  +** recorded for it as if the PRIMARY KEY is (tbl,idx). Additionally, changes 
          161  +** are recorded for rows for which (idx IS NULL) is true. However, for such
          162  +** rows a zero-length blob (SQL value X'') is stored in the changeset or
          163  +** patchset instead of a NULL value. This allows such changesets to be
          164  +** manipulated by legacy implementations of sqlite3changeset_invert(),
          165  +** concat() and similar.
          166  +**
          167  +** The sqlite3changeset_apply() function automatically converts the 
          168  +** zero-length blob back to a NULL value when updating the sqlite_stat1
          169  +** table. However, if the application calls sqlite3changeset_new(),
          170  +** sqlite3changeset_old() or sqlite3changeset_conflict on a changeset 
          171  +** iterator directly (including on a changeset iterator passed to a
          172  +** conflict-handler callback) then the X'' value is returned. The application
          173  +** must translate X'' to NULL itself if required.
          174  +**
          175  +** Legacy (older than 3.22.0) versions of the sessions module cannot capture
          176  +** changes made to the sqlite_stat1 table. Legacy versions of the
          177  +** sqlite3changeset_apply() function silently ignore any modifications to the
          178  +** sqlite_stat1 table that are part of a changeset or patchset.
   150    179   */
   151    180   int sqlite3session_attach(
   152    181     sqlite3_session *pSession,      /* Session object */
   153    182     const char *zTab                /* Table name */
   154    183   );
   155    184   
   156    185   /*

Changes to src/analyze.c.

  1305   1305       jZeroRows = sqlite3VdbeAddOp1(v, OP_IfNot, regStat1); VdbeCoverage(v);
  1306   1306       sqlite3VdbeAddOp2(v, OP_Null, 0, regIdxname);
  1307   1307       assert( "BBB"[0]==SQLITE_AFF_TEXT );
  1308   1308       sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 3, regTemp, "BBB", 0);
  1309   1309       sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur, regNewRowid);
  1310   1310       sqlite3VdbeAddOp3(v, OP_Insert, iStatCur, regTemp, regNewRowid);
  1311   1311       sqlite3VdbeChangeP5(v, OPFLAG_APPEND);
         1312  +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
         1313  +    sqlite3VdbeChangeP4(v, -1, (char*)pStat1, P4_TABLE);
         1314  +#endif
  1312   1315       sqlite3VdbeJumpHere(v, jZeroRows);
  1313   1316     }
  1314   1317   }
  1315   1318   
  1316   1319   
  1317   1320   /*
  1318   1321   ** Generate code that will cause the most recent index analysis to