Browse Source

Update sqlite3

mingodad 4 years ago
parent
commit
85c3013da7
2 changed files with 323 additions and 36 deletions
  1. 265 35
      SquiLu-ext/sqlite3.c
  2. 58 1
      SquiLu-ext/sqlite3.h

+ 265 - 35
SquiLu-ext/sqlite3.c

@@ -1188,7 +1188,7 @@ extern "C" {
 */
 #define SQLITE_VERSION        "3.36.0"
 #define SQLITE_VERSION_NUMBER 3036000
-#define SQLITE_SOURCE_ID      "2021-04-20 22:48:25 ca70c8ac72c9fe3b92f8e63151229bebdccc769c5c4d603ad4f466dc4bcdalt1"
+#define SQLITE_SOURCE_ID      "2021-04-23 00:59:38 fac12115a994a1b4347586e68faf38895ee9cb588eaa84c6f71cf9afd4c4alt1"
 
 /*
 ** CAPI3REF: Run-Time Library Version Numbers
@@ -10676,6 +10676,15 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*);
 ** triggers; or 2 for changes resulting from triggers called by top-level
 ** triggers; and so forth.
 **
+** When the [sqlite3_blob_write()] API is used to update a blob column,
+** the pre-update hook is invoked with SQLITE_DELETE. This is because the
+** in this case the new values are not available. In this case, when a
+** callback made with op==SQLITE_DELETE is actuall a write using the
+** sqlite3_blob_write() API, the [sqlite3_preupdate_blobwrite()] returns
+** the index of the column being written. In other cases, where the
+** pre-update hook is being invoked for some other reason, including a
+** regular DELETE, sqlite3_preupdate_blobwrite() returns -1.
+**
 ** See also:  [sqlite3_update_hook()]
 */
 #if defined(SQLITE_ENABLE_PREUPDATE_HOOK)
@@ -10696,6 +10705,7 @@ SQLITE_API int sqlite3_preupdate_old(sqlite3 *, int, sqlite3_value **);
 SQLITE_API int sqlite3_preupdate_count(sqlite3 *);
 SQLITE_API int sqlite3_preupdate_depth(sqlite3 *);
 SQLITE_API int sqlite3_preupdate_new(sqlite3 *, int, sqlite3_value **);
+SQLITE_API int sqlite3_preupdate_blobwrite(sqlite3 *);
 #endif
 
 /*
@@ -11236,6 +11246,37 @@ SQLITE_API int sqlite3session_create(
 */
 SQLITE_API void sqlite3session_delete(sqlite3_session *pSession);
 
+/*
+** CAPIREF: Conigure a Session Object
+** METHOD: sqlite3_session
+**
+** This method is used to configure a session object after it has been
+** created. At present the only valid value for the second parameter is
+** [SQLITE_SESSION_OBJCONFIG_SIZE].
+*/
+SQLITE_API int sqlite3session_object_config(sqlite3_session*, int op, void *pArg);
+
+/*
+** CAPI3REF: Arguments for sqlite3session_object_config()
+**
+** The following values may passed as the the 4th parameter to
+** [sqlite3session_object_config].
+**
+** <dt>SQLITE_SESSION_OBJCONFIG_SIZE <dd>
+**   This option is used to set, clear or query the flag that enables
+**   the [sqlite3session_changeset_size()] API. Because it imposes some
+**   computational overhead, this API is disabled by default. Argument
+**   pArg must point to a value of type (int). If the value is initially
+**   0, then the sqlite3session_changeset_size() API is disabled. If it
+**   is greater than 0, then the same API is enabled. Or, if the initial
+**   value is less than zero, no change is made. In all cases the (int)
+**   variable is set to 1 if the sqlite3session_changeset_size() API is
+**   enabled following the current call, or 0 otherwise.
+**
+**   It is an error (SQLITE_MISUSE) to attempt to modify this setting after
+**   the first table has been attached to the session object.
+*/
+#define SQLITE_SESSION_OBJCONFIG_SIZE 1
 
 /*
 ** CAPI3REF: Enable Or Disable A Session Object
@@ -11480,6 +11521,22 @@ SQLITE_API int sqlite3session_changeset(
   void **ppChangeset              /* OUT: Buffer containing changeset */
 );
 
+/*
+** CAPI3REF: Return An Upper-limit For The Size Of The Changeset
+** METHOD: sqlite3session_changeset_size()
+**
+** By default, this function always returns 0. For it to return
+** a useful result, the sqlite3_session object must have been configured
+** to enable this API using [sqlite3session_object_config()] with the
+** SQLITE_SESSION_OBJCONFIG_SIZE verb.
+**
+** When enabled, this function returns an upper limit, in bytes, for the size
+** of the changeset that might be produced if sqlite3session_changeset() were
+** called. The final changeset size might be equal to or smaller than the
+** size in bytes returned by this function.
+*/
+SQLITE_API sqlite3_int64 sqlite3session_changeset_size(sqlite3_session *pSession);
+
 /*
 ** CAPI3REF: Load The Difference Between Tables Into A Session
 ** METHOD: sqlite3_session
@@ -17798,6 +17855,7 @@ struct Table {
 #define TF_Shadow          0x1000    /* True for a shadow table */
 #define TF_HasStat4        0x2000    /* STAT4 info available for this table */
 #define TF_Ephemeral       0x4000    /* An ephemeral table */
+#define TF_Eponymous       0x8000    /* An eponymous virtual table */
 
 /*
 ** Test to see whether or not a table is a virtual table.  This is
@@ -21577,6 +21635,7 @@ struct PreUpdate {
   UnpackedRecord *pUnpacked;      /* Unpacked version of aRecord[] */
   UnpackedRecord *pNewUnpacked;   /* Unpacked version of new.* record */
   int iNewReg;                    /* Register for new.* values */
+  int iBlobWrite;                 /* Value returned by preupdate_blobwrite() */
   i64 iKey1;                      /* First key value passed to hook */
   i64 iKey2;                      /* Second key value passed to hook */
   Mem *aNew;                      /* Array of new.* values */
@@ -21665,7 +21724,8 @@ SQLITE_PRIVATE void sqlite3VdbeFrameMemDel(void*);      /* Destructor on Mem */
 SQLITE_PRIVATE void sqlite3VdbeFrameDelete(VdbeFrame*); /* Actually deletes the Frame */
 SQLITE_PRIVATE int sqlite3VdbeFrameRestore(VdbeFrame *);
 #ifdef SQLITE_ENABLE_PREUPDATE_HOOK
-SQLITE_PRIVATE void sqlite3VdbePreUpdateHook(Vdbe*,VdbeCursor*,int,const char*,Table*,i64,int);
+SQLITE_PRIVATE void sqlite3VdbePreUpdateHook(
+    Vdbe*,VdbeCursor*,int,const char*,Table*,i64,int,int);
 #endif
 SQLITE_PRIVATE int sqlite3VdbeTransferError(Vdbe *p);
 
@@ -72282,7 +72342,7 @@ static int rebuildPage(
     put2byte(pCellptr, (pData - aData));
     pCellptr += 2;
     if( pData < pCellptr ) return SQLITE_CORRUPT_BKPT;
-    memcpy(pData, pCell, sz);
+    memmove(pData, pCell, sz);
     assert( sz==pPg->xCellSize(pPg, pCell) || CORRUPT_DB );
     i++;
     if( i>=iEnd ) break;
@@ -83872,7 +83932,8 @@ SQLITE_PRIVATE void sqlite3VdbePreUpdateHook(
   const char *zDb,                /* Database name */
   Table *pTab,                    /* Modified table */
   i64 iKey1,                      /* Initial key value */
-  int iReg                        /* Register for new.* record */
+  int iReg,                       /* Register for new.* record */
+  int iBlobWrite
 ){
   sqlite3 *db = v->db;
   i64 iKey2;
@@ -83908,6 +83969,7 @@ SQLITE_PRIVATE void sqlite3VdbePreUpdateHook(
   preupdate.iKey1 = iKey1;
   preupdate.iKey2 = iKey2;
   preupdate.pTab = pTab;
+  preupdate.iBlobWrite = iBlobWrite;
 
   db->pPreUpdate = &preupdate;
   db->xPreUpdateCallback(db->pPreUpdateArg, db, op, zDb, zTbl, iKey1, iKey2);
@@ -85834,6 +85896,17 @@ SQLITE_API int sqlite3_preupdate_depth(sqlite3 *db){
 }
 #endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
 
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+/*
+** This function is designed to be called from within a pre-update callback
+** only.
+*/
+SQLITE_API int sqlite3_preupdate_blobwrite(sqlite3 *db){
+  PreUpdate *p = db->pPreUpdate;
+  return (p ? p->iBlobWrite : -1);
+}
+#endif
+
 #ifdef SQLITE_ENABLE_PREUPDATE_HOOK
 /*
 ** This function is called from within a pre-update callback to retrieve
@@ -91349,7 +91422,7 @@ case OP_Insert: {
   /* Invoke the pre-update hook, if any */
   if( pTab ){
     if( db->xPreUpdateCallback && !(pOp->p5 & OPFLAG_ISUPDATE) ){
-      sqlite3VdbePreUpdateHook(p, pC, SQLITE_INSERT, zDb, pTab, x.nKey,pOp->p2);
+      sqlite3VdbePreUpdateHook(p,pC,SQLITE_INSERT,zDb,pTab,x.nKey,pOp->p2,-1);
     }
     if( db->xUpdateCallback==0 || pTab->aCol==0 ){
       /* Prevent post-update hook from running in cases when it should not */
@@ -91509,7 +91582,7 @@ case OP_Delete: {
     sqlite3VdbePreUpdateHook(p, pC,
         (opflags & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_DELETE,
         zDb, pTab, pC->movetoTarget,
-        pOp->p3
+        pOp->p3, -1
     );
   }
   if( opflags & OPFLAG_ISNOOP ) break;
@@ -94938,7 +95011,7 @@ static int blobReadWrite(
       sqlite3_int64 iKey;
       iKey = sqlite3BtreeIntegerKey(p->pCsr);
       sqlite3VdbePreUpdateHook(
-          v, v->apCsr[0], SQLITE_DELETE, p->zDb, p->pTab, iKey, -1
+          v, v->apCsr[0], SQLITE_DELETE, p->zDb, p->pTab, iKey, -1, p->iCol
       );
     }
 #endif
@@ -104144,7 +104217,7 @@ SQLITE_PRIVATE int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){
 */
 SQLITE_PRIVATE int sqlite3ExprCheckIN(Parse *pParse, Expr *pIn){
   int nVector = sqlite3ExprVectorSize(pIn->pLeft);
-  if( (pIn->flags & EP_xIsSelect) ){
+  if( (pIn->flags & EP_xIsSelect)!=0 && !pParse->db->mallocFailed ){
     if( nVector!=pIn->x.pSelect->pEList->nExpr ){
       sqlite3SubselectError(pParse, pIn->x.pSelect->pEList->nExpr, nVector);
       return 1;
@@ -107136,6 +107209,7 @@ SQLITE_PRIVATE int sqlite3NoTempsInRange(Parse *pParse, int iFirst, int iLast){
 static int isAlterableTable(Parse *pParse, Table *pTab){
   if( 0==sqlite3StrNICmp(pTab->zName, "sqlite_", 7)
 #ifndef SQLITE_OMIT_VIRTUALTABLE
+   || (pTab->tabFlags & TF_Eponymous)!=0
    || ( (pTab->tabFlags & TF_Shadow)!=0
         && sqlite3ReadOnlyShadowTables(pParse->db)
    )
@@ -108023,7 +108097,9 @@ static RenameToken *renameTokenFind(
   void *pPtr
 ){
   RenameToken **pp;
-  assert( pPtr!=0 );
+  if( NEVER(pPtr==0) ){
+    return 0;
+  }
   for(pp=&pParse->pRename; (*pp); pp=&(*pp)->pNext){
     if( (*pp)->p==pPtr ){
       RenameToken *pToken = *pp;
@@ -111787,14 +111863,16 @@ SQLITE_PRIVATE int sqlite3FixTriggerStep(
       return 1;
     }
 #ifndef SQLITE_OMIT_UPSERT
-    if( pStep->pUpsert ){
-      Upsert *pUp = pStep->pUpsert;
-      if( sqlite3WalkExprList(&pFix->w, pUp->pUpsertTarget)
-       || sqlite3WalkExpr(&pFix->w, pUp->pUpsertTargetWhere)
-       || sqlite3WalkExprList(&pFix->w, pUp->pUpsertSet)
-       || sqlite3WalkExpr(&pFix->w, pUp->pUpsertWhere)
-      ){
-        return 1;
+    {
+      Upsert *pUp;
+      for(pUp=pStep->pUpsert; pUp; pUp=pUp->pNextUpsert){
+        if( sqlite3WalkExprList(&pFix->w, pUp->pUpsertTarget)
+         || sqlite3WalkExpr(&pFix->w, pUp->pUpsertTargetWhere)
+         || sqlite3WalkExprList(&pFix->w, pUp->pUpsertSet)
+         || sqlite3WalkExpr(&pFix->w, pUp->pUpsertWhere)
+        ){
+          return 1;
+        }
       }
     }
 #endif
@@ -143956,6 +144034,7 @@ SQLITE_PRIVATE int sqlite3VtabEponymousTableInit(Parse *pParse, Module *pMod){
   pTab->pSchema = db->aDb[0].pSchema;
   assert( pTab->nModuleArg==0 );
   pTab->iPKey = -1;
+  pTab->tabFlags |= TF_Eponymous;
   addModuleArgument(pParse, pTab, sqlite3DbStrDup(db, pTab->zName));
   addModuleArgument(pParse, pTab, 0);
   addModuleArgument(pParse, pTab, sqlite3DbStrDup(db, pTab->zName));
@@ -145368,6 +145447,7 @@ static int codeAllEqualityTerms(
 
   if( nSkip ){
     int iIdxCur = pLevel->iIdxCur;
+    sqlite3VdbeAddOp3(v, OP_Null, 0, regBase, regBase+nSkip-1);
     sqlite3VdbeAddOp1(v, (bRev?OP_Last:OP_Rewind), iIdxCur);
     VdbeCoverageIf(v, bRev==0);
     VdbeCoverageIf(v, bRev!=0);
@@ -151004,7 +151084,7 @@ static int whereLoopResize(sqlite3 *db, WhereLoop *p, int n){
 static int whereLoopXfer(sqlite3 *db, WhereLoop *pTo, WhereLoop *pFrom){
   whereLoopClearUnion(db, pTo);
   if( whereLoopResize(db, pTo, pFrom->nLTerm) ){
-    memset(&pTo->u, 0, sizeof(pTo->u));
+    memset(pTo, 0, WHERE_LOOP_XFER_SZ);
     return SQLITE_NOMEM_BKPT;
   }
   memcpy(pTo, pFrom, WHERE_LOOP_XFER_SZ);
@@ -208077,6 +208157,7 @@ struct SessionHook {
 struct sqlite3_session {
   sqlite3 *db;                    /* Database handle session is attached to */
   char *zDb;                      /* Name of database session is attached to */
+  int bEnableSize;                /* True if changeset_size() enabled */
   int bEnable;                    /* True if currently recording */
   int bIndirect;                  /* True if all changes are indirect */
   int bAutoAttach;                /* True to auto-attach tables */
@@ -208084,6 +208165,7 @@ struct sqlite3_session {
   void *pFilterCtx;               /* First argument to pass to xTableFilter */
   int (*xTableFilter)(void *pCtx, const char *zTab);
   i64 nMalloc;                    /* Number of bytes of data allocated */
+  i64 nMaxChangesetSize;
   sqlite3_value *pZeroBlob;       /* Value containing X'' */
   sqlite3_session *pNext;         /* Next session object on same db. */
   SessionTable *pTable;           /* List of attached tables */
@@ -208326,8 +208408,9 @@ struct SessionTable {
 ** this structure stored in a SessionTable.aChange[] hash table.
 */
 struct SessionChange {
-  int op;                         /* One of UPDATE, DELETE, INSERT */
-  int bIndirect;                  /* True if this change is "indirect" */
+  u8 op;                          /* One of UPDATE, DELETE, INSERT */
+  u8 bIndirect;                   /* True if this change is "indirect" */
+  int nMaxSize;                   /* Max size of eventual changeset record */
   int nRecord;                    /* Number of bytes in buffer aRecord[] */
   u8 *aRecord;                    /* Buffer containing old.* record */
   SessionChange *pNext;           /* For hash-table collisions */
@@ -209156,6 +209239,12 @@ static int sessionInitTable(sqlite3_session *pSession, SessionTable *pTab){
       if( 0==sqlite3_stricmp("sqlite_stat1", pTab->zName) ){
         pTab->bStat1 = 1;
       }
+
+      if( pSession->bEnableSize ){
+        pSession->nMaxChangesetSize += (
+          1 + sessionVarintLen(pTab->nCol) + pTab->nCol + strlen(pTab->zName)+1
+        );
+      }
     }
   }
   return (pSession->rc || pTab->abPK==0);
@@ -209201,6 +209290,103 @@ static int sessionStat1Depth(void *pCtx){
   return p->hook.xDepth(p->hook.pCtx);
 }
 
+static int sessionUpdateMaxSize(
+  int op,
+  sqlite3_session *pSession,      /* Session object pTab is attached to */
+  SessionTable *pTab,             /* Table that change applies to */
+  SessionChange *pC               /* Update pC->nMaxSize */
+){
+  i64 nNew = 2;
+  if( pC->op==SQLITE_INSERT ){
+    if( op!=SQLITE_DELETE ){
+      int ii;
+      for(ii=0; ii<pTab->nCol; ii++){
+        sqlite3_value *p = 0;
+        pSession->hook.xNew(pSession->hook.pCtx, ii, &p);
+        sessionSerializeValue(0, p, &nNew);
+      }
+    }
+  }else if( op==SQLITE_DELETE ){
+    nNew += pC->nRecord;
+    if( sqlite3_preupdate_blobwrite(pSession->db)>=0 ){
+      nNew += pC->nRecord;
+    }
+  }else{
+    int ii;
+    u8 *pCsr = pC->aRecord;
+    for(ii=0; ii<pTab->nCol; ii++){
+      int bChanged = 1;
+      int nOld = 0;
+      int eType;
+      sqlite3_value *p = 0;
+      pSession->hook.xNew(pSession->hook.pCtx, ii, &p);
+      if( p==0 ){
+        return SQLITE_NOMEM;
+      }
+
+      eType = *pCsr++;
+      switch( eType ){
+        case SQLITE_NULL:
+          bChanged = sqlite3_value_type(p)!=SQLITE_NULL;
+          break;
+
+        case SQLITE_FLOAT:
+        case SQLITE_INTEGER: {
+          if( eType==sqlite3_value_type(p) ){
+            sqlite3_int64 iVal = sessionGetI64(pCsr);
+            if( eType==SQLITE_INTEGER ){
+              bChanged = (iVal!=sqlite3_value_int64(p));
+            }else{
+              double dVal;
+              memcpy(&dVal, &iVal, 8);
+              bChanged = (dVal!=sqlite3_value_double(p));
+            }
+          }
+          nOld = 8;
+          pCsr += 8;
+          break;
+        }
+
+        default: {
+          int nByte;
+          nOld = sessionVarintGet(pCsr, &nByte);
+          pCsr += nOld;
+          nOld += nByte;
+          assert( eType==SQLITE_TEXT || eType==SQLITE_BLOB );
+          if( eType==sqlite3_value_type(p)
+           && nByte==sqlite3_value_bytes(p)
+           && (nByte==0 || 0==memcmp(pCsr, sqlite3_value_blob(p), nByte))
+          ){
+            bChanged = 0;
+          }
+          pCsr += nByte;
+          break;
+        }
+      }
+
+      if( bChanged && pTab->abPK[ii] ){
+        nNew = pC->nRecord + 2;
+        break;
+      }
+
+      if( bChanged ){
+        nNew += 1 + nOld;
+        sessionSerializeValue(0, p, &nNew);
+      }else if( pTab->abPK[ii] ){
+        nNew += 2 + nOld;
+      }else{
+        nNew += 2;
+      }
+    }
+  }
+
+  if( nNew>pC->nMaxSize ){
+    int nIncr = nNew - pC->nMaxSize;
+    pC->nMaxSize = nNew;
+    pSession->nMaxChangesetSize += nIncr;
+  }
+  return SQLITE_OK;
+}
 
 /*
 ** This function is only called from with a pre-update-hook reporting a
@@ -209274,7 +209460,6 @@ static void sessionPreupdateOneChange(
       /* Create a new change object containing all the old values (if
       ** this is an SQLITE_UPDATE or SQLITE_DELETE), or just the PK
       ** values (if this is an INSERT). */
-      SessionChange *pChange; /* New change object */
       sqlite3_int64 nByte;    /* Number of bytes to allocate */
       int i;                  /* Used to iterate through columns */
 
@@ -209300,13 +209485,13 @@ static void sessionPreupdateOneChange(
       }
 
       /* Allocate the change object */
-      pChange = (SessionChange *)sessionMalloc64(pSession, nByte);
-      if( !pChange ){
+      pC = (SessionChange *)sessionMalloc64(pSession, nByte);
+      if( !pC ){
         rc = SQLITE_NOMEM;
         goto error_out;
       }else{
-        memset(pChange, 0, sizeof(SessionChange));
-        pChange->aRecord = (u8 *)&pChange[1];
+        memset(pC, 0, sizeof(SessionChange));
+        pC->aRecord = (u8 *)&pC[1];
       }
 
       /* Populate the change object. None of the preupdate_old(),
@@ -209321,17 +209506,17 @@ static void sessionPreupdateOneChange(
         }else if( pTab->abPK[i] ){
           pSession->hook.xNew(pSession->hook.pCtx, i, &p);
         }
-        sessionSerializeValue(&pChange->aRecord[nByte], p, &nByte);
+        sessionSerializeValue(&pC->aRecord[nByte], p, &nByte);
       }
 
       /* Add the change to the hash-table */
       if( pSession->bIndirect || pSession->hook.xDepth(pSession->hook.pCtx) ){
-        pChange->bIndirect = 1;
+        pC->bIndirect = 1;
       }
-      pChange->nRecord = nByte;
-      pChange->op = op;
-      pChange->pNext = pTab->apChange[iHash];
-      pTab->apChange[iHash] = pChange;
+      pC->nRecord = nByte;
+      pC->op = op;
+      pC->pNext = pTab->apChange[iHash];
+      pTab->apChange[iHash] = pC;
 
     }else if( pC->bIndirect ){
       /* If the existing change is considered "indirect", but this current
@@ -209342,8 +209527,14 @@ static void sessionPreupdateOneChange(
         pC->bIndirect = 0;
       }
     }
+
+    assert( rc==SQLITE_OK );
+    if( pSession->bEnableSize ){
+      rc = sessionUpdateMaxSize(op, pSession, pTab, pC);
+    }
   }
 
+
   /* If an error has occurred, mark the session object as failed. */
  error_out:
   if( pTab->bStat1 ){
@@ -210555,7 +210746,11 @@ SQLITE_API int sqlite3session_changeset(
   int *pnChangeset,               /* OUT: Size of buffer at *ppChangeset */
   void **ppChangeset              /* OUT: Buffer containing changeset */
 ){
-  return sessionGenerateChangeset(pSession, 0, 0, 0, pnChangeset, ppChangeset);
+  int rc = sessionGenerateChangeset(pSession, 0, 0, 0, pnChangeset,ppChangeset);
+  assert( rc || pnChangeset==0
+       || pSession->bEnableSize==0 || *pnChangeset<=pSession->nMaxChangesetSize
+  );
+  return rc;
 }
 
 /*
@@ -210647,6 +210842,39 @@ SQLITE_API sqlite3_int64 sqlite3session_memory_used(sqlite3_session *pSession){
   return pSession->nMalloc;
 }
 
+/*
+** Configure the session object passed as the first argument.
+*/
+SQLITE_API int sqlite3session_object_config(sqlite3_session *pSession, int op, void *pArg){
+  int rc = SQLITE_OK;
+  switch( op ){
+    case SQLITE_SESSION_OBJCONFIG_SIZE: {
+      int iArg = *(int*)pArg;
+      if( iArg>=0 ){
+        if( pSession->pTable ){
+          rc = SQLITE_MISUSE;
+        }else{
+          pSession->bEnableSize = (iArg!=0);
+        }
+      }
+      *(int*)pArg = pSession->bEnableSize;
+      break;
+    }
+
+    default:
+      rc = SQLITE_MISUSE;
+  }
+
+  return rc;
+}
+
+/*
+** Return the maximum size of sqlite3session_changeset() output.
+*/
+SQLITE_API sqlite3_int64 sqlite3session_changeset_size(sqlite3_session *pSession){
+  return pSession->nMaxChangesetSize;
+}
+
 /*
 ** Do the work for either sqlite3changeset_start() or start_strm().
 */
@@ -221539,6 +221767,7 @@ static int sqlite3Fts5ExprInit(Fts5Global *pGlobal, sqlite3 *db){
   }
 #else
   int rc = SQLITE_OK;
+  UNUSED_PARAM2(pGlobal,db);
 #endif
 
   /* Avoid warnings indicating that sqlite3Fts5ParserTrace() and
@@ -229075,6 +229304,7 @@ static int sqlite3Fts5IndexInit(sqlite3 *db){
   return rc;
 #else
   return SQLITE_OK;
+  UNUSED_PARAM(db);
 #endif
 }
 
@@ -231879,7 +232109,7 @@ static void fts5SourceIdFunc(
 ){
   assert( nArg==0 );
   UNUSED_PARAM2(nArg, apUnused);
-  sqlite3_result_text(pCtx, "fts5: 2021-04-19 20:36:13 3e863cd09355abd80c1053d6d4dabb55841f806e3c418f923d67d36bf8313cb0", -1, SQLITE_TRANSIENT);
+  sqlite3_result_text(pCtx, "fts5: 2021-04-23 00:59:38 fac12115a994a1b4347586e68faf38895ee9cb588eaa84c6f71cf9afd4c488b4", -1, SQLITE_TRANSIENT);
 }
 
 /*
@@ -245388,9 +245618,9 @@ SQLITE_API int sqlite3_uuid_init(
 }
 
 /************** End of uuid.c ************************************************/
-#if __LINE__!=245391
+#if __LINE__!=245621
 #undef SQLITE_SOURCE_ID
-#define SQLITE_SOURCE_ID      "2021-04-20 22:48:25 ca70c8ac72c9fe3b92f8e63151229bebdccc769c5c4d603ad4f466dc4bcdalt2"
+#define SQLITE_SOURCE_ID      "2021-04-23 00:59:38 fac12115a994a1b4347586e68faf38895ee9cb588eaa84c6f71cf9afd4c4alt2"
 #endif
 /* Return the source-id for this library */
 SQLITE_API const char *sqlite3_sourceid(void){ return SQLITE_SOURCE_ID; }

+ 58 - 1
SquiLu-ext/sqlite3.h

@@ -125,7 +125,7 @@ extern "C" {
 */
 #define SQLITE_VERSION        "3.36.0"
 #define SQLITE_VERSION_NUMBER 3036000
-#define SQLITE_SOURCE_ID      "2021-04-20 22:48:25 ca70c8ac72c9fe3b92f8e63151229bebdccc769c5c4d603ad4f466dc4bcdalt1"
+#define SQLITE_SOURCE_ID      "2021-04-23 00:59:38 fac12115a994a1b4347586e68faf38895ee9cb588eaa84c6f71cf9afd4c4alt1"
 
 /*
 ** CAPI3REF: Run-Time Library Version Numbers
@@ -9613,6 +9613,15 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*);
 ** triggers; or 2 for changes resulting from triggers called by top-level
 ** triggers; and so forth.
 **
+** When the [sqlite3_blob_write()] API is used to update a blob column,
+** the pre-update hook is invoked with SQLITE_DELETE. This is because the
+** in this case the new values are not available. In this case, when a
+** callback made with op==SQLITE_DELETE is actuall a write using the
+** sqlite3_blob_write() API, the [sqlite3_preupdate_blobwrite()] returns
+** the index of the column being written. In other cases, where the
+** pre-update hook is being invoked for some other reason, including a
+** regular DELETE, sqlite3_preupdate_blobwrite() returns -1.
+**
 ** See also:  [sqlite3_update_hook()]
 */
 #if defined(SQLITE_ENABLE_PREUPDATE_HOOK)
@@ -9633,6 +9642,7 @@ SQLITE_API int sqlite3_preupdate_old(sqlite3 *, int, sqlite3_value **);
 SQLITE_API int sqlite3_preupdate_count(sqlite3 *);
 SQLITE_API int sqlite3_preupdate_depth(sqlite3 *);
 SQLITE_API int sqlite3_preupdate_new(sqlite3 *, int, sqlite3_value **);
+SQLITE_API int sqlite3_preupdate_blobwrite(sqlite3 *);
 #endif
 
 /*
@@ -10173,6 +10183,37 @@ SQLITE_API int sqlite3session_create(
 */
 SQLITE_API void sqlite3session_delete(sqlite3_session *pSession);
 
+/*
+** CAPIREF: Conigure a Session Object
+** METHOD: sqlite3_session
+**
+** This method is used to configure a session object after it has been
+** created. At present the only valid value for the second parameter is
+** [SQLITE_SESSION_OBJCONFIG_SIZE].
+*/
+SQLITE_API int sqlite3session_object_config(sqlite3_session*, int op, void *pArg);
+
+/*
+** CAPI3REF: Arguments for sqlite3session_object_config()
+**
+** The following values may passed as the the 4th parameter to
+** [sqlite3session_object_config].
+**
+** <dt>SQLITE_SESSION_OBJCONFIG_SIZE <dd>
+**   This option is used to set, clear or query the flag that enables
+**   the [sqlite3session_changeset_size()] API. Because it imposes some
+**   computational overhead, this API is disabled by default. Argument
+**   pArg must point to a value of type (int). If the value is initially
+**   0, then the sqlite3session_changeset_size() API is disabled. If it
+**   is greater than 0, then the same API is enabled. Or, if the initial
+**   value is less than zero, no change is made. In all cases the (int)
+**   variable is set to 1 if the sqlite3session_changeset_size() API is
+**   enabled following the current call, or 0 otherwise.
+**
+**   It is an error (SQLITE_MISUSE) to attempt to modify this setting after
+**   the first table has been attached to the session object.
+*/
+#define SQLITE_SESSION_OBJCONFIG_SIZE 1
 
 /*
 ** CAPI3REF: Enable Or Disable A Session Object
@@ -10417,6 +10458,22 @@ SQLITE_API int sqlite3session_changeset(
   void **ppChangeset              /* OUT: Buffer containing changeset */
 );
 
+/*
+** CAPI3REF: Return An Upper-limit For The Size Of The Changeset
+** METHOD: sqlite3session_changeset_size()
+**
+** By default, this function always returns 0. For it to return
+** a useful result, the sqlite3_session object must have been configured
+** to enable this API using [sqlite3session_object_config()] with the
+** SQLITE_SESSION_OBJCONFIG_SIZE verb.
+**
+** When enabled, this function returns an upper limit, in bytes, for the size
+** of the changeset that might be produced if sqlite3session_changeset() were
+** called. The final changeset size might be equal to or smaller than the
+** size in bytes returned by this function.
+*/
+SQLITE_API sqlite3_int64 sqlite3session_changeset_size(sqlite3_session *pSession);
+
 /*
 ** CAPI3REF: Load The Difference Between Tables Into A Session
 ** METHOD: sqlite3_session