1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-29 08:01:23 +03:00

Allow a session object to generate a changeset, even if columns were added to one of the tables using ALTER TABLE ADD COLUMN while the changeset was being collected.

FossilOrigin-Name: a3f435eccf3a2aa11cb7420e94af5efcdfa04e9c169c5aaf61fa5cdcb165ceef
This commit is contained in:
dan
2023-10-04 21:15:24 +00:00
parent 10e751937c
commit 6d8e91be9d
6 changed files with 499 additions and 150 deletions

View 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++){