/ Check-in [3c7d3d95]
Login

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

Overview
Comment:Begin adding 'streaming' APIs to sessions module. This is a work in progress.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | sessions
Files: files | file ages | folders
SHA1: 3c7d3d950bbf5f5ed3696ebc61c77ca48bafe2b5
User & Date: dan 2014-09-23 20:39:55
Context
2014-09-24
17:13
Add streaming version of sqlite3changeset_apply(). Tests and fixes for the same and sqlite3changeset_start_str(). check-in: b917fc14 user: dan tags: sessions
2014-09-23
20:39
Begin adding 'streaming' APIs to sessions module. This is a work in progress. check-in: 3c7d3d95 user: dan tags: sessions
2014-09-21
22:49
Merge all recent trunk changes into the sessions branch. check-in: 6406b77f user: drh tags: sessions
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to ext/session/sqlite3session.c.

8
9
10
11
12
13
14






15
16
17
18
19
20
21
..
25
26
27
28
29
30
31

























32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
...
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
....
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
....
1772
1773
1774
1775
1776
1777
1778


1779
1780
1781
1782
1783
1784
1785


1786
1787
1788
1789

1790
1791

1792
1793
1794
1795
1796
1797
1798
....
1842
1843
1844
1845
1846
1847
1848













1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860

1861
1862
1863
1864


1865
1866


1867
1868
1869
1870
1871
1872
1873
....
1877
1878
1879
1880
1881
1882
1883











1884











1885
1886
1887
1888
1889
1890
1891
....
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
....
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951


1952
1953
1954
1955
1956


1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968



1969
1970
1971
1972
1973






























































1974
1975
1976
1977
1978
1979
1980
....
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011




2012
2013
2014
2015
2016
2017
2018



2019
2020
2021

2022

2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
















2045






















































2046
2047
2048
2049
2050
2051
2052
....
2062
2063
2064
2065
2066
2067
2068
2069
2070

2071

2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
....
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
....
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
....
2335
2336
2337
2338
2339
2340
2341

2342
2343
2344

2345
2346
2347
2348
2349
2350





2351
2352
2353
2354
2355
2356
2357


2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370








2371
2372
2373
2374

2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387

2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415

2416
2417
2418
2419
2420
2421
2422
....
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
....
2454
2455
2456
2457
2458
2459
2460

2461
2462
2463
2464
2465
2466
2467
....
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
....
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366

3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384




3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
# include "sqliteInt.h"
# include "vdbeInt.h"
#endif

typedef struct SessionTable SessionTable;
typedef struct SessionChange SessionChange;
typedef struct SessionBuffer SessionBuffer;







/*
** Session handle structure.
*/
struct sqlite3_session {
  sqlite3 *db;                    /* Database handle session is attached to */
  char *zDb;                      /* Name of database session is attached to */
................................................................................
  int rc;                         /* Non-zero if an error has occurred */
  void *pFilterCtx;               /* First argument to pass to xTableFilter */
  int (*xTableFilter)(void *pCtx, const char *zTab);
  sqlite3_session *pNext;         /* Next session object on same db. */
  SessionTable *pTable;           /* List of attached tables */
};


























/*
** Structure for changeset iterators.
*/
struct sqlite3_changeset_iter {
  u8 *aChangeset;                 /* Pointer to buffer containing changeset */
  int nChangeset;                 /* Number of bytes in aChangeset */
  int bPatchset;                  /* True if this is a patchset */
  u8 *pNext;                      /* Pointer to next change within aChangeset */
  int rc;                         /* Iterator error code */
  sqlite3_stmt *pConflict;        /* Points to conflicting row, if any */
  char *zTab;                     /* Current table */
  int nCol;                       /* Number of columns in zTab */
  int op;                         /* Current operation */
  int bIndirect;                  /* True if current change was indirect */
  u8 *abPK;                       /* Primary key array */
................................................................................
  int op;                         /* One of UPDATE, DELETE, INSERT */
  int bIndirect;                  /* True if this change is "indirect" */
  int nRecord;                    /* Number of bytes in buffer aRecord[] */
  u8 *aRecord;                    /* Buffer containing old.* record */
  SessionChange *pNext;           /* For hash-table collisions */
};

/*
** Instances of this structure are used to build strings or binary records.
*/
struct SessionBuffer {
  u8 *aBuf;                       /* Pointer to changeset buffer */
  int nBuf;                       /* Size of buffer aBuf */
  int nAlloc;                     /* Size of allocation containing aBuf */
};

/*
** Write a varint with value iVal into the buffer at aBuf. Return the 
** number of bytes written.
*/
static int sessionVarintPut(u8 *aBuf, int iVal){
  return putVarint32(aBuf, iVal);
}
................................................................................
*/
static int sessionBufferGrow(SessionBuffer *p, int nByte, int *pRc){
  if( *pRc==SQLITE_OK && p->nAlloc-p->nBuf<nByte ){
    u8 *aNew;
    int nNew = p->nAlloc ? p->nAlloc : 128;
    do {
      nNew = nNew*2;
    }while( nNew<(p->nAlloc+nByte) );

    aNew = (u8 *)sqlite3_realloc(p->aBuf, nNew);
    if( 0==aNew ){
      *pRc = SQLITE_NOMEM;
    }else{
      p->aBuf = aNew;
      p->nAlloc = nNew;
................................................................................
** stored in output variables *pnChangeset and *ppChangeset. Or, if an error
** occurs, an SQLite error code is returned and both output variables set 
** to 0.
*/
int sessionGenerateChangeset(
  sqlite3_session *pSession,      /* Session object */
  int bPatchset,                  /* True for patchset, false for changeset */


  int *pnChangeset,               /* OUT: Size of buffer at *ppChangeset */
  void **ppChangeset              /* OUT: Buffer containing changeset */
){
  sqlite3 *db = pSession->db;     /* Source database handle */
  SessionTable *pTab;             /* Used to iterate through attached tables */
  SessionBuffer buf = {0,0,0};    /* Buffer in which to accumlate changeset */
  int rc;                         /* Return code */



  /* Zero the output variables in case an error occurs. If this session
  ** object is already in the error state (sqlite3_session.rc != SQLITE_OK),
  ** this call will be a no-op.  */

  *pnChangeset = 0;
  *ppChangeset = 0;


  if( pSession->rc ) return pSession->rc;
  rc = sqlite3_exec(pSession->db, "SAVEPOINT changeset", 0, 0, 0);
  if( rc!=SQLITE_OK ) return rc;

  sqlite3_mutex_enter(sqlite3_db_mutex(db));

................................................................................
            }
          }else if( p->op!=SQLITE_INSERT ){
            rc = sessionAppendDelete(&buf, bPatchset, p, nCol, abPK);
          }
          if( rc==SQLITE_OK ){
            rc = sqlite3_reset(pSel);
          }













        }
      }

      sqlite3_finalize(pSel);
      if( buf.nBuf==nNoop ){
        buf.nBuf = nRewind;
      }
      sqlite3_free((char*)azCol);  /* cast works around VC++ bug */
    }
  }

  if( rc==SQLITE_OK ){

    *pnChangeset = buf.nBuf;
    *ppChangeset = buf.aBuf;
  }else{
    sqlite3_free(buf.aBuf);


  }



  sqlite3_exec(db, "RELEASE changeset", 0, 0, 0);
  sqlite3_mutex_leave(sqlite3_db_mutex(db));
  return rc;
}

/*
** Obtain a changeset object containing all changes recorded by the 
................................................................................
** using sqlite3_free().
*/
int sqlite3session_changeset(
  sqlite3_session *pSession,      /* Session object */
  int *pnChangeset,               /* OUT: Size of buffer at *ppChangeset */
  void **ppChangeset              /* OUT: Buffer containing changeset */
){











  return sessionGenerateChangeset(pSession, 0, pnChangeset, ppChangeset);











}

/*
** Obtain a patchset object containing all changes recorded by the 
** session object passed as the first argument.
**
** It is the responsibility of the caller to eventually free the buffer 
................................................................................
** using sqlite3_free().
*/
int sqlite3session_patchset(
  sqlite3_session *pSession,      /* Session object */
  int *pnPatchset,                /* OUT: Size of buffer at *ppChangeset */
  void **ppPatchset               /* OUT: Buffer containing changeset */
){
  return sessionGenerateChangeset(pSession, 1, pnPatchset, ppPatchset);
}

/*
** Enable or disable the session object passed as the first argument.
*/
int sqlite3session_enable(sqlite3_session *pSession, int bEnable){
  int ret;
................................................................................
  }
  sqlite3_mutex_leave(sqlite3_db_mutex(pSession->db));

  return (ret==0);
}

/*
** Create an iterator used to iterate through the contents of a changeset.
*/
int sqlite3changeset_start(
  sqlite3_changeset_iter **pp,    /* OUT: Changeset iterator handle */


  int nChangeset,                 /* Size of buffer pChangeset in bytes */
  void *pChangeset                /* Pointer to buffer containing changeset */
){
  sqlite3_changeset_iter *pRet;   /* Iterator to return */
  int nByte;                      /* Number of bytes to allocate for iterator */



  /* Zero the output variable in case an error occurs. */
  *pp = 0;

  /* Allocate and initialize the iterator structure. */
  nByte = sizeof(sqlite3_changeset_iter);
  pRet = (sqlite3_changeset_iter *)sqlite3_malloc(nByte);
  if( !pRet ) return SQLITE_NOMEM;
  memset(pRet, 0, sizeof(sqlite3_changeset_iter));
  pRet->aChangeset = (u8 *)pChangeset;
  pRet->nChangeset = nChangeset;
  pRet->pNext = pRet->aChangeset;




  /* Populate the output variable and return success. */
  *pp = pRet;
  return SQLITE_OK;
}































































/*
** Deserialize a single record from a buffer in memory. See "RECORD FORMAT"
** for details.
**
** When this function is called, *paChange points to the start of the record
** to deserialize. Assuming no error occurs, *paChange is set to point to
................................................................................
** It is the responsibility of the caller to free all sqlite_value structures
** using sqlite3_free().
**
** If an error occurs, an SQLite error code (e.g. SQLITE_NOMEM) is returned.
** The apOut[] array may have been partially populated in this case.
*/
static int sessionReadRecord(
  u8 **paChange,                  /* IN/OUT: Pointer to binary record */
  int nCol,                       /* Number of values in record */
  u8 *abPK,                       /* Array of primary key flags, or NULL */
  sqlite3_value **apOut           /* Write values to this array */
){
  int i;                          /* Used to iterate through columns */
  u8 *aRec = *paChange;           /* Cursor for the serialized record */

  for(i=0; i<nCol; i++){
    int eType;
    if( abPK && abPK[i]==0 ) continue;
    eType = *aRec++;              /* Type of value (SQLITE_NULL, TEXT etc.) */




    assert( !apOut || apOut[i]==0 );
    if( eType ){
      if( apOut ){
        apOut[i] = sqlite3ValueNew(0);
        if( !apOut[i] ) return SQLITE_NOMEM;
      }




      if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){
        int nByte;
        aRec += sessionVarintGet(aRec, &nByte);

        if( apOut ){

          u8 enc = (eType==SQLITE_TEXT ? SQLITE_UTF8 : 0);
          sqlite3ValueSetStr(apOut[i], nByte, (char *)aRec, enc, SQLITE_STATIC);
        }
        aRec += nByte;
      }
      if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
        if( apOut ){
          sqlite3_int64 v = sessionGetI64(aRec);
          if( eType==SQLITE_INTEGER ){
            sqlite3VdbeMemSetInt64(apOut[i], v);
          }else{
            double d;
            memcpy(&d, &v, 8);
            sqlite3VdbeMemSetDouble(apOut[i], d);
          }
        }
        aRec += 8;
      }
    }
  }

  *paChange = aRec;
















  return SQLITE_OK;






















































}

/*
** Advance the changeset iterator to the next change.
**
** If both paRec and pnRec are NULL, then this function works like the public
** API sqlite3changeset_next(). If SQLITE_ROW is returned, then the
................................................................................
** changes in the changeset.
*/
static int sessionChangesetNext(
  sqlite3_changeset_iter *p,      /* Changeset iterator */
  u8 **paRec,                     /* If non-NULL, store record pointer here */
  int *pnRec                      /* If non-NULL, store size of record here */
){
  u8 *aChange;
  int i;



  assert( (paRec==0 && pnRec==0) || (paRec && pnRec) );

  /* If the iterator is in the error-state, return immediately. */
  if( p->rc!=SQLITE_OK ) return p->rc;

  /* Free the current contents of p->apValue[], if any. */
  if( p->apValue ){
    for(i=0; i<p->nCol*2; i++){
      sqlite3ValueFree(p->apValue[i]);
    }
    memset(p->apValue, 0, sizeof(sqlite3_value*)*p->nCol*2);
  }

  /* If the iterator is already at the end of the changeset, return DONE. */
  if( p->pNext>=&p->aChangeset[p->nChangeset] ){
    return SQLITE_DONE;
  }
  aChange = p->pNext;

  if( aChange[0]=='T' || aChange[0]=='P' ){
    int nByte;                    /* Bytes to allocate for apValue */
    p->bPatchset = (aChange[0]=='P');
    aChange++;
    aChange += sessionVarintGet(aChange, &p->nCol);
    p->abPK = (u8 *)aChange;
    aChange += p->nCol;
    p->zTab = (char *)aChange;
    aChange += (sqlite3Strlen30((char *)aChange) + 1);
    
    if( paRec==0 ){
      sqlite3_free(p->apValue);
      nByte = sizeof(sqlite3_value *) * p->nCol * 2;
      p->apValue = (sqlite3_value **)sqlite3_malloc(nByte);
      if( !p->apValue ){
        return (p->rc = SQLITE_NOMEM);
      }
      memset(p->apValue, 0, sizeof(sqlite3_value*)*p->nCol*2);
    }
  }

  p->op = *(aChange++);
  p->bIndirect = *(aChange++);
  if( p->op!=SQLITE_UPDATE && p->op!=SQLITE_DELETE && p->op!=SQLITE_INSERT ){
    return (p->rc = SQLITE_CORRUPT);
  }

  if( paRec ){ *paRec = aChange; }

  /* If this is an UPDATE or DELETE, read the old.* record. */
  if( p->op!=SQLITE_INSERT && (p->bPatchset==0 || p->op==SQLITE_DELETE) ){
    u8 *abPK = p->bPatchset ? p->abPK : 0;
    p->rc = sessionReadRecord(&aChange, p->nCol, abPK, paRec?0:p->apValue);
    if( p->rc!=SQLITE_OK ) return p->rc;
  }

  /* If this is an INSERT or UPDATE, read the new.* record. */
  if( p->op!=SQLITE_DELETE ){
    sqlite3_value **apOut = (paRec ? 0 : &p->apValue[p->nCol]);
    p->rc = sessionReadRecord(&aChange, p->nCol, 0, apOut);
    if( p->rc!=SQLITE_OK ) return p->rc;
  }

  if( pnRec ){ 
    *pnRec = (int)(aChange - *paRec); 
  }else if( p->bPatchset && p->op==SQLITE_UPDATE ){
    /* If this is an UPDATE that is part of a patchset, then all PK and
    ** modified fields are present in the new.* record. The old.* record
    ** is currently completely empty. This block shifts the PK fields from
    ** new.* to old.*, to accommodate the code that reads these arrays.  */
    int i;
    for(i=0; i<p->nCol; i++){
................................................................................
      assert( p->abPK[i]==0 || p->apValue[i+p->nCol] );
      if( p->abPK[i] ){
        p->apValue[i] = p->apValue[i+p->nCol];
        p->apValue[i+p->nCol] = 0;
      }
    }
  }
  p->pNext = aChange;
  return SQLITE_ROW;
}

/*
** Advance an iterator created by sqlite3changeset_start() to the next
** change in the changeset. This function may return SQLITE_ROW, SQLITE_DONE
** or SQLITE_CORRUPT.
................................................................................
*/
int sqlite3changeset_finalize(sqlite3_changeset_iter *p){
  int i;                          /* Used to iterate through p->apValue[] */
  int rc = p->rc;                 /* Return code */
  if( p->apValue ){
    for(i=0; i<p->nCol*2; i++) sqlite3ValueFree(p->apValue[i]);
  }
  sqlite3_free(p->apValue);
  sqlite3_free(p);
  return rc;
}

/*
** Invert a changeset object.
*/
................................................................................
  int *pnInverted,                /* OUT: Number of bytes in output changeset */
  void **ppInverted               /* OUT: Inverse of pChangeset */
){
  int rc = SQLITE_OK;             /* Return value */
  u8 *aOut;
  u8 *aIn;
  int i;

  int nCol = 0;                   /* Number of cols in current table */
  u8 *abPK = 0;                   /* PK array for current table */
  sqlite3_value **apVal = 0;      /* Space for values for UPDATE inversion */


  /* Zero the output variables in case an error occurs. */
  *ppInverted = 0;
  *pnInverted = 0;
  if( nChangeset==0 ) return SQLITE_OK;






  aOut = (u8 *)sqlite3_malloc(nChangeset);
  if( !aOut ) return SQLITE_NOMEM;
  aIn = (u8 *)pChangeset;

  i = 0;
  while( i<nChangeset ){
    u8 eType = aIn[i];


    switch( eType ){
      case 'T': {
        /* A 'table' record consists of:
        **
        **   * A constant 'T' character,
        **   * Number of columns in said table (a varint),
        **   * An array of nCol bytes (abPK),
        **   * A nul-terminated table name.
        */
        int nByte = 1 + sessionVarintGet(&aIn[i+1], &nCol);
        abPK = &aIn[i+nByte];
        nByte += nCol;
        nByte += 1 + sqlite3Strlen30((char *)&aIn[i+nByte]);








        memcpy(&aOut[i], &aIn[i], nByte);
        i += nByte;
        sqlite3_free(apVal);
        apVal = 0;

        break;
      }

      case SQLITE_INSERT:
      case SQLITE_DELETE: {
        int nByte;
        u8 *aEnd = &aIn[i+2];

        sessionReadRecord(&aEnd, nCol, 0, 0);
        aOut[i] = (eType==SQLITE_DELETE ? SQLITE_INSERT : SQLITE_DELETE);
        aOut[i+1] = aIn[i+1];
        nByte = (int)(aEnd - &aIn[i+2]);
        memcpy(&aOut[i+2], &aIn[i+2], nByte);

        i += 2 + nByte;
        break;
      }

      case SQLITE_UPDATE: {
        int iCol;
        int nWrite = 0;
        u8 *aEnd = &aIn[i+2];

        if( 0==apVal ){
          apVal = (sqlite3_value **)sqlite3_malloc(sizeof(apVal[0])*nCol*2);
          if( 0==apVal ){
            rc = SQLITE_NOMEM;
            goto finished_invert;
          }
          memset(apVal, 0, sizeof(apVal[0])*nCol*2);
        }

        /* Read the old.* and new.* records for the update change. */
        rc = sessionReadRecord(&aEnd, nCol, 0, &apVal[0]);
        if( rc==SQLITE_OK ){
          rc = sessionReadRecord(&aEnd, nCol, 0, &apVal[nCol]);
        }

        /* Write the header for the new UPDATE change. Same as the original. */
        aOut[i] = SQLITE_UPDATE;
        aOut[i+1] = aIn[i+1];
        nWrite = 2;


        /* Write the new old.* record. Consists of the PK columns from the
        ** original old.* record, and the other values from the original
        ** new.* record. */
        for(iCol=0; rc==SQLITE_OK && iCol<nCol; iCol++){
          sqlite3_value *pVal = apVal[iCol + (abPK[iCol] ? 0 : nCol)];
          rc = sessionSerializeValue(&aOut[i+nWrite], pVal, &nWrite);
................................................................................
        }
        memset(apVal, 0, sizeof(apVal[0])*nCol*2);
        if( rc!=SQLITE_OK ){
          goto finished_invert;
        }

        i += nWrite;
        assert( &aIn[i]==aEnd );
        break;
      }

      default:
        rc = SQLITE_CORRUPT;
        goto finished_invert;
    }
................................................................................
  *ppInverted = (void *)aOut;

 finished_invert:
  if( rc!=SQLITE_OK ){
    sqlite3_free(aOut);
  }
  sqlite3_free(apVal);

  return rc;
}

typedef struct SessionApplyCtx SessionApplyCtx;
struct SessionApplyCtx {
  sqlite3 *db;
  sqlite3_stmt *pDelete;          /* DELETE statement */
................................................................................
      pNew->bIndirect = (bIndirect && pExist->bIndirect);
      aCsr = pNew->aRecord = (u8 *)&pNew[1];

      if( op1==SQLITE_INSERT ){             /* INSERT + UPDATE */
        u8 *a1 = aRec;
        assert( op2==SQLITE_UPDATE );
        pNew->op = SQLITE_INSERT;
        if( bPatchset==0 ) sessionReadRecord(&a1, pTab->nCol, 0, 0);
        sessionMergeRecord(&aCsr, pTab->nCol, aExist, a1);
      }else if( op1==SQLITE_DELETE ){       /* DELETE + INSERT */
        assert( op2==SQLITE_INSERT );
        pNew->op = SQLITE_UPDATE;
        if( 0==sessionMergeUpdate(&aCsr, pTab, bPatchset, aExist, 0, aRec, 0) ){
          sqlite3_free(pNew);
          pNew = 0;
        }
      }else if( op2==SQLITE_UPDATE ){       /* UPDATE + UPDATE */
        u8 *a1 = aExist;
        u8 *a2 = aRec;
        assert( op1==SQLITE_UPDATE );
        if( bPatchset==0 ){
          sessionReadRecord(&a1, pTab->nCol, 0, 0);
          sessionReadRecord(&a2, pTab->nCol, 0, 0);
        }
        pNew->op = SQLITE_UPDATE;
        if( 0==sessionMergeUpdate(&aCsr, pTab, bPatchset, aRec, aExist,a1,a2) ){
          sqlite3_free(pNew);
          pNew = 0;
        }
      }else{                                /* UPDATE + DELETE */
................................................................................
    assert( bPatchset==0 || bPatchset==1 );
    assert( pIter->bPatchset==0 || pIter->bPatchset==1 );
    if( pIter->bPatchset!=bPatchset ){
      rc = SQLITE_ERROR;
      break;
    }

    assert( pIter->apValue==0 );
    sqlite3changeset_op(pIter, &zNew, &nCol, &op, &bIndirect);

    assert( zNew>=(char *)pChangeset && zNew-nChangeset<((char *)pChangeset) );
    assert( !pTab || pTab->zName-nChangeset<(char *)pChangeset );
    assert( !pTab || zNew>=pTab->zName );

    if( !pTab || zNew!=pTab->zName ){

      /* Search the list for a matching table */
      int nNew = (int)strlen(zNew);
      u8 *abPK;

      sqlite3changeset_pk(pIter, &abPK, 0);
      for(pTab = *ppTabList; pTab; pTab=pTab->pNext){
        if( 0==sqlite3_strnicmp(pTab->zName, zNew, nNew+1) ) break;
      }
      if( !pTab ){
        pTab = sqlite3_malloc(sizeof(SessionTable));
        if( !pTab ){
          rc = SQLITE_NOMEM;
          break;
        }
        memset(pTab, 0, sizeof(SessionTable));
        pTab->pNext = *ppTabList;
        pTab->abPK = abPK;
        pTab->nCol = nCol;




        *ppTabList = pTab;
      }else if( pTab->nCol!=nCol || memcmp(pTab->abPK, abPK, nCol) ){
        rc = SQLITE_SCHEMA;
        break;
      }
      pTab->zName = (char *)zNew;
    }

    if( sessionGrowHash(bPatchset, pTab) ){
      rc = SQLITE_NOMEM;
      break;
    }
    iHash = sessionChangeHash(







>
>
>
>
>
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>




|
|

<







 







<
<
<
<
<
<
<
<
<







 







|







 







>
>







>
>




>
|
|
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>












>
|
|
<
|
>
>
|
|
>
>







 







>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>







 







|







 







|

|

>
>





>
>









|
|
|
>
>
>





>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







|





|

|
|

|
>
>
>
>




|

|
>
>
>


|
>
|
>



|



|








|




|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







<

>

>












|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<
<
<
<
<
<
<




|




|






|



|
|







 







|







 







|







 







>



>






>
>
>
>
>






|
>
>






|


|
|
|
|
>
>
>
>
>
>
>
>
|
|


>





|
|
|
|
|
|
|
|
>







<










|
|
|
|
|
|
|
|
|
|
>







 







|







 







>







 







|













|
|







 







<

<
<
<
<
<
<
>









|






<

>
>
>
>





<







8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
..
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

70
71
72
73
74
75
76
...
191
192
193
194
195
196
197









198
199
200
201
202
203
204
....
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
....
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
....
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903

1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
....
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
....
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
....
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
....
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
....
2276
2277
2278
2279
2280
2281
2282

2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321







2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
....
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
....
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
....
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621

2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
....
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
....
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
....
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
....
3580
3581
3582
3583
3584
3585
3586

3587






3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598
3599
3600
3601
3602
3603
3604

3605
3606
3607
3608
3609
3610
3611
3612
3613
3614

3615
3616
3617
3618
3619
3620
3621
# include "sqliteInt.h"
# include "vdbeInt.h"
#endif

typedef struct SessionTable SessionTable;
typedef struct SessionChange SessionChange;
typedef struct SessionBuffer SessionBuffer;
typedef struct SessionInput SessionInput;

/*
** Minimum chunk size used by streaming versions of functions.
*/
#define SESSIONS_STR_CHUNK_SIZE 1024

/*
** Session handle structure.
*/
struct sqlite3_session {
  sqlite3 *db;                    /* Database handle session is attached to */
  char *zDb;                      /* Name of database session is attached to */
................................................................................
  int rc;                         /* Non-zero if an error has occurred */
  void *pFilterCtx;               /* First argument to pass to xTableFilter */
  int (*xTableFilter)(void *pCtx, const char *zTab);
  sqlite3_session *pNext;         /* Next session object on same db. */
  SessionTable *pTable;           /* List of attached tables */
};

/*
** Instances of this structure are used to build strings or binary records.
*/
struct SessionBuffer {
  u8 *aBuf;                       /* Pointer to changeset buffer */
  int nBuf;                       /* Size of buffer aBuf */
  int nAlloc;                     /* Size of allocation containing aBuf */
};

/*
** An object of this type is used internally as an abstraction for the 
** input data read by changeset iterators. Input data may be supplied 
** either as a single large buffer (sqlite3changeset_start()) or using
** a stream function (sqlite3changeset_start_str()).
*/
struct SessionInput {
  int iNext;                      /* Offset in aChangeset[] of next change */
  u8 *aChangeset;                 /* Pointer to buffer containing changeset */
  int nChangeset;                 /* Number of bytes in aChangeset */
  SessionBuffer buf;              /* Current read buffer */
  int (*xInput)(void*, void*, int*);        /* Input stream call (or NULL) */
  void *pIn;                                /* First argument to xInput */
  int bEof;                       /* Set to true after xInput finished */
};

/*
** Structure for changeset iterators.
*/
struct sqlite3_changeset_iter {
  SessionInput in;                /* Input buffer or stream */
  SessionBuffer tblhdr;           /* Buffer to hold apValue/zTab/abPK/ */
  int bPatchset;                  /* True if this is a patchset */

  int rc;                         /* Iterator error code */
  sqlite3_stmt *pConflict;        /* Points to conflicting row, if any */
  char *zTab;                     /* Current table */
  int nCol;                       /* Number of columns in zTab */
  int op;                         /* Current operation */
  int bIndirect;                  /* True if current change was indirect */
  u8 *abPK;                       /* Primary key array */
................................................................................
  int op;                         /* One of UPDATE, DELETE, INSERT */
  int bIndirect;                  /* True if this change is "indirect" */
  int nRecord;                    /* Number of bytes in buffer aRecord[] */
  u8 *aRecord;                    /* Buffer containing old.* record */
  SessionChange *pNext;           /* For hash-table collisions */
};










/*
** Write a varint with value iVal into the buffer at aBuf. Return the 
** number of bytes written.
*/
static int sessionVarintPut(u8 *aBuf, int iVal){
  return putVarint32(aBuf, iVal);
}
................................................................................
*/
static int sessionBufferGrow(SessionBuffer *p, int nByte, int *pRc){
  if( *pRc==SQLITE_OK && p->nAlloc-p->nBuf<nByte ){
    u8 *aNew;
    int nNew = p->nAlloc ? p->nAlloc : 128;
    do {
      nNew = nNew*2;
    }while( nNew<(p->nBuf+nByte) );

    aNew = (u8 *)sqlite3_realloc(p->aBuf, nNew);
    if( 0==aNew ){
      *pRc = SQLITE_NOMEM;
    }else{
      p->aBuf = aNew;
      p->nAlloc = nNew;
................................................................................
** stored in output variables *pnChangeset and *ppChangeset. Or, if an error
** occurs, an SQLite error code is returned and both output variables set 
** to 0.
*/
int sessionGenerateChangeset(
  sqlite3_session *pSession,      /* Session object */
  int bPatchset,                  /* True for patchset, false for changeset */
  int (*xOutput)(void *pOut, const void *pData, int nData),
  void *pOut,                     /* First argument for xOutput */
  int *pnChangeset,               /* OUT: Size of buffer at *ppChangeset */
  void **ppChangeset              /* OUT: Buffer containing changeset */
){
  sqlite3 *db = pSession->db;     /* Source database handle */
  SessionTable *pTab;             /* Used to iterate through attached tables */
  SessionBuffer buf = {0,0,0};    /* Buffer in which to accumlate changeset */
  int rc;                         /* Return code */

  assert( xOutput==0 || (pnChangeset==0 && ppChangeset==0 ) );

  /* Zero the output variables in case an error occurs. If this session
  ** object is already in the error state (sqlite3_session.rc != SQLITE_OK),
  ** this call will be a no-op.  */
  if( xOutput==0 ){
    *pnChangeset = 0;
    *ppChangeset = 0;
  }

  if( pSession->rc ) return pSession->rc;
  rc = sqlite3_exec(pSession->db, "SAVEPOINT changeset", 0, 0, 0);
  if( rc!=SQLITE_OK ) return rc;

  sqlite3_mutex_enter(sqlite3_db_mutex(db));

................................................................................
            }
          }else if( p->op!=SQLITE_INSERT ){
            rc = sessionAppendDelete(&buf, bPatchset, p, nCol, abPK);
          }
          if( rc==SQLITE_OK ){
            rc = sqlite3_reset(pSel);
          }

          /* If the buffer is now larger than SESSIONS_STR_CHUNK_SIZE, pass
          ** its contents to the xOutput() callback. */
          if( xOutput 
           && rc==SQLITE_OK 
           && buf.nBuf>nNoop 
           && buf.nBuf>SESSIONS_STR_CHUNK_SIZE 
          ){
            rc = xOutput(pOut, (void*)buf.aBuf, buf.nBuf);
            nNoop = -1;
            buf.nBuf = 0;
          }

        }
      }

      sqlite3_finalize(pSel);
      if( buf.nBuf==nNoop ){
        buf.nBuf = nRewind;
      }
      sqlite3_free((char*)azCol);  /* cast works around VC++ bug */
    }
  }

  if( rc==SQLITE_OK ){
    if( xOutput==0 ){
      *pnChangeset = buf.nBuf;
      *ppChangeset = buf.aBuf;

      buf.aBuf = 0;
    }else if( buf.nBuf>0 ){
      rc = xOutput(pOut, (void*)buf.aBuf, buf.nBuf);
    }
  }

  sqlite3_free(buf.aBuf);
  sqlite3_exec(db, "RELEASE changeset", 0, 0, 0);
  sqlite3_mutex_leave(sqlite3_db_mutex(db));
  return rc;
}

/*
** Obtain a changeset object containing all changes recorded by the 
................................................................................
** using sqlite3_free().
*/
int sqlite3session_changeset(
  sqlite3_session *pSession,      /* Session object */
  int *pnChangeset,               /* OUT: Size of buffer at *ppChangeset */
  void **ppChangeset              /* OUT: Buffer containing changeset */
){
  return sessionGenerateChangeset(pSession, 0, 0, 0, pnChangeset, ppChangeset);
}

/*
** Streaming version of sqlite3session_changeset().
*/
int sqlite3session_changeset_str(
  sqlite3_session *pSession,
  int (*xOutput)(void *pOut, const void *pData, int nData),
  void *pOut
){
  return sessionGenerateChangeset(pSession, 0, xOutput, pOut, 0, 0);
}

/*
** Streaming version of sqlite3session_patchset().
*/
int sqlite3session_patchset_str(
  sqlite3_session *pSession,
  int (*xOutput)(void *pOut, const void *pData, int nData),
  void *pOut
){
  return sessionGenerateChangeset(pSession, 1, xOutput, pOut, 0, 0);
}

/*
** Obtain a patchset object containing all changes recorded by the 
** session object passed as the first argument.
**
** It is the responsibility of the caller to eventually free the buffer 
................................................................................
** using sqlite3_free().
*/
int sqlite3session_patchset(
  sqlite3_session *pSession,      /* Session object */
  int *pnPatchset,                /* OUT: Size of buffer at *ppChangeset */
  void **ppPatchset               /* OUT: Buffer containing changeset */
){
  return sessionGenerateChangeset(pSession, 1, 0, 0, pnPatchset, ppPatchset);
}

/*
** Enable or disable the session object passed as the first argument.
*/
int sqlite3session_enable(sqlite3_session *pSession, int bEnable){
  int ret;
................................................................................
  }
  sqlite3_mutex_leave(sqlite3_db_mutex(pSession->db));

  return (ret==0);
}

/*
** Do the work for either sqlite3changeset_start() or start_str().
*/
int sessionChangesetStart(
  sqlite3_changeset_iter **pp,    /* OUT: Changeset iterator handle */
  int (*xInput)(void *pIn, void *pData, int *pnData),
  void *pIn,
  int nChangeset,                 /* Size of buffer pChangeset in bytes */
  void *pChangeset                /* Pointer to buffer containing changeset */
){
  sqlite3_changeset_iter *pRet;   /* Iterator to return */
  int nByte;                      /* Number of bytes to allocate for iterator */

  assert( xInput==0 || (pChangeset==0 && nChangeset==0) );

  /* Zero the output variable in case an error occurs. */
  *pp = 0;

  /* Allocate and initialize the iterator structure. */
  nByte = sizeof(sqlite3_changeset_iter);
  pRet = (sqlite3_changeset_iter *)sqlite3_malloc(nByte);
  if( !pRet ) return SQLITE_NOMEM;
  memset(pRet, 0, sizeof(sqlite3_changeset_iter));
  pRet->in.aChangeset = (u8 *)pChangeset;
  pRet->in.nChangeset = nChangeset;
  pRet->in.xInput = xInput;
  pRet->in.pIn = pIn;
  pRet->in.iNext = 0;
  pRet->in.bEof = (xInput ? 0 : 1);

  /* Populate the output variable and return success. */
  *pp = pRet;
  return SQLITE_OK;
}

/*
** Create an iterator used to iterate through the contents of a changeset.
*/
int sqlite3changeset_start(
  sqlite3_changeset_iter **pp,    /* OUT: Changeset iterator handle */
  int nChangeset,                 /* Size of buffer pChangeset in bytes */
  void *pChangeset                /* Pointer to buffer containing changeset */
){
  return sessionChangesetStart(pp, 0, 0, nChangeset, pChangeset);
}

/*
** Streaming version of sqlite3changeset_start().
*/
int sqlite3changeset_start_str(
  sqlite3_changeset_iter **pp,    /* OUT: Changeset iterator handle */
  int (*xInput)(void *pIn, void *pData, int *pnData),
  void *pIn
){
  return sessionChangesetStart(pp, xInput, pIn, 0, 0);
}

/*
** Ensure that there are at least nByte bytes available in the buffer. Or,
** if there are not nByte bytes remaining in the input, that all available
** data is in the buffer.
**
** Return an SQLite error code if an error occurs, or SQLITE_OK otherwise.
*/
static int sessionInputBuffer(SessionInput *pInput, int nByte){
  int rc = SQLITE_OK;
  if( pInput->xInput && !pInput->bEof ){
    assert( 0 );
  }
  return rc;
}

/*
** When this function is called, *ppRec points to the start of a record
** that contains nCol values. This function advances the pointer *ppRec
** until it points to the byte immediately following that record.
*/
static void sessionSkipRecord(
  u8 **ppRec,                     /* IN/OUT: Record pointer */
  int nCol                        /* Number of values in record */
){
  u8 *aRec = *ppRec;
  int i;
  for(i=0; i<nCol; i++){
    int eType = *aRec++;
    if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){
      int nByte;
      aRec += sessionVarintGet((u8*)aRec, &nByte);
      aRec += nByte;
    }else if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
      aRec += 8;
    }
  }

  *ppRec = aRec;
}

/*
** Deserialize a single record from a buffer in memory. See "RECORD FORMAT"
** for details.
**
** When this function is called, *paChange points to the start of the record
** to deserialize. Assuming no error occurs, *paChange is set to point to
................................................................................
** It is the responsibility of the caller to free all sqlite_value structures
** using sqlite3_free().
**
** If an error occurs, an SQLite error code (e.g. SQLITE_NOMEM) is returned.
** The apOut[] array may have been partially populated in this case.
*/
static int sessionReadRecord(
  SessionInput *pIn,              /* Input data */
  int nCol,                       /* Number of values in record */
  u8 *abPK,                       /* Array of primary key flags, or NULL */
  sqlite3_value **apOut           /* Write values to this array */
){
  int i;                          /* Used to iterate through columns */
  int rc = SQLITE_OK;

  for(i=0; i<nCol && rc==SQLITE_OK; i++){
    int eType = 0;                /* Type of value (SQLITE_NULL, TEXT etc.) */
    if( abPK && abPK[i]==0 ) continue;
    rc = sessionInputBuffer(pIn, 9);
    if( rc==SQLITE_OK ){
      eType = pIn->aChangeset[pIn->iNext++];
    }

    assert( !apOut || apOut[i]==0 );
    if( eType ){
      if( apOut ){
        apOut[i] = sqlite3ValueNew(0);
        if( !apOut[i] ) rc = SQLITE_NOMEM;
      }
    }

    if( rc==SQLITE_OK ){
      u8 *aVal = &pIn->aChangeset[pIn->iNext];
      if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){
        int nByte;
        pIn->iNext += sessionVarintGet(aVal, &nByte);
        rc = sessionInputBuffer(pIn, nByte);
        if( apOut && rc==SQLITE_OK ){
          u8 *aRec = &pIn->aChangeset[pIn->iNext];
          u8 enc = (eType==SQLITE_TEXT ? SQLITE_UTF8 : 0);
          sqlite3ValueSetStr(apOut[i], nByte, (char *)aRec, enc, SQLITE_STATIC);
        }
        pIn->iNext += nByte;
      }
      if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
        if( apOut ){
          sqlite3_int64 v = sessionGetI64(aVal);
          if( eType==SQLITE_INTEGER ){
            sqlite3VdbeMemSetInt64(apOut[i], v);
          }else{
            double d;
            memcpy(&d, &v, 8);
            sqlite3VdbeMemSetDouble(apOut[i], d);
          }
        }
        pIn->iNext += 8;
      }
    }
  }

  return rc;
}

/*
** The input pointer currently points to the second byte of a table-header.
** Specifically, to the following:
**
**   + number of columns in table (varint)
**   + array of PK flags (1 byte per column),
**   + table name (nul terminated).
**
** This function ensures that all of the above is present in the input 
** buffer (i.e. that it can be accessed without any calls to xInput()).
** If successful, SQLITE_OK is returned. Otherwise, an SQLite error code.
** The input pointer is not moved.
*/
static int sessionChangesetBufferTblhdr(SessionInput *pIn, int *pnByte){
  int rc = SQLITE_OK;
  int nCol = 0;
  int iIn = pIn->iNext;

  rc = sessionInputBuffer(pIn, 9);
  if( rc==SQLITE_OK ){
    iIn += sessionVarintGet(&pIn->aChangeset[iIn], &nCol);
    rc = sessionInputBuffer(pIn, nCol+100);
    iIn += nCol;
  }
  while( rc==SQLITE_OK ){
    while( iIn<pIn->nChangeset && pIn->aChangeset[iIn] ) iIn++;
    if( pIn->aChangeset[iIn]==0 ) break;
    rc = sessionInputBuffer(pIn, 100);
  }
  if( pnByte ) *pnByte = (iIn+1 - pIn->iNext);
  return rc;
}

/*
** The input pointer currently points to the second byte of a table-header.
** Specifically, to the following:
**
**   + number of columns in table (varint)
**   + array of PK flags (1 byte per column),
**   + table name (nul terminated).
*/
static int sessionChangesetReadTblhdr(sqlite3_changeset_iter *p){
  int rc;
  int nCopy;
  assert( p->rc==SQLITE_OK );

  rc = sessionChangesetBufferTblhdr(&p->in, &nCopy);
  if( rc==SQLITE_OK ){
    int nByte;
    int nVarint;
    nVarint = sessionVarintGet(&p->in.aChangeset[p->in.iNext], &p->nCol);
    nCopy -= nVarint;
    p->in.iNext += nVarint;
    nByte = p->nCol * sizeof(sqlite3_value*) * 2 + nCopy;
    p->tblhdr.nBuf = 0;
    sessionBufferGrow(&p->tblhdr, nByte, &rc);
  }

  if( rc==SQLITE_OK ){
    int iPK = sizeof(sqlite3_value*)*p->nCol*2;
    memset(p->tblhdr.aBuf, 0, iPK);
    memcpy(&p->tblhdr.aBuf[iPK], &p->in.aChangeset[p->in.iNext], nCopy);
    p->in.iNext += nCopy;
  }

  p->apValue = (sqlite3_value**)p->tblhdr.aBuf;
  p->abPK = (u8*)&p->apValue[p->nCol*2];
  p->zTab = (char*)&p->abPK[p->nCol];
  return (p->rc = rc);
}

/*
** Advance the changeset iterator to the next change.
**
** If both paRec and pnRec are NULL, then this function works like the public
** API sqlite3changeset_next(). If SQLITE_ROW is returned, then the
................................................................................
** changes in the changeset.
*/
static int sessionChangesetNext(
  sqlite3_changeset_iter *p,      /* Changeset iterator */
  u8 **paRec,                     /* If non-NULL, store record pointer here */
  int *pnRec                      /* If non-NULL, store size of record here */
){

  int i;
  u8 op;

  assert( paRec==0 || p->in.xInput==0 ); /* fixme! */
  assert( (paRec==0 && pnRec==0) || (paRec && pnRec) );

  /* If the iterator is in the error-state, return immediately. */
  if( p->rc!=SQLITE_OK ) return p->rc;

  /* Free the current contents of p->apValue[], if any. */
  if( p->apValue ){
    for(i=0; i<p->nCol*2; i++){
      sqlite3ValueFree(p->apValue[i]);
    }
    memset(p->apValue, 0, sizeof(sqlite3_value*)*p->nCol*2);
  }

  /* Make sure the buffer contains at least 10 bytes of input data, or all
  ** remaining data if there are less than 10 bytes available. This is
  ** sufficient either for the 'T' or 'P' byte and the varint that follows
  ** it, or for the two single byte values otherwise. */
  p->rc = sessionInputBuffer(&p->in, 2);
  if( p->rc!=SQLITE_OK ) return p->rc;

  /* If the iterator is already at the end of the changeset, return DONE. */
  if( p->in.iNext>=p->in.nChangeset ){
    return SQLITE_DONE;
  }

  op = p->in.aChangeset[p->in.iNext++];
  if( op=='T' || op=='P' ){
    p->bPatchset = (op=='P');
    if( sessionChangesetReadTblhdr(p) ) return p->rc;
    if( (p->rc = sessionInputBuffer(&p->in, 2)) ) return p->rc;
    op = p->in.aChangeset[p->in.iNext++];
  }

  p->op = op;
  p->bIndirect = p->in.aChangeset[p->in.iNext++];







  if( p->op!=SQLITE_UPDATE && p->op!=SQLITE_DELETE && p->op!=SQLITE_INSERT ){
    return (p->rc = SQLITE_CORRUPT);
  }

  if( paRec ){ *paRec = &p->in.aChangeset[p->in.iNext]; }

  /* If this is an UPDATE or DELETE, read the old.* record. */
  if( p->op!=SQLITE_INSERT && (p->bPatchset==0 || p->op==SQLITE_DELETE) ){
    u8 *abPK = p->bPatchset ? p->abPK : 0;
    p->rc = sessionReadRecord(&p->in, p->nCol, abPK, paRec?0:p->apValue);
    if( p->rc!=SQLITE_OK ) return p->rc;
  }

  /* If this is an INSERT or UPDATE, read the new.* record. */
  if( p->op!=SQLITE_DELETE ){
    sqlite3_value **apOut = (paRec ? 0 : &p->apValue[p->nCol]);
    p->rc = sessionReadRecord(&p->in, p->nCol, 0, apOut);
    if( p->rc!=SQLITE_OK ) return p->rc;
  }

  if( pnRec ){
    *pnRec = (int)(&p->in.aChangeset[p->in.iNext] - *paRec);
  }else if( p->bPatchset && p->op==SQLITE_UPDATE ){
    /* If this is an UPDATE that is part of a patchset, then all PK and
    ** modified fields are present in the new.* record. The old.* record
    ** is currently completely empty. This block shifts the PK fields from
    ** new.* to old.*, to accommodate the code that reads these arrays.  */
    int i;
    for(i=0; i<p->nCol; i++){
................................................................................
      assert( p->abPK[i]==0 || p->apValue[i+p->nCol] );
      if( p->abPK[i] ){
        p->apValue[i] = p->apValue[i+p->nCol];
        p->apValue[i+p->nCol] = 0;
      }
    }
  }

  return SQLITE_ROW;
}

/*
** Advance an iterator created by sqlite3changeset_start() to the next
** change in the changeset. This function may return SQLITE_ROW, SQLITE_DONE
** or SQLITE_CORRUPT.
................................................................................
*/
int sqlite3changeset_finalize(sqlite3_changeset_iter *p){
  int i;                          /* Used to iterate through p->apValue[] */
  int rc = p->rc;                 /* Return code */
  if( p->apValue ){
    for(i=0; i<p->nCol*2; i++) sqlite3ValueFree(p->apValue[i]);
  }
  sqlite3_free(p->tblhdr.aBuf);
  sqlite3_free(p);
  return rc;
}

/*
** Invert a changeset object.
*/
................................................................................
  int *pnInverted,                /* OUT: Number of bytes in output changeset */
  void **ppInverted               /* OUT: Inverse of pChangeset */
){
  int rc = SQLITE_OK;             /* Return value */
  u8 *aOut;
  u8 *aIn;
  int i;
  SessionInput sInput;
  int nCol = 0;                   /* Number of cols in current table */
  u8 *abPK = 0;                   /* PK array for current table */
  sqlite3_value **apVal = 0;      /* Space for values for UPDATE inversion */
  SessionBuffer sPK = {0, 0, 0};  /* PK array for current table */

  /* Zero the output variables in case an error occurs. */
  *ppInverted = 0;
  *pnInverted = 0;
  if( nChangeset==0 ) return SQLITE_OK;

  /* Set up the input stream */
  memset(&sInput, 0, sizeof(SessionInput));
  sInput.nChangeset = nChangeset;
  sInput.aChangeset = (u8*)pChangeset;

  aOut = (u8 *)sqlite3_malloc(nChangeset);
  if( !aOut ) return SQLITE_NOMEM;
  aIn = (u8 *)pChangeset;

  i = 0;
  while( i<nChangeset ){
    u8 eType;
    if( (rc = sessionInputBuffer(&sInput, 2)) ) goto finished_invert;
    eType = sInput.aChangeset[sInput.iNext];
    switch( eType ){
      case 'T': {
        /* A 'table' record consists of:
        **
        **   * A constant 'T' character,
        **   * Number of columns in said table (a varint),
        **   * An array of nCol bytes (sPK),
        **   * A nul-terminated table name.
        */
        int nByte;
        int nVarint;
        int iNext = sInput.iNext;
        sInput.iNext++;
        if( (rc = sessionChangesetBufferTblhdr(&sInput, &nByte)) ){
          goto finished_invert;
        }
        nVarint = sessionVarintGet(&sInput.aChangeset[iNext+1], &nCol);
        sPK.nBuf = 0;
        sessionAppendBlob(&sPK, &sInput.aChangeset[iNext+1+nVarint], nCol, &rc);
        if( rc ) goto finished_invert;
        sInput.iNext += nByte;
        memcpy(&aOut[i], &sInput.aChangeset[iNext], nByte+1);
        i += nByte+1;
        sqlite3_free(apVal);
        apVal = 0;
        abPK = sPK.aBuf;
        break;
      }

      case SQLITE_INSERT:
      case SQLITE_DELETE: {
        int iStart;
        int nByte;
        sInput.iNext += 2;
        iStart = sInput.iNext;
        sessionReadRecord(&sInput, nCol, 0, 0);
        aOut[i] = (eType==SQLITE_DELETE ? SQLITE_INSERT : SQLITE_DELETE);
        aOut[i+1] = aIn[i+1];               /* indirect-flag */
        nByte = sInput.iNext - iStart;
        memcpy(&aOut[i+2], &sInput.aChangeset[iStart], nByte);
        i += 2 + nByte;
        break;
      }

      case SQLITE_UPDATE: {
        int iCol;
        int nWrite = 0;


        if( 0==apVal ){
          apVal = (sqlite3_value **)sqlite3_malloc(sizeof(apVal[0])*nCol*2);
          if( 0==apVal ){
            rc = SQLITE_NOMEM;
            goto finished_invert;
          }
          memset(apVal, 0, sizeof(apVal[0])*nCol*2);
        }

        /* Write the header for the new UPDATE change. Same as the original. */
        aOut[i] = SQLITE_UPDATE;
        aOut[i+1] = sInput.aChangeset[sInput.iNext+1];
        nWrite = 2;

        /* Read the old.* and new.* records for the update change. */
        sInput.iNext += 2;
        rc = sessionReadRecord(&sInput, nCol, 0, &apVal[0]);
        if( rc==SQLITE_OK ){
          rc = sessionReadRecord(&sInput, nCol, 0, &apVal[nCol]);
        }

        /* Write the new old.* record. Consists of the PK columns from the
        ** original old.* record, and the other values from the original
        ** new.* record. */
        for(iCol=0; rc==SQLITE_OK && iCol<nCol; iCol++){
          sqlite3_value *pVal = apVal[iCol + (abPK[iCol] ? 0 : nCol)];
          rc = sessionSerializeValue(&aOut[i+nWrite], pVal, &nWrite);
................................................................................
        }
        memset(apVal, 0, sizeof(apVal[0])*nCol*2);
        if( rc!=SQLITE_OK ){
          goto finished_invert;
        }

        i += nWrite;
        assert( i==sInput.iNext );
        break;
      }

      default:
        rc = SQLITE_CORRUPT;
        goto finished_invert;
    }
................................................................................
  *ppInverted = (void *)aOut;

 finished_invert:
  if( rc!=SQLITE_OK ){
    sqlite3_free(aOut);
  }
  sqlite3_free(apVal);
  sqlite3_free(sPK.aBuf);
  return rc;
}

typedef struct SessionApplyCtx SessionApplyCtx;
struct SessionApplyCtx {
  sqlite3 *db;
  sqlite3_stmt *pDelete;          /* DELETE statement */
................................................................................
      pNew->bIndirect = (bIndirect && pExist->bIndirect);
      aCsr = pNew->aRecord = (u8 *)&pNew[1];

      if( op1==SQLITE_INSERT ){             /* INSERT + UPDATE */
        u8 *a1 = aRec;
        assert( op2==SQLITE_UPDATE );
        pNew->op = SQLITE_INSERT;
        if( bPatchset==0 ) sessionSkipRecord(&a1, pTab->nCol);
        sessionMergeRecord(&aCsr, pTab->nCol, aExist, a1);
      }else if( op1==SQLITE_DELETE ){       /* DELETE + INSERT */
        assert( op2==SQLITE_INSERT );
        pNew->op = SQLITE_UPDATE;
        if( 0==sessionMergeUpdate(&aCsr, pTab, bPatchset, aExist, 0, aRec, 0) ){
          sqlite3_free(pNew);
          pNew = 0;
        }
      }else if( op2==SQLITE_UPDATE ){       /* UPDATE + UPDATE */
        u8 *a1 = aExist;
        u8 *a2 = aRec;
        assert( op1==SQLITE_UPDATE );
        if( bPatchset==0 ){
          sessionSkipRecord(&a1, pTab->nCol);
          sessionSkipRecord(&a2, pTab->nCol);
        }
        pNew->op = SQLITE_UPDATE;
        if( 0==sessionMergeUpdate(&aCsr, pTab, bPatchset, aRec, aExist,a1,a2) ){
          sqlite3_free(pNew);
          pNew = 0;
        }
      }else{                                /* UPDATE + DELETE */
................................................................................
    assert( bPatchset==0 || bPatchset==1 );
    assert( pIter->bPatchset==0 || pIter->bPatchset==1 );
    if( pIter->bPatchset!=bPatchset ){
      rc = SQLITE_ERROR;
      break;
    }


    sqlite3changeset_op(pIter, &zNew, &nCol, &op, &bIndirect);






    if( !pTab || sqlite3_stricmp(zNew, pTab->zName) ){
      /* Search the list for a matching table */
      int nNew = (int)strlen(zNew);
      u8 *abPK;

      sqlite3changeset_pk(pIter, &abPK, 0);
      for(pTab = *ppTabList; pTab; pTab=pTab->pNext){
        if( 0==sqlite3_strnicmp(pTab->zName, zNew, nNew+1) ) break;
      }
      if( !pTab ){
        pTab = sqlite3_malloc(sizeof(SessionTable) + nCol + nNew+1);
        if( !pTab ){
          rc = SQLITE_NOMEM;
          break;
        }
        memset(pTab, 0, sizeof(SessionTable));
        pTab->pNext = *ppTabList;

        pTab->nCol = nCol;
        pTab->abPK = (u8*)&pTab[1];
        memcpy(pTab->abPK, abPK, nCol);
        pTab->zName = (char*)&pTab->abPK[nCol];
        memcpy(pTab->zName, zNew, nNew+1);
        *ppTabList = pTab;
      }else if( pTab->nCol!=nCol || memcmp(pTab->abPK, abPK, nCol) ){
        rc = SQLITE_SCHEMA;
        break;
      }

    }

    if( sessionGrowHash(bPatchset, pTab) ){
      rc = SQLITE_NOMEM;
      break;
    }
    iHash = sessionChangeHash(

Changes to ext/session/sqlite3session.h.

268
269
270
271
272
273
274

























275
276
277
278
279
280
281
...
297
298
299
300
301
302
303









304
305
306
307
308
309
310
...
354
355
356
357
358
359
360
























361
362
363
364
365
366
367
** resulting changeset will contain an UPDATE change that updates both fields.
*/
int sqlite3session_changeset(
  sqlite3_session *pSession,      /* Session object */
  int *pnChangeset,               /* OUT: Size of buffer at *ppChangeset */
  void **ppChangeset              /* OUT: Buffer containing changeset */
);


























/*
** CAPI3REF: Generate A Patchset From A Session Object
**
** The differences between a patchset and a changeset are that:
**
** <ul>
................................................................................
** in the same way as for changesets.
*/
int sqlite3session_patchset(
  sqlite3_session *pSession,      /* Session object */
  int *pnPatchset,                /* OUT: Size of buffer at *ppChangeset */
  void **ppPatchset               /* OUT: Buffer containing changeset */
);










/*
** CAPI3REF: Test if a changeset has recorded any changes.
**
** Return non-zero if no changes to attached tables have been recorded by 
** the session object passed as the first argument. Otherwise, if one or 
** more changes have been recorded, return zero.
................................................................................
*/
int sqlite3changeset_start(
  sqlite3_changeset_iter **pp,    /* OUT: New changeset iterator handle */
  int nChangeset,                 /* Size of changeset blob in bytes */
  void *pChangeset                /* Pointer to blob containing changeset */
);

























/*
** CAPI3REF: Advance A Changeset Iterator
**
** This function may only be used with iterators created by function
** [sqlite3changeset_start()]. If it is called on an iterator passed to
** a conflict-handler callback by [sqlite3changeset_apply()], SQLITE_MISUSE
** is returned and the call has no effect.







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>
>
>
>
>
>
>
>
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
...
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
...
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
** resulting changeset will contain an UPDATE change that updates both fields.
*/
int sqlite3session_changeset(
  sqlite3_session *pSession,      /* Session object */
  int *pnChangeset,               /* OUT: Size of buffer at *ppChangeset */
  void **ppChangeset              /* OUT: Buffer containing changeset */
);


/*
** This function is similar to sqlite3session_changeset(), except that instead
** of storing the output changeset in a buffer obtained from sqlite3_malloc()
** it invokes the supplied xOutput() callback zero or more times to stream the
** changeset to the application. This is useful in order to avoid large memory
** allocations when working with very large changesets.
**
** The first parameter passed to each call to the xOutput callback is a copy
** of the pOut parameter passed to this function. The following two parameters
** are a pointer to the buffer containing the next chunk of the output changeset
** and the size of that buffer in bytes.
**
** If the data is successfully processed by the xOutput callback, it should
** return SQLITE_OK. Or, if an error occurs, some other SQLite error code. In
** this case the sqlite3session_changeset_str() call is abandoned immediately
** and returns a copy of the xOutput return code.
*/
int sqlite3session_changeset_str(
  sqlite3_session *pSession,
  int (*xOutput)(void *pOut, const void *pData, int nData),
  void *pOut
);


/*
** CAPI3REF: Generate A Patchset From A Session Object
**
** The differences between a patchset and a changeset are that:
**
** <ul>
................................................................................
** in the same way as for changesets.
*/
int sqlite3session_patchset(
  sqlite3_session *pSession,      /* Session object */
  int *pnPatchset,                /* OUT: Size of buffer at *ppChangeset */
  void **ppPatchset               /* OUT: Buffer containing changeset */
);

/*
** Streaming version of sqlite3session_patchset().
*/
int sqlite3session_patchset_str(
  sqlite3_session *pSession,
  int (*xOutput)(void *pOut, const void *pData, int nData),
  void *pOut
);

/*
** CAPI3REF: Test if a changeset has recorded any changes.
**
** Return non-zero if no changes to attached tables have been recorded by 
** the session object passed as the first argument. Otherwise, if one or 
** more changes have been recorded, return zero.
................................................................................
*/
int sqlite3changeset_start(
  sqlite3_changeset_iter **pp,    /* OUT: New changeset iterator handle */
  int nChangeset,                 /* Size of changeset blob in bytes */
  void *pChangeset                /* Pointer to blob containing changeset */
);


/*
** This function is similar to sqlite3changeset_start(), except that instead
** of reading data from a single buffer, it requests it one chunk at a time
** from the application by invoking the supplied xInput() callback. The xInput()
** callback may be invoked at any time during the lifetime of the iterator.
**
** Each time the xInput callback is invoked, the first argument passed is a
** copy of the third parameter passed to this function. The second argument,
** pData, points to a buffer (*pnData) bytes in size. Assuming no error occurs
** the xInput method should copy up to (*pnData) bytes of data into the buffer
** and set (*pnData) to the actual number of bytes copied before returning
** SQLITE_OK. If the input is completely exhausted, (*pnData) should be set
** to zero to indicate this. Or, if an error occurs, an SQLite error code
** should be returned. In this case the iterator is put into an error state and
** all subsequent calls to iterator methods return a copy of the xInput error
** code.
*/
int sqlite3changeset_start_str(
  sqlite3_changeset_iter **pp,
  int (*xInput)(void *pIn, void *pData, int *pnData),
  void *pIn
);

/*
** CAPI3REF: Advance A Changeset Iterator
**
** This function may only be used with iterators created by function
** [sqlite3changeset_start()]. If it is called on an iterator passed to
** a conflict-handler callback by [sqlite3changeset_apply()], SQLITE_MISUSE
** is returned and the call has no effect.

Changes to ext/session/test_session.c.

9
10
11
12
13
14
15

















16
17
18
19
20
21
22
..
39
40
41
42
43
44
45

























46
47
48
49
50
51
52

53
54
55
56
57
58
59
...
101
102
103
104
105
106
107
108

109
110






111
112
113
114

115
116

117
118

119
120
121
122
123
124
125

typedef struct TestSession TestSession;
struct TestSession {
  sqlite3_session *pSession;
  Tcl_Interp *interp;
  Tcl_Obj *pFilterScript;
};


















static int test_session_error(Tcl_Interp *interp, int rc){
  extern const char *sqlite3ErrName(int);
  Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
  return TCL_ERROR;
}

................................................................................
    /* printf("error: %s\n", Tcl_GetStringResult(p->interp)); */
    Tcl_BackgroundError(p->interp);
  }
  Tcl_DecrRefCount(pEval);

  return bRes;
}


























/*
** Tclcmd:  $session attach TABLE
**          $session changeset
**          $session delete
**          $session enable BOOL
**          $session indirect INTEGER

**          $session table_filter SCRIPT
*/
static int test_session_cmd(
  void *clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
................................................................................
        return test_session_error(interp, rc);
      }
      break;
    }

    case 7:        /* patchset */
    case 1: {      /* changeset */
      int nChange;

      void *pChange;
      if( iSub==7 ){






        rc = sqlite3session_patchset(pSession, &nChange, &pChange);
      }else{
        rc = sqlite3session_changeset(pSession, &nChange, &pChange);
      }

      if( rc==SQLITE_OK ){
        Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pChange, nChange)); 

        sqlite3_free(pChange);
      }else{

        return test_session_error(interp, rc);
      }
      break;
    }

    case 2:        /* delete */
      Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







>







 







|
>
|
|
>
>
>
>
>
>
|
|
|
|
>

|
>
|
<
>







9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
..
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
...
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169

170
171
172
173
174
175
176
177

typedef struct TestSession TestSession;
struct TestSession {
  sqlite3_session *pSession;
  Tcl_Interp *interp;
  Tcl_Obj *pFilterScript;
};

#define SESSION_STREAM_TCL_VAR "sqlite3session_streams"

/*
** Attempt to find the global variable zVar within interpreter interp
** and extract a boolean value from it. Return this value.
**
** If the named variable cannot be found, or if it cannot be interpreted
** as a boolean, return 0.
*/
static int test_tcl_boolean(Tcl_Interp *interp, const char *zVar){
  Tcl_Obj *pObj;
  int bVal = 0;
  pObj = Tcl_ObjGetVar2(interp, Tcl_NewStringObj(zVar, -1), 0, TCL_GLOBAL_ONLY);
  if( pObj ) Tcl_GetBooleanFromObj(0, pObj, &bVal);
  return bVal;
}

static int test_session_error(Tcl_Interp *interp, int rc){
  extern const char *sqlite3ErrName(int);
  Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
  return TCL_ERROR;
}

................................................................................
    /* printf("error: %s\n", Tcl_GetStringResult(p->interp)); */
    Tcl_BackgroundError(p->interp);
  }
  Tcl_DecrRefCount(pEval);

  return bRes;
}

struct TestSessionsBlob {
  void *p;
  int n;
};
typedef struct TestSessionsBlob TestSessionsBlob;

static int testSessionsOutput(
  void *pCtx,
  const void *pData,
  int nData
){
  TestSessionsBlob *pBlob = (TestSessionsBlob*)pCtx;
  char *pNew;

  assert( nData>0 );
  pNew = (char*)sqlite3_realloc(pBlob->p, pBlob->n + nData);
  if( pNew==0 ){
    return SQLITE_NOMEM;
  }
  pBlob->p = (void*)pNew;
  memcpy(&pNew[pBlob->n], pData, nData);
  pBlob->n += nData;
  return SQLITE_OK;
}

/*
** Tclcmd:  $session attach TABLE
**          $session changeset
**          $session delete
**          $session enable BOOL
**          $session indirect INTEGER
**          $session patchset
**          $session table_filter SCRIPT
*/
static int test_session_cmd(
  void *clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
................................................................................
        return test_session_error(interp, rc);
      }
      break;
    }

    case 7:        /* patchset */
    case 1: {      /* changeset */
      TestSessionsBlob o = {0, 0};
      if( test_tcl_boolean(interp, SESSION_STREAM_TCL_VAR) ){
        void *pCtx = (void*)&o;
        if( iSub==7 ){
          rc = sqlite3session_patchset_str(pSession, testSessionsOutput, pCtx);
        }else{
          rc = sqlite3session_changeset_str(pSession, testSessionsOutput, pCtx);
        }
      }else{
        if( iSub==7 ){
          rc = sqlite3session_patchset(pSession, &o.n, &o.p);
        }else{
          rc = sqlite3session_changeset(pSession, &o.n, &o.p);
        }
      }
      if( rc==SQLITE_OK ){
        Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(o.p, o.n)); 
      }
      sqlite3_free(o.p);

      if( rc!=SQLITE_OK ){
        return test_session_error(interp, rc);
      }
      break;
    }

    case 2:        /* delete */
      Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));

Changes to test/permutations.test.

933
934
935
936
937
938
939








940
941
942
943
944
945
946
test_suite "session_eec" -description {
  All session module related tests with sqlite3_extended_result_codes() set. 
} -files [
  glob -nocomplain $::testdir/../ext/session/*.test
] -dbconfig {
  sqlite3_extended_result_codes $::dbhandle 1
}









test_suite "no_optimization" -description {
  Run test scripts with optimizations disabled using the
  sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS) interface.
} -files {
  where.test where2.test where3.test where4.test where5.test
  where6.test where7.test where8.test where9.test







>
>
>
>
>
>
>
>







933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
test_suite "session_eec" -description {
  All session module related tests with sqlite3_extended_result_codes() set. 
} -files [
  glob -nocomplain $::testdir/../ext/session/*.test
] -dbconfig {
  sqlite3_extended_result_codes $::dbhandle 1
}

test_suite "session_str" -description {
  All session module related tests using the streaming APIs.
} -files [
  glob -nocomplain $::testdir/../ext/session/*.test
] -dbconfig {
  set ::sqlite3session_streams 1
}

test_suite "no_optimization" -description {
  Run test scripts with optimizations disabled using the
  sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS) interface.
} -files {
  where.test where2.test where3.test where4.test where5.test
  where6.test where7.test where8.test where9.test