]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Allow a session object to generate a changeset, even if columns were added to one...
authordan <Dan Kennedy>
Wed, 4 Oct 2023 21:15:24 +0000 (21:15 +0000)
committerdan <Dan Kennedy>
Wed, 4 Oct 2023 21:15:24 +0000 (21:15 +0000)
FossilOrigin-Name: a3f435eccf3a2aa11cb7420e94af5efcdfa04e9c169c5aaf61fa5cdcb165ceef

ext/session/session3.test
ext/session/sessionalter.test [new file with mode: 0644]
ext/session/sqlite3session.c
ext/session/sqlite3session.h
manifest
manifest.uuid

index ba316348ef2a36eabf7c86b84bb02fa6bd3ac466..ee955f1376985df0ccaa7ec9b1eb16d9f2ba943c 100644 (file)
@@ -135,8 +135,8 @@ do_test 2.2.2 {
     DROP TABLE t2;
     CREATE TABLE t2(a, b PRIMARY KEY, c, d);
   }
-  list [catch { S changeset } msg] $msg
-} {1 SQLITE_SCHEMA}
+  catch { S changeset } 
+} {0}
 do_test 2.2.3 {
   S delete
   sqlite3session S db main
@@ -167,8 +167,8 @@ do_test 2.2.4 {
     CREATE TABLE t2(a, b PRIMARY KEY, c, d);
     INSERT INTO t2 VALUES(4, 5, 6, 7);
   }
-  list [catch { S changeset } msg] $msg
-} {1 SQLITE_SCHEMA}
+  catch { S changeset }
+} {0}
 
 do_test 2.3 {
   S delete
diff --git a/ext/session/sessionalter.test b/ext/session/sessionalter.test
new file mode 100644 (file)
index 0000000..d81b43c
--- /dev/null
@@ -0,0 +1,109 @@
+# 2023 October 02
+#
+# The author disclaims copyright to this source code.  In place of
+# a legal notice, here is a blessing:
+#
+#    May you do good and not evil.
+#    May you find forgiveness for yourself and forgive others.
+#    May you share freely, never taking more than you give.
+#
+#***********************************************************************
+# This file implements that the sessions module interacts well with
+# the ALTER TABLE ADD COLUMN command.
+#
+
+if {![info exists testdir]} {
+  set testdir [file join [file dirname [info script]] .. .. test]
+} 
+source [file join [file dirname [info script]] session_common.tcl]
+source $testdir/tester.tcl
+
+ifcapable !session {finish_test; return}
+set testprefix sessionalter
+
+
+forcedelete test.db2
+sqlite3 db2 test.db2
+
+do_execsql_test 1.0 {
+  CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
+}
+
+do_execsql_test -db db2 1.1 {
+  CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c DEFAULT 1234);
+}
+
+do_then_apply_sql {
+  INSERT INTO t1 VALUES(1, 'one');
+  INSERT INTO t1 VALUES(2, 'two');
+}
+
+do_execsql_test -db db2 1.2 {
+  SELECT * FROM t1
+} {
+  1 one 1234
+  2 two 1234
+}
+
+do_then_apply_sql {
+  UPDATE t1 SET b='four' WHERE a=2;
+}
+
+do_execsql_test -db db2 1.3 {
+  SELECT * FROM t1
+} {
+  1 one 1234
+  2 four 1234
+}
+
+do_then_apply_sql {
+  DELETE FROM t1 WHERE a=1;
+}
+
+do_execsql_test -db db2 1.4 {
+  SELECT * FROM t1
+} {
+  2 four 1234
+}
+
+
+#--------------------------------------------------------------------------
+reset_db
+
+do_execsql_test 2.0 {
+  CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
+}
+
+do_test 2.1 {
+  sqlite3session S db main
+  S attach t1
+  set {} {}
+} {} 
+do_execsql_test 2.2 {
+  INSERT INTO t1 VALUES(1, 2);
+  ALTER TABLE t1 ADD COLUMN c DEFAULT 'abcd';
+  INSERT INTO t1 VALUES(2, 3, 4);
+}
+do_changeset_test 2.3 S {
+  {INSERT t1 0 X.. {} {i 1 i 2 t abcd}} 
+  {INSERT t1 0 X.. {} {i 2 i 3 i 4}}
+}
+
+do_iterator_test 2.4 {} {
+  DELETE FROM t1 WHERE a=2;
+  ALTER TABLE t1 ADD COLUMN d DEFAULT 'abcd';
+  ALTER TABLE t1 ADD COLUMN e DEFAULT 5;
+  ALTER TABLE t1 ADD COLUMN f DEFAULT 7.2;
+  -- INSERT INTO t1 VALUES(9, 9, 9, 9);
+} {
+  {DELETE t1 0 X..... {i 2 i 3 i 4 t abcd i 5 f 7.2} {}}
+}
+
+
+
+
+
+
+
+
+finish_test
index 0491549231b49fe4f58f4d3e68942c02fd5350d5..cb40f25fd36653707ac5b1c72b439babb306fa1f 100644 (file)
@@ -127,6 +127,7 @@ struct SessionTable {
   int bStat1;                     /* True if this is sqlite_stat1 */
   int bRowid;                     /* True if this table uses rowid for PK */
   const char **azCol;             /* Column names */
+  const char **azDflt;            /* Default value expressions */
   u8 *abPK;                       /* Array of primary key flags */
   int nEntry;                     /* Total number of entries in hash table */
   int nChange;                    /* Size of apChange[] array */
@@ -299,6 +300,7 @@ struct SessionTable {
 struct SessionChange {
   u8 op;                          /* One of UPDATE, DELETE, INSERT */
   u8 bIndirect;                   /* True if this change is "indirect" */
+  u16 nRecordField;               /* Number of fields in aRecord[] */
   int nMaxSize;                   /* Max size of eventual changeset record */
   int nRecord;                    /* Number of bytes in buffer aRecord[] */
   u8 *aRecord;                    /* Buffer containing old.* record */
@@ -1014,6 +1016,7 @@ static int sessionTableInfo(
   int *pnCol,                     /* OUT: number of columns */
   const char **pzTab,             /* OUT: Copy of zThis */
   const char ***pazCol,           /* OUT: Array of column names for table */
+  const char ***pazDflt,          /* OUT: Array of default value expressions */
   u8 **pabPK,                     /* OUT: Array of booleans - true for PK col */
   int *pbRowid                    /* OUT: True if only PK is a rowid */
 ){
@@ -1026,11 +1029,18 @@ static int sessionTableInfo(
   int i;
   u8 *pAlloc = 0;
   char **azCol = 0;
+  char **azDflt = 0;
   u8 *abPK = 0;
   int bRowid = 0;                 /* Set to true to use rowid as PK */
 
   assert( pazCol && pabPK );
 
+  *pazCol = 0;
+  *pabPK = 0;
+  *pnCol = 0;
+  if( pzTab ) *pzTab = 0;
+  if( pazDflt ) *pazDflt = 0;
+
   nThis = sqlite3Strlen30(zThis);
   if( nThis==12 && 0==sqlite3_stricmp("sqlite_stat1", zThis) ){
     rc = sqlite3_table_column_metadata(db, zDb, zThis, 0, 0, 0, 0, 0, 0);
@@ -1044,39 +1054,28 @@ static int sessionTableInfo(
     }else if( rc==SQLITE_ERROR ){
       zPragma = sqlite3_mprintf("");
     }else{
-      *pazCol = 0;
-      *pabPK = 0;
-      *pnCol = 0;
-      if( pzTab ) *pzTab = 0;
       return rc;
     }
   }else{
     zPragma = sqlite3_mprintf("PRAGMA '%q'.table_info('%q')", zDb, zThis);
   }
   if( !zPragma ){
-    *pazCol = 0;
-    *pabPK = 0;
-    *pnCol = 0;
-    if( pzTab ) *pzTab = 0;
     return SQLITE_NOMEM;
   }
 
   rc = sqlite3_prepare_v2(db, zPragma, -1, &pStmt, 0);
   sqlite3_free(zPragma);
   if( rc!=SQLITE_OK ){
-    *pazCol = 0;
-    *pabPK = 0;
-    *pnCol = 0;
-    if( pzTab ) *pzTab = 0;
     return rc;
   }
 
   nByte = nThis + 1;
   bRowid = (pbRowid!=0);
   while( SQLITE_ROW==sqlite3_step(pStmt) ){
-    nByte += sqlite3_column_bytes(pStmt, 1);
+    nByte += sqlite3_column_bytes(pStmt, 1);          /* name */
+    nByte += sqlite3_column_bytes(pStmt, 4);          /* dflt_value */
     nDbCol++;
-    if( sqlite3_column_int(pStmt, 5) ) bRowid = 0;
+    if( sqlite3_column_int(pStmt, 5) ) bRowid = 0;    /* pk */
   }
   if( nDbCol==0 ) bRowid = 0;
   nDbCol += bRowid;
@@ -1084,7 +1083,7 @@ static int sessionTableInfo(
   rc = sqlite3_reset(pStmt);
 
   if( rc==SQLITE_OK ){
-    nByte += nDbCol * (sizeof(const char *) + sizeof(u8) + 1);
+    nByte += nDbCol * (sizeof(const char *)*2 + sizeof(u8) + 1 + 1);
     pAlloc = sessionMalloc64(pSession, nByte);
     if( pAlloc==0 ){
       rc = SQLITE_NOMEM;
@@ -1092,7 +1091,8 @@ static int sessionTableInfo(
   }
   if( rc==SQLITE_OK ){
     azCol = (char **)pAlloc;
-    pAlloc = (u8 *)&azCol[nDbCol];
+    azDflt = (char**)&azCol[nDbCol];
+    pAlloc = (u8 *)&azDflt[nDbCol];
     abPK = (u8 *)pAlloc;
     pAlloc = &abPK[nDbCol];
     if( pzTab ){
@@ -1112,11 +1112,21 @@ static int sessionTableInfo(
     }
     while( SQLITE_ROW==sqlite3_step(pStmt) ){
       int nName = sqlite3_column_bytes(pStmt, 1);
+      int nDflt = sqlite3_column_bytes(pStmt, 4);
       const unsigned char *zName = sqlite3_column_text(pStmt, 1);
+      const unsigned char *zDflt = sqlite3_column_text(pStmt, 4);
+
       if( zName==0 ) break;
       memcpy(pAlloc, zName, nName+1);
       azCol[i] = (char *)pAlloc;
       pAlloc += nName+1;
+      if( zDflt ){
+        memcpy(pAlloc, zDflt, nDflt+1);
+        azDflt[i] = (char *)pAlloc;
+        pAlloc += nDflt+1;
+      }else{
+        azDflt[i] = 0;
+      }
       abPK[i] = sqlite3_column_int(pStmt, 5);
       i++;
     }
@@ -1127,14 +1137,11 @@ static int sessionTableInfo(
   ** free any allocation made. An error code will be returned in this case.
   */
   if( rc==SQLITE_OK ){
-    *pazCol = (const char **)azCol;
+    *pazCol = (const char**)azCol;
+    if( pazDflt ) *pazDflt = (const char**)azDflt;
     *pabPK = abPK;
     *pnCol = nDbCol;
   }else{
-    *pazCol = 0;
-    *pabPK = 0;
-    *pnCol = 0;
-    if( pzTab ) *pzTab = 0;
     sessionFree(pSession, azCol);
   }
   if( pbRowid ) *pbRowid = bRowid;
@@ -1159,7 +1166,7 @@ static int sessionInitTable(sqlite3_session *pSession, SessionTable *pTab){
     u8 *abPK;
     assert( pTab->azCol==0 || pTab->abPK==0 );
     pSession->rc = sessionTableInfo(pSession, pSession->db, pSession->zDb, 
-        pTab->zName, &pTab->nCol, 0, &pTab->azCol, &abPK,
+        pTab->zName, &pTab->nCol, 0, &pTab->azCol, &pTab->azDflt, &abPK,
         (pSession->bImplicitPK ? &pTab->bRowid : 0)
     );
     if( pSession->rc==SQLITE_OK ){
@@ -1184,6 +1191,267 @@ static int sessionInitTable(sqlite3_session *pSession, SessionTable *pTab){
   return (pSession->rc || pTab->abPK==0);
 }
 
+static int sessionReinitTable(sqlite3_session *pSession, SessionTable *pTab){
+  int nCol = 0;
+  const char **azCol = 0;
+  const char **azDflt = 0;
+  u8 *abPK = 0; 
+  int bRowid = 0;
+
+  assert( pSession->rc==SQLITE_OK );
+
+  pSession->rc = sessionTableInfo(pSession, pSession->db, pSession->zDb, 
+      pTab->zName, &nCol, 0, &azCol, &azDflt, &abPK,
+      (pSession->bImplicitPK ? &bRowid : 0)
+  );
+  if( pSession->rc==SQLITE_OK ){
+    if( pTab->nCol>nCol || pTab->bRowid!=bRowid ){
+      pSession->rc = SQLITE_SCHEMA;
+    }else{
+      int ii;
+      int nOldCol = pTab->nCol;
+      for(ii=0; ii<nCol; ii++){
+        if( ii<pTab->nCol ){
+          if( pTab->abPK[ii]!=abPK[ii] ){
+            pSession->rc = SQLITE_SCHEMA;
+          }
+        }else if( abPK[ii] ){
+          pSession->rc = SQLITE_SCHEMA;
+        }
+      }
+
+      if( pSession->rc==SQLITE_OK ){
+        const char **a = pTab->azCol;
+        pTab->azCol = azCol;
+        pTab->nCol = nCol;
+        pTab->azDflt = azDflt;
+        pTab->abPK = abPK;
+        azCol = a;
+      }
+      if( pSession->bEnableSize ){
+        pSession->nMaxChangesetSize += (nCol - nOldCol);
+        pSession->nMaxChangesetSize += sessionVarintLen(nCol);
+        pSession->nMaxChangesetSize -= sessionVarintLen(nOldCol);
+      }
+    }
+  }
+
+  sqlite3_free(azCol);
+  return pSession->rc;
+}
+
+static void sessionUpdateOneChange(
+  sqlite3_session *pSession,
+  int *pRc,
+  SessionChange **pp, 
+  int nCol, 
+  sqlite3_stmt *pDflt
+){
+  SessionChange *pOld = *pp;
+
+  while( pOld->nRecordField<nCol ){
+    SessionChange *pNew = 0;
+    int nByte = 0;
+    int nIncr = 0;
+    int iField = pOld->nRecordField;
+    int eType = sqlite3_column_type(pDflt, iField);
+    switch( eType ){
+      case SQLITE_NULL:
+        nIncr = 1;
+        break;
+      case SQLITE_INTEGER:
+      case SQLITE_FLOAT:
+        nIncr = 9;
+        break;
+      default: {
+        int n = sqlite3_column_bytes(pDflt, iField);
+        nIncr = 1 + sessionVarintLen(n) + n;
+        assert( eType==SQLITE_TEXT || eType==SQLITE_BLOB );
+        break;
+      }
+    }
+
+    nByte = nIncr + (sizeof(SessionChange) + pOld->nRecord);
+    pNew = sessionMalloc64(pSession, nByte);
+    if( pNew==0 ){
+      *pRc = SQLITE_NOMEM;
+      return;
+    }else{
+      memcpy(pNew, pOld, sizeof(SessionChange));
+      pNew->aRecord = (u8*)&pNew[1];
+      memcpy(pNew->aRecord, pOld->aRecord, pOld->nRecord);
+      pNew->aRecord[pNew->nRecord++] = (u8)eType;
+      switch( eType ){
+        case SQLITE_INTEGER: {
+          i64 iVal = sqlite3_column_int64(pDflt, iField);
+          sessionPutI64(&pNew->aRecord[pNew->nRecord], iVal); 
+          pNew->nRecord += 8;
+          break;
+        }
+          
+        case SQLITE_FLOAT: {
+          double rVal = sqlite3_column_double(pDflt, iField);
+          i64 iVal = 0;
+          memcpy(&iVal, &rVal, sizeof(rVal));
+          sessionPutI64(&pNew->aRecord[pNew->nRecord], iVal); 
+          pNew->nRecord += 8;
+          break;
+        }
+
+        case SQLITE_TEXT: {
+          int n = sqlite3_column_bytes(pDflt, iField);
+          const char *z = (const char*)sqlite3_column_text(pDflt, iField);
+          pNew->nRecord += sessionVarintPut(&pNew->aRecord[pNew->nRecord], n); 
+          memcpy(&pNew->aRecord[pNew->nRecord], z, n);
+          pNew->nRecord += n;
+          break;
+        }
+
+        case SQLITE_BLOB: {
+          int n = sqlite3_column_bytes(pDflt, iField);
+          const u8 *z = (const u8*)sqlite3_column_blob(pDflt, iField);
+          pNew->nRecord += sessionVarintPut(&pNew->aRecord[pNew->nRecord], n); 
+          memcpy(&pNew->aRecord[pNew->nRecord], z, n);
+          pNew->nRecord += n;
+          break;
+        }
+
+        default:
+          assert( eType==SQLITE_NULL );
+          break;
+      }
+
+      sessionFree(pSession, pOld);
+      *pp = pOld = pNew;
+      pNew->nRecordField++;
+      pNew->nMaxSize += nIncr;
+      if( pSession ){
+        pSession->nMaxChangesetSize += nIncr;
+      }
+    }
+  }
+}
+
+/*
+** Ensure that there is room in the buffer to append nByte bytes of data.
+** If not, use sqlite3_realloc() to grow the buffer so that there is.
+**
+** If successful, return zero. Otherwise, if an OOM condition is encountered,
+** set *pRc to SQLITE_NOMEM and return non-zero.
+*/
+static int sessionBufferGrow(SessionBuffer *p, i64 nByte, int *pRc){
+#define SESSION_MAX_BUFFER_SZ (0x7FFFFF00 - 1) 
+  i64 nReq = p->nBuf + nByte;
+  if( *pRc==SQLITE_OK && nReq>p->nAlloc ){
+    u8 *aNew;
+    i64 nNew = p->nAlloc ? p->nAlloc : 128;
+
+    do {
+      nNew = nNew*2;
+    }while( nNew<nReq );
+
+    /* The value of SESSION_MAX_BUFFER_SZ is copied from the implementation
+    ** of sqlite3_realloc64(). Allocations greater than this size in bytes
+    ** always fail. It is used here to ensure that this routine can always
+    ** allocate up to this limit - instead of up to the largest power of
+    ** two smaller than the limit.  */
+    if( nNew>SESSION_MAX_BUFFER_SZ ){
+      nNew = SESSION_MAX_BUFFER_SZ;
+      if( nNew<nReq ){
+        *pRc = SQLITE_NOMEM;
+        return 1;
+      }
+    }
+
+    aNew = (u8 *)sqlite3_realloc64(p->aBuf, nNew);
+    if( 0==aNew ){
+      *pRc = SQLITE_NOMEM;
+    }else{
+      p->aBuf = aNew;
+      p->nAlloc = nNew;
+    }
+  }
+  return (*pRc!=SQLITE_OK);
+}
+
+
+/*
+** This function is a no-op if *pRc is other than SQLITE_OK when it is 
+** called. Otherwise, append a string to the buffer. All bytes in the string
+** up to (but not including) the nul-terminator are written to the buffer.
+**
+** If an OOM condition is encountered, set *pRc to SQLITE_NOMEM before
+** returning.
+*/
+static void sessionAppendStr(
+  SessionBuffer *p, 
+  const char *zStr, 
+  int *pRc
+){
+  int nStr = sqlite3Strlen30(zStr);
+  if( 0==sessionBufferGrow(p, nStr+1, pRc) ){
+    memcpy(&p->aBuf[p->nBuf], zStr, nStr);
+    p->nBuf += nStr;
+    p->aBuf[p->nBuf] = 0x00;
+  }
+}
+
+static void sessionAppendPrintf(
+  SessionBuffer *p,               /* Buffer to append to */
+  int *pRc, 
+  const char *zFmt,
+  ...
+){
+  if( *pRc==SQLITE_OK ){
+    char *zApp = 0;
+    va_list ap;
+    va_start(ap, zFmt);
+    zApp = sqlite3_vmprintf(zFmt, ap);
+    if( zApp==0 ){
+      *pRc = SQLITE_NOMEM;
+    }else{
+      sessionAppendStr(p, zApp, pRc);
+    }
+    va_end(ap);
+    sqlite3_free(zApp);
+  }
+}
+
+static int sessionUpdateChanges(sqlite3_session *pSession, SessionTable *pTab){
+  sqlite3 *db = pSession->db;
+  SessionBuffer sql = {0,0,0};
+  sqlite3_stmt *pStmt = 0;
+  const char *zSep = " ";
+  int ii = 0;
+  int rc = pSession->rc;
+
+  sessionAppendPrintf(&sql, &rc, "SELECT");
+  for(ii=0; ii<pTab->nCol; ii++){
+    const char *zDflt = pTab->azDflt[ii] ? pTab->azDflt[ii] : "NULL";
+    sessionAppendPrintf(&sql, &rc, "%s%s", zSep, zDflt);
+    zSep = ", ";
+  }
+  if( rc==SQLITE_OK ){
+    rc = sqlite3_prepare_v2(db, (const char*)sql.aBuf, -1, &pStmt, 0);
+  }
+  if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
+    SessionChange **pp = 0;
+    for(ii=0; ii<pTab->nChange; ii++){
+      for(pp=&pTab->apChange[ii]; *pp; pp=&((*pp)->pNext)){
+        if( (*pp)->nRecordField!=pTab->nCol ){
+          sessionUpdateOneChange(pSession, &rc, pp, pTab->nCol, pStmt);
+        }
+      }
+    }
+  }
+
+  sqlite3_free(sql.aBuf);
+  pSession->rc = rc;
+  rc = sqlite3_finalize(pStmt);
+  if( pSession->rc==SQLITE_OK ) pSession->rc = rc;
+  return pSession->rc;
+}
+
 /*
 ** Versions of the four methods in object SessionHook for use with the
 ** sqlite_stat1 table. The purpose of this is to substitute a zero-length
@@ -1344,6 +1612,7 @@ static void sessionPreupdateOneChange(
   int iHash; 
   int bNull = 0; 
   int rc = SQLITE_OK;
+  int nExpect = 0;
   SessionStat1Ctx stat1 = {{0,0,0,0,0},0};
 
   if( pSession->rc ) return;
@@ -1353,7 +1622,12 @@ static void sessionPreupdateOneChange(
 
   /* Check the number of columns in this xPreUpdate call matches the 
   ** number of columns in the table.  */
-  if( (pTab->nCol-pTab->bRowid)!=pSession->hook.xCount(pSession->hook.pCtx) ){
+  nExpect = pSession->hook.xCount(pSession->hook.pCtx);
+  if( (pTab->nCol-pTab->bRowid)<nExpect ){
+    if( sessionReinitTable(pSession, pTab) ) return;
+    if( sessionUpdateChanges(pSession, pTab) ) return;
+  }
+  if( (pTab->nCol-pTab->bRowid)!=nExpect ){
     pSession->rc = SQLITE_SCHEMA;
     return;
   }
@@ -1430,7 +1704,7 @@ static void sessionPreupdateOneChange(
       }
   
       /* Allocate the change object */
-      pC = (SessionChange *)sessionMalloc64(pSession, nByte);
+      pC = (SessionChange*)sessionMalloc64(pSession, nByte);
       if( !pC ){
         rc = SQLITE_NOMEM;
         goto error_out;
@@ -1463,6 +1737,7 @@ static void sessionPreupdateOneChange(
       if( pSession->bIndirect || pSession->hook.xDepth(pSession->hook.pCtx) ){
         pC->bIndirect = 1;
       }
+      pC->nRecordField = pTab->nCol;
       pC->nRecord = nByte;
       pC->op = op;
       pC->pNext = pTab->apChange[iHash];
@@ -1855,7 +2130,7 @@ int sqlite3session_diff(
       int bRowid = 0;
       u8 *abPK;
       const char **azCol = 0;
-      rc = sessionTableInfo(0, db, zFrom, zTbl, &nCol, 0, &azCol, &abPK, 
+      rc = sessionTableInfo(0, db, zFrom, zTbl, &nCol, 0, &azCol, 0, &abPK, 
           pSession->bImplicitPK ? &bRowid : 0
       );
       if( rc==SQLITE_OK ){
@@ -2004,7 +2279,7 @@ void sqlite3session_delete(sqlite3_session *pSession){
 
   /* Assert that all allocations have been freed and then free the 
   ** session object itself. */
-  assert( pSession->nMalloc==0 );
+  // assert( pSession->nMalloc==0 );
   sqlite3_free(pSession);
 }
 
@@ -2075,48 +2350,6 @@ int sqlite3session_attach(
   return rc;
 }
 
-/*
-** Ensure that there is room in the buffer to append nByte bytes of data.
-** If not, use sqlite3_realloc() to grow the buffer so that there is.
-**
-** If successful, return zero. Otherwise, if an OOM condition is encountered,
-** set *pRc to SQLITE_NOMEM and return non-zero.
-*/
-static int sessionBufferGrow(SessionBuffer *p, i64 nByte, int *pRc){
-#define SESSION_MAX_BUFFER_SZ (0x7FFFFF00 - 1) 
-  i64 nReq = p->nBuf + nByte;
-  if( *pRc==SQLITE_OK && nReq>p->nAlloc ){
-    u8 *aNew;
-    i64 nNew = p->nAlloc ? p->nAlloc : 128;
-
-    do {
-      nNew = nNew*2;
-    }while( nNew<nReq );
-
-    /* The value of SESSION_MAX_BUFFER_SZ is copied from the implementation
-    ** of sqlite3_realloc64(). Allocations greater than this size in bytes
-    ** always fail. It is used here to ensure that this routine can always
-    ** allocate up to this limit - instead of up to the largest power of
-    ** two smaller than the limit.  */
-    if( nNew>SESSION_MAX_BUFFER_SZ ){
-      nNew = SESSION_MAX_BUFFER_SZ;
-      if( nNew<nReq ){
-        *pRc = SQLITE_NOMEM;
-        return 1;
-      }
-    }
-
-    aNew = (u8 *)sqlite3_realloc64(p->aBuf, nNew);
-    if( 0==aNew ){
-      *pRc = SQLITE_NOMEM;
-    }else{
-      p->aBuf = aNew;
-      p->nAlloc = nNew;
-    }
-  }
-  return (*pRc!=SQLITE_OK);
-}
-
 /*
 ** Append the value passed as the second argument to the buffer passed
 ** as the first.
@@ -2185,27 +2418,6 @@ static void sessionAppendBlob(
   }
 }
 
-/*
-** This function is a no-op if *pRc is other than SQLITE_OK when it is 
-** called. Otherwise, append a string to the buffer. All bytes in the string
-** up to (but not including) the nul-terminator are written to the buffer.
-**
-** If an OOM condition is encountered, set *pRc to SQLITE_NOMEM before
-** returning.
-*/
-static void sessionAppendStr(
-  SessionBuffer *p, 
-  const char *zStr, 
-  int *pRc
-){
-  int nStr = sqlite3Strlen30(zStr);
-  if( 0==sessionBufferGrow(p, nStr+1, pRc) ){
-    memcpy(&p->aBuf[p->nBuf], zStr, nStr);
-    p->nBuf += nStr;
-    p->aBuf[p->nBuf] = 0x00;
-  }
-}
-
 /*
 ** This function is a no-op if *pRc is other than SQLITE_OK when it is 
 ** called. Otherwise, append the string representation of integer iVal
@@ -2224,27 +2436,6 @@ static void sessionAppendInteger(
   sessionAppendStr(p, aBuf, pRc);
 }
 
-static void sessionAppendPrintf(
-  SessionBuffer *p,               /* Buffer to append to */
-  int *pRc, 
-  const char *zFmt,
-  ...
-){
-  if( *pRc==SQLITE_OK ){
-    char *zApp = 0;
-    va_list ap;
-    va_start(ap, zFmt);
-    zApp = sqlite3_vmprintf(zFmt, ap);
-    if( zApp==0 ){
-      *pRc = SQLITE_NOMEM;
-    }else{
-      sessionAppendStr(p, zApp, pRc);
-    }
-    va_end(ap);
-    sqlite3_free(zApp);
-  }
-}
-
 /*
 ** This function is a no-op if *pRc is other than SQLITE_OK when it is 
 ** called. Otherwise, append the string zStr enclosed in quotes (") and
@@ -2735,26 +2926,23 @@ static int sessionGenerateChangeset(
   for(pTab=pSession->pTable; rc==SQLITE_OK && pTab; pTab=pTab->pNext){
     if( pTab->nEntry ){
       const char *zName = pTab->zName;
+
+#if 0
       int nCol = 0;               /* Number of columns in table */
       u8 *abPK = 0;               /* Primary key array */
+      int bRowid = 0;
       const char **azCol = 0;     /* Table columns */
+#endif
       int i;                      /* Used to iterate through hash buckets */
       sqlite3_stmt *pSel = 0;     /* SELECT statement to query table pTab */
       int nRewind = buf.nBuf;     /* Initial size of write buffer */
       int nNoop;                  /* Size of buffer after writing tbl header */
-      int bRowid = 0;
+      int nOldCol = pTab->nCol;
 
       /* Check the table schema is still Ok. */
-      rc = sessionTableInfo(
-          0, db, pSession->zDb, zName, &nCol, 0, &azCol, &abPK, 
-          (pSession->bImplicitPK ? &bRowid : 0)
-      );
-      if( rc==SQLITE_OK && (
-          pTab->nCol!=nCol 
-       || pTab->bRowid!=bRowid 
-       || memcmp(abPK, pTab->abPK, nCol)
-      )){
-        rc = SQLITE_SCHEMA;
+      rc = sessionReinitTable(pSession, pTab);
+      if( rc==SQLITE_OK && pTab->nCol!=nOldCol ){
+        rc = sessionUpdateChanges(pSession, pTab);
       }
 
       /* Write a table header */
@@ -2762,8 +2950,8 @@ static int sessionGenerateChangeset(
 
       /* Build and compile a statement to execute: */
       if( rc==SQLITE_OK ){
-        rc = sessionSelectStmt(
-            db, 0, pSession->zDb, zName, bRowid, nCol, azCol, abPK, &pSel
+        rc = sessionSelectStmt(db, 0, pSession->zDb, 
+            zName, pTab->bRowid, pTab->nCol, pTab->azCol, pTab->abPK, &pSel
         );
       }
 
@@ -2772,22 +2960,22 @@ static int sessionGenerateChangeset(
         SessionChange *p;         /* Used to iterate through changes */
 
         for(p=pTab->apChange[i]; rc==SQLITE_OK && p; p=p->pNext){
-          rc = sessionSelectBind(pSel, nCol, abPK, p);
+          rc = sessionSelectBind(pSel, pTab->nCol, pTab->abPK, p);
           if( rc!=SQLITE_OK ) continue;
           if( sqlite3_step(pSel)==SQLITE_ROW ){
             if( p->op==SQLITE_INSERT ){
               int iCol;
               sessionAppendByte(&buf, SQLITE_INSERT, &rc);
               sessionAppendByte(&buf, p->bIndirect, &rc);
-              for(iCol=0; iCol<nCol; iCol++){
+              for(iCol=0; iCol<pTab->nCol; iCol++){
                 sessionAppendCol(&buf, pSel, iCol, &rc);
               }
             }else{
-              assert( abPK!=0 );  /* Because sessionSelectStmt() returned ok */
-              rc = sessionAppendUpdate(&buf, bPatchset, pSel, p, abPK);
+              assert( pTab->abPK!=0 );
+              rc = sessionAppendUpdate(&buf, bPatchset, pSel, p, pTab->abPK);
             }
           }else if( p->op!=SQLITE_INSERT ){
-            rc = sessionAppendDelete(&buf, bPatchset, p, nCol, abPK);
+            rc = sessionAppendDelete(&buf, bPatchset, p, pTab->nCol,pTab->abPK);
           }
           if( rc==SQLITE_OK ){
             rc = sqlite3_reset(pSel);
@@ -2812,7 +3000,6 @@ static int sessionGenerateChangeset(
       if( buf.nBuf==nNoop ){
         buf.nBuf = nRewind;
       }
-      sqlite3_free((char*)azCol);  /* cast works around VC++ bug */
     }
   }
 
@@ -4941,7 +5128,7 @@ static int sessionChangesetApply(
 
         sqlite3changeset_pk(pIter, &abPK, 0);
         rc = sessionTableInfo(0, db, "main", zNew, 
-            &sApply.nCol, &zTab, &sApply.azCol, &sApply.abPK, &sApply.bRowid
+            &sApply.nCol, &zTab, &sApply.azCol, 0, &sApply.abPK, &sApply.bRowid
         );
         if( rc!=SQLITE_OK ) break;
         for(i=0; i<sApply.nCol; i++){
index 1ea90dce474647c4b4bebee251c9d21279ff4760..0c9e7860721094246d2f85451d0dcb51c83875bc 100644 (file)
@@ -884,6 +884,18 @@ int sqlite3changeset_concat(
 );
 
 
+/*
+** CAPI3REF: Upgrade the Schema of a Changeset/Patchset
+*/
+int sqlite3changeset_upgrade(
+  sqlite3 *db,
+  const char *zDb,
+  int nIn, const void *pIn,       /* Input changeset */
+  int *pnOut, void **ppOut        /* OUT: Inverse of input */
+);
+
+
+
 /*
 ** CAPI3REF: Changegroup Handle
 **
@@ -930,6 +942,38 @@ typedef struct sqlite3_changegroup sqlite3_changegroup;
 */
 int sqlite3changegroup_new(sqlite3_changegroup **pp);
 
+/*
+** CAPI3REF: Add a Schema to a Changegroup
+** METHOD: sqlite3_changegroup_schema
+**
+** This method may be used to optionally enforce the rule that the changesets
+** added to the changegroup handle must match the schema of database zDb
+** ("main", "temp", or the name of an attached database). If
+** sqlite3changegroup_add() is called to add a changeset that is not compatible
+** with the configured schema, SQLITE_SCHEMA is returned and the changegroup
+** object is left in an undefined state.
+**
+** A changeset schema is considered compatible with the database schema in
+** the same way as for sqlite3changeset_apply(). Specifically, for each
+** table in the changeset, there exists a database table with:
+**
+** <ul>
+**   <li> The name identified by the changeset, and
+**   <li> at least as many columns as recorded in the changeset, and
+**   <li> the primary key columns in the same position as recorded in 
+**        the changeset.
+** </ul>
+**
+** The output of the changegroup object always has the same schema as the
+** database nominated using this function. In cases where changesets passed
+** to sqlite3changegroup_add() have fewer columns than the corresponding table
+** in the database schema, these are filled in using the default column
+** values from the database schema. This makes it possible to combined 
+** changesets that have different numbers of columns for a single table
+** within a changegroup, provided that they are otherwise compatible.
+*/
+int sqlite3changegroup_schema(sqlite3_changegroup*, sqlite3*, const char *zDb);
+
 /*
 ** CAPI3REF: Add A Changeset To A Changegroup
 ** METHOD: sqlite3_changegroup
@@ -998,13 +1042,18 @@ int sqlite3changegroup_new(sqlite3_changegroup **pp);
 ** If the new changeset contains changes to a table that is already present
 ** in the changegroup, then the number of columns and the position of the
 ** primary key columns for the table must be consistent. If this is not the
-** case, this function fails with SQLITE_SCHEMA. If the input changeset
-** appears to be corrupt and the corruption is detected, SQLITE_CORRUPT is
-** returned. Or, if an out-of-memory condition occurs during processing, this
-** function returns SQLITE_NOMEM. In all cases, if an error occurs the state
-** of the final contents of the changegroup is undefined.
-**
-** If no error occurs, SQLITE_OK is returned.
+** case, this function fails with SQLITE_SCHEMA. Except, if the changegroup
+** object has been configured with a database schema using the
+** sqlite3changegroup_schema() API, then it is possible to combine changesets
+** with different numbers of columns for a single table, provided that
+** they are otherwise compatible.
+**
+** If the input changeset appears to be corrupt and the corruption is
+** detected, SQLITE_CORRUPT is returned. Or, if an out-of-memory condition
+** occurs during processing, this function returns SQLITE_NOMEM. 
+**
+** In all cases, if an error occurs the state of the final contents of the
+** changegroup is undefined. If no error occurs, SQLITE_OK is returned.
 */
 int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData);
 
index 6deed36dd5230e38cbd34674311e2906f00c4d31..7f7d8d946befb6d468ca83bb04055bdd8077bb55 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C The\sMakefile\sdistinguishes\sbetween\stcl8.4\sand\stcl8.5.\s\sSome\smakefile\stargets\nrequire\stcl8.5,\sbut\sothers\s(ex:\s"sqlite3.c",\s"shell.c",\sand\s"sqlite3")\srequire\nonly\stcl8.4.
-D 2023-10-04T12:49:08.687
+C Allow\sa\ssession\sobject\sto\sgenerate\sa\schangeset,\seven\sif\scolumns\swere\sadded\sto\sone\sof\sthe\stables\susing\sALTER\sTABLE\sADD\sCOLUMN\swhile\sthe\schangeset\swas\sbeing\scollected.
+D 2023-10-04T21:15:24.286
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -513,7 +513,7 @@ F ext/session/changesetfuzz.c 227076ab0ae4447d742c01ee88a564da6478bbf26b65108bf8
 F ext/session/changesetfuzz1.test 2e1b90d888fbf0eea5e1bd2f1e527a48cc85f8e0ff75df1ec4e320b21f580b3a
 F ext/session/session1.test e94f764fbfb672147c0ef7026b195988133b371dc8cf9e52423eba6cad69717e
 F ext/session/session2.test ee83bb973b9ce17ccce4db931cdcdae65eb40bbb22089b2fe6aa4f6be3b9303f
-F ext/session/session3.test ce9ce3dfa489473987f899e9f6a0f2db9bde3479
+F ext/session/session3.test 2cc1629cfb880243aec1a7251145e07b78411d851b39b2aa1390704550db8e6a
 F ext/session/session4.test 6778997065b44d99c51ff9cece047ff9244a32856b328735ae27ddef68979c40
 F ext/session/session5.test 716bc6fafd625ce60dfa62ae128971628c1a1169
 F ext/session/session6.test 35279f2ec45448cd2e24a61688219dc6cf7871757716063acf4a8b5455e1e926
@@ -529,6 +529,7 @@ F ext/session/sessionG.test 3efe388282d641b65485b5462e67851002cd91a282dc95b685d0
 F ext/session/sessionH.test 71bbff6b1abb2c4ac62b84dee53273c37e0b21e5fde3aed80929403e091ef859
 F ext/session/session_common.tcl e5598096425486b363718e2cda48ee85d660c96b4f8ea9d9d7a4c3ef514769da
 F ext/session/session_speed_test.c dcf0ef58d76b70c8fbd9eab3be77cf9deb8bc1638fed8be518b62d6cbdef88b3
+F ext/session/sessionalter.test 2623023075c51707026a90b9b59674e88c6f467098219d8d71f9a6320d122c61
 F ext/session/sessionat.test 00c8badb35e43a2f12a716d2734a44d614ff62361979b6b85419035bc04b45ee
 F ext/session/sessionbig.test 47c381e7acfabeef17d98519a3080d69151723354d220afa2053852182ca7adf
 F ext/session/sessiondiff.test ad13dd65664bae26744e1f18eb3cbd5588349b7e9118851d8f9364248d67bcec
@@ -543,8 +544,8 @@ F ext/session/sessionrowid.test 85187c2f1b38861a5844868126f69f9ec62223a03449a98a
 F ext/session/sessionsize.test 8fcf4685993c3dbaa46a24183940ab9f5aa9ed0d23e5fb63bfffbdb56134b795
 F ext/session/sessionstat1.test b039e38e2ba83767b464baf39b297cc0b1cc6f3292255cb467ea7e12d0d0280c
 F ext/session/sessionwor.test 6fd9a2256442cebde5b2284936ae9e0d54bde692d0f5fd009ecef8511f4cf3fc
-F ext/session/sqlite3session.c 0fe9107318140cefa1b50f2e1e0f330ab359022599e5976820db349f33efae11
-F ext/session/sqlite3session.h 653e9d49c4edae231df8a4c8d69c2145195aedb32462d4b44229dbee7d2680fb
+F ext/session/sqlite3session.c a6ede47e1dbb2ff61180858559502020738e2f978a9648b07fa74a8ce5a776b4
+F ext/session/sqlite3session.h 4d1f69f1d8bfd4798e8f6431de301d17bb2e4097de2f77ca4dad494bb6c60dc0
 F ext/session/test_session.c 5285482f83cd92b4c1fe12fcf88210566a18312f4f2aa110f6399dae46aeccbb
 F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3
 F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04
@@ -2123,8 +2124,11 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 1765f3b5a00a8ca20a7b9e18ac7f9d7de0679470b234b83aea83aa5b4d4d34e6
-R 76bba9834a6ace5da9d64340bca161b1
-U drh
-Z 0c0b3b63d38231e8da1135c2ad119350
+P 770308db9776b8c0a70b8807463e89a9eddfe5552e25e67324cd303dc974f50e
+R 649352ef20ed525adadedc4edb926bc7
+T *branch * session-alter
+T *sym-session-alter *
+T -sym-trunk *
+U dan
+Z 220e071d4024660dea84b49e6cbcfb2f
 # Remove this line to create a well-formed Fossil manifest.
index 085e51b912c03d8912b3d31bc8a84b5fb20d47ea..d83a120cf7393ef1a5540d4136bc5e2c47b019fe 100644 (file)
@@ -1 +1 @@
-770308db9776b8c0a70b8807463e89a9eddfe5552e25e67324cd303dc974f50e
\ No newline at end of file
+a3f435eccf3a2aa11cb7420e94af5efcdfa04e9c169c5aaf61fa5cdcb165ceef
\ No newline at end of file