/ Check-in [089d7cec]
Login

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

Overview
Comment:Simplify the sessions preupdate-hook logic for transforming NULL to X'' for column sqlite_stat1.idx.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | sessions-stat1
Files: files | file ages | folders
SHA3-256: 089d7cecaaa47db58320b216a111a5e56123d022008be6c81bc0746148bbdb58
User & Date: dan 2018-01-18 15:06:23
Context
2018-01-18
16:42
Fix sessions module conflict handling for the sqlite_stat1 table. check-in: f05ee74e user: dan tags: sessions-stat1
15:06
Simplify the sessions preupdate-hook logic for transforming NULL to X'' for column sqlite_stat1.idx. check-in: 089d7cec user: dan tags: sessions-stat1
2018-01-17
20:57
Fix a problem in the sessions module with logging sqlite_stat1 rows for which (idx IS NULL) is true. check-in: 25bf734b user: dan tags: sessions-stat1
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

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.
................................................................................
   469    470           rc = pSession->hook.xNew(pSession->hook.pCtx, i, &pVal);
   470    471         }else{
   471    472           rc = pSession->hook.xOld(pSession->hook.pCtx, i, &pVal);
   472    473         }
   473    474         if( rc!=SQLITE_OK ) return rc;
   474    475   
   475    476         eType = sqlite3_value_type(pVal);
   476         -      if( pTab->bStat1 && eType==SQLITE_NULL ){
   477         -        h = sessionHashAppendType(h, SQLITE_BLOB);
   478         -      }else{
   479         -        h = sessionHashAppendType(h, eType);
   480         -        if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
   481         -          i64 iVal;
   482         -          if( eType==SQLITE_INTEGER ){
   483         -            iVal = sqlite3_value_int64(pVal);
   484         -          }else{
   485         -            double rVal = sqlite3_value_double(pVal);
   486         -            assert( sizeof(iVal)==8 && sizeof(rVal)==8 );
   487         -            memcpy(&iVal, &rVal, 8);
   488         -          }
   489         -          h = sessionHashAppendI64(h, iVal);
   490         -        }else if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){
   491         -          const u8 *z;
   492         -          int n;
   493         -          if( eType==SQLITE_TEXT ){
   494         -            z = (const u8 *)sqlite3_value_text(pVal);
   495         -          }else{
   496         -            z = (const u8 *)sqlite3_value_blob(pVal);
   497         -          }
   498         -          n = sqlite3_value_bytes(pVal);
   499         -          if( !z && (eType!=SQLITE_BLOB || n>0) ) return SQLITE_NOMEM;
   500         -          h = sessionHashAppendBlob(h, n, z);
          477  +      h = sessionHashAppendType(h, eType);
          478  +      if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
          479  +        i64 iVal;
          480  +        if( eType==SQLITE_INTEGER ){
          481  +          iVal = sqlite3_value_int64(pVal);
          482  +        }else{
          483  +          double rVal = sqlite3_value_double(pVal);
          484  +          assert( sizeof(iVal)==8 && sizeof(rVal)==8 );
          485  +          memcpy(&iVal, &rVal, 8);
          486  +        }
          487  +        h = sessionHashAppendI64(h, iVal);
          488  +      }else if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){
          489  +        const u8 *z;
          490  +        int n;
          491  +        if( eType==SQLITE_TEXT ){
          492  +          z = (const u8 *)sqlite3_value_text(pVal);
   501    493           }else{
   502         -          assert( eType==SQLITE_NULL );
   503         -          *pbNullPK = 1;
          494  +          z = (const u8 *)sqlite3_value_blob(pVal);
   504    495           }
          496  +        n = sqlite3_value_bytes(pVal);
          497  +        if( !z && (eType!=SQLITE_BLOB || n>0) ) return SQLITE_NOMEM;
          498  +        h = sessionHashAppendBlob(h, n, z);
          499  +      }else{
          500  +        assert( eType==SQLITE_NULL );
          501  +        assert( pTab->bStat1==0 || i!=1 );
          502  +        *pbNullPK = 1;
   505    503         }
   506    504       }
   507    505     }
   508    506   
   509    507     *piHash = (h % pTab->nChange);
   510    508     return SQLITE_OK;
   511    509   }
................................................................................
   551    549       /* It is not possible for eType to be SQLITE_NULL here. The session 
   552    550       ** module does not record changes for rows with NULL values stored in
   553    551       ** primary key columns. */
   554    552       assert( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT 
   555    553            || eType==SQLITE_TEXT || eType==SQLITE_BLOB 
   556    554            || eType==SQLITE_NULL || eType==0 
   557    555       );
   558         -    assert( !isPK || (eType!=0 && (pTab->bStat1 || eType!=SQLITE_NULL)) );
          556  +    assert( !isPK || (eType!=0 && eType!=SQLITE_NULL) );
   559    557   
   560    558       if( isPK ){
   561    559         a++;
   562    560         h = sessionHashAppendType(h, eType);
   563    561         if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
   564    562           h = sessionHashAppendI64(h, sessionGetI64(a));
   565    563           a += 8;
   566         -      }else if( eType!=SQLITE_NULL ){
          564  +      }else{
   567    565           int n; 
   568    566           a += sessionVarintGet(a, &n);
   569    567           h = sessionHashAppendBlob(h, n, a);
   570    568           a += n;
   571    569         }
   572    570       }else{
   573    571         a += sessionSerialLen(a);
................................................................................
   795    793     for(iCol=0; iCol<pTab->nCol; iCol++){
   796    794       if( !pTab->abPK[iCol] ){
   797    795         a += sessionSerialLen(a);
   798    796       }else{
   799    797         sqlite3_value *pVal;        /* Value returned by preupdate_new/old */
   800    798         int rc;                     /* Error code from preupdate_new/old */
   801    799         int eType = *a++;           /* Type of value from change record */
   802         -      int eValType;
   803    800   
   804    801         /* The following calls to preupdate_new() and preupdate_old() can not
   805    802         ** fail. This is because they cache their return values, and by the
   806    803         ** time control flows to here they have already been called once from
   807    804         ** within sessionPreupdateHash(). The first two asserts below verify
   808    805         ** this (that the method has already been called). */
   809    806         if( op==SQLITE_INSERT ){
................................................................................
   810    807           /* assert( db->pPreUpdate->pNewUnpacked || db->pPreUpdate->aNew ); */
   811    808           rc = pSession->hook.xNew(pSession->hook.pCtx, iCol, &pVal);
   812    809         }else{
   813    810           /* assert( db->pPreUpdate->pUnpacked ); */
   814    811           rc = pSession->hook.xOld(pSession->hook.pCtx, iCol, &pVal);
   815    812         }
   816    813         assert( rc==SQLITE_OK );
   817         -      eValType = sqlite3_value_type(pVal);
   818         -      if( eType==SQLITE_BLOB && eValType==SQLITE_NULL && pTab->bStat1 ){
   819         -        int n;
   820         -        a += sessionVarintGet(a, &n);
   821         -        if( n!=0 ) return 0;
   822         -        continue;
   823         -      }
   824         -      if( eValType!=eType ) return 0;
          814  +      if( sqlite3_value_type(pVal)!=eType ) return 0;
   825    815   
   826    816         /* A SessionChange object never has a NULL value in a PK column */
   827    817         assert( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT
   828    818              || eType==SQLITE_BLOB    || eType==SQLITE_TEXT
   829    819         );
   830    820   
   831    821         if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
................................................................................
  1063   1053         if( 0==sqlite3_stricmp("sqlite_stat1", pTab->zName) ){
  1064   1054           pTab->bStat1 = 1;
  1065   1055         }
  1066   1056       }
  1067   1057     }
  1068   1058     return (pSession->rc || pTab->abPK==0);
  1069   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  +
  1070   1101   
  1071   1102   /*
  1072   1103   ** This function is only called from with a pre-update-hook reporting a 
  1073   1104   ** change on table pTab (attached to session pSession). The type of change
  1074   1105   ** (UPDATE, INSERT, DELETE) is specified by the first argument.
  1075   1106   **
  1076   1107   ** Unless one is already present or an error occurs, an entry is added
................................................................................
  1080   1111     int op,                         /* One of SQLITE_UPDATE, INSERT, DELETE */
  1081   1112     sqlite3_session *pSession,      /* Session object pTab is attached to */
  1082   1113     SessionTable *pTab              /* Table that change applies to */
  1083   1114   ){
  1084   1115     int iHash; 
  1085   1116     int bNull = 0; 
  1086   1117     int rc = SQLITE_OK;
         1118  +  SessionStat1Ctx stat1;
  1087   1119   
  1088   1120     if( pSession->rc ) return;
  1089   1121   
  1090   1122     /* Load table details if required */
  1091   1123     if( sessionInitTable(pSession, pTab) ) return;
  1092   1124   
  1093   1125     /* Check the number of columns in this xPreUpdate call matches the 
................................................................................
  1098   1130     }
  1099   1131   
  1100   1132     /* Grow the hash table if required */
  1101   1133     if( sessionGrowHash(0, pTab) ){
  1102   1134       pSession->rc = SQLITE_NOMEM;
  1103   1135       return;
  1104   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  +  }
  1105   1156   
  1106   1157     /* Calculate the hash-key for this change. If the primary key of the row
  1107   1158     ** includes a NULL value, exit early. Such changes are ignored by the
  1108   1159     ** session module. */
  1109   1160     rc = sessionPreupdateHash(pSession, pTab, op==SQLITE_INSERT, &iHash, &bNull);
  1110   1161     if( rc!=SQLITE_OK ) goto error_out;
  1111   1162   
  1112         -  if( bNull==0 || pTab->bStat1 ){
         1163  +  if( bNull==0 ){
  1113   1164       /* Search the hash table for an existing record for this row. */
  1114   1165       SessionChange *pC;
  1115   1166       for(pC=pTab->apChange[iHash]; pC; pC=pC->pNext){
  1116   1167         if( sessionPreupdateEqual(pSession, pTab, pC, op) ) break;
  1117   1168       }
  1118   1169   
  1119   1170       if( pC==0 ){
................................................................................
  1140   1191           }
  1141   1192   
  1142   1193           /* This may fail if SQLite value p contains a utf-16 string that must
  1143   1194           ** be converted to utf-8 and an OOM error occurs while doing so. */
  1144   1195           rc = sessionSerializeValue(0, p, &nByte);
  1145   1196           if( rc!=SQLITE_OK ) goto error_out;
  1146   1197         }
  1147         -      if( pTab->bStat1 ) nByte += 30;
  1148   1198     
  1149   1199         /* Allocate the change object */
  1150   1200         pChange = (SessionChange *)sqlite3_malloc(nByte);
  1151   1201         if( !pChange ){
  1152   1202           rc = SQLITE_NOMEM;
  1153   1203           goto error_out;
  1154   1204         }else{
................................................................................
  1164   1214         for(i=0; i<pTab->nCol; i++){
  1165   1215           sqlite3_value *p = 0;
  1166   1216           if( op!=SQLITE_INSERT ){
  1167   1217             pSession->hook.xOld(pSession->hook.pCtx, i, &p);
  1168   1218           }else if( pTab->abPK[i] ){
  1169   1219             pSession->hook.xNew(pSession->hook.pCtx, i, &p);
  1170   1220           }
  1171         -        if( p && pTab->bStat1 && sqlite3_value_type(p)==SQLITE_NULL ){
  1172         -          pChange->aRecord[nByte++] = SQLITE_BLOB;
  1173         -          nByte += sessionVarintPut(&pChange->aRecord[nByte], 0);
  1174         -        }else{
  1175         -          sessionSerializeValue(&pChange->aRecord[nByte], p, &nByte);
  1176         -        }
         1221  +        sessionSerializeValue(&pChange->aRecord[nByte], p, &nByte);
  1177   1222         }
  1178   1223   
  1179   1224         /* Add the change to the hash-table */
  1180   1225         if( pSession->bIndirect || pSession->hook.xDepth(pSession->hook.pCtx) ){
  1181   1226           pChange->bIndirect = 1;
  1182   1227         }
  1183   1228         pChange->nRecord = nByte;
................................................................................
  1194   1239           pC->bIndirect = 0;
  1195   1240         }
  1196   1241       }
  1197   1242     }
  1198   1243   
  1199   1244     /* If an error has occurred, mark the session object as failed. */
  1200   1245    error_out:
         1246  +  if( pTab->bStat1 ){
         1247  +    pSession->hook = stat1.hook;
         1248  +  }
  1201   1249     if( rc!=SQLITE_OK ){
  1202   1250       pSession->rc = rc;
  1203   1251     }
  1204   1252   }
  1205   1253   
  1206   1254   static int sessionFindTable(
  1207   1255     sqlite3_session *pSession, 
................................................................................
  1655   1703       if( (*pp)==pSession ){
  1656   1704         *pp = (*pp)->pNext;
  1657   1705         if( pHead ) sqlite3_preupdate_hook(db, xPreUpdate, (void*)pHead);
  1658   1706         break;
  1659   1707       }
  1660   1708     }
  1661   1709     sqlite3_mutex_leave(sqlite3_db_mutex(db));
         1710  +  sqlite3ValueFree(pSession->pZeroBlob);
  1662   1711   
  1663   1712     /* Delete all attached table objects. And the contents of their 
  1664   1713     ** associated hash-tables. */
  1665   1714     sessionDeleteTable(pSession->pTable);
  1666   1715   
  1667   1716     /* Free the session object itself. */
  1668   1717     sqlite3_free(pSession);
................................................................................
  2184   2233   
  2185   2234     for(i=0; i<nCol && rc==SQLITE_OK; i++){
  2186   2235       int eType = *a++;
  2187   2236   
  2188   2237       switch( eType ){
  2189   2238         case 0:
  2190   2239         case SQLITE_NULL:
  2191         -        /* assert( abPK[i]==0 ); */
         2240  +        assert( abPK[i]==0 );
  2192   2241           break;
  2193   2242   
  2194   2243         case SQLITE_INTEGER: {
  2195   2244           if( abPK[i] ){
  2196   2245             i64 iVal = sessionGetI64(a);
  2197   2246             rc = sqlite3_bind_int64(pSelect, i+1, iVal);
  2198   2247           }