From e8d5648e168300c5ed3f0cf89158a55474f7520e Mon Sep 17 00:00:00 2001 From: dan Date: Thu, 17 Mar 2011 19:20:27 +0000 Subject: [PATCH] Change to the session module to use user-defined primary keys instead of rowids when collecting changes. FossilOrigin-Name: 6614cfcb9c41da71ddec3c44a3de0d4d849e1cdd --- ext/session/sqlite3session.c | 775 +++++++++++++++++++++++------------ manifest | 14 +- manifest.uuid | 2 +- test/session1.test | 37 +- 4 files changed, 548 insertions(+), 280 deletions(-) diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c index d454d4d345..296875052e 100644 --- a/ext/session/sqlite3session.c +++ b/ext/session/sqlite3session.c @@ -8,7 +8,6 @@ #include "sqliteInt.h" #include "vdbeInt.h" -typedef struct RowChange RowChange; typedef struct SessionTable SessionTable; typedef struct SessionChange SessionChange; typedef struct SessionBuffer SessionBuffer; @@ -57,6 +56,8 @@ struct SessionTable { SessionTable *pNext; char *zName; /* Local name of table */ int nCol; /* Number of columns in table zName */ + const char **azCol; /* Column names */ + u8 *abPK; /* Array of primary key flags */ int nEntry; /* Total number of entries in hash table */ int nChange; /* Size of apChange[] array */ SessionChange **apChange; /* Hash table buckets */ @@ -128,7 +129,7 @@ struct SessionTable { ** this structure stored in a SessionTable.aChange[] hash table. */ struct SessionChange { - sqlite3_int64 iKey; /* Key value */ + int bInsert; /* True if row was inserted this session */ int nRecord; /* Number of bytes in buffer aRecord[] */ u8 *aRecord; /* Buffer containing old.* record */ SessionChange *pNext; /* For hash-table collisions */ @@ -264,12 +265,188 @@ static int sessionSerializeValue( return SQLITE_OK; } +#define HASH_APPEND(hash, add) ((hash) << 3) ^ (hash) ^ (int)(add) +static int sessionHashAppendI64(int h, i64 i){ + h = HASH_APPEND(h, i & 0xFFFFFFFF); + return HASH_APPEND(h, (i>>32)&0xFFFFFFFF); +} +static int sessionHashAppendBlob(int h, int n, const u8 *z){ + int i; + for(i=0; inCol==sqlite3_preupdate_count(db) ); + for(i=0; inCol; i++){ + if( pTab->abPK[i] ){ + int rc; + int eType; + sqlite3_value *pVal; + + if( bNew ){ + rc = sqlite3_preupdate_new(db, i, &pVal); + }else{ + rc = sqlite3_preupdate_old(db, i, &pVal); + } + + eType = sqlite3_value_type(pVal); + h = HASH_APPEND(h, eType); + switch( eType ){ + case SQLITE_INTEGER: + case SQLITE_FLOAT: { + i64 iVal; + if( eType==SQLITE_INTEGER ){ + iVal = sqlite3_value_int64(pVal); + }else{ + double rVal = sqlite3_value_double(pVal); + assert( sizeof(iVal)==8 && sizeof(rVal)==8 ); + memcpy(&iVal, &rVal, 8); + } + h = sessionHashAppendI64(h, iVal); + break; + } + + case SQLITE_TEXT: + case SQLITE_BLOB: { + int n = sqlite3_value_bytes(pVal); + const u8 *z = eType==SQLITE_TEXT ? + sqlite3_value_text(pVal) : sqlite3_value_blob(pVal); + h = sessionHashAppendBlob(h, n, z); + break; + } + } + } + } + + *piHash = (h % pTab->nChange); + return SQLITE_OK; +} + +static int sessionChangeHash( + sqlite3 *db, + SessionTable *pTab, + SessionChange *pChange, + int nBucket +){ + int h = 0; + int i; + u8 *a = pChange->aRecord; + + for(i=0; inCol; i++){ + int eType = *a++; + int isPK = pTab->abPK[i]; + + if( isPK ) h = HASH_APPEND(h, eType); + switch( eType ){ + case SQLITE_INTEGER: + case SQLITE_FLOAT: { + if( isPK ){ + i64 iVal = sessionGetI64(a); + h = sessionHashAppendI64(h, iVal); + } + a += 8; + break; + } + case SQLITE_TEXT: + case SQLITE_BLOB: { + int n; + a += sessionVarintGet(a, &n); + if( isPK ){ + h = sessionHashAppendBlob(h, n, a); + } + a += n; + break; + } + } + } + return (h % nBucket); +} + +static int sessionPreupdateEqual( + sqlite3 *db, + SessionTable *pTab, + SessionChange *pChange, + int bNew, + int *pbEqual +){ + int i; + u8 *a = pChange->aRecord; + + *pbEqual = 0; + + for(i=0; inCol; i++){ + int eType = *a++; + if( !pTab->abPK[i] ){ + switch( eType ){ + case SQLITE_INTEGER: + case SQLITE_FLOAT: + a += 8; + break; + + case SQLITE_TEXT: + case SQLITE_BLOB: { + int n; + a += sessionVarintGet(a, &n); + a += n; + break; + } + } + }else{ + sqlite3_value *pVal; + int rc; + if( bNew ){ + rc = sqlite3_preupdate_new(db, i, &pVal); + }else{ + rc = sqlite3_preupdate_old(db, i, &pVal); + } + if( rc!=SQLITE_OK || sqlite3_value_type(pVal)!=eType ) return rc; + + switch( eType ){ + case SQLITE_INTEGER: + case SQLITE_FLOAT: { + i64 iVal = sessionGetI64(a); + a += 8; + if( eType==SQLITE_INTEGER ){ + if( sqlite3_value_int64(pVal)!=iVal ) return SQLITE_OK; + }else{ + double rVal; + assert( sizeof(iVal)==8 && sizeof(rVal)==8 ); + memcpy(&rVal, &iVal, 8); + if( sqlite3_value_double(pVal)!=rVal ) return SQLITE_OK; + } + break; + } + case SQLITE_TEXT: + case SQLITE_BLOB: { + int n; + const u8 *z; + a += sessionVarintGet(a, &n); + if( sqlite3_value_bytes(pVal)!=n ) return SQLITE_OK; + z = eType==SQLITE_TEXT ? + sqlite3_value_text(pVal) : sqlite3_value_blob(pVal); + if( memcmp(a, z, n) ) return SQLITE_OK; + a += n; + break; + } + } + } + } + + *pbEqual = 1; + return SQLITE_OK; } /* @@ -303,7 +480,7 @@ static int sessionGrowHash(sqlite3_session *pSession, SessionTable *pTab){ SessionChange *p; SessionChange *pNext; for(p=pTab->apChange[i]; p; p=pNext){ - int iHash = sessionKeyhash(nNew, p->iKey); + int iHash = sessionChangeHash(pSession->db, pTab, p, nNew); pNext = p->pNext; p->pNext = apNew[iHash]; apNew[iHash] = p; @@ -318,6 +495,130 @@ static int sessionGrowHash(sqlite3_session *pSession, SessionTable *pTab){ return SQLITE_OK; } +/* +** This function queries the database for the names of the columns of table +** zThis, in schema zDb. It is expected that the table has nCol columns. If +** not, SQLITE_SCHEMA is returned and none of the output variables are +** populated. +** +** Otherwise, if it is not NULL, variable *pzTab is set to point to a +** nul-terminated copy of the table name. *pazCol (if not NULL) is set to +** point to an array of pointers to column names. And *pabPK (again, if not +** NULL) is set to point to an array of booleans - true if the corresponding +** column is part of the primary key. +** +** For example, if the table is declared as: +** +** CREATE TABLE tbl1(w, x, y, z, PRIMARY KEY(w, z)); +** +** Then the three output variables are populated as follows: +** +** *pzTab = "tbl1" +** *pazCol = {"w", "x", "y", "z"} +** *pabPK = {1, 0, 0, 1} +** +** All returned buffers are part of the same single allocation, which must +** be freed using sqlite3_free() by the caller. If pazCol was not NULL, then +** pointer *pazCol should be freed to release all memory. Otherwise, pointer +** *pabPK. It is illegal for both pazCol and pabPK to be NULL. +*/ +static int sessionTableInfo( + sqlite3 *db, /* Database connection */ + const char *zDb, /* Name of attached database (e.g. "main") */ + const char *zThis, /* Table name */ + int nCol, /* Expected number of columns */ + const char **pzTab, /* OUT: Copy of zThis */ + const char ***pazCol, /* OUT: Array of column names for table */ + u8 **pabPK /* OUT: Array of booleans - true for PK col */ +){ + char *zPragma; + sqlite3_stmt *pStmt; + int rc; + int nByte; + int nDbCol = 0; + int nThis; + int i; + u8 *pAlloc; + u8 *pFree = 0; + char **azCol; + u8 *abPK; + + assert( pazCol || pabPK ); + + nThis = strlen(zThis); + zPragma = sqlite3_mprintf("PRAGMA '%q'.table_info('%q')", zDb, zThis); + if( !zPragma ) return SQLITE_NOMEM; + + rc = sqlite3_prepare_v2(db, zPragma, -1, &pStmt, 0); + sqlite3_free(zPragma); + if( rc!=SQLITE_OK ) return rc; + + nByte = nThis + 1; + while( SQLITE_ROW==sqlite3_step(pStmt) ){ + nByte += sqlite3_column_bytes(pStmt, 1); + nDbCol++; + } + rc = sqlite3_reset(pStmt); + + if( nDbCol!=nCol ){ + rc = SQLITE_SCHEMA; + } + if( rc==SQLITE_OK ){ + nByte += nDbCol * (sizeof(const char *) + sizeof(u8) + 1); + pAlloc = sqlite3_malloc(nByte); + if( pAlloc==0 ){ + rc = SQLITE_NOMEM; + } + } + if( rc==SQLITE_OK ){ + pFree = pAlloc; + if( pazCol ){ + azCol = (char **)pAlloc; + pAlloc = (u8 *)&azCol[nCol]; + } + if( pabPK ){ + abPK = (u8 *)pAlloc; + pAlloc = &abPK[nCol]; + } + if( pzTab ){ + memcpy(pAlloc, zThis, nThis+1); + *pzTab = (char *)pAlloc; + pAlloc += nThis+1; + } + + i = 0; + while( SQLITE_ROW==sqlite3_step(pStmt) ){ + int nName = sqlite3_column_bytes(pStmt, 1); + const unsigned char *zName = sqlite3_column_text(pStmt, 1); + if( zName==0 ) break; + if( pazCol ){ + memcpy(pAlloc, zName, nName+1); + azCol[i] = (char *)pAlloc; + pAlloc += nName+1; + } + if( pabPK ) abPK[i] = sqlite3_column_int(pStmt, 5); + i++; + } + rc = sqlite3_reset(pStmt); + + } + + /* If successful, populate the output variables. Otherwise, zero them and + ** free any allocation made. An error code will be returned in this case. + */ + if( rc==SQLITE_OK ){ + if( pazCol ) *pazCol = (const char **)azCol; + if( pabPK ) *pabPK = abPK; + }else{ + if( pazCol ) *pazCol = 0; + if( pabPK ) *pabPK = 0; + if( pzTab ) *pzTab = 0; + sqlite3_free(pFree); + } + sqlite3_finalize(pStmt); + return rc; +} + /* ** This function is only called from within a pre-update handler for a ** write to table pTab, part of session pSession. If this is the first @@ -336,12 +637,104 @@ static int sessionGrowHash(sqlite3_session *pSession, SessionTable *pTab){ */ static int sessionInitTable(sqlite3_session *pSession, SessionTable *pTab){ if( pTab->nCol==0 ){ + assert( pTab->azCol==0 || pTab->abPK==0 ); pTab->nCol = sqlite3_preupdate_count(pSession->db); + pSession->rc = sessionTableInfo(pSession->db, pSession->zDb, + pTab->zName, pTab->nCol, 0, &pTab->azCol, &pTab->abPK + ); }else if( pTab->nCol!=sqlite3_preupdate_count(pSession->db) ){ pSession->rc = SQLITE_SCHEMA; - return SQLITE_ERROR; } - return SQLITE_OK; + return pSession->rc; +} + +static void sessionPreupdateOneChange( + int op, + sqlite3_session *pSession, + SessionTable *pTab +){ + sqlite3 *db = pSession->db; + SessionChange *pChange; + SessionChange *pC; + int iHash; + int rc = SQLITE_OK; + + if( pSession->rc ) return; + + /* Load table details if required */ + if( sessionInitTable(pSession, pTab) ) return; + + /* Grow the hash table if required */ + if( sessionGrowHash(pSession, pTab) ) return; + + /* Search the hash table for an existing entry for rowid=iKey2. If + ** one is found, store a pointer to it in pChange and unlink it from + ** the hash table. Otherwise, set pChange to NULL. + */ + rc = sessionPreupdateHash(db, pTab, op==SQLITE_INSERT, &iHash); + for(pC=pTab->apChange[iHash]; rc==SQLITE_OK && pC; pC=pC->pNext){ + int bEqual; + rc = sessionPreupdateEqual(db, pTab, pC, op==SQLITE_INSERT, &bEqual); + if( bEqual ) break; + } + if( pC==0 ){ + /* Create a new change object containing all the old values (if + ** this is an SQLITE_UPDATE or SQLITE_DELETE), or just the PK + ** values (if this is an INSERT). */ + int nByte; /* Number of bytes to allocate */ + int i; /* Used to iterate through columns */ + + pTab->nEntry++; + + /* Figure out how large an allocation is required */ + nByte = sizeof(SessionChange); + for(i=0; inCol && rc==SQLITE_OK; i++){ + sqlite3_value *p = 0; + if( op!=SQLITE_INSERT ){ + rc = sqlite3_preupdate_old(pSession->db, i, &p); + }else if( 1 || pTab->abPK[i] ){ + rc = sqlite3_preupdate_new(pSession->db, i, &p); + } + if( p && rc==SQLITE_OK ){ + rc = sessionSerializeValue(0, p, &nByte); + } + } + + /* Allocate the change object */ + pChange = (SessionChange *)sqlite3_malloc(nByte); + if( !pChange ){ + rc = SQLITE_NOMEM; + }else{ + memset(pChange, 0, sizeof(SessionChange)); + pChange->aRecord = (u8 *)&pChange[1]; + } + + /* Populate the change object */ + nByte = 0; + for(i=0; inCol && rc==SQLITE_OK; i++){ + sqlite3_value *p = 0; + if( op!=SQLITE_INSERT ){ + rc = sqlite3_preupdate_old(pSession->db, i, &p); + }else if( 1 || pTab->abPK[i] ){ + rc = sqlite3_preupdate_new(pSession->db, i, &p); + } + if( p && rc==SQLITE_OK ){ + rc = sessionSerializeValue(&pChange->aRecord[nByte], p, &nByte); + } + } + pChange->nRecord = nByte; + + /* If an error has occurred, mark the session object as failed. */ + if( rc!=SQLITE_OK ){ + sqlite3_free(pChange); + pSession->rc = rc; + }else{ + /* Add the change back to the hash-table */ + pChange->bInsert = (op==SQLITE_INSERT); + pChange->pNext = pTab->apChange[iHash]; + pTab->apChange[iHash] = pChange; + } + } } /* @@ -363,93 +756,19 @@ static void xPreUpdate( for(pSession=(sqlite3_session *)pCtx; pSession; pSession=pSession->pNext){ SessionTable *pTab; - /* If this session is already in the error-state, or if it is attached - ** to a different database ("main", "temp" etc.), or if it is not - ** currently enabled, there is nothing to do. Skip to the next session - ** object attached to this database. */ + /* If this session is attached to a different database ("main", "temp" + ** etc.), or if it is not currently enabled, there is nothing to do. Skip + ** to the next session object attached to this database. */ if( pSession->bEnable==0 ) continue; if( pSession->rc ) continue; if( sqlite3_strnicmp(zDb, pSession->zDb, nDb+1) ) continue; for(pTab=pSession->pTable; pTab; pTab=pTab->pNext){ if( 0==sqlite3_strnicmp(pTab->zName, zName, nName+1) ){ - SessionChange *pChange; - SessionChange *pC; - int iHash; - int rc = SQLITE_OK; - - /* Load table details if required */ - if( sessionInitTable(pSession, pTab) ) return; - - /* Grow the hash table if required */ - if( sessionGrowHash(pSession, pTab) ) return; - - /* Search the hash table for an existing entry for rowid=iKey2. If - ** one is found, store a pointer to it in pChange and unlink it from - ** the hash table. Otherwise, set pChange to NULL. - */ - iHash = sessionKeyhash(pTab->nChange, iKey2); - for(pC=pTab->apChange[iHash]; pC; pC=pC->pNext){ - if( pC->iKey==iKey2 ) break; + sessionPreupdateOneChange(op, pSession, pTab); + if( op==SQLITE_UPDATE ){ + sessionPreupdateOneChange(SQLITE_INSERT, pSession, pTab); } - if( pC ) continue; - - pTab->nEntry++; - - /* Create a new change object containing all the old values (if - ** this is an SQLITE_UPDATE or SQLITE_DELETE), or no record at - ** all (if this is an INSERT). */ - if( op==SQLITE_INSERT ){ - pChange = (SessionChange *)sqlite3_malloc(sizeof(SessionChange)); - if( pChange ){ - memset(pChange, 0, sizeof(SessionChange)); - } - }else{ - int nByte; /* Number of bytes to allocate */ - int i; /* Used to iterate through columns */ - - /* Figure out how large an allocation is required */ - nByte = sizeof(SessionChange); - for(i=0; inCol && rc==SQLITE_OK; i++){ - sqlite3_value *p; /* old.* value */ - rc = sqlite3_preupdate_old(pSession->db, i, &p); - if( rc==SQLITE_OK ){ - rc = sessionSerializeValue(0, p, &nByte); - } - } - - /* Allocate the change object */ - pChange = (SessionChange *)sqlite3_malloc(nByte); - if( !pChange ){ - rc = SQLITE_NOMEM; - }else{ - memset(pChange, 0, sizeof(SessionChange)); - pChange->aRecord = (u8 *)&pChange[1]; - } - - /* Populate the change object */ - nByte = 0; - for(i=0; inCol && rc==SQLITE_OK; i++){ - sqlite3_value *p; /* old.* value */ - rc = sqlite3_preupdate_old(pSession->db, i, &p); - if( rc==SQLITE_OK ){ - rc = sessionSerializeValue(&pChange->aRecord[nByte], p, &nByte); - } - } - pChange->nRecord = nByte; - } - - /* If an error has occurred, mark the session object as failed. */ - if( rc!=SQLITE_OK ){ - sqlite3_free(pChange); - pSession->rc = rc; - return; - } - - /* Add the change back to the hash-table */ - pChange->iKey = iKey2; - pChange->pNext = pTab->apChange[iHash]; - pTab->apChange[iHash] = pChange; break; } } @@ -524,6 +843,7 @@ void sqlite3session_delete(sqlite3_session *pSession){ sqlite3_free(p); } } + sqlite3_free(pTab->azCol); sqlite3_free(pTab->apChange); sqlite3_free(pTab); } @@ -852,127 +1172,99 @@ static void sessionAppendUpdate( } } -/* -** This function queries the database for the names of the columns of table -** zThis, in schema zDb. It is expected that the table has nCol columns. If -** not, SQLITE_SCHEMA is returned and none of the output variables are -** populated. -** -** Otherwise, if it is not NULL, variable *pzTab is set to point to a -** nul-terminated copy of the table name. *pazCol (if not NULL) is set to -** point to an array of pointers to column names. And *pabPK (again, if not -** NULL) is set to point to an array of booleans - true if the corresponding -** column is part of the primary key. -** -** For example, if the table is declared as: -** -** CREATE TABLE tbl1(w, x, y, z, PRIMARY KEY(w, z)); -** -** Then the three output variables are populated as follows: -** -** *pzTab = "tbl1" -** *pazCol = {"w", "x", "y", "z"} -** *pabPK = {1, 0, 0, 1} -** -** All returned buffers are part of the same single allocation, which must -** be freed using sqlite3_free() by the caller. If pazCol was not NULL, then -** pointer *pazCol should be freed to release all memory. Otherwise, pointer -** *pabPK. It is illegal for both pazCol and pabPK to be NULL. -*/ -static int sessionTableInfo( - sqlite3 *db, /* Database connection */ - const char *zDb, /* Name of attached database (e.g. "main") */ - const char *zThis, /* Table name */ - int nCol, /* Expected number of columns */ - const char **pzTab, /* OUT: Copy of zThis */ - const char ***pazCol, /* OUT: Array of column names for table */ - u8 **pabPK /* OUT: Array of booleans - true for PK col */ +static int sessionSelectStmt( + sqlite3 *db, /* Database handle */ + const char *zTab, /* Table name */ + int nCol, + const char **azCol, + u8 *abPK, + sqlite3_stmt **ppStmt ){ - char *zPragma; - sqlite3_stmt *pStmt; - int rc; - int nByte; - int nDbCol = 0; - int nThis; + int rc = SQLITE_OK; int i; - u8 *pAlloc; - u8 *pFree = 0; - char **azCol; - u8 *abPK; + const char *zSep = ""; + SessionBuffer buf = {0, 0, 0}; - assert( pazCol || pabPK ); - - nThis = strlen(zThis); - zPragma = sqlite3_mprintf("PRAGMA '%q'.table_info('%q')", zDb, zThis); - if( !zPragma ) return SQLITE_NOMEM; - - rc = sqlite3_prepare_v2(db, zPragma, -1, &pStmt, 0); - sqlite3_free(zPragma); - if( rc!=SQLITE_OK ) return rc; - - nByte = nThis + 1; - while( SQLITE_ROW==sqlite3_step(pStmt) ){ - nByte += sqlite3_column_bytes(pStmt, 1); - nDbCol++; - } - rc = sqlite3_reset(pStmt); - - if( nDbCol!=nCol ){ - rc = SQLITE_SCHEMA; - } - if( rc==SQLITE_OK ){ - nByte += nDbCol * (sizeof(const char *) + sizeof(u8) + 1); - pAlloc = sqlite3_malloc(nByte); - if( pAlloc==0 ){ - rc = SQLITE_NOMEM; + sessionAppendStr(&buf, "SELECT * FROM ", &rc); + sessionAppendIdent(&buf, zTab, &rc); + sessionAppendStr(&buf, " WHERE ", &rc); + for(i=0; ipTable; rc==SQLITE_OK && pTab; pTab=pTab->pNext){ if( pTab->nEntry ){ + int nCol = pTab->nCol; /* Local copy of member variable */ + u8 *abPK = pTab->abPK; /* Local copy of member variable */ int i; sqlite3_stmt *pStmt = 0; int bNoop = 1; int nRewind = buf.nBuf; - u8 *abPK = 0; /* Write a table header */ sessionAppendByte(&buf, 'T', &rc); - sessionAppendVarint(&buf, pTab->nCol, &rc); + sessionAppendVarint(&buf, nCol, &rc); sessionAppendBlob(&buf, (u8 *)pTab->zName, strlen(pTab->zName)+1, &rc); /* Build and compile a statement to execute: */ if( rc==SQLITE_OK ){ - char *zSql = sqlite3_mprintf("SELECT * FROM %Q.%Q WHERE _rowid_ = ?", - pSession->zDb, pTab->zName - ); - if( !zSql ){ - rc = SQLITE_NOMEM; - }else{ - rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); - } - sqlite3_free(zSql); + rc = sessionSelectStmt(db, pTab->zName, nCol, pTab->azCol, abPK,&pStmt); } - if( rc==SQLITE_OK && pTab->nCol!=sqlite3_column_count(pStmt) ){ + if( rc==SQLITE_OK && nCol!=sqlite3_column_count(pStmt) ){ rc = SQLITE_SCHEMA; } - if( rc==SQLITE_OK ){ - rc = sessionTableInfo( - db, pSession->zDb, pTab->zName, pTab->nCol, 0, 0, &abPK); - } - for(i=0; inChange; i++){ - SessionChange *p; + SessionChange *p; /* Used to iterate through changes */ + for(p=pTab->apChange[i]; rc==SQLITE_OK && p; p=p->pNext){ - sqlite3_bind_int64(pStmt, 1, p->iKey); - if( sqlite3_step(pStmt)==SQLITE_ROW ){ - int iCol; - if( p->aRecord ){ - sessionAppendUpdate(&buf, pStmt, p, abPK, &rc); - }else{ - sessionAppendByte(&buf, SQLITE_INSERT, &rc); - for(iCol=0; iColnCol; iCol++){ - sessionAppendCol(&buf, pStmt, iCol, &rc); + rc = sessionSelectBind(pStmt, nCol, abPK, p->aRecord, p->nRecord); + if( rc==SQLITE_OK ){ + if( sqlite3_step(pStmt)==SQLITE_ROW ){ + int iCol; + if( p->bInsert ){ + sessionAppendByte(&buf, SQLITE_INSERT, &rc); + for(iCol=0; iColbInsert ){ + /* A DELETE change */ + sessionAppendByte(&buf, SQLITE_DELETE, &rc); + sessionAppendBlob(&buf, p->aRecord, p->nRecord, &rc); + bNoop = 0; } - bNoop = 0; - }else if( p->aRecord ){ - /* A DELETE change */ - sessionAppendByte(&buf, SQLITE_DELETE, &rc); - sessionAppendBlob(&buf, p->aRecord, p->nRecord, &rc); - bNoop = 0; + rc = sqlite3_reset(pStmt); } - rc = sqlite3_reset(pStmt); } } sqlite3_finalize(pStmt); - sqlite3_free(abPK); if( bNoop ){ buf.nBuf = nRewind; @@ -1632,28 +1914,7 @@ static int sessionSelectRow( const char *zTab, /* Table name */ SessionApplyCtx *p /* Session changeset-apply context */ ){ - int rc = SQLITE_OK; - int i; - const char *zSep = ""; - SessionBuffer buf = {0, 0, 0}; - - sessionAppendStr(&buf, "SELECT * FROM ", &rc); - sessionAppendIdent(&buf, zTab, &rc); - sessionAppendStr(&buf, " WHERE ", &rc); - for(i=0; inCol; i++){ - if( p->abPK[i] ){ - sessionAppendStr(&buf, zSep, &rc); - sessionAppendIdent(&buf, p->azCol[i], &rc); - sessionAppendStr(&buf, " = ?", &rc); - sessionAppendInteger(&buf, i+1, &rc); - zSep = " AND "; - } - } - if( rc==SQLITE_OK ){ - rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pSelect, 0); - } - sqlite3_free(buf.aBuf); - return rc; + return sessionSelectStmt(db, zTab, p->nCol, p->azCol, p->abPK, &p->pSelect); } /* diff --git a/manifest b/manifest index 796df369be..2ab7a1eec9 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\sthe\ssqlite3_preupdate_new()\sAPI,\sfor\sretrieving\sthe\snew.*\svalues\sfrom\swithin\sa\spre-update\scallback. -D 2011-03-16T19:59:19 +C Change\sto\sthe\ssession\smodule\sto\suse\suser-defined\sprimary\skeys\sinstead\sof\srowids\swhen\scollecting\schanges. +D 2011-03-17T19:20:27 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in 27701a1653595a1f2187dc61c8117e00a6c1d50f F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -98,7 +98,7 @@ F ext/rtree/rtree_util.tcl 06aab2ed5b826545bf215fff90ecb9255a8647ea F ext/rtree/sqlite3rtree.h 1af0899c63a688e272d69d8e746f24e76f10a3f0 F ext/rtree/tkt3363.test 142ab96eded44a3615ec79fba98c7bde7d0f96de F ext/rtree/viewrtree.tcl eea6224b3553599ae665b239bd827e182b466024 -F ext/session/sqlite3session.c 9b8d123418c024f6851163375fca99042757772f +F ext/session/sqlite3session.c 4183792547af5b5d3ec1c8a3f858beb172682503 F ext/session/sqlite3session.h 63045871564085669b5cb1fb92e6efc2e1b1120a F ext/session/test_session.c 2559ef68e421c7fb83e2c19ef08a17343b70d535 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 @@ -639,7 +639,7 @@ F test/selectA.test 06d1032fa9009314c95394f2ca2e60d9f7ae8532 F test/selectB.test f305cc6660804cb239aab4e2f26b0e288b59958b F test/selectC.test f9bf1bc4581b5b8158caa6e4e4f682acb379fb25 F test/server1.test f5b790d4c0498179151ca8a7715a65a7802c859c -F test/session1.test 1e85c2eedb14587dff41901faa5a66c117fe0405 +F test/session1.test c6851e2e1066269cad5370566ce5494c7dbdfa7e F test/shared.test b9114eaea7e748a3a4c8ff7b9ca806c8f95cef3e F test/shared2.test 7f6ad2d857d0f4e5d6a0b9a897b5e56a6b6ea18c F test/shared3.test d69bdd5f156580876c5345652d21dc2092e85962 @@ -913,7 +913,7 @@ F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224 F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f -P b0015a1cfe63c924ee5f250aa08460522882009b -R 4b62a15aa3feb03b0e54e624839e53a1 +P 526545c49f64d9063d1b888cfc14ece62fa3c13c +R cab2f9d8485568249e1bae732085445e U dan -Z 0b4af75dcf5347bcbba47251e978ee95 +Z 0451d1109f54d52c16342adf23f8ece9 diff --git a/manifest.uuid b/manifest.uuid index 69c5255486..412c6f3bf5 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -526545c49f64d9063d1b888cfc14ece62fa3c13c \ No newline at end of file +6614cfcb9c41da71ddec3c44a3de0d4d849e1cdd \ No newline at end of file diff --git a/test/session1.test b/test/session1.test index ca8d9a78f1..872bee416f 100644 --- a/test/session1.test +++ b/test/session1.test @@ -37,7 +37,7 @@ proc do_changeset_invert_test {tn session res} { } do_execsql_test 1.0 { - CREATE TABLE t1(x, y); + CREATE TABLE t1(x PRIMARY KEY, y); INSERT INTO t1 VALUES('abc', 'def'); } @@ -113,6 +113,7 @@ do_changeset_invert_test 2.2.3 S { do_test 2.2.4 { S delete } {} do_test 2.3.1 { + execsql { DELETE FROM t1 } sqlite3session S db main execsql { INSERT INTO t1 VALUES(1, 'Sukhothai') } execsql { INSERT INTO t1 VALUES(2, 'Ayutthaya') } @@ -126,14 +127,19 @@ do_test 2.3.1 { } {} do_changeset_test 2.3.2 S { - {UPDATE t1 {i 1 {} {}} {i 10 {} {}}} - {UPDATE t1 {{} {} t Ayutthaya} {{} {} t Surin}} - {UPDATE t1 {i 3 t Thonburi} {i 20 t Thapae}} + {INSERT t1 {} {i 10 t Sukhothai}} + {DELETE t1 {i 1 t Sukhothai} {}} + {UPDATE t1 {i 2 t Ayutthaya} {{} {} t Surin}} + {DELETE t1 {i 3 t Thonburi} {}} + {INSERT t1 {} {i 20 t Thapae}} } + do_changeset_invert_test 2.3.3 S { - {UPDATE t1 {i 10 {} {}} {i 1 {} {}}} - {UPDATE t1 {{} {} t Surin} {{} {} t Ayutthaya}} - {UPDATE t1 {i 20 t Thapae} {i 3 t Thonburi}} + {DELETE t1 {i 10 t Sukhothai} {}} + {INSERT t1 {} {i 1 t Sukhothai}} + {UPDATE t1 {{} {} t Surin} {i 2 t Ayutthaya}} + {INSERT t1 {} {i 3 t Thonburi}} + {DELETE t1 {i 20 t Thapae} {}} } do_test 2.3.4 { S delete } {} @@ -209,7 +215,7 @@ do_test 3.1.0 { } db } {} do_db2_test 3.1.1 "INSERT INTO t1 VALUES(6, 'VI')" -do_conflict_test 3.2.2 -tables t1 -sql { +do_conflict_test 3.1.2 -tables t1 -sql { INSERT INTO t1 VALUES(3, 'three'); INSERT INTO t1 VALUES(4, 'four'); INSERT INTO t1 VALUES(5, 'five'); @@ -217,13 +223,14 @@ do_conflict_test 3.2.2 -tables t1 -sql { INSERT INTO t1 VALUES(7, 'seven'); INSERT INTO t1 VALUES(8, NULL); } -conflicts { - {INSERT t1 CONFLICT {i 6 t six} {i 6 t VI}} {INSERT t1 CONSTRAINT {i 8 n {}}} + {INSERT t1 CONFLICT {i 6 t six} {i 6 t VI}} } -do_db2_test 3.1.2 "SELECT * FROM t1" { + +do_db2_test 3.1.3 "SELECT * FROM t1" { 6 VI 3 three 4 four 5 five 7 seven } -do_execsql_test 3.1.3 "SELECT * FROM t1" { +do_execsql_test 3.1.4 "SELECT * FROM t1" { 1 one 2 two 3 three 4 four 5 five 6 six 7 seven 8 {} } @@ -282,9 +289,9 @@ do_conflict_test 3.3.3 -tables t4 -sql { UPDATE t4 SET a = NULL WHERE c = 9; UPDATE t4 SET a = 'x' WHERE b = 11; } -conflicts { + {UPDATE t4 CONSTRAINT {i 7 i 8 i 9} {n {} {} {} {} {}}} {UPDATE t4 DATA {i 1 i 2 i 3} {i -1 {} {} {} {}} {i 0 i 2 i 3}} {UPDATE t4 NOTFOUND {i 4 i 5 i 6} {i -1 {} {} {} {}}} - {UPDATE t4 CONSTRAINT {i 7 i 8 i 9} {n {} {} {} {} {}}} } do_db2_test 3.3.4 { SELECT * FROM t4 } {0 2 3 4 5 7 7 8 9 x 11 12} do_execsql_test 3.3.5 { SELECT * FROM t4 } {-1 2 3 -1 5 6 {} 8 9 x 11 12} @@ -308,8 +315,8 @@ proc xConflict {args} { } foreach {tn conflict_return after} { - 1 OMIT {1 2 value1 4 5 7 7 8 9 10 x x} - 2 REPLACE {1 2 value1 4 5 value2 10 8 9} + 1 OMIT {1 2 value1 4 5 7 10 x x} + 2 REPLACE {1 2 value1 4 5 value2 10 8 9} } { test_reset @@ -333,8 +340,8 @@ foreach {tn conflict_return after} { UPDATE t1 SET c = 'value2' WHERE a = 4; -- DATA conflict UPDATE t1 SET a = 10 WHERE a = 7; -- CONFLICT conflict } -conflicts { + {INSERT t1 CONFLICT {i 10 i 8 i 9} {i 10 t x t x}} {UPDATE t1 DATA {i 4 {} {} i 6} {{} {} {} {} t value2} {i 4 i 5 i 7}} - {UPDATE t1 CONFLICT {i 7 {} {} {} {}} {i 10 {} {} {} {}} {i 10 t x t x}} } do_db2_test 4.$tn.3 "SELECT * FROM t1 ORDER BY a" $after