mirror of
https://github.com/sqlite/sqlite.git
synced 2025-07-29 08:01:23 +03:00
Start adding the sqlite3changeset_concat() function to the session module.
FossilOrigin-Name: 8927b2260b8d84f53776cb29e1d2fa41b6b0de0e
This commit is contained in:
124
ext/session/session5.test
Normal file
124
ext/session/session5.test
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
# 2011 April 13
|
||||||
|
#
|
||||||
|
# The author disclaims copyright to this source code. In place of
|
||||||
|
# a legal notice, here is a blessing:
|
||||||
|
#
|
||||||
|
# May you do good and not evil.
|
||||||
|
# May you find forgiveness for yourself and forgive others.
|
||||||
|
# May you share freely, never taking more than you give.
|
||||||
|
#
|
||||||
|
#***********************************************************************
|
||||||
|
# This file implements regression tests for the session module.
|
||||||
|
# Specifically, for the sqlite3changeset_concat() command.
|
||||||
|
#
|
||||||
|
|
||||||
|
if {![info exists testdir]} {
|
||||||
|
set testdir [file join [file dirname [info script]] .. .. test]
|
||||||
|
}
|
||||||
|
source [file join [file dirname [info script]] session_common.tcl]
|
||||||
|
source $testdir/tester.tcl
|
||||||
|
ifcapable !session {finish_test; return}
|
||||||
|
|
||||||
|
set testprefix session5
|
||||||
|
|
||||||
|
proc do_concat_test {tn sql1 sql2} {
|
||||||
|
sqlite3session S1 db main ; S1 attach *
|
||||||
|
sqlite3session S2 db main ; S2 attach *
|
||||||
|
|
||||||
|
execsql $sql1
|
||||||
|
set C1 [S1 changeset]
|
||||||
|
S1 delete
|
||||||
|
|
||||||
|
sqlite3session S1 db main ; S1 attach *
|
||||||
|
|
||||||
|
execsql $sql2
|
||||||
|
set C2 [S1 changeset]
|
||||||
|
S1 delete
|
||||||
|
|
||||||
|
set C3 [S2 changeset]
|
||||||
|
S2 delete
|
||||||
|
|
||||||
|
set C4 [sqlite3changeset_concat $C1 $C2]
|
||||||
|
|
||||||
|
set c3 [list]
|
||||||
|
set c4 [list]
|
||||||
|
sqlite3session_foreach c $C3 { lappend c3 $c }
|
||||||
|
sqlite3session_foreach c $C4 { lappend c4 $c }
|
||||||
|
set c3 [lsort $c3]
|
||||||
|
set c4 [lsort $c4]
|
||||||
|
|
||||||
|
do_test $tn [list set {} $c4] $c3
|
||||||
|
}
|
||||||
|
|
||||||
|
do_execsql_test 1.0 {
|
||||||
|
CREATE TABLE t1(a PRIMARY KEY, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
do_concat_test 1.1.1 {
|
||||||
|
INSERT INTO t1 VALUES(1, 'one');
|
||||||
|
} {
|
||||||
|
INSERT INTO t1 VALUES(2, 'two');
|
||||||
|
}
|
||||||
|
|
||||||
|
do_concat_test 1.1.2 {
|
||||||
|
UPDATE t1 SET b = 'five' WHERE a = 1;
|
||||||
|
} {
|
||||||
|
UPDATE t1 SET b = 'six' WHERE a = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
do_concat_test 1.1.3 {
|
||||||
|
DELETE FROM t1 WHERE a = 1;
|
||||||
|
} {
|
||||||
|
DELETE FROM t1 WHERE a = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# 1.2.1: INSERT + DELETE -> (none)
|
||||||
|
# 1.2.2: INSERT + UPDATE -> INSERT
|
||||||
|
#
|
||||||
|
# 1.2.3: DELETE + INSERT (matching data) -> (none)
|
||||||
|
# 1.2.4: DELETE + INSERT (non-matching data) -> UPDATE
|
||||||
|
#
|
||||||
|
# 1.2.5: UPDATE + UPDATE (matching data) -> (none)
|
||||||
|
# 1.2.6: UPDATE + UPDATE (non-matching data) -> UPDATE
|
||||||
|
# 1.2.7: UPDATE + DELETE -> DELETE
|
||||||
|
#
|
||||||
|
do_concat_test 1.2.1 {
|
||||||
|
INSERT INTO t1 VALUES('x', 'y');
|
||||||
|
} {
|
||||||
|
DELETE FROM t1 WHERE a = 'x';
|
||||||
|
}
|
||||||
|
do_concat_test 1.2.2 {
|
||||||
|
INSERT INTO t1 VALUES(5.0, 'five');
|
||||||
|
} {
|
||||||
|
UPDATE t1 SET b = 'six' WHERE a = 5.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
do_execsql_test 1.2.3.1 "INSERT INTO t1 VALUES('I', 'one')"
|
||||||
|
do_concat_test 1.2.3.2 {
|
||||||
|
DELETE FROM t1 WHERE a = 'I';
|
||||||
|
} {
|
||||||
|
INSERT INTO t1 VALUES('I', 'one');
|
||||||
|
}
|
||||||
|
do_concat_test 1.2.4 {
|
||||||
|
DELETE FROM t1 WHERE a = 'I';
|
||||||
|
} {
|
||||||
|
INSERT INTO t1 VALUES('I', 'two');
|
||||||
|
}
|
||||||
|
do_concat_test 1.2.5 {
|
||||||
|
UPDATE t1 SET b = 'five' WHERE a = 'I';
|
||||||
|
} {
|
||||||
|
UPDATE t1 SET b = 'two' WHERE a = 'I';
|
||||||
|
}
|
||||||
|
do_concat_test 1.2.6 {
|
||||||
|
UPDATE t1 SET b = 'six' WHERE a = 'I';
|
||||||
|
} {
|
||||||
|
UPDATE t1 SET b = 'seven' WHERE a = 'I';
|
||||||
|
}
|
||||||
|
do_concat_test 1.2.7 {
|
||||||
|
UPDATE t1 SET b = 'eight' WHERE a = 'I';
|
||||||
|
} {
|
||||||
|
DELETE FROM t1 WHERE a = 'I';
|
||||||
|
}
|
||||||
|
|
||||||
|
finish_test
|
@ -126,6 +126,7 @@ struct SessionTable {
|
|||||||
** Followed by one or more changes to the table.
|
** Followed by one or more changes to the table.
|
||||||
**
|
**
|
||||||
** 1 byte: Either SQLITE_INSERT, UPDATE or DELETE.
|
** 1 byte: Either SQLITE_INSERT, UPDATE or DELETE.
|
||||||
|
** 1 byte: The "indirect-change" flag.
|
||||||
** old.* record: (delete and update only)
|
** old.* record: (delete and update only)
|
||||||
** new.* record: (insert and update only)
|
** new.* record: (insert and update only)
|
||||||
*/
|
*/
|
||||||
@ -353,20 +354,19 @@ static unsigned int sessionPreupdateHash(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** Based on the primary key values stored in change pChange, calculate a
|
** Based on the primary key values stored in change aRecord, calculate a
|
||||||
** hash key, assuming the has table has nBucket buckets. The hash keys
|
** hash key, assuming the has table has nBucket buckets. The hash keys
|
||||||
** calculated by this function are compatible with those calculated by
|
** calculated by this function are compatible with those calculated by
|
||||||
** sessionPreupdateHash().
|
** sessionPreupdateHash().
|
||||||
*/
|
*/
|
||||||
static unsigned int sessionChangeHash(
|
static unsigned int sessionChangeHash(
|
||||||
sqlite3 *db, /* Database handle */
|
|
||||||
SessionTable *pTab, /* Table handle */
|
SessionTable *pTab, /* Table handle */
|
||||||
SessionChange *pChange, /* Change handle */
|
u8 *aRecord, /* Change record */
|
||||||
int nBucket /* Assume this many buckets in hash table */
|
int nBucket /* Assume this many buckets in hash table */
|
||||||
){
|
){
|
||||||
unsigned int h = 0; /* Value to return */
|
unsigned int h = 0; /* Value to return */
|
||||||
int i; /* Used to iterate through columns */
|
int i; /* Used to iterate through columns */
|
||||||
u8 *a = pChange->aRecord; /* Used to iterate through change record */
|
u8 *a = aRecord; /* Used to iterate through change record */
|
||||||
|
|
||||||
for(i=0; i<pTab->nCol; i++){
|
for(i=0; i<pTab->nCol; i++){
|
||||||
int eType = *a++;
|
int eType = *a++;
|
||||||
@ -393,6 +393,160 @@ static unsigned int sessionChangeHash(
|
|||||||
return (h % nBucket);
|
return (h % nBucket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int sessionSerialLen(u8 *a){
|
||||||
|
int e = *a;
|
||||||
|
int n;
|
||||||
|
if( e==0 ) return 1;
|
||||||
|
if( e==SQLITE_NULL ) return 1;
|
||||||
|
if( e==SQLITE_INTEGER || e==SQLITE_FLOAT ) return 9;
|
||||||
|
return sessionVarintGet(&a[1], &n) + 1 + n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sessionChangeEqual(
|
||||||
|
SessionTable *pTab,
|
||||||
|
u8 *aLeft, /* Change record */
|
||||||
|
u8 *aRight /* Change record */
|
||||||
|
){
|
||||||
|
u8 *a1 = aLeft;
|
||||||
|
u8 *a2 = aRight;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i=0; i<pTab->nCol; i++){
|
||||||
|
int n1 = sessionSerialLen(a1);
|
||||||
|
int n2 = sessionSerialLen(a2);
|
||||||
|
|
||||||
|
if( pTab->abPK[i] && (n1!=n2 || memcmp(a1, a2, n1)) ){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
a1 += n1;
|
||||||
|
a2 += n1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sessionMergeRecord(
|
||||||
|
u8 **paOut,
|
||||||
|
SessionTable *pTab,
|
||||||
|
u8 *aLeft,
|
||||||
|
u8 *aRight
|
||||||
|
){
|
||||||
|
u8 *a1 = aLeft;
|
||||||
|
u8 *a2 = aRight;
|
||||||
|
u8 *aOut = *paOut;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i=0; i<pTab->nCol; i++){
|
||||||
|
int n1 = sessionSerialLen(a1);
|
||||||
|
int n2 = sessionSerialLen(a2);
|
||||||
|
if( *a2 ){
|
||||||
|
memcpy(aOut, a2, n2);
|
||||||
|
aOut += n2;
|
||||||
|
}else{
|
||||||
|
memcpy(aOut, a1, n1);
|
||||||
|
aOut += n1;
|
||||||
|
}
|
||||||
|
a1 += n1;
|
||||||
|
a2 += n2;
|
||||||
|
}
|
||||||
|
|
||||||
|
*paOut = aOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u8 *sessionMergeValue(
|
||||||
|
u8 **paOne,
|
||||||
|
u8 **paTwo,
|
||||||
|
int *pnVal
|
||||||
|
){
|
||||||
|
u8 *a1 = *paOne;
|
||||||
|
u8 *a2 = *paTwo;
|
||||||
|
u8 *pRet = 0;
|
||||||
|
int n1;
|
||||||
|
|
||||||
|
assert( a1 );
|
||||||
|
if( a2 ){
|
||||||
|
int n2 = sessionSerialLen(a2);
|
||||||
|
if( *a2 ){
|
||||||
|
*pnVal = n2;
|
||||||
|
pRet = a2;
|
||||||
|
}
|
||||||
|
*paTwo = &a2[n2];
|
||||||
|
}
|
||||||
|
|
||||||
|
n1 = sessionSerialLen(a1);
|
||||||
|
if( pRet==0 ){
|
||||||
|
*pnVal = n1;
|
||||||
|
pRet = a1;
|
||||||
|
}
|
||||||
|
*paOne = &a1[n1];
|
||||||
|
|
||||||
|
return pRet;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sessionMergeUpdate(
|
||||||
|
u8 **paOut,
|
||||||
|
SessionTable *pTab,
|
||||||
|
u8 *aOldRecord1,
|
||||||
|
u8 *aOldRecord2,
|
||||||
|
u8 *aNewRecord1,
|
||||||
|
u8 *aNewRecord2
|
||||||
|
){
|
||||||
|
u8 *aOld1 = aOldRecord1;
|
||||||
|
u8 *aOld2 = aOldRecord2;
|
||||||
|
u8 *aNew1 = aNewRecord1;
|
||||||
|
u8 *aNew2 = aNewRecord2;
|
||||||
|
|
||||||
|
u8 *aOut = *paOut;
|
||||||
|
int i;
|
||||||
|
int bRequired = 0;
|
||||||
|
|
||||||
|
assert( aOldRecord1 && aNewRecord1 );
|
||||||
|
|
||||||
|
/* Write the old.* vector first. */
|
||||||
|
for(i=0; i<pTab->nCol; i++){
|
||||||
|
int nOld;
|
||||||
|
u8 *aOld;
|
||||||
|
int nNew;
|
||||||
|
u8 *aNew;
|
||||||
|
|
||||||
|
aOld = sessionMergeValue(&aOld1, &aOld2, &nOld);
|
||||||
|
aNew = sessionMergeValue(&aNew1, &aNew2, &nNew);
|
||||||
|
if( pTab->abPK[i] || nOld!=nNew || memcmp(aOld, aNew, nNew) ){
|
||||||
|
if( pTab->abPK[i]==0 ) bRequired = 1;
|
||||||
|
memcpy(aOut, aOld, nOld);
|
||||||
|
aOut += nOld;
|
||||||
|
}else{
|
||||||
|
*(aOut++) = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !bRequired ) return 0;
|
||||||
|
|
||||||
|
/* Write the new.* vector */
|
||||||
|
aOld1 = aOldRecord1;
|
||||||
|
aOld2 = aOldRecord2;
|
||||||
|
aNew1 = aNewRecord1;
|
||||||
|
aNew2 = aNewRecord2;
|
||||||
|
for(i=0; i<pTab->nCol; i++){
|
||||||
|
int nOld;
|
||||||
|
u8 *aOld;
|
||||||
|
int nNew;
|
||||||
|
u8 *aNew;
|
||||||
|
|
||||||
|
aOld = sessionMergeValue(&aOld1, &aOld2, &nOld);
|
||||||
|
aNew = sessionMergeValue(&aNew1, &aNew2, &nNew);
|
||||||
|
if( pTab->abPK[i] || (nOld==nNew && 0==memcmp(aOld, aNew, nNew)) ){
|
||||||
|
*(aOut++) = '\0';
|
||||||
|
}else{
|
||||||
|
memcpy(aOut, aNew, nNew);
|
||||||
|
aOut += nNew;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*paOut = aOut;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
static int sessionPreupdateEqual(
|
static int sessionPreupdateEqual(
|
||||||
sqlite3 *db,
|
sqlite3 *db,
|
||||||
SessionTable *pTab,
|
SessionTable *pTab,
|
||||||
@ -480,7 +634,7 @@ static int sessionPreupdateEqual(
|
|||||||
** Growing the hash table in this case is a performance optimization only,
|
** Growing the hash table in this case is a performance optimization only,
|
||||||
** it is not required for correct operation.
|
** it is not required for correct operation.
|
||||||
*/
|
*/
|
||||||
static int sessionGrowHash(sqlite3_session *pSession, SessionTable *pTab){
|
static int sessionGrowHash(SessionTable *pTab){
|
||||||
if( pTab->nChange==0 || pTab->nEntry>=(pTab->nChange/2) ){
|
if( pTab->nChange==0 || pTab->nEntry>=(pTab->nChange/2) ){
|
||||||
int i;
|
int i;
|
||||||
SessionChange **apNew;
|
SessionChange **apNew;
|
||||||
@ -489,7 +643,6 @@ static int sessionGrowHash(sqlite3_session *pSession, SessionTable *pTab){
|
|||||||
apNew = (SessionChange **)sqlite3_malloc(sizeof(SessionChange *) * nNew);
|
apNew = (SessionChange **)sqlite3_malloc(sizeof(SessionChange *) * nNew);
|
||||||
if( apNew==0 ){
|
if( apNew==0 ){
|
||||||
if( pTab->nChange==0 ){
|
if( pTab->nChange==0 ){
|
||||||
pSession->rc = SQLITE_NOMEM;
|
|
||||||
return SQLITE_ERROR;
|
return SQLITE_ERROR;
|
||||||
}
|
}
|
||||||
return SQLITE_OK;
|
return SQLITE_OK;
|
||||||
@ -500,7 +653,7 @@ static int sessionGrowHash(sqlite3_session *pSession, SessionTable *pTab){
|
|||||||
SessionChange *p;
|
SessionChange *p;
|
||||||
SessionChange *pNext;
|
SessionChange *pNext;
|
||||||
for(p=pTab->apChange[i]; p; p=pNext){
|
for(p=pTab->apChange[i]; p; p=pNext){
|
||||||
int iHash = sessionChangeHash(pSession->db, pTab, p, nNew);
|
int iHash = sessionChangeHash(pTab, p->aRecord, nNew);
|
||||||
pNext = p->pNext;
|
pNext = p->pNext;
|
||||||
p->pNext = apNew[iHash];
|
p->pNext = apNew[iHash];
|
||||||
apNew[iHash] = p;
|
apNew[iHash] = p;
|
||||||
@ -677,7 +830,10 @@ static void sessionPreupdateOneChange(
|
|||||||
if( sessionInitTable(pSession, pTab) ) return;
|
if( sessionInitTable(pSession, pTab) ) return;
|
||||||
|
|
||||||
/* Grow the hash table if required */
|
/* Grow the hash table if required */
|
||||||
if( sessionGrowHash(pSession, pTab) ) return;
|
if( sessionGrowHash(pTab) ){
|
||||||
|
pSession->rc = SQLITE_NOMEM;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/* Search the hash table for an existing entry for rowid=iKey2. If
|
/* 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
|
** one is found, store a pointer to it in pChange and unlink it from
|
||||||
@ -693,8 +849,8 @@ static void sessionPreupdateOneChange(
|
|||||||
}
|
}
|
||||||
if( pC==0 ){
|
if( pC==0 ){
|
||||||
/* Create a new change object containing all the old values (if
|
/* Create a new change object containing all the old values (if
|
||||||
** this is an SQLITE_UPDATE or SQLITE_DELETE), or just the PK
|
** this is an SQLITE_UPDATE or SQLITE_DELETE), or just the PK
|
||||||
** values (if this is an INSERT). */
|
** values (if this is an INSERT). */
|
||||||
SessionChange *pChange; /* New change object */
|
SessionChange *pChange; /* New change object */
|
||||||
int nByte; /* Number of bytes to allocate */
|
int nByte; /* Number of bytes to allocate */
|
||||||
int i; /* Used to iterate through columns */
|
int i; /* Used to iterate through columns */
|
||||||
@ -851,6 +1007,27 @@ int sqlite3session_create(
|
|||||||
return SQLITE_OK;
|
return SQLITE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sessionDeleteTable(SessionTable *pList){
|
||||||
|
SessionTable *pNext;
|
||||||
|
SessionTable *pTab;
|
||||||
|
|
||||||
|
for(pTab=pList; pTab; pTab=pNext){
|
||||||
|
int i;
|
||||||
|
pNext = pTab->pNext;
|
||||||
|
for(i=0; i<pTab->nChange; i++){
|
||||||
|
SessionChange *p;
|
||||||
|
SessionChange *pNext;
|
||||||
|
for(p=pTab->apChange[i]; p; p=pNext){
|
||||||
|
pNext = p->pNext;
|
||||||
|
sqlite3_free(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sqlite3_free((char*)pTab->azCol); /* cast works around VC++ bug */
|
||||||
|
sqlite3_free(pTab->apChange);
|
||||||
|
sqlite3_free(pTab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** Delete a session object previously allocated using sqlite3session_create().
|
** Delete a session object previously allocated using sqlite3session_create().
|
||||||
*/
|
*/
|
||||||
@ -870,22 +1047,7 @@ void sqlite3session_delete(sqlite3_session *pSession){
|
|||||||
|
|
||||||
/* Delete all attached table objects. And the contents of their
|
/* Delete all attached table objects. And the contents of their
|
||||||
** associated hash-tables. */
|
** associated hash-tables. */
|
||||||
while( pSession->pTable ){
|
sessionDeleteTable(pSession->pTable);
|
||||||
int i;
|
|
||||||
SessionTable *pTab = pSession->pTable;
|
|
||||||
pSession->pTable = pTab->pNext;
|
|
||||||
for(i=0; i<pTab->nChange; i++){
|
|
||||||
SessionChange *p;
|
|
||||||
SessionChange *pNext;
|
|
||||||
for(p=pTab->apChange[i]; p; p=pNext){
|
|
||||||
pNext = p->pNext;
|
|
||||||
sqlite3_free(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sqlite3_free((char*)pTab->azCol); /* cast works around VC++ bug */
|
|
||||||
sqlite3_free(pTab->apChange);
|
|
||||||
sqlite3_free(pTab);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Free the session object itself. */
|
/* Free the session object itself. */
|
||||||
sqlite3_free(pSession);
|
sqlite3_free(pSession);
|
||||||
@ -1322,6 +1484,18 @@ static int sessionSelectBind(
|
|||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void sessionAppendTableHdr(
|
||||||
|
SessionBuffer *pBuf,
|
||||||
|
SessionTable *pTab,
|
||||||
|
int *pRc
|
||||||
|
){
|
||||||
|
/* Write a table header */
|
||||||
|
sessionAppendByte(pBuf, 'T', pRc);
|
||||||
|
sessionAppendVarint(pBuf, pTab->nCol, pRc);
|
||||||
|
sessionAppendBlob(pBuf, pTab->abPK, pTab->nCol, pRc);
|
||||||
|
sessionAppendBlob(pBuf, (u8 *)pTab->zName, strlen(pTab->zName)+1, pRc);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** Obtain a changeset object containing all changes recorded by the
|
** Obtain a changeset object containing all changes recorded by the
|
||||||
** session object passed as the first argument.
|
** session object passed as the first argument.
|
||||||
@ -1369,10 +1543,7 @@ int sqlite3session_changeset(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Write a table header */
|
/* Write a table header */
|
||||||
sessionAppendByte(&buf, 'T', &rc);
|
sessionAppendTableHdr(&buf, pTab, &rc);
|
||||||
sessionAppendVarint(&buf, nCol, &rc);
|
|
||||||
sessionAppendBlob(&buf, pTab->abPK, nCol, &rc);
|
|
||||||
sessionAppendBlob(&buf, (u8 *)zName, sqlite3Strlen30(zName)+1, &rc);
|
|
||||||
|
|
||||||
/* Build and compile a statement to execute: */
|
/* Build and compile a statement to execute: */
|
||||||
if( rc==SQLITE_OK ){
|
if( rc==SQLITE_OK ){
|
||||||
@ -1525,9 +1696,9 @@ static int sessionReadRecord(
|
|||||||
|
|
||||||
if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){
|
if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){
|
||||||
int nByte;
|
int nByte;
|
||||||
int enc = (eType==SQLITE_TEXT ? SQLITE_UTF8 : 0);
|
|
||||||
aRec += sessionVarintGet(aRec, &nByte);
|
aRec += sessionVarintGet(aRec, &nByte);
|
||||||
if( apOut ){
|
if( apOut ){
|
||||||
|
int enc = (eType==SQLITE_TEXT ? SQLITE_UTF8 : 0);
|
||||||
sqlite3ValueSetStr(apOut[i], nByte, aRec, enc, SQLITE_STATIC);
|
sqlite3ValueSetStr(apOut[i], nByte, aRec, enc, SQLITE_STATIC);
|
||||||
}
|
}
|
||||||
aRec += nByte;
|
aRec += nByte;
|
||||||
@ -1552,23 +1723,21 @@ static int sessionReadRecord(
|
|||||||
return SQLITE_OK;
|
return SQLITE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
static int sessionChangesetNext(
|
||||||
** Advance an iterator created by sqlite3changeset_start() to the next
|
sqlite3_changeset_iter *p,
|
||||||
** change in the changeset. This function may return SQLITE_ROW, SQLITE_DONE
|
u8 **paRec,
|
||||||
** or SQLITE_CORRUPT.
|
int *pnRec
|
||||||
**
|
){
|
||||||
** This function may not be called on iterators passed to a conflict handler
|
|
||||||
** callback by changeset_apply().
|
|
||||||
*/
|
|
||||||
int sqlite3changeset_next(sqlite3_changeset_iter *p){
|
|
||||||
u8 *aChange;
|
u8 *aChange;
|
||||||
int i;
|
int i;
|
||||||
u8 c;
|
u8 c;
|
||||||
|
|
||||||
|
assert( (paRec==0 && pnRec==0) || (paRec && pnRec) );
|
||||||
|
|
||||||
/* If the iterator is in the error-state, return immediately. */
|
/* If the iterator is in the error-state, return immediately. */
|
||||||
if( p->rc!=SQLITE_OK ) return p->rc;
|
if( p->rc!=SQLITE_OK ) return p->rc;
|
||||||
|
|
||||||
/* Free the current contents of p->apValue[]. */
|
/* Free the current contents of p->apValue[], if any. */
|
||||||
if( p->apValue ){
|
if( p->apValue ){
|
||||||
for(i=0; i<p->nCol*2; i++){
|
for(i=0; i<p->nCol*2; i++){
|
||||||
sqlite3ValueFree(p->apValue[i]);
|
sqlite3ValueFree(p->apValue[i]);
|
||||||
@ -1582,47 +1751,64 @@ int sqlite3changeset_next(sqlite3_changeset_iter *p){
|
|||||||
}
|
}
|
||||||
aChange = p->pNext;
|
aChange = p->pNext;
|
||||||
|
|
||||||
c = *(aChange++);
|
if( aChange[0]=='T' ){
|
||||||
if( c=='T' ){
|
|
||||||
int nByte; /* Bytes to allocate for apValue */
|
int nByte; /* Bytes to allocate for apValue */
|
||||||
|
aChange++;
|
||||||
aChange += sessionVarintGet(aChange, &p->nCol);
|
aChange += sessionVarintGet(aChange, &p->nCol);
|
||||||
p->abPK = (u8 *)aChange;
|
p->abPK = (u8 *)aChange;
|
||||||
aChange += p->nCol;
|
aChange += p->nCol;
|
||||||
p->zTab = (char *)aChange;
|
p->zTab = (char *)aChange;
|
||||||
aChange += (sqlite3Strlen30((char *)aChange) + 1);
|
aChange += (sqlite3Strlen30((char *)aChange) + 1);
|
||||||
p->op = *(aChange++);
|
|
||||||
p->bIndirect = *(aChange++);
|
if( paRec==0 ){
|
||||||
sqlite3_free(p->apValue);
|
sqlite3_free(p->apValue);
|
||||||
nByte = sizeof(sqlite3_value *) * p->nCol * 2;
|
nByte = sizeof(sqlite3_value *) * p->nCol * 2;
|
||||||
p->apValue = (sqlite3_value **)sqlite3_malloc(nByte);
|
p->apValue = (sqlite3_value **)sqlite3_malloc(nByte);
|
||||||
if( !p->apValue ){
|
if( !p->apValue ){
|
||||||
return (p->rc = SQLITE_NOMEM);
|
return (p->rc = SQLITE_NOMEM);
|
||||||
|
}
|
||||||
|
memset(p->apValue, 0, sizeof(sqlite3_value*)*p->nCol*2);
|
||||||
}
|
}
|
||||||
memset(p->apValue, 0, sizeof(sqlite3_value*)*p->nCol*2);
|
|
||||||
}else{
|
|
||||||
p->op = c;
|
|
||||||
p->bIndirect = *(aChange++);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p->op = *(aChange++);
|
||||||
|
p->bIndirect = *(aChange++);
|
||||||
if( p->op!=SQLITE_UPDATE && p->op!=SQLITE_DELETE && p->op!=SQLITE_INSERT ){
|
if( p->op!=SQLITE_UPDATE && p->op!=SQLITE_DELETE && p->op!=SQLITE_INSERT ){
|
||||||
return (p->rc = SQLITE_CORRUPT);
|
return (p->rc = SQLITE_CORRUPT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( paRec ){ *paRec = aChange; }
|
||||||
|
|
||||||
/* If this is an UPDATE or DELETE, read the old.* record. */
|
/* If this is an UPDATE or DELETE, read the old.* record. */
|
||||||
if( p->op!=SQLITE_INSERT ){
|
if( p->op!=SQLITE_INSERT ){
|
||||||
p->rc = sessionReadRecord(&aChange, p->nCol, p->apValue);
|
p->rc = sessionReadRecord(&aChange, p->nCol, paRec?0:p->apValue);
|
||||||
if( p->rc!=SQLITE_OK ) return p->rc;
|
if( p->rc!=SQLITE_OK ) return p->rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If this is an INSERT or UPDATE, read the new.* record. */
|
/* If this is an INSERT or UPDATE, read the new.* record. */
|
||||||
if( p->op!=SQLITE_DELETE ){
|
if( p->op!=SQLITE_DELETE ){
|
||||||
p->rc = sessionReadRecord(&aChange, p->nCol, &p->apValue[p->nCol]);
|
p->rc = sessionReadRecord(&aChange, p->nCol, paRec?0:&p->apValue[p->nCol]);
|
||||||
if( p->rc!=SQLITE_OK ) return p->rc;
|
if( p->rc!=SQLITE_OK ) return p->rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( pnRec ){ *pnRec = aChange - *paRec; }
|
||||||
p->pNext = aChange;
|
p->pNext = aChange;
|
||||||
return SQLITE_ROW;
|
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.
|
||||||
|
**
|
||||||
|
** This function may not be called on iterators passed to a conflict handler
|
||||||
|
** callback by changeset_apply().
|
||||||
|
*/
|
||||||
|
int sqlite3changeset_next(sqlite3_changeset_iter *p){
|
||||||
|
return sessionChangesetNext(p, 0, 0);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** The following function extracts information on the current change
|
** The following function extracts information on the current change
|
||||||
** from a changeset iterator. They may only be called after changeset_next()
|
** from a changeset iterator. They may only be called after changeset_next()
|
||||||
@ -2535,4 +2721,245 @@ int sqlite3changeset_apply(
|
|||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int sessionChangeMerge(
|
||||||
|
SessionTable *pTab,
|
||||||
|
SessionChange *pExist,
|
||||||
|
int op2,
|
||||||
|
int bIndirect,
|
||||||
|
u8 *aRec,
|
||||||
|
int nRec,
|
||||||
|
SessionChange **ppNew
|
||||||
|
){
|
||||||
|
SessionChange *pNew = 0;
|
||||||
|
|
||||||
|
if( !pExist ){
|
||||||
|
pNew = (SessionChange *)sqlite3_malloc(sizeof(SessionChange));
|
||||||
|
if( !pNew ){
|
||||||
|
return SQLITE_NOMEM;
|
||||||
|
}
|
||||||
|
memset(pNew, 0, sizeof(SessionChange));
|
||||||
|
pNew->bInsert = op2;
|
||||||
|
pNew->bIndirect = bIndirect;
|
||||||
|
pNew->nRecord = nRec;
|
||||||
|
pNew->aRecord = aRec;
|
||||||
|
}else{
|
||||||
|
int op1 = pExist->bInsert;
|
||||||
|
|
||||||
|
/*
|
||||||
|
** op1=INSERT, op2=INSERT -> Unsupported. Discard op2.
|
||||||
|
** op1=INSERT, op2=UPDATE -> INSERT.
|
||||||
|
** op1=INSERT, op2=DELETE -> (none)
|
||||||
|
**
|
||||||
|
** op1=UPDATE, op2=INSERT -> Unsupported. Discard op2.
|
||||||
|
** op1=UPDATE, op2=UPDATE -> UPDATE.
|
||||||
|
** op1=UPDATE, op2=DELETE -> DELETE.
|
||||||
|
**
|
||||||
|
** op1=DELETE, op2=INSERT -> UPDATE.
|
||||||
|
** op1=DELETE, op2=UPDATE -> Unsupported. Discard op2.
|
||||||
|
** op1=DELETE, op2=DELETE -> Unsupported. Discard op2.
|
||||||
|
*/
|
||||||
|
if( (op1==SQLITE_INSERT && op2==SQLITE_INSERT)
|
||||||
|
|| (op1==SQLITE_UPDATE && op2==SQLITE_INSERT)
|
||||||
|
|| (op1==SQLITE_DELETE && op2==SQLITE_UPDATE)
|
||||||
|
|| (op1==SQLITE_DELETE && op2==SQLITE_DELETE)
|
||||||
|
){
|
||||||
|
pNew = pExist;
|
||||||
|
}else if( op1==SQLITE_INSERT && op2==SQLITE_DELETE ){
|
||||||
|
sqlite3_free(pExist);
|
||||||
|
assert( pNew==0 );
|
||||||
|
}else{
|
||||||
|
int nByte;
|
||||||
|
u8 *aCsr;
|
||||||
|
|
||||||
|
nByte = sizeof(SessionChange) + pExist->nRecord + nRec;
|
||||||
|
pNew = (SessionChange *)sqlite3_malloc(nByte);
|
||||||
|
if( !pNew ){
|
||||||
|
return SQLITE_NOMEM;
|
||||||
|
}
|
||||||
|
memset(pNew, 0, sizeof(SessionChange));
|
||||||
|
pNew->bIndirect = (bIndirect && pExist->bIndirect);
|
||||||
|
aCsr = pNew->aRecord = (u8 *)&pNew[1];
|
||||||
|
|
||||||
|
if( op1==SQLITE_INSERT && op2==SQLITE_UPDATE ){
|
||||||
|
u8 *a1 = aRec;
|
||||||
|
pNew->bInsert = SQLITE_INSERT;
|
||||||
|
sessionReadRecord(&a1, pTab->nCol, 0);
|
||||||
|
sessionMergeRecord(&aCsr, pTab, pExist->aRecord, a1);
|
||||||
|
}
|
||||||
|
else if( op1==SQLITE_UPDATE && op2==SQLITE_UPDATE ){
|
||||||
|
u8 *a1 = pExist->aRecord;
|
||||||
|
u8 *a2 = aRec;
|
||||||
|
sessionReadRecord(&a1, pTab->nCol, 0);
|
||||||
|
sessionReadRecord(&a2, pTab->nCol, 0);
|
||||||
|
pNew->bInsert = SQLITE_UPDATE;
|
||||||
|
if( 0==sessionMergeUpdate(&aCsr, pTab, aRec, pExist->aRecord, a1, a2) ){
|
||||||
|
sqlite3_free(pNew);
|
||||||
|
pNew = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if( op1==SQLITE_UPDATE && op2==SQLITE_DELETE ){
|
||||||
|
pNew->bInsert = SQLITE_DELETE;
|
||||||
|
sessionMergeRecord(&aCsr, pTab, aRec, pExist->aRecord);
|
||||||
|
}
|
||||||
|
else if( op1==SQLITE_DELETE && op2==SQLITE_INSERT ){
|
||||||
|
pNew->bInsert = SQLITE_UPDATE;
|
||||||
|
if( 0==sessionMergeUpdate(&aCsr, pTab, pExist->aRecord, 0, aRec, 0) ){
|
||||||
|
sqlite3_free(pNew);
|
||||||
|
pNew = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( pNew ){
|
||||||
|
pNew->nRecord = (aCsr - pNew->aRecord);
|
||||||
|
}
|
||||||
|
sqlite3_free(pExist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*ppNew = pNew;
|
||||||
|
return SQLITE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sessionConcatChangeset(
|
||||||
|
int nChangeset,
|
||||||
|
void *pChangeset,
|
||||||
|
SessionTable **ppTabList
|
||||||
|
){
|
||||||
|
u8 *aRec;
|
||||||
|
int nRec;
|
||||||
|
sqlite3_changeset_iter *pIter;
|
||||||
|
int rc;
|
||||||
|
SessionTable *pTab = 0;
|
||||||
|
|
||||||
|
rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset);
|
||||||
|
if( rc!=SQLITE_OK ) return rc;
|
||||||
|
|
||||||
|
while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec) ){
|
||||||
|
const char *zNew;
|
||||||
|
int nCol;
|
||||||
|
int op;
|
||||||
|
int iHash;
|
||||||
|
int bIndirect;
|
||||||
|
SessionChange *pChange;
|
||||||
|
SessionChange *pExist = 0;
|
||||||
|
SessionChange **pp;
|
||||||
|
|
||||||
|
assert( pIter->apValue==0 );
|
||||||
|
sqlite3changeset_op(pIter, &zNew, &nCol, &op, &bIndirect);
|
||||||
|
|
||||||
|
if( !pTab || zNew!=pTab->zName ){
|
||||||
|
/* Search the list for a matching table */
|
||||||
|
int nNew = strlen(zNew);
|
||||||
|
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 ) break;
|
||||||
|
memset(pTab, 0, sizeof(SessionTable));
|
||||||
|
pTab->pNext = *ppTabList;
|
||||||
|
*ppTabList = pTab;
|
||||||
|
}
|
||||||
|
pTab->zName = (char *)zNew;
|
||||||
|
pTab->nCol = nCol;
|
||||||
|
sqlite3changeset_pk(pIter, &pTab->abPK, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if( sessionGrowHash(pTab) ) break;
|
||||||
|
iHash = sessionChangeHash(pTab, aRec, pTab->nChange);
|
||||||
|
|
||||||
|
/* Search for existing entry. If found, remove it from the hash table.
|
||||||
|
** Code below may link it back in.
|
||||||
|
*/
|
||||||
|
for(pp=&pTab->apChange[iHash]; *pp; pp=&(*pp)->pNext){
|
||||||
|
if( sessionChangeEqual(pTab, (*pp)->aRecord, aRec) ){
|
||||||
|
pExist = *pp;
|
||||||
|
*pp = (*pp)->pNext;
|
||||||
|
pTab->nEntry--;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = sessionChangeMerge(pTab, pExist, op, bIndirect, aRec, nRec, &pChange);
|
||||||
|
if( rc ) break;
|
||||||
|
if( pChange ){
|
||||||
|
pChange->pNext = pTab->apChange[iHash];
|
||||||
|
pTab->apChange[iHash] = pChange;
|
||||||
|
pTab->nEntry++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( rc==SQLITE_OK ){
|
||||||
|
rc = sqlite3changeset_finalize(pIter);
|
||||||
|
}else{
|
||||||
|
sqlite3changeset_finalize(pIter);
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
** 1. Iterate through the left-hand changeset. Add an entry to a table
|
||||||
|
** specific hash table for each change in the changeset. The hash table
|
||||||
|
** key is the PK of the row affected by the change.
|
||||||
|
**
|
||||||
|
** 2. Then interate through the right-hand changeset. Attempt to add an
|
||||||
|
** entry to a hash table for each component change. If a change already
|
||||||
|
** exists with the same PK values, combine the two into a single change.
|
||||||
|
**
|
||||||
|
** 3. Write an output changeset based on the contents of the hash table.
|
||||||
|
*/
|
||||||
|
int sqlite3changeset_concat(
|
||||||
|
int nLeft, /* Number of bytes in lhs input */
|
||||||
|
void *pLeft, /* Lhs input changeset */
|
||||||
|
int nRight /* Number of bytes in rhs input */,
|
||||||
|
void *pRight, /* Rhs input changeset */
|
||||||
|
int *pnOut, /* OUT: Number of bytes in output changeset */
|
||||||
|
void **ppOut /* OUT: changeset (left <concat> right) */
|
||||||
|
){
|
||||||
|
SessionTable *pList = 0; /* List of SessionTable objects */
|
||||||
|
int rc; /* Return code */
|
||||||
|
|
||||||
|
*pnOut = 0;
|
||||||
|
*ppOut = 0;
|
||||||
|
|
||||||
|
rc = sessionConcatChangeset(nLeft, pLeft, &pList);
|
||||||
|
if( rc==SQLITE_OK ){
|
||||||
|
rc = sessionConcatChangeset(nRight, pRight, &pList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create the serialized output changeset based on the contents of the
|
||||||
|
** hash tables attached to the SessionTable objects in list pList.
|
||||||
|
*/
|
||||||
|
if( rc==SQLITE_OK ){
|
||||||
|
SessionTable *pTab;
|
||||||
|
SessionBuffer buf = {0, 0, 0};
|
||||||
|
for(pTab=pList; pTab; pTab=pTab->pNext){
|
||||||
|
int i;
|
||||||
|
if( pTab->nEntry==0 ) continue;
|
||||||
|
|
||||||
|
sessionAppendTableHdr(&buf, pTab, &rc);
|
||||||
|
for(i=0; i<pTab->nChange; i++){
|
||||||
|
SessionChange *p;
|
||||||
|
for(p=pTab->apChange[i]; p; p=p->pNext){
|
||||||
|
sessionAppendByte(&buf, p->bInsert, &rc);
|
||||||
|
sessionAppendByte(&buf, p->bIndirect, &rc);
|
||||||
|
sessionAppendBlob(&buf, p->aRecord, p->nRecord, &rc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( rc==SQLITE_OK ){
|
||||||
|
*ppOut = buf.aBuf;
|
||||||
|
*pnOut = buf.nBuf;
|
||||||
|
}else{
|
||||||
|
sqlite3_free(buf.aBuf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
concat_out:
|
||||||
|
sessionDeleteTable(pList);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
#endif /* SQLITE_ENABLE_SESSION && SQLITE_ENABLE_PREUPDATE_HOOK */
|
#endif /* SQLITE_ENABLE_SESSION && SQLITE_ENABLE_PREUPDATE_HOOK */
|
||||||
|
@ -522,6 +522,12 @@ int sqlite3changeset_invert(
|
|||||||
int *pnOut, void **ppOut /* OUT: Inverse of input */
|
int *pnOut, void **ppOut /* OUT: Inverse of input */
|
||||||
);
|
);
|
||||||
|
|
||||||
|
int sqlite3changeset_concat(
|
||||||
|
int nLeft, void *pLeft, /* Input changeset */
|
||||||
|
int nRight, void *Right, /* Input changeset */
|
||||||
|
int *pnOut, void **ppOut /* OUT: Inverse of input */
|
||||||
|
);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** CAPI3REF: Apply A Changeset To A Database
|
** CAPI3REF: Apply A Changeset To A Database
|
||||||
**
|
**
|
||||||
|
@ -410,6 +410,39 @@ static int test_sqlite3changeset_invert(
|
|||||||
return TCL_OK;
|
return TCL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** sqlite3changeset_concat LEFT RIGHT
|
||||||
|
*/
|
||||||
|
static int test_sqlite3changeset_concat(
|
||||||
|
void * clientData,
|
||||||
|
Tcl_Interp *interp,
|
||||||
|
int objc,
|
||||||
|
Tcl_Obj *CONST objv[]
|
||||||
|
){
|
||||||
|
int rc; /* Return code from changeset_invert() */
|
||||||
|
void *aLeft; /* Input changeset */
|
||||||
|
int nLeft; /* Size of buffer aChangeset in bytes */
|
||||||
|
void *aRight; /* Input changeset */
|
||||||
|
int nRight; /* Size of buffer aChangeset in bytes */
|
||||||
|
void *aOut; /* Output changeset */
|
||||||
|
int nOut; /* Size of buffer aOut in bytes */
|
||||||
|
|
||||||
|
if( objc!=3 ){
|
||||||
|
Tcl_WrongNumArgs(interp, 1, objv, "LEFT RIGHT");
|
||||||
|
return TCL_ERROR;
|
||||||
|
}
|
||||||
|
aLeft = (void *)Tcl_GetByteArrayFromObj(objv[1], &nLeft);
|
||||||
|
aRight = (void *)Tcl_GetByteArrayFromObj(objv[2], &nRight);
|
||||||
|
|
||||||
|
rc = sqlite3changeset_concat(nLeft, aLeft, nRight, aRight, &nOut, &aOut);
|
||||||
|
if( rc!=SQLITE_OK ){
|
||||||
|
return test_session_error(interp, rc);
|
||||||
|
}
|
||||||
|
Tcl_SetObjResult(interp, Tcl_NewByteArrayObj((unsigned char *)aOut, nOut));
|
||||||
|
sqlite3_free(aOut);
|
||||||
|
return TCL_OK;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** sqlite3session_foreach VARNAME CHANGESET SCRIPT
|
** sqlite3session_foreach VARNAME CHANGESET SCRIPT
|
||||||
*/
|
*/
|
||||||
@ -514,6 +547,9 @@ int TestSession_Init(Tcl_Interp *interp){
|
|||||||
Tcl_CreateObjCommand(
|
Tcl_CreateObjCommand(
|
||||||
interp, "sqlite3changeset_invert", test_sqlite3changeset_invert, 0, 0
|
interp, "sqlite3changeset_invert", test_sqlite3changeset_invert, 0, 0
|
||||||
);
|
);
|
||||||
|
Tcl_CreateObjCommand(
|
||||||
|
interp, "sqlite3changeset_concat", test_sqlite3changeset_concat, 0, 0
|
||||||
|
);
|
||||||
Tcl_CreateObjCommand(
|
Tcl_CreateObjCommand(
|
||||||
interp, "sqlite3changeset_apply", test_sqlite3changeset_apply, 0, 0
|
interp, "sqlite3changeset_apply", test_sqlite3changeset_apply, 0, 0
|
||||||
);
|
);
|
||||||
|
19
manifest
19
manifest
@ -1,5 +1,5 @@
|
|||||||
C Merge\sthe\slatest\strunk\schanges\sinto\sthe\ssessions\sbranch.
|
C Start\sadding\sthe\ssqlite3changeset_concat()\sfunction\sto\sthe\ssession\smodule.
|
||||||
D 2011-04-09T18:07:51.034
|
D 2011-04-14T11:16:21.630
|
||||||
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
|
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
|
||||||
F Makefile.in 7a4d9524721d40ef9ee26f93f9bd6a51dba106f2
|
F Makefile.in 7a4d9524721d40ef9ee26f93f9bd6a51dba106f2
|
||||||
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
|
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
|
||||||
@ -103,11 +103,12 @@ F ext/session/session1.test 7a92a2a6f531aef1e9764ffb7f983fb8b315376d
|
|||||||
F ext/session/session2.test c3e5f78d5eb988e35cc2ba9ce3678f706283cfdb
|
F ext/session/session2.test c3e5f78d5eb988e35cc2ba9ce3678f706283cfdb
|
||||||
F ext/session/session3.test bfa2376db7cbb2ac69496f84d93a8d81b13110d3
|
F ext/session/session3.test bfa2376db7cbb2ac69496f84d93a8d81b13110d3
|
||||||
F ext/session/session4.test a6ed685da7a5293c5d6f99855bcf41dbc352ca84
|
F ext/session/session4.test a6ed685da7a5293c5d6f99855bcf41dbc352ca84
|
||||||
|
F ext/session/session5.test ed5025c96693d406fb13bcf330d1a962dcb68c24
|
||||||
F ext/session/session_common.tcl fb91560b6dbd086010df8b3a137a452f1ac21a28
|
F ext/session/session_common.tcl fb91560b6dbd086010df8b3a137a452f1ac21a28
|
||||||
F ext/session/sessionfault.test 2544a2e2ecad56e3c07a32c09799871d243c114c
|
F ext/session/sessionfault.test 2544a2e2ecad56e3c07a32c09799871d243c114c
|
||||||
F ext/session/sqlite3session.c bc6fc77d70d4d9994598b1daf0a43d48965b2155
|
F ext/session/sqlite3session.c 124ac6d43ac5820add2e736b25432c3f6b5a733a
|
||||||
F ext/session/sqlite3session.h 9b91addc5bd1777137d4f1c0252da9fbe2d4618e
|
F ext/session/sqlite3session.h dc7c85fd27fa3a9a17b34e0951ed36cdced1bc67
|
||||||
F ext/session/test_session.c 82e3fd7d94f485ea63bcfb15d636c95a01db97a9
|
F ext/session/test_session.c f4d1dca94db71ec2177ee61eab51e718e58476d7
|
||||||
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
|
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
|
||||||
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
|
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
|
||||||
F main.mk f942406cb7df55d1aec40a88a7ae399b730cd94f
|
F main.mk f942406cb7df55d1aec40a88a7ae399b730cd94f
|
||||||
@ -937,7 +938,7 @@ F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
|
|||||||
F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
|
F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
|
||||||
F tool/split-sqlite3c.tcl d9be87f1c340285a3e081eb19b4a247981ed290c
|
F tool/split-sqlite3c.tcl d9be87f1c340285a3e081eb19b4a247981ed290c
|
||||||
F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
|
F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
|
||||||
P 29090b695a95feaba1b74f9894997083a060263a 1c2f0f8477bcf251fe874a2cfae4d7a403cb88ff
|
P 83705e90a54bad462a5b7fbca70cc129998f871c
|
||||||
R a40e5f171b5b8b5d469c7b8e3eba3790
|
R 2d3308935368f3ab85a5bea7204d6f97
|
||||||
U drh
|
U dan
|
||||||
Z 7e220d1108384435c7d9af48b2056273
|
Z 4aed6546b387a7c5d8dbcb80467675b2
|
||||||
|
@ -1 +1 @@
|
|||||||
83705e90a54bad462a5b7fbca70cc129998f871c
|
8927b2260b8d84f53776cb29e1d2fa41b6b0de0e
|
Reference in New Issue
Block a user