1
0
mirror of https://github.com/sqlite/sqlite.git synced 2026-10-23 21:49:02 +03:00

Add an API to the sessions module to add changes one at a time to an sqlite3_changegroup object.

FossilOrigin-Name: cf1122ea6409bcbd341b67ae45147abb8d5cd491ae95491c801d0621181da40b
This commit is contained in:
dan
2026-03-31 10:59:13 +00:00
7 changed files with 1323 additions and 79 deletions

View File

@@ -0,0 +1,400 @@
# 2026 January 09
#
# 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 SQLite library.
#
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 sessionchange2
do_test 1.1.1 {
sqlite3changegroup grp
list [catch { grp change_begin INSERT "nosuchtable" 0 } msg] $msg
} {1 {SQLITE_ERROR - no such table: nosuchtable}}
do_test 1.1.2 {
grp schema db main
list [catch { grp change_begin INSERT "nosuchtable" 0 } msg] $msg
} {1 {SQLITE_ERROR - no such table: nosuchtable}}
do_test 1.1.3 {
list [catch { grp change_begin_ne INSERT "nosuchtable" 0 } msg] $msg
} {1 SQLITE_ERROR}
do_execsql_test 1.2.1 {
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
}
do_test 1.2.2 {
list [catch { grp change_begin 435 "t1" 0 } msg] $msg
} {1 SQLITE_ERROR}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 2.0 {
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
CREATE TABLE t2(a, b);
}
do_test 2.1.1 {
sqlite3changegroup grp
grp schema db main
grp change_begin INSERT "t1" 0
grp change_int64 new 0 55
grp change_int64 new 1 101
grp change_finish false
} {}
do_test 2.1.2 {
set C [grp output]
grp delete
changeset_to_list $C
} {
{INSERT t1 0 X. {} {i 55 i 101}}
}
do_test 2.2.1 {
sqlite3changegroup grp
grp schema db main
grp change_begin INSERT "t2" 0
grp change_int64 new 0 -5
grp change_int64 new 1 -10
grp change_int64 new 2 -20
grp change_finish false
} {}
do_test 2.2.2 {
set C [grp output]
grp delete
changeset_to_list $C
} {
{INSERT t2 0 X.. {} {i -5 i -10 i -20}}
}
do_test 2.2.3 {
sqlite3changegroup grp
grp schema db main
grp change_begin INSERT "t1" 0
grp change_int64 new 0 223344
grp change_null new 1
grp change_finish false
} {}
do_test 2.2.4 {
set C [grp output]
grp delete
changeset_to_list $C
} {
{INSERT t1 0 X. {} {i 223344 n {}}}
}
do_test 2.2.5 {
sqlite3changegroup grp
grp schema db main
grp change_begin DELETE "t1" 0
grp change_int64 old 0 1
grp change_int64 old 1 123
grp change_finish false
} {}
do_test 2.2.6 {
set C [grp output]
grp delete
changeset_to_list $C
} {
{DELETE t1 0 X. {i 1 i 123} {}}
}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 3.0 {
CREATE TABLE t1(a PRIMARY KEY, b, c);
CREATE TABLE t2(x, y);
}
foreach {tn script error} {
1 {
grp change_begin UPDATE t1 0
} {SQLITE_ERROR - invalid change: undefined value in PK of old.* record}
2 {
grp change_begin UPDATE t1 0
grp change_int64 old 0 1234
grp change_int64 new 0 5678
} {SQLITE_ERROR - invalid change: defined value in PK of new.* record}
3 {
grp change_begin UPDATE t1 0
grp change_null old 0
} {SQLITE_ERROR - invalid change: null value in PK of old.* record}
4 {
grp change_begin UPDATE t1 0
grp change_int64 old 0 1234
grp change_int64 old 1 20
} {SQLITE_ERROR - invalid change: column 1 - old.* value is defined but new.* is undefined}
5 {
grp change_begin UPDATE t1 0
grp change_int64 old 0 1234
grp change_int64 new 1 20
} {SQLITE_ERROR - invalid change: column 1 - old.* value is undefined but new.* is defined}
6 {
grp change_begin INSERT t1 0
grp change_null new 0
grp change_int64 new 1 20
} {SQLITE_ERROR - invalid change: null value in PK}
7 {
grp change_begin INSERT t1 0
grp change_int64 new 1 20
} {SQLITE_ERROR - invalid change: column 0 is undefined}
8 {
grp change_begin INSERT t1 0
grp change_int64 new 0 20
} {SQLITE_ERROR - invalid change: column 1 is undefined}
9 {
grp change_begin DELETE t1 0
grp change_int64 old 0 20
} {SQLITE_ERROR - invalid change: column 1 is undefined}
10 {
grp change_begin DELETE t1 0
grp change_int64 old 1 20
} {SQLITE_ERROR - invalid change: column 0 is undefined}
} {
sqlite3changegroup grp
grp schema db main
eval $script
do_test 3.1.$tn {
list [catch { grp change_finish false } msg] $msg
} [list 1 $error]
grp delete
}
do_test 3.2.1 {
sqlite3changegroup grp
grp schema db main
grp change_begin DELETE t1 0
list [catch {grp change_int64 new 0 20} msg] $msg
} {1 SQLITE_ERROR}
do_test 3.2.2 {
grp change_finish true
grp change_begin INSERT t1 0
list [catch {grp change_int64 old 0 20} msg] $msg
} {1 SQLITE_ERROR}
grp delete
#-------------------------------------------------------------------------
reset_db
do_execsql_test 3.0 {
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
CREATE TABLE t2(a, b);
}
proc do_changegroup_script_test {tn script changeset patchset} {
breakpoint
sqlite3changegroup grp
grp schema db main
eval $script
do_test $tn.1 {
changeset_to_list [grp output]
} [list {*}$changeset]
grp delete
sqlite3changegroup grp
grp config patchset 1
grp schema db main
eval $script
do_test $tn.2 {
changeset_to_list [grp output]
} [list {*}$patchset]
grp delete
}
proc do_changegroup_patchset_test {tn script patchset} {
sqlite3changegroup grp
grp config patchset 1
grp schema db main
eval $script
do_test $tn {
changeset_to_list [grp output]
} [list {*}$patchset]
grp delete
}
do_changegroup_script_test 3.1 {
grp change_begin UPDATE t1 0
grp change_int64 old 0 123
grp change_int64 old 1 456
grp change_int64 new 1 789
grp change_finish false
grp change_begin INSERT t1 0
grp change_int64 new 0 124
grp change_text new 1 hello_world
grp change_finish false
grp change_begin DELETE t1 0
grp change_int64 old 0 125
grp change_double old 1 45.67
grp change_finish false
} {
{DELETE t1 0 X. {i 125 f 45.67} {}}
{INSERT t1 0 X. {} {i 124 t hello_world}}
{UPDATE t1 0 X. {i 123 i 456} {{} {} i 789}}
} {
{DELETE t1 0 X. {i 125 {} {}} {}}
{INSERT t1 0 X. {} {i 124 t hello_world}}
{UPDATE t1 0 X. {i 123 {} {}} {{} {} i 789}}
}
do_changegroup_script_test 3.1.2 {
grp change_begin INSERT t1 0
grp change_int64 new 0 124
grp change_text new 1 hello_world
grp change_finish false
} {
{INSERT t1 0 X. {} {i 124 t hello_world}}
} {
{INSERT t1 0 X. {} {i 124 t hello_world}}
}
do_changegroup_patchset_test 3.2 {
grp change_begin UPDATE t1 0
grp change_int64 old 0 123
grp change_int64 new 1 789
grp change_finish false
grp change_begin DELETE t1 1
grp change_int64 old 0 122
grp change_finish false
} {
{DELETE t1 1 X. {i 122 {} {}} {}}
{UPDATE t1 0 X. {i 123 {} {}} {{} {} i 789}}
}
do_changegroup_patchset_test 3.3 {
grp change_begin DELETE t1 0
grp change_int64 old 0 123
grp change_finish false
} {
{DELETE t1 0 X. {i 123 {} {}} {}}
}
do_changegroup_script_test 3.4 {
grp change_begin INSERT t1 0
grp change_int64 new 0 124
grp change_text new 1 hello_world
grp change_finish false
grp change_begin DELETE t1 0
grp change_int64 old 0 124
grp change_text old 1 hello_world
grp change_finish false
grp change_begin INSERT t1 0
grp change_int64 new 0 1000
grp change_blob new 1 abcd
grp change_finish false
grp change_begin UPDATE t1 0
grp change_int64 old 0 1000
grp change_blob old 1 abcd
grp change_blob new 1 bcda
grp change_finish false
} {
{INSERT t1 0 X. {} {i 1000 b bcda}}
} {
{INSERT t1 0 X. {} {i 1000 b bcda}}
}
do_changegroup_script_test 3.5 {
grp change_begin DELETE t1 0
grp change_int64 old 0 1000
grp change_text-1 old 1 "hello world"
grp change_finish false
} {
{DELETE t1 0 X. {i 1000 t {hello world}} {}}
} {
{DELETE t1 0 X. {i 1000 {} {}} {}}
}
#-------------------------------------------------------------------------
# Calling sqlite3changegroup_change_finish() if a change has not been
# started is a no-op.
#
do_test 4.0 {
sqlite3changegroup grp
grp change_finish false
} {}
# But calling any other sqlite3changegroup_change_xxx() function if a
# change has not been started is a misuse.
#
foreach {tn cmd} {
1 { grp change_blob old 0 abcd }
2 { grp change_text new 1 helloworld }
3 { grp change_int64 old 2 12345678 }
4 { grp change_double new 3 12.345 }
5 { grp change_null old 4 }
} {
do_test 4.1.$tn {
list [catch $::cmd msg] $msg
} {1 SQLITE_MISUSE}
}
grp delete
do_execsql_test 4.2.1 {
CREATE TABLE t3(a, b PRIMARY KEY);
}
do_test 4.2 {
sqlite3changegroup grp
grp schema db main
grp change_begin INSERT t3 0
list [catch { grp change_begin INSERT t3 0 } msg] $msg
} {1 SQLITE_MISUSE}
grp delete
do_test 4.3 {
sqlite3changegroup grp
grp schema db main
grp change_begin UPDATE t3 0
} {}
foreach {tn cmd} {
1 { grp change_blob old -1 abcd }
2 { grp change_text new 2 helloworld }
} {
do_test 4.4.$tn {
list [catch $::cmd msg] $msg
} {1 SQLITE_RANGE}
}
grp delete
finish_test

View File

@@ -43,7 +43,7 @@ set C2 [changeset_from_sql {
INSERT INTO t1 VALUES(0, 0, 0, 0);
}]
do_faultsim_test 1 -faults oom* -prep {
do_faultsim_test 1.2 -faults oom* -prep {
sqlite3changegroup G
} -body {
G schema db main
@@ -56,6 +56,20 @@ do_faultsim_test 1 -faults oom* -prep {
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
}
do_faultsim_test 1.3 -faults oom* -prep {
sqlite3changegroup G
} -body {
G schema db main
G change_begin INSERT t1 0
G change_int64 new 0 12345
G change_int64 new 1 67890
G output
set {} {}
} -test {
catch { G delete }
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 2.0 {

View File

@@ -391,6 +391,19 @@ static void sessionPutI64(u8 *aBuf, sqlite3_int64 i){
aBuf[7] = (i>> 0) & 0xFF;
}
/*
** Write a double value to the buffer aBuf[].
*/
static void sessionPutDouble(u8 *aBuf, double r){
/* TODO: SQLite does something special to deal with mixed-endian
** floating point values (e.g. ARM7). This code probably should
** too. */
u64 i;
assert( sizeof(double)==8 && sizeof(u64)==8 );
memcpy(&i, &r, 8);
sessionPutI64(aBuf, i);
}
/*
** This function is used to serialize the contents of value pValue (see
** comment titled "RECORD FORMAT" above).
@@ -428,16 +441,13 @@ static int sessionSerializeValue(
/* TODO: SQLite does something special to deal with mixed-endian
** floating point values (e.g. ARM7). This code probably should
** too. */
u64 i;
if( eType==SQLITE_INTEGER ){
i = (u64)sqlite3_value_int64(pValue);
u64 i = (u64)sqlite3_value_int64(pValue);
sessionPutI64(&aBuf[1], i);
}else{
double r;
assert( sizeof(double)==8 && sizeof(u64)==8 );
r = sqlite3_value_double(pValue);
memcpy(&i, &r, 8);
double r = sqlite3_value_double(pValue);
sessionPutDouble(&aBuf[1], r);
}
sessionPutI64(&aBuf[1], i);
}
nByte = 9;
break;
@@ -1369,9 +1379,7 @@ static void sessionUpdateOneChange(
case SQLITE_FLOAT: {
double rVal = sqlite3_column_double(pDflt, iField);
i64 iVal = 0;
memcpy(&iVal, &rVal, sizeof(rVal));
sessionPutI64(&pNew->aRecord[pNew->nRecord], iVal);
sessionPutDouble(&pNew->aRecord[pNew->nRecord], rVal);
pNew->nRecord += 8;
break;
}
@@ -2628,15 +2636,14 @@ static void sessionAppendCol(
int eType = sqlite3_column_type(pStmt, iCol);
sessionAppendByte(p, (u8)eType, pRc);
if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
sqlite3_int64 i;
u8 aBuf[8];
if( eType==SQLITE_INTEGER ){
i = sqlite3_column_int64(pStmt, iCol);
sqlite3_int64 i = sqlite3_column_int64(pStmt, iCol);
sessionPutI64(aBuf, i);
}else{
double r = sqlite3_column_double(pStmt, iCol);
memcpy(&i, &r, 8);
sessionPutDouble(aBuf, r);
}
sessionPutI64(aBuf, i);
sessionAppendBlob(p, aBuf, 8, pRc);
}
if( eType==SQLITE_BLOB || eType==SQLITE_TEXT ){
@@ -5677,6 +5684,21 @@ int sqlite3changeset_apply_strm(
);
}
/*
** The parts of the sqlite3_changegroup structure used by the
** sqlite3changegroup_change_xxx() APIs.
*/
typedef struct ChangeData ChangeData;
struct ChangeData {
SessionTable *pTab;
int bIndirect;
int eOp;
int nBufAlloc;
SessionBuffer *aBuf;
SessionBuffer record;
};
/*
** sqlite3_changegroup handle.
*/
@@ -5688,6 +5710,7 @@ struct sqlite3_changegroup {
sqlite3 *db; /* Configured by changegroup_schema() */
char *zDb; /* Configured by changegroup_schema() */
ChangeData cd; /* Used by changegroup_change_xxx() APIs. */
};
/*
@@ -5927,15 +5950,14 @@ static int sessionChangesetExtendRecord(
switch( eType ){
case SQLITE_FLOAT:
case SQLITE_INTEGER: {
i64 iVal;
if( eType==SQLITE_INTEGER ){
iVal = sqlite3_column_int64(pTab->pDfltStmt, ii);
}else{
double rVal = sqlite3_column_int64(pTab->pDfltStmt, ii);
memcpy(&iVal, &rVal, sizeof(i64));
}
if( SQLITE_OK==sessionBufferGrow(pOut, 8, &rc) ){
sessionPutI64(&pOut->aBuf[pOut->nBuf], iVal);
if( eType==SQLITE_INTEGER ){
sqlite3_int64 iVal = sqlite3_column_int64(pTab->pDfltStmt, ii);
sessionPutI64(&pOut->aBuf[pOut->nBuf], iVal);
}else{
double rVal = sqlite3_column_double(pTab->pDfltStmt, ii);
sessionPutDouble(&pOut->aBuf[pOut->nBuf], rVal);
}
pOut->nBuf += 8;
}
break;
@@ -6006,13 +6028,19 @@ static int sessionChangesetFindTable(
int nCol = 0;
*ppTab = 0;
sqlite3changeset_pk(pIter, &abPK, &nCol);
/* Search the list for an existing table */
for(pTab = pGrp->pList; pTab; pTab=pTab->pNext){
if( 0==sqlite3_strnicmp(pTab->zName, zTab, nTab+1) ) break;
}
if( pIter ){
sqlite3changeset_pk(pIter, &abPK, &nCol);
}else if( !pTab && !pGrp->db ){
return SQLITE_OK;
}
/* If one was not found above, create a new table now */
if( !pTab ){
SessionTable **ppNew;
@@ -6031,8 +6059,8 @@ static int sessionChangesetFindTable(
if( pGrp->db ){
pTab->nCol = 0;
rc = sessionInitTable(0, pTab, pGrp->db, pGrp->zDb);
if( rc ){
assert( pTab->azCol==0 );
if( rc || pTab->nCol==0 ){
sqlite3_free(pTab->azCol);
sqlite3_free(pTab);
return rc;
}
@@ -6047,7 +6075,7 @@ static int sessionChangesetFindTable(
}
/* Check that the table is compatible. */
if( !sessionChangesetCheckCompat(pTab, nCol, abPK) ){
if( pIter && !sessionChangesetCheckCompat(pTab, nCol, abPK) ){
rc = SQLITE_SCHEMA;
}
@@ -6056,44 +6084,27 @@ static int sessionChangesetFindTable(
}
/*
** Add the change currently indicated by iterator pIter to the hash table
** belonging to changegroup pGrp.
** Add a single change to the changegroup pGrp.
*/
static int sessionOneChangeToHash(
sqlite3_changegroup *pGrp,
sqlite3_changeset_iter *pIter,
int bRebase
sqlite3_changegroup *pGrp, /* Changegroup to update */
SessionTable *pTab, /* Table change pertains to */
int op, /* One of SQLITE_INSERT, UPDATE, DELETE */
int bIndirect, /* True to flag change as "indirect" */
int nCol, /* Number of columns in record(s) */
u8 *aRec, /* Serialized change record(s) */
int nRec, /* Size of aRec[] in bytes */
int bRebase /* True if this is a rebase blob */
){
int rc = SQLITE_OK;
int nCol = 0;
int op = 0;
int iHash = 0;
int bIndirect = 0;
SessionChange *pChange = 0;
SessionChange *pExist = 0;
SessionChange **pp = 0;
SessionTable *pTab = 0;
u8 *aRec = &pIter->in.aData[pIter->in.iCurrent + 2];
int nRec = (pIter->in.iNext - pIter->in.iCurrent) - 2;
assert( nRec>0 );
/* Ensure that only changesets, or only patchsets, but not a mixture
** of both, are being combined. It is an error to try to combine a
** changeset and a patchset. */
if( pGrp->pList==0 ){
pGrp->bPatch = pIter->bPatchset;
}else if( pIter->bPatchset!=pGrp->bPatch ){
rc = SQLITE_ERROR;
}
if( rc==SQLITE_OK ){
const char *zTab = 0;
sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
rc = sessionChangesetFindTable(pGrp, zTab, pIter, &pTab);
}
if( rc==SQLITE_OK && nCol<pTab->nCol ){
if( nCol<pTab->nCol ){
SessionBuffer *pBuf = &pGrp->rec;
rc = sessionChangesetExtendRecord(pGrp, pTab, nCol, op, aRec, nRec, pBuf);
aRec = pBuf->aBuf;
@@ -6101,7 +6112,7 @@ static int sessionOneChangeToHash(
assert( pGrp->db );
}
if( rc==SQLITE_OK && sessionGrowHash(0, pIter->bPatchset, pTab) ){
if( rc==SQLITE_OK && sessionGrowHash(0, pGrp->bPatch, pTab) ){
rc = SQLITE_NOMEM;
}
@@ -6109,12 +6120,12 @@ static int sessionOneChangeToHash(
/* Search for existing entry. If found, remove it from the hash table.
** Code below may link it back in. */
iHash = sessionChangeHash(
pTab, (pIter->bPatchset && op==SQLITE_DELETE), aRec, pTab->nChange
pTab, (pGrp->bPatch && op==SQLITE_DELETE), aRec, pTab->nChange
);
for(pp=&pTab->apChange[iHash]; *pp; pp=&(*pp)->pNext){
int bPkOnly1 = 0;
int bPkOnly2 = 0;
if( pIter->bPatchset ){
if( pGrp->bPatch ){
bPkOnly1 = (*pp)->op==SQLITE_DELETE;
bPkOnly2 = op==SQLITE_DELETE;
}
@@ -6129,7 +6140,7 @@ static int sessionOneChangeToHash(
if( rc==SQLITE_OK ){
rc = sessionChangeMerge(pTab, bRebase,
pIter->bPatchset, pExist, op, bIndirect, aRec, nRec, &pChange
pGrp->bPatch, pExist, op, bIndirect, aRec, nRec, &pChange
);
}
if( rc==SQLITE_OK && pChange ){
@@ -6138,6 +6149,47 @@ static int sessionOneChangeToHash(
pTab->nEntry++;
}
return rc;
}
/*
** Add the change currently indicated by iterator pIter to the hash table
** belonging to changegroup pGrp.
*/
static int sessionOneChangeIterToHash(
sqlite3_changegroup *pGrp,
sqlite3_changeset_iter *pIter,
int bRebase
){
u8 *aRec = &pIter->in.aData[pIter->in.iCurrent + 2];
int nRec = (pIter->in.iNext - pIter->in.iCurrent) - 2;
const char *zTab = 0;
int nCol = 0;
int op = 0;
int bIndirect = 0;
int rc = SQLITE_OK;
SessionTable *pTab = 0;
/* Ensure that only changesets, or only patchsets, but not a mixture
** of both, are being combined. It is an error to try to combine a
** changeset and a patchset. */
if( pGrp->pList==0 ){
pGrp->bPatch = pIter->bPatchset;
}else if( pIter->bPatchset!=pGrp->bPatch ){
rc = SQLITE_ERROR;
}
if( rc==SQLITE_OK ){
sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
rc = sessionChangesetFindTable(pGrp, zTab, pIter, &pTab);
}
if( rc==SQLITE_OK ){
rc = sessionOneChangeToHash(
pGrp, pTab, op, bIndirect, nCol, aRec, nRec, bRebase
);
}
if( rc==SQLITE_OK ) rc = pIter->rc;
return rc;
}
@@ -6157,7 +6209,7 @@ static int sessionChangesetToHash(
pIter->in.bNoDiscard = 1;
while( SQLITE_ROW==(sessionChangesetNext(pIter, &aRec, &nRec, 0)) ){
rc = sessionOneChangeToHash(pGrp, pIter, bRebase);
rc = sessionOneChangeIterToHash(pGrp, pIter, bRebase);
if( rc!=SQLITE_OK ) break;
}
@@ -6247,6 +6299,33 @@ int sqlite3changegroup_new(sqlite3_changegroup **pp){
return rc;
}
/*
** Configure a changegroup object.
*/
int sqlite3changegroup_config(
sqlite3_changegroup *pGrp,
int op,
void *pArg
){
int rc = SQLITE_OK;
switch( op ){
case SQLITE_CHANGEGROUP_CONFIG_PATCHSET: {
int arg = *(int*)pArg;
if( pGrp->pList==0 && arg>=0 ){
pGrp->bPatch = (arg>0);
}
*(int*)pArg = pGrp->bPatch;
break;
}
default:
rc = SQLITE_MISUSE;
break;
}
return rc;
}
/*
** Provide a database schema to the changegroup object.
*/
@@ -6305,7 +6384,7 @@ int sqlite3changegroup_add_change(
rc = SQLITE_ERROR;
}else{
pIter->in.bNoDiscard = 1;
rc = sessionOneChangeToHash(pGrp, pIter, 0);
rc = sessionOneChangeIterToHash(pGrp, pIter, 0);
}
return rc;
}
@@ -6357,6 +6436,12 @@ int sqlite3changegroup_output_strm(
*/
void sqlite3changegroup_delete(sqlite3_changegroup *pGrp){
if( pGrp ){
int ii;
for(ii=0; ii<pGrp->cd.nBufAlloc; ii++){
sqlite3_free(pGrp->cd.aBuf[ii].aBuf);
}
sqlite3_free(pGrp->cd.record.aBuf);
sqlite3_free(pGrp->cd.aBuf);
sqlite3_free(pGrp->zDb);
sessionDeleteTable(0, pGrp->pList);
sqlite3_free(pGrp->rec.aBuf);
@@ -6787,4 +6872,326 @@ int sqlite3session_config(int op, void *pArg){
return rc;
}
/*
** Begin adding a change to a changegroup object.
*/
int sqlite3changegroup_change_begin(
sqlite3_changegroup *pGrp,
int eOp,
const char *zTab,
int bIndirect,
char **pzErr
){
SessionTable *pTab = 0;
int rc = SQLITE_OK;
if( pGrp->cd.pTab ){
rc = SQLITE_MISUSE;
}else if( eOp!=SQLITE_INSERT && eOp!=SQLITE_UPDATE && eOp!=SQLITE_DELETE ){
rc = SQLITE_ERROR;
}else{
rc = sessionChangesetFindTable(pGrp, zTab, 0, &pTab);
}
if( rc==SQLITE_OK ){
if( pTab==0 ){
if( pzErr ){
*pzErr = sqlite3_mprintf("no such table: %s", zTab);
}
rc = SQLITE_ERROR;
}else{
int nReq = pTab->nCol * (eOp==SQLITE_UPDATE ? 2 : 1);
pGrp->cd.pTab = pTab;
pGrp->cd.eOp = eOp;
pGrp->cd.bIndirect = bIndirect;
if( pGrp->cd.nBufAlloc<nReq ){
SessionBuffer *aBuf = (SessionBuffer*)sqlite3_realloc(
pGrp->cd.aBuf, nReq * sizeof(SessionBuffer)
);
if( aBuf==0 ){
rc = SQLITE_NOMEM;
}else{
memset(&aBuf[pGrp->cd.nBufAlloc], 0,
sizeof(SessionBuffer) * (nReq - pGrp->cd.nBufAlloc)
);
pGrp->cd.aBuf = aBuf;
pGrp->cd.nBufAlloc = nReq;
}
}
#ifdef SQLITE_DEBUG
{
/* Assert that all column values are currently undefined */
int ii;
for(ii=0; ii<pGrp->cd.nBufAlloc; ii++){
assert( pGrp->cd.aBuf[ii].nBuf==0 );
}
}
#endif
}
}
return rc;
}
/*
** This function does processing common to the _change_int64(), _change_text()
** and other similar APIs.
*/
static int checkChangeParams(
sqlite3_changegroup *pGrp,
int bNew,
int iCol,
sqlite3_int64 nReq,
SessionBuffer **ppBuf
){
int rc = SQLITE_OK;
if( pGrp->cd.pTab==0 ){
rc = SQLITE_MISUSE;
}else if( iCol<0 || iCol>=pGrp->cd.pTab->nCol ){
rc = SQLITE_RANGE;
}else if(
(bNew && pGrp->cd.eOp==SQLITE_DELETE)
|| (!bNew && pGrp->cd.eOp==SQLITE_INSERT)
){
rc = SQLITE_ERROR;
}else{
SessionBuffer *pBuf = &pGrp->cd.aBuf[iCol];
if( pGrp->cd.eOp==SQLITE_UPDATE && bNew ){
pBuf += pGrp->cd.pTab->nCol;
}
pBuf->nBuf = 0;
sessionBufferGrow(pBuf, nReq, &rc);
pBuf->nBuf = nReq;
*ppBuf = pBuf;
}
return rc;
}
/*
** Configure the change currently under construction with an integer value.
*/
int sqlite3changegroup_change_int64(
sqlite3_changegroup *pGrp,
int bNew,
int iCol,
sqlite3_int64 iVal
){
int rc = SQLITE_OK;
SessionBuffer *pBuf = 0;
if( SQLITE_OK!=(rc = checkChangeParams(pGrp, bNew, iCol, 9, &pBuf)) ){
return rc;
}
pBuf->aBuf[0] = SQLITE_INTEGER;
sessionPutI64(&pBuf->aBuf[1], iVal);
return SQLITE_OK;
}
/*
** Configure the change currently under construction with a null value.
*/
int sqlite3changegroup_change_null(
sqlite3_changegroup *pGrp,
int bNew,
int iCol
){
int rc = SQLITE_OK;
SessionBuffer *pBuf = 0;
if( SQLITE_OK!=(rc = checkChangeParams(pGrp, bNew, iCol, 1, &pBuf)) ){
return rc;
}
pBuf->aBuf[0] = SQLITE_NULL;
return SQLITE_OK;
}
/*
** Configure the change currently under construction with a real value.
*/
int sqlite3changegroup_change_double(
sqlite3_changegroup *pGrp,
int bNew,
int iCol,
double fVal
){
int rc = SQLITE_OK;
SessionBuffer *pBuf = 0;
if( SQLITE_OK!=(rc = checkChangeParams(pGrp, bNew, iCol, 9, &pBuf)) ){
return rc;
}
pBuf->aBuf[0] = SQLITE_FLOAT;
sessionPutDouble(&pBuf->aBuf[1], fVal);
return SQLITE_OK;
}
/*
** Configure the change currently under construction with a text value.
*/
int sqlite3changegroup_change_text(
sqlite3_changegroup *pGrp,
int bNew,
int iCol,
const char *pVal,
int nVal
){
int nText = nVal>=0 ? nVal : strlen(pVal);
sqlite3_int64 nByte = 1 + sessionVarintLen(nText) + nText;
int rc = SQLITE_OK;
SessionBuffer *pBuf = 0;
if( SQLITE_OK!=(rc = checkChangeParams(pGrp, bNew, iCol, nByte, &pBuf)) ){
return rc;
}
pBuf->aBuf[0] = SQLITE_TEXT;
pBuf->nBuf = (1 + sessionVarintPut(&pBuf->aBuf[1], nText));
memcpy(&pBuf->aBuf[pBuf->nBuf], pVal, nText);
pBuf->nBuf += nText;
return SQLITE_OK;
}
/*
** Configure the change currently under construction with a blob value.
*/
int sqlite3changegroup_change_blob(
sqlite3_changegroup *pGrp,
int bNew,
int iCol,
const void *pVal,
int nVal
){
sqlite3_int64 nByte = 1 + sessionVarintLen(nVal) + nVal;
int rc = SQLITE_OK;
SessionBuffer *pBuf = 0;
if( SQLITE_OK!=(rc = checkChangeParams(pGrp, bNew, iCol, nByte, &pBuf)) ){
return rc;
}
pBuf->aBuf[0] = SQLITE_BLOB;
pBuf->nBuf = (1 + sessionVarintPut(&pBuf->aBuf[1], nVal));
memcpy(&pBuf->aBuf[pBuf->nBuf], pVal, nVal);
pBuf->nBuf += nVal;
return SQLITE_OK;
}
/*
** Finish any change currently being constructed by the changegroup object.
*/
int sqlite3changegroup_change_finish(
sqlite3_changegroup *pGrp,
int bDiscard,
char **pzErr
){
int rc = SQLITE_OK;
if( pGrp->cd.pTab ){
SessionBuffer *aBuf = pGrp->cd.aBuf;
int ii;
if( bDiscard==0 ){
int nBuf = pGrp->cd.pTab->nCol;
u8 eUndef = SQLITE_NULL;
if( pGrp->cd.eOp==SQLITE_UPDATE ){
for(ii=0; ii<nBuf; ii++){
if( pGrp->cd.pTab->abPK[ii] ){
if( aBuf[ii].nBuf<=1 ){
*pzErr = sqlite3_mprintf(
"invalid change: %s value in PK of old.* record",
aBuf[ii].nBuf==1 ? "null" : "undefined"
);
rc = SQLITE_ERROR;
break;
}else if( aBuf[ii + nBuf].nBuf>0 ){
*pzErr = sqlite3_mprintf(
"invalid change: defined value in PK of new.* record"
);
rc = SQLITE_ERROR;
break;
}
}else
if( pGrp->bPatch==0 && (aBuf[ii].nBuf>0)!=(aBuf[ii+nBuf].nBuf>0) ){
*pzErr = sqlite3_mprintf(
"invalid change: column %d "
"- old.* value is %sdefined but new.* is %sdefined",
ii, aBuf[ii].nBuf ? "" : "un", aBuf[ii+nBuf].nBuf ? "" : "un"
);
rc = SQLITE_ERROR;
break;
}
}
eUndef = 0x00;
if( pGrp->bPatch==0 ) nBuf = nBuf * 2;
}else{
for(ii=0; ii<nBuf; ii++){
int isPK = pGrp->cd.pTab->abPK[ii];
if( (pGrp->cd.eOp==SQLITE_INSERT || pGrp->bPatch==0 || isPK)
&& aBuf[ii].nBuf==0
){
*pzErr = sqlite3_mprintf(
"invalid change: column %d is undefined", ii
);
rc = SQLITE_ERROR;
break;
}
if( aBuf[ii].nBuf==1 && isPK ){
*pzErr = sqlite3_mprintf(
"invalid change: null value in PK"
);
rc = SQLITE_ERROR;
break;
}
}
}
pGrp->cd.record.nBuf = 0;
for(ii=0; ii<nBuf; ii++){
SessionBuffer *p = &pGrp->cd.aBuf[ii];
if( pGrp->bPatch ){
if( pGrp->cd.pTab->abPK[ii]==0 ){
if( pGrp->cd.eOp==SQLITE_UPDATE ){
p += pGrp->cd.pTab->nCol;
}else if( pGrp->cd.eOp==SQLITE_DELETE ){
continue;
}
}
}
if( 0==sessionBufferGrow(&pGrp->cd.record, p->nBuf?p->nBuf:1, &rc) ){
if( p->nBuf ){
memcpy(&pGrp->cd.record.aBuf[pGrp->cd.record.nBuf],p->aBuf,p->nBuf);
pGrp->cd.record.nBuf += p->nBuf;
}else{
pGrp->cd.record.aBuf[pGrp->cd.record.nBuf++] = eUndef;
}
}
}
if( rc==SQLITE_OK ){
rc = sessionOneChangeToHash(
pGrp, pGrp->cd.pTab,
pGrp->cd.eOp, pGrp->cd.bIndirect, pGrp->cd.pTab->nCol,
pGrp->cd.record.aBuf, pGrp->cd.record.nBuf, 0
);
}
}
/* Reset all aBuf[] entries to "undefined". */
{
int nZero = pGrp->cd.pTab->nCol;
if( pGrp->cd.eOp==SQLITE_UPDATE ) nZero += nZero;
for(ii=0; ii<nZero; ii++){
pGrp->cd.aBuf[ii].nBuf = 0;
}
}
pGrp->cd.pTab = 0;
}
return rc;
}
#endif /* SQLITE_ENABLE_SESSION && SQLITE_ENABLE_PREUPDATE_HOOK */

View File

@@ -1853,6 +1853,220 @@ int sqlite3session_config(int op, void *pArg);
*/
#define SQLITE_SESSION_CONFIG_STRMSIZE 1
/*
** CAPI3REF: Configure a changegroup object
**
** Configure the changegroup object passed as the first argument.
** At present the only valid value for the second parameter is
** [SQLITE_CHANGEGROUP_CONFIG_PATCHSET].
*/
int sqlite3changegroup_config(sqlite3_changegroup*, int, void *pArg);
/*
** CAPI3REF: Options for sqlite3changegroup_config().
**
** The following values may be passed as the 2nd parameter to
** sqlite3changegroup_config().
**
** <dt>SQLITE_CHANGEGROUP_CONFIG_PATCHSET <dd>
** A changegroup object generates either a changeset or patchset. Usually,
** this is determined by whether the first call to sqlite3changegroup_add()
** is passed a changeset or a patchset. Or, if the first changes are added
** to the changegroup object using the sqlite3changegroup_change_xxx()
** APIs, then this option may be used to configure whether the changegroup
** object generates a changeset or patchset.
**
** When this option is invoked, parameter pArg must point to a value of
** type int. If the changegroup currently contains zero changes, and the
** value of the int variable is zero or greater than zero, then the
** changegroup is configured to generate a changeset or patchset,
** respectively. It is a no-op, not an error, if the changegroup is not
** configured because it has already started accumulating changes.
**
** Before returning, the int variable is set to 0 if the changegroup is
** configured to generate a changeset, or 1 if it is configured to generate
** a patchset.
*/
#define SQLITE_CHANGEGROUP_CONFIG_PATCHSET 1
/*
** CAPI3REF: Begin adding a change to a changegroup
**
** This API is used, in concert with other sqlite3changegroup_change_xxx()
** APIs, to add changes to a changegroup object one at a time. To add a
** single change, the caller must:
**
** 1. Invoke sqlite3changegroup_change_begin() to indicate the type of
** change (INSERT, UPDATE or DELETE), the affected table and whether
** or not the change should be marked as indirect.
**
** 2. Invoke sqlite3changegroup_change_int64() or one of the other four
** value functions - _null(), _double(), _text() or _blob() - one or
** more times to specify old.* and new.* values for the change being
** constructed.
**
** 3. Invoke sqlite3changegroup_change_finish() to either finish adding
** the change to the group, or to discard the change altogether.
**
** The first argument to this function must be a pointer to the existing
** changegroup object that the change will be added to. The second argument
** must be SQLITE_INSERT, SQLITE_UPDATE or SQLITE_DELETE. The third is the
** name of the table that the change affects, and the fourth is a boolean
** flag specifying whether the change should be marked as "indirect" (if
** bIndirect is non-zero) or not indirect (if bIndirect is zero).
**
** Following a successful call to this function, this function may not be
** called again on the same changegroup object until after
** sqlite3changegroup_change_finish() has been called. Doing so is an
** SQLITE_MISUSE error.
**
** The changegroup object passed as the first argument must be already
** configured with schema data for the specified table. It may be configured
** either by calling sqlite3changegroup_schema() with a database that contains
** the table, or sqlite3changegroup_add() with a changeset that contains the
** table. If the changegroup object has not been configured with a schema for
** the specified table when this function is called, SQLITE_ERROR is returned.
**
** If successful, SQLITE_OK is returned. Otherwise, if an error occurs, an
** SQLite error code is returned. In this case, if argument pzErr is non-NULL,
** then (*pzErr) may be set to point to a buffer containing a utf-8 formated,
** nul-terminated, English language error message. It is the responsibility
** of the caller to eventually free this buffer using sqlite3_free().
*/
int sqlite3changegroup_change_begin(
sqlite3_changegroup*,
int eOp,
const char *zTab,
int bIndirect,
char **pzErr
);
/*
** This function may only be called between a successful call to
** sqlite3changegroup_change_begin() and its matching
** sqlite3changegroup_change_finish() call. If it is called at any
** other time, it is an SQLITE_MISUSE error. Calling this function
** specifies a 64-bit integer value to be used in the change currently being
** added to the changegroup object passed as the first argument.
**
** The second parameter, bNew, specifies whether the value is to be part of
** the new.* (if bNew is non-zero) or old.* (if bNew is zero) record of
** the change under construction. If this does not match the type of change
** specified by the preceding call to sqlite3changegroup_change_begin() (i.e.
** an old.* value for an SQLITE_INSERT change, or a new.* value for an
** SQLITE_DELETE), then SQLITE_ERROR is returned.
**
** The third parameter specifies the column of the old.* or new.* record that
** the value will be a part of. If the specified table has an explicit primary
** key, then this is the index of the table column, numbered from 0 in the order
** specified within the CREATE TABLE statement. Or, if the table uses an
** implicit rowid key, then the column 0 is the rowid and the explicit columns
** are numbered starting from 1. If the iCol parameter is less than 0 or greater
** than the index of the last column in the table, SQLITE_RANGE is returned.
**
** The fourth parameter is the integer value to use as part of the old.* or
** new.* record.
**
** If this call is successful, SQLITE_OK is returned. Otherwise, if an
** error occurs, an SQLite error code is returned.
*/
int sqlite3changegroup_change_int64(
sqlite3_changegroup*,
int bNew,
int iCol,
sqlite3_int64 iVal
);
/*
** This function is similar to sqlite3changegroup_change_int64(). Except that
** it configures the change currently under construction with a NULL value
** instead of a 64-bit integer.
*/
int sqlite3changegroup_change_null(sqlite3_changegroup*, int, int);
/*
** This function is similar to sqlite3changegroup_change_int64(). Except that
** it configures the change currently being constructed with a real value
** instead of a 64-bit integer.
*/
int sqlite3changegroup_change_double(sqlite3_changegroup*, int, int, double);
/*
** This function is similar to sqlite3changegroup_change_int64(). It configures
** the currently accumulated change with a text value instead of a 64-bit
** integer. Parameter pVal points to a buffer containing the text encoded using
** utf-8. Parameter nVal may either be the size of the text value in bytes, or
** else a negative value, in which case the buffer pVal points to is assumed to
** be nul-terminated.
*/
int sqlite3changegroup_change_text(
sqlite3_changegroup*, int, int, const char *pVal, int nVal
);
/*
** This function is similar to sqlite3changegroup_change_int64(). It configures
** the currently accumulated change with a blob value instead of a 64-bit
** integer. Parameter pVal points to a buffer containing the blob. Parameter
** nVal is the size of the blob in bytes.
*/
int sqlite3changegroup_change_blob(
sqlite3_changegroup*, int, int, const void *pVal, int nVal
);
/*
** This function may only be called following a successful call to
** sqlite3changegroup_change_begin(). Otherwise, it is an SQLITE_MISUSE error.
**
** If parameter bDiscard is non-zero, then the current change is simply
** discarded. In this case this function is always successful and SQLITE_OK
** returned.
**
** If parameter bDiscard is zero, then an attempt is made to add the current
** change to the changegroup. Assuming the changegroup is configured to
** produce a changeset (not a patchset), this requires that:
**
** * If the change is an INSERT or DELETE, then a value must be specified
** for all columns of the new.* or old.* record, respectively.
**
** * If the change is an UPDATE record, then values must be provided for
** the PRIMARY KEY columns of the old.* record, but must not be provided
** for PRIMARY KEY columns of the new.* record.
**
** * If the change is an UPDATE record, then for each non-PRIMARY KEY
** column in the old.* record for which a value has been provided, a
** value must also be provided for the same column in the new.* record.
** Similarly, for each non-PK column in the old.* record for which
** a value is not provided, a value must not be provided for the same
** column in the new.* record.
**
** * All values specified for PRIMARY KEY columns must be non-NULL.
**
** Otherwise, it is an error.
**
** If the changegroup already contains a change for the same row (identified
** by PRIMARY KEY columns), then the current change is combined with the
** existing change in the same way as for sqlite3changegroup_add().
**
** For a patchset, all of the above rules apply except that it doesn't matter
** whether or not values are provided for the non-PK old.* record columns
** for an UPDATE or DELETE change. This means that code used to produce
** a changeset using the sqlite3changegroup_change_xxx() APIs may also
** be used to produce patchsets.
**
** If the call is successful, SQLITE_OK is returned. Otherwise, if an error
** occurs, an SQLite error code is returned. If an error is returned and
** parameter pzErr is not NULL, then (*pzErr) may be set to point to a buffer
** containing a nul-terminated, utf-8 encoded, English language error message.
** It is the responsibility of the caller to eventually free any such error
** message buffer using sqlite3_free().
*/
int sqlite3changegroup_change_finish(
sqlite3_changegroup*,
int bDiscard,
char **pzErr
);
/*
** Make sure we can call this stuff from C++.
*/

View File

@@ -13,6 +13,8 @@
typedef unsigned char u8;
#endif
extern const char *sqlite3ErrName(int);
typedef struct TestSession TestSession;
struct TestSession {
sqlite3_session *pSession;
@@ -397,7 +399,6 @@ static int SQLITE_TCLAPI test_session_cmd(
}
rc = sqlite3session_object_config(pSession, aOpt[iOpt].opt, &iArg);
if( rc!=SQLITE_OK ){
extern const char *sqlite3ErrName(int);
Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
}else{
Tcl_SetObjResult(interp, Tcl_NewIntObj(iArg));
@@ -1554,6 +1555,14 @@ static void test_changegroup_del(void *clientData){
ckfree(pGrp);
}
static int testGetNewOrOld(Tcl_Interp *interp, Tcl_Obj *pObj, int *pbNew){
const char *azVal[] = { "old", "new", 0 };
int iIdx = 0;
int rc = Tcl_GetIndexFromObj(interp, pObj, azVal, "record", 0, &iIdx);
*pbNew = iIdx;
return rc;
}
/*
** Tclcmd: $changegroup schema DB DBNAME
** Tclcmd: $changegroup add CHANGESET
@@ -1571,14 +1580,25 @@ static int SQLITE_TCLAPI test_changegroup_cmd(
const char *zSub;
int nArg;
const char *zMsg;
int iSub;
} aSub[] = {
{ "schema", 2, "DB DBNAME", }, /* 0 */
{ "add", 1, "CHANGESET", }, /* 1 */
{ "output", 0, "", }, /* 2 */
{ "delete", 0, "", }, /* 3 */
{ "add_change", 1, "ITERATOR", }, /* 4 */
{ 0 }
{ "schema", 2, "DB DBNAME" }, /* 0 */
{ "add", 1, "CHANGESET" }, /* 1 */
{ "output", 0, "" }, /* 2 */
{ "delete", 0, "" }, /* 3 */
{ "add_change", 1, "ITERATOR" }, /* 4 */
{ "change_begin", 3, "TYPE TABLE INDIRECT" }, /* 5 */
{ "change_int64", 3, "[new|old] ICOL VALUE" }, /* 6 */
{ "change_null", 2, "[new|old] ICOL" }, /* 7 */
{ "change_double", 3, "[new|old] ICOL VALUE" }, /* 8 */
{ "change_text", 3, "[new|old] ICOL VALUE" }, /* 9 */
{ "change_blob", 3, "[new|old] ICOL VALUE" }, /* 10 */
{ "change_finish", 1, "BDISCARD" }, /* 11 */
{ "config", 2, "OPTION INTVAL" }, /* 12 */
{ "change_text-1", 3, "[new|old] ICOL VALUE" }, /* 13 */
{ "change_begin_ne", 3, "TYPE TABLE INDIRECT" }, /* 14 */
{ 0, 0, 0 }
};
int rc = TCL_OK;
int iSub = 0;
@@ -1647,6 +1667,193 @@ static int SQLITE_TCLAPI test_changegroup_cmd(
break;
};
case 14: /* change_beginne */
case 5: { /* change_begin */
struct ChangeType {
const char *zType;
int eType;
} aType[] = {
{ "INSERT", SQLITE_INSERT },
{ "UPDATE", SQLITE_UPDATE },
{ "DELETE", SQLITE_DELETE },
{ 0, 0 }
};
int eType = 0;
const char *zTab = 0;
int bIndirect;
int iIdx = 0;
char *zErr = 0;
char **pz = ((iSub==5) ? &zErr : 0);
if( TCL_OK!=Tcl_GetIntFromObj(0, objv[2], &eType) ){
rc = Tcl_GetIndexFromObjStruct(
interp, objv[2], aType, sizeof(aType[0]), "TYPE", 0, &iIdx
);
if( rc!=TCL_OK ) return rc;
eType = aType[iIdx].eType;
}
zTab = Tcl_GetString(objv[3]);
if( Tcl_GetBooleanFromObj(interp, objv[4], &bIndirect) ){
return TCL_ERROR;
}
rc = sqlite3changegroup_change_begin(p->pGrp, eType, zTab, bIndirect, pz);
assert( zErr==0 || rc!=SQLITE_OK );
if( rc!=SQLITE_OK ){
rc = test_session_error(interp, rc, zErr);
}
break;
}
case 6: { /* change_int64 */
int bNew = 0;
int iCol = 0;
sqlite3_int64 iVal = 0;
if( TCL_OK!=testGetNewOrOld(interp, objv[2], &bNew)
|| TCL_OK!=Tcl_GetIntFromObj(interp, objv[3], &iCol)
|| TCL_OK!=Tcl_GetWideIntFromObj(interp, objv[4], &iVal)
){
rc = TCL_ERROR;
}else{
rc = sqlite3changegroup_change_int64(p->pGrp, bNew, iCol, iVal);
if( rc!=SQLITE_OK ){
rc = test_session_error(interp, rc, 0);
}
}
break;
}
case 7: { /* change_null */
int bNew = 0;
int iCol = 0;
if( TCL_OK!=testGetNewOrOld(interp, objv[2], &bNew)
|| TCL_OK!=Tcl_GetIntFromObj(interp, objv[3], &iCol)
){
rc = TCL_ERROR;
}else{
rc = sqlite3changegroup_change_null(p->pGrp, bNew, iCol);
if( rc!=SQLITE_OK ){
rc = test_session_error(interp, rc, 0);
}
}
break;
}
case 8: { /* change_double */
int bNew = 0;
int iCol = 0;
double rVal = 0;
if( TCL_OK!=testGetNewOrOld(interp, objv[2], &bNew)
|| TCL_OK!=Tcl_GetIntFromObj(interp, objv[3], &iCol)
|| TCL_OK!=Tcl_GetDoubleFromObj(interp, objv[4], &rVal)
){
rc = TCL_ERROR;
}else{
rc = sqlite3changegroup_change_double(p->pGrp, bNew, iCol, rVal);
if( rc!=SQLITE_OK ){
rc = test_session_error(interp, rc, 0);
}
}
break;
}
case 9: { /* change_text */
int bNew = 0;
int iCol = 0;
if( TCL_OK!=testGetNewOrOld(interp, objv[2], &bNew)
|| TCL_OK!=Tcl_GetIntFromObj(interp, objv[3], &iCol)
){
rc = TCL_ERROR;
}else{
int nVal = 0;
const char *pVal = Tcl_GetStringFromObj(objv[4], &nVal);
rc = sqlite3changegroup_change_text(p->pGrp, bNew, iCol, pVal, nVal);
if( rc!=SQLITE_OK ){
rc = test_session_error(interp, rc, 0);
}
}
break;
}
case 10: { /* change_blob */
int bNew = 0;
int iCol = 0;
if( TCL_OK!=testGetNewOrOld(interp, objv[2], &bNew)
|| TCL_OK!=Tcl_GetIntFromObj(interp, objv[3], &iCol)
){
rc = TCL_ERROR;
}else{
int nVal = 0;
const u8 *pVal = Tcl_GetByteArrayFromObj(objv[4], &nVal);
rc = sqlite3changegroup_change_blob(p->pGrp, bNew, iCol, pVal, nVal);
if( rc!=SQLITE_OK ){
rc = test_session_error(interp, rc, 0);
}
}
break;
}
case 11: { /* change_finish */
int bDiscard = 0;
if( TCL_OK!=Tcl_GetBooleanFromObj(interp, objv[2], &bDiscard) ){
rc = TCL_ERROR;
}else{
char *zErr = 0;
rc = sqlite3changegroup_change_finish(p->pGrp, bDiscard, &zErr);
if( rc!=SQLITE_OK ){
rc = test_session_error(interp, rc, zErr);
}
}
break;
}
case 12: { /* config */
struct OptionName {
const char *zOpt;
int op;
} aOp[] = {
{ "patchset", SQLITE_CHANGEGROUP_CONFIG_PATCHSET },
{ 0, 0 }
};
int iIdx = 0;
int iArg = 0;
rc = Tcl_GetIndexFromObjStruct(
interp, objv[2], aOp, sizeof(aOp[0]), "option", 0, &iIdx
);
if( rc==TCL_OK
&& (rc = Tcl_GetIntFromObj(interp, objv[3], &iArg))==TCL_OK
){
int op = aOp[iIdx].op;
void *pArg = (void*)&iArg;
rc = sqlite3changegroup_config(p->pGrp, op, pArg);
if( rc!=SQLITE_OK ){
rc = test_session_error(interp, rc, 0);
}else{
Tcl_SetObjResult(interp, Tcl_NewIntObj(iArg));
}
}
break;
}
case 13: { /* change_text-1 */
int bNew = 0;
int iCol = 0;
if( TCL_OK!=testGetNewOrOld(interp, objv[2], &bNew)
|| TCL_OK!=Tcl_GetIntFromObj(interp, objv[3], &iCol)
){
rc = TCL_ERROR;
}else{
const char *pVal = Tcl_GetString(objv[4]);
rc = sqlite3changegroup_change_text(p->pGrp, bNew, iCol, pVal, -1);
if( rc!=SQLITE_OK ){
rc = test_session_error(interp, rc, 0);
}
}
break;
}
default: { /* delete */
assert( iSub==3 );
Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));

View File

@@ -1,5 +1,5 @@
C Turn\san\sincorrect\sassert()\sinto\sa\stestcase().\s\sFix\sover-length\scode\slines\nin\sthe\ssqlite.h.in\ssource\sfile.\s\sFix\sthe\sdocumentation\son\sOOM\sconditions\nin\sthe\ssqlite3_value_text()\sfamily\sof\sAPIs.
D 2026-03-31T10:32:23.251
C Add\san\sAPI\sto\sthe\ssessions\smodule\sto\sadd\schanges\sone\sat\sa\stime\sto\san\ssqlite3_changegroup\sobject.
D 2026-03-31T10:59:13.118
F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
@@ -555,11 +555,12 @@ F ext/session/sessionat.test 00c8badb35e43a2f12a716d2734a44d614ff62361979b6b8541
F ext/session/sessionbig.test 47c381e7acfabeef17d98519a3080d69151723354d220afa2053852182ca7adf
F ext/session/sessionblob.test 87faf667870b72f08e91969abd9f52a383ab7b514506ee194d64a39d8faff00a
F ext/session/sessionchange.test 6618cb1c1338a4b6df173b6ac42d09623fb71269962abf23ebb7617fe9f45a50
F ext/session/sessionchange2.test 6b5b7e3d1cc4ede43817f7fb68e3771aac4aa6500adb21a458b3e5a9fd841f83
F ext/session/sessionconflict.test 19e4a53795c4c930bfec49e809311e09b2a9e202d9446e56d7a8b139046a0c07 x
F ext/session/sessiondiff.test e89f7aedcdd89e5ebac3a455224eb553a171e9586fc3e1e6a7b3388d2648ba8d
F ext/session/sessionfault.test c2b43d01213b389a3f518e90775fca2120812ba51e50444c4066962263e45c11
F ext/session/sessionfault2.test b0d6a7c1d7398a7e800d84657404909c7d385965ea8576dc79ed344c46fbf41c
F ext/session/sessionfault3.test ce0b5d182133935c224d72507dbf1c5be1a1febf7e85d0b0fbd6d2f724b32b96
F ext/session/sessionfault3.test 9397819ec25b0960c5bc03c78613f9cb5cacc970f83e817aec1775c2a839a787
F ext/session/sessioninvert.test 9018f6a7387ac745084b6374c5e1aa14d648b372e6e1181cfab3df632b662d26 x
F ext/session/sessionmem.test f2a735db84a3e9e19f571033b725b0b2daf847f3f28b1da55a0c1a4e74f1de09
F ext/session/sessionnoact.test 4c7ae5c7d351cb5323bca62b6b095592ad24bd90a6713c178b62ab0063d23e19
@@ -570,9 +571,9 @@ F ext/session/sessionrowid.test 85187c2f1b38861a5844868126f69f9ec62223a03449a98a
F ext/session/sessionsize.test 8fcf4685993c3dbaa46a24183940ab9f5aa9ed0d23e5fb63bfffbdb56134b795
F ext/session/sessionstat1.test 5e718d5888c0c49bbb33a7a4f816366db85f59f6a4f97544a806421b85dc2dec
F ext/session/sessionwor.test 6fd9a2256442cebde5b2284936ae9e0d54bde692d0f5fd009ecef8511f4cf3fc
F ext/session/sqlite3session.c 6ebd02be470f36d41c4bd78927f39d507b62051ba025eacaed9936c769902a07
F ext/session/sqlite3session.h 7404723606074fcb2afdc6b72c206072cdb2b7d8ba097ca1559174a80bc26f7a
F ext/session/test_session.c 190110e3bd9463717248dec1272b44fe9943e57b7646d0b4200dcf11e4dccee6
F ext/session/sqlite3session.c bcec91b7fc7f7acdf7fae6020de5f9131ce91c61b6d9953d5f96fc058f6d25d4
F ext/session/sqlite3session.h b117cddb6ce93411b3b25c8a355d155a120e30efbdf506fc6899b71a333b0c56
F ext/session/test_session.c beefbb051a2baa2636f463ad1c50558294a418f955219be9423de54dff946f0f
F ext/wasm/GNUmakefile 68c750f173106d9d63f12c1edf1256c6f4bad9894b155da5db64322f4912de4b
F ext/wasm/README-dist.txt f01081a850ce38a56706af6b481e3a7878e24e42b314cfcd4b129f0f8427066a
F ext/wasm/README.md 2e87804e12c98f1d194b7a06162a88441d33bb443efcfe00dc6565a780d2f259
@@ -2196,8 +2197,9 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee
F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c
P 240294653ceb202b847699e9691140db4fe623a03d86fa3cf7495f8831576331
R 219a9645b2361c943ce9fe72fc39f9f5
U drh
Z 00514e08c5b641bd2fdcb8859fb87fc8
P 934582706109d2ebda26952c7c95f7a5095fa47ec837570a66c991866f7d08cf 716e62e3d1d6ff96f6e4906175818439103e9534e89bec4a86776265a04954b9
R aa5223b212f1ad46f0f376c6a9330268
T +closed 716e62e3d1d6ff96f6e4906175818439103e9534e89bec4a86776265a04954b9
U dan
Z 567722104ff813fa7a84013ce4b3243e
# Remove this line to create a well-formed Fossil manifest.

View File

@@ -1 +1 @@
934582706109d2ebda26952c7c95f7a5095fa47ec837570a66c991866f7d08cf
cf1122ea6409bcbd341b67ae45147abb8d5cd491ae95491c801d0621181da40b