Adding the sqlite sourcecode

This commit is contained in:
Jiří Stach committed 2025-07-17 11:54:48 +02:00
1 parent bdb54a1f8f
commit 406b584760
2208 files changed
+1172785 -1

No files matched your search

+472
View File
@@ -0,0 +1,472 @@
/*
** 2014-08-18
**
** 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 contains code to implement the "changeset" command line
** utility for displaying and transforming changesets generated by
** the Sessions extension.
*/
#include "sqlite3.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
/*
** Show a usage message on stderr then quit.
*/
static void usage(const char *argv0){
fprintf(stderr, "Usage: %s FILENAME COMMAND ...\n", argv0);
fprintf(stderr,
"COMMANDs:\n"
" apply DB [OPTIONS] Apply the changeset to database file DB. OPTIONS:\n"
" -n|--dryrun Test run. Don't apply changes\n"
" --enablefk Enable FOREIGN KEY support\n"
" --nosavepoint \\\n"
" --invert \\___ Flags passed into\n"
" --ignorenoop / changeset_apply_v2()\n"
" --fknoaction /\n"
" concat FILE2 OUT Concatenate FILENAME and FILE2 into OUT\n"
" dump Show the complete content of the changeset\n"
" invert OUT Write an inverted changeset into file OUT\n"
" sql Give a pseudo-SQL rendering of the changeset\n"
);
exit(1);
}
/*
** Read the content of a disk file into an in-memory buffer
*/
static void readFile(const char *zFilename, int *pSz, void **ppBuf){
FILE *f;
sqlite3_int64 sz;
void *pBuf;
f = fopen(zFilename, "rb");
if( f==0 ){
fprintf(stderr, "cannot open \"%s\" for reading\n", zFilename);
exit(1);
}
fseek(f, 0, SEEK_END);
sz = ftell(f);
rewind(f);
pBuf = sqlite3_malloc64( sz ? sz : 1 );
if( pBuf==0 ){
fprintf(stderr, "cannot allocate %d to hold content of \"%s\"\n",
(int)sz, zFilename);
exit(1);
}
if( sz>0 ){
if( fread(pBuf, (size_t)sz, 1, f)!=1 ){
fprintf(stderr, "cannot read all %d bytes of \"%s\"\n",
(int)sz, zFilename);
exit(1);
}
fclose(f);
}
*pSz = (int)sz;
*ppBuf = pBuf;
}
/* Array for converting from half-bytes (nybbles) into ASCII hex
** digits. */
static const char hexdigits[] = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
};
/*
** Render an sqlite3_value as an SQL string.
*/
static void renderValue(sqlite3_value *pVal){
switch( sqlite3_value_type(pVal) ){
case SQLITE_FLOAT: {
double r1;
char zBuf[50];
r1 = sqlite3_value_double(pVal);
sqlite3_snprintf(sizeof(zBuf), zBuf, "%!.15g", r1);
printf("%s", zBuf);
break;
}
case SQLITE_INTEGER: {
printf("%lld", sqlite3_value_int64(pVal));
break;
}
case SQLITE_BLOB: {
char const *zBlob = sqlite3_value_blob(pVal);
int nBlob = sqlite3_value_bytes(pVal);
int i;
printf("x'");
for(i=0; i<nBlob; i++){
putchar(hexdigits[(zBlob[i]>>4)&0x0F]);
putchar(hexdigits[(zBlob[i])&0x0F]);
}
putchar('\'');
break;
}
case SQLITE_TEXT: {
const unsigned char *zArg = sqlite3_value_text(pVal);
putchar('\'');
while( zArg[0] ){
putchar(zArg[0]);
if( zArg[0]=='\'' ) putchar(zArg[0]);
zArg++;
}
putchar('\'');
break;
}
default: {
assert( sqlite3_value_type(pVal)==SQLITE_NULL );
printf("NULL");
break;
}
}
}
/*
** Number of conflicts seen
*/
static int nConflict = 0;
/*
** The conflict callback
*/
static int conflictCallback(
void *pCtx,
int eConflict,
sqlite3_changeset_iter *pIter
){
int op, bIndirect, nCol, i;
const char *zTab;
unsigned char *abPK;
const char *zType = "";
const char *zOp = "";
const char *zSep = " ";
nConflict++;
sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
sqlite3changeset_pk(pIter, &abPK, 0);
switch( eConflict ){
case SQLITE_CHANGESET_DATA: zType = "DATA"; break;
case SQLITE_CHANGESET_NOTFOUND: zType = "NOTFOUND"; break;
case SQLITE_CHANGESET_CONFLICT: zType = "PRIMARY KEY"; break;
case SQLITE_CHANGESET_FOREIGN_KEY: zType = "FOREIGN KEY"; break;
case SQLITE_CHANGESET_CONSTRAINT: zType = "CONSTRAINT"; break;
}
switch( op ){
case SQLITE_UPDATE: zOp = "UPDATE of"; break;
case SQLITE_INSERT: zOp = "INSERT into"; break;
case SQLITE_DELETE: zOp = "DELETE from"; break;
}
printf("%s conflict on %s table %s with primary key", zType, zOp, zTab);
for(i=0; i<nCol && abPK; i++){
sqlite3_value *pVal;
if( abPK[i]==0 ) continue;
printf("%s", zSep);
if( op==SQLITE_INSERT ){
sqlite3changeset_new(pIter, i, &pVal);
}else{
sqlite3changeset_old(pIter, i, &pVal);
}
renderValue(pVal);
zSep = ",";
}
printf("\n");
return SQLITE_CHANGESET_OMIT;
}
int main(int argc, char **argv){
int sz, rc;
void *pBuf = 0;
if( argc<3 ) usage(argv[0]);
readFile(argv[1], &sz, &pBuf);
/* changeset FILENAME apply DB
** Apply the changeset in FILENAME to the database file DB
*/
if( strcmp(argv[2],"apply")==0 ){
sqlite3 *db;
int bDryRun = 0;
int bEnableFK = 0;
const char *zDb = 0;
int i;
int applyFlags = 0;
for(i=3; i<argc; i++){
const char *zArg = argv[i];
if( zArg[0]=='-' ){
if( zArg[1]=='-' && zArg[2]!=0 ) zArg++;
if( strcmp(zArg, "-n")==0 || strcmp(zArg,"-dryrun")==0 ){
bDryRun = 1;
continue;
}
if( strcmp(zArg, "-nosavepoint")==0 ){
applyFlags |= SQLITE_CHANGESETAPPLY_NOSAVEPOINT;
continue;
}
if( strcmp(zArg, "-invert")==0 ){
applyFlags |= SQLITE_CHANGESETAPPLY_INVERT;
continue;
}
if( strcmp(zArg, "-ignorenoop")==0 ){
applyFlags |= SQLITE_CHANGESETAPPLY_IGNORENOOP;
continue;
}
if( strcmp(zArg, "-fknoaction")==0 ){
applyFlags |= SQLITE_CHANGESETAPPLY_FKNOACTION;
continue;
}
if( strcmp(zArg, "-enablefk")==0 ){
bEnableFK = 1;
continue;
}
fprintf(stderr, "unknown option: \"%s\"\n", argv[i]);
exit(1);
}else if( zDb ){
fprintf(stderr, "unknown argument: \"%s\"\n", argv[i]);
exit(1);
}else{
zDb = zArg;
}
}
rc = sqlite3_open(zDb, &db);
if( rc!=SQLITE_OK ){
fprintf(stderr, "unable to open database file \"%s\": %s\n",
zDb, sqlite3_errmsg(db));
sqlite3_close(db);
exit(1);
}
if( bEnableFK ){
sqlite3_exec(db, "PRAGMA foreign_keys=1;", 0, 0, 0);
}
sqlite3_exec(db, "BEGIN", 0, 0, 0);
nConflict = 0;
if( applyFlags ){
rc = sqlite3changeset_apply_v2(db, sz, pBuf, 0, conflictCallback, 0,
0, 0, applyFlags);
}else{
rc = sqlite3changeset_apply(db, sz, pBuf, 0, conflictCallback, 0);
}
if( rc ){
fprintf(stderr, "sqlite3changeset_apply() returned %d\n", rc);
}
if( nConflict || bDryRun ){
fprintf(stderr, "%d conflicts - no changes applied\n", nConflict);
sqlite3_exec(db, "ROLLBACK", 0, 0, 0);
}else if( rc ){
fprintf(stderr, "sqlite3changeset_apply() returns %d "
"- no changes applied\n", rc);
sqlite3_exec(db, "ROLLBACK", 0, 0, 0);
}else{
sqlite3_exec(db, "COMMIT", 0, 0, 0);
}
sqlite3_close(db);
}else
/* changeset FILENAME concat FILE2 OUT
** Add changeset FILE2 onto the end of the changeset in FILENAME
** and write the result into OUT.
*/
if( strcmp(argv[2],"concat")==0 ){
int szB;
void *pB;
int szOut;
void *pOutBuf;
FILE *out;
const char *zOut = argv[4];
if( argc!=5 ) usage(argv[0]);
out = fopen(zOut, "wb");
if( out==0 ){
fprintf(stderr, "cannot open \"%s\" for writing\n", zOut);
exit(1);
}
readFile(argv[3], &szB, &pB);
rc = sqlite3changeset_concat(sz, pBuf, szB, pB, &szOut, &pOutBuf);
if( rc!=SQLITE_OK ){
fprintf(stderr, "sqlite3changeset_concat() returns %d\n", rc);
}else if( szOut>0 && fwrite(pOutBuf, szOut, 1, out)!=1 ){
fprintf(stderr, "unable to write all %d bytes of output to \"%s\"\n",
szOut, zOut);
}
fclose(out);
sqlite3_free(pOutBuf);
sqlite3_free(pB);
}else
/* changeset FILENAME dump
** Show the complete content of the changeset in FILENAME
*/
if( strcmp(argv[2],"dump")==0 ){
int cnt = 0;
int i;
sqlite3_changeset_iter *pIter;
rc = sqlite3changeset_start(&pIter, sz, pBuf);
if( rc!=SQLITE_OK ){
fprintf(stderr, "sqlite3changeset_start() returns %d\n", rc);
exit(1);
}
while( sqlite3changeset_next(pIter)==SQLITE_ROW ){
int op, bIndirect, nCol;
const char *zTab;
unsigned char *abPK;
sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
cnt++;
printf("%d: %s table=[%s] indirect=%d nColumn=%d\n",
cnt, op==SQLITE_INSERT ? "INSERT" :
op==SQLITE_UPDATE ? "UPDATE" : "DELETE",
zTab, bIndirect, nCol);
sqlite3changeset_pk(pIter, &abPK, 0);
for(i=0; i<nCol; i++){
sqlite3_value *pVal;
pVal = 0;
sqlite3changeset_old(pIter, i, &pVal);
if( pVal ){
printf(" old[%d]%s = ", i, abPK[i] ? "pk" : " ");
renderValue(pVal);
printf("\n");
}
pVal = 0;
sqlite3changeset_new(pIter, i, &pVal);
if( pVal ){
printf(" new[%d]%s = ", i, abPK[i] ? "pk" : " ");
renderValue(pVal);
printf("\n");
}
}
}
sqlite3changeset_finalize(pIter);
}else
/* changeset FILENAME invert OUT
** Invert the changes in FILENAME and writes the result on OUT
*/
if( strcmp(argv[2],"invert")==0 ){
FILE *out;
int szOut = 0;
void *pOutBuf = 0;
const char *zOut = argv[3];
if( argc!=4 ) usage(argv[0]);
out = fopen(zOut, "wb");
if( out==0 ){
fprintf(stderr, "cannot open \"%s\" for writing\n", zOut);
exit(1);
}
rc = sqlite3changeset_invert(sz, pBuf, &szOut, &pOutBuf);
if( rc!=SQLITE_OK ){
fprintf(stderr, "sqlite3changeset_invert() returns %d\n", rc);
}else if( szOut>0 && fwrite(pOutBuf, szOut, 1, out)!=1 ){
fprintf(stderr, "unable to write all %d bytes of output to \"%s\"\n",
szOut, zOut);
}
fclose(out);
sqlite3_free(pOutBuf);
}else
/* changeset FILE sql
** Show the content of the changeset as pseudo-SQL
*/
if( strcmp(argv[2],"sql")==0 ){
int cnt = 0;
char *zPrevTab = 0;
char *zSQLTabName = 0;
sqlite3_changeset_iter *pIter = 0;
rc = sqlite3changeset_start(&pIter, sz, pBuf);
if( rc!=SQLITE_OK ){
fprintf(stderr, "sqlite3changeset_start() returns %d\n", rc);
exit(1);
}
printf("BEGIN;\n");
while( sqlite3changeset_next(pIter)==SQLITE_ROW ){
int op, bIndirect, nCol;
const char *zTab;
sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
cnt++;
if( zPrevTab==0 || strcmp(zPrevTab,zTab)!=0 ){
sqlite3_free(zPrevTab);
sqlite3_free(zSQLTabName);
zPrevTab = sqlite3_mprintf("%s", zTab);
if( !isalnum(zTab[0]) || sqlite3_strglob("*[^a-zA-Z0-9]*",zTab)==0 ){
zSQLTabName = sqlite3_mprintf("\"%w\"", zTab);
}else{
zSQLTabName = sqlite3_mprintf("%s", zTab);
}
printf("/****** Changes for table %s ***************/\n", zSQLTabName);
}
switch( op ){
case SQLITE_DELETE: {
unsigned char *abPK;
int i;
const char *zSep = " ";
sqlite3changeset_pk(pIter, &abPK, 0);
printf("/* %d */ DELETE FROM %s WHERE", cnt, zSQLTabName);
for(i=0; i<nCol; i++){
sqlite3_value *pVal;
if( abPK[i]==0 ) continue;
printf("%sc%d=", zSep, i+1);
zSep = " AND ";
sqlite3changeset_old(pIter, i, &pVal);
renderValue(pVal);
}
printf(";\n");
break;
}
case SQLITE_UPDATE: {
unsigned char *abPK;
int i;
const char *zSep = " ";
sqlite3changeset_pk(pIter, &abPK, 0);
printf("/* %d */ UPDATE %s SET", cnt, zSQLTabName);
for(i=0; i<nCol; i++){
sqlite3_value *pVal = 0;
sqlite3changeset_new(pIter, i, &pVal);
if( pVal ){
printf("%sc%d=", zSep, i+1);
zSep = ", ";
renderValue(pVal);
}
}
printf(" WHERE");
zSep = " ";
for(i=0; i<nCol; i++){
sqlite3_value *pVal;
if( abPK[i]==0 ) continue;
printf("%sc%d=", zSep, i+1);
zSep = " AND ";
sqlite3changeset_old(pIter, i, &pVal);
renderValue(pVal);
}
printf(";\n");
break;
}
case SQLITE_INSERT: {
int i;
printf("/* %d */ INSERT INTO %s VALUES", cnt, zSQLTabName);
for(i=0; i<nCol; i++){
sqlite3_value *pVal;
printf("%c", i==0 ? '(' : ',');
sqlite3changeset_new(pIter, i, &pVal);
renderValue(pVal);
}
printf(");\n");
break;
}
}
}
printf("COMMIT;\n");
sqlite3changeset_finalize(pIter);
sqlite3_free(zPrevTab);
sqlite3_free(zSQLTabName);
}else
/* If nothing else matches, show the usage comment */
usage(argv[0]);
sqlite3_free(pBuf);
return 0;
}
File diff suppressed because it is too large. Load diff
+83
View File
@@ -0,0 +1,83 @@
# 2018 November 08
#
# 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.
#
#***********************************************************************
#
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 changesetfuzz1
set CF [test_find_binary changesetfuzz]
if {$CF==""} {
finish_test
return
}
proc writefile {zFile data} {
set fd [open $zFile w]
fconfigure $fd -translation binary
puts -nonewline $fd $data
close $fd
}
do_execsql_test 1.0 {
CREATE TABLE t1(a, b, c, d, PRIMARY KEY(c, d));
CREATE TABLE t2(a INTEGER PRIMARY KEY, b, c);
INSERT INTO t1 VALUES ('one', 'two', 'three', 'four'),
('five', 'six', 'seven', 'eight'),
('nine', 'ten', 'eleven', 'twelve');
INSERT INTO t2 VALUES (1, 2, 3), (4, 5, 6), (7, 8, 9);
}
set C [changeset_from_sql {
INSERT INTO t2 VALUES(10, 11, 12);
DELETE FROM t2 WHERE a=1;
UPDATE t1 SET b='forty-five' WHERE a='one';
UPDATE t1 SET a='twenty-nine', b='seventy' WHERE a='five';
}]
writefile c1.changeset $C
do_test 1.1 {
for {set j 0} {$j < 200} {incr j} {
forcecopy c1.changeset input.changeset
for {set i 0} {$i < 6} {incr i} {
exec $CF input.changeset $i 1
exec $CF input.changeset-0
forcecopy input.changeset-0 input.changeset
}
}
} {}
set P [patchset_from_sql {
INSERT INTO t2 VALUES(13, 14, 15);
DELETE FROM t2 WHERE a=4;
UPDATE t1 SET b='thirteen' WHERE a='one';
UPDATE t1 SET a='ninety-seven', b='twenty' WHERE a='five';
}]
writefile p1.patchset $P
do_test 1.2 {
for {set j 0} {$j < 200} {incr j} {
forcecopy p1.patchset input.patchset
for {set i 0} {$i < 6} {incr i} {
exec $CF input.patchset $i 1
exec $CF input.patchset-0
forcecopy input.patchset-0 input.patchset
}
}
} {}
finish_test
+807
View File
@@ -0,0 +1,807 @@
# 2011 March 07
#
# 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 session1
# Run all tests in this file twice. Once with "WITHOUT ROWID", and once
# with regular rowid tables.
#
foreach {tn trailing} {
1 ""
2 " WITHOUT ROWID "
} {
eval [string map [list %WR% $trailing] {
db close
forcedelete test.db test.db2
reset_db
do_execsql_test $tn.1.0 {
CREATE TABLE t1(x PRIMARY KEY, y) %WR%;
INSERT INTO t1 VALUES('abc', 'def');
}
#-------------------------------------------------------------------------
# Test creating, attaching tables to and deleting session objects.
#
do_test $tn.1.1 { sqlite3session S db main } {S}
do_test $tn.1.2 { S delete } {}
do_test $tn.1.3 { sqlite3session S db main } {S}
do_test $tn.1.4 { S attach t1 } {}
do_test $tn.1.5 { S delete } {}
do_test $tn.1.6 { sqlite3session S db main } {S}
do_test $tn.1.7 { S attach t1 ; S attach t2 ; S attach t3 } {}
do_test $tn.1.8 { S attach t1 ; S attach t2 ; S attach t3 } {}
do_test $tn.1.9 { S delete } {}
do_test $tn.1.10 {
sqlite3session S db main
S attach t1
execsql { INSERT INTO t1 VALUES('ghi', 'jkl') }
} {}
do_test $tn.1.11 { S delete } {}
if {$tn==1} {
do_test $tn.1.12 {
sqlite3session S db main
S attach t1
execsql { INSERT INTO t1 VALUES('mno', 'pqr') }
execsql { UPDATE t1 SET x = 111 WHERE rowid = 1 }
execsql { DELETE FROM t1 WHERE rowid = 2 }
} {}
do_test $tn.1.13 {
S changeset
S delete
} {}
}
#-------------------------------------------------------------------------
# Simple changeset tests. Also test the sqlite3changeset_invert()
# function.
#
do_test $tn.2.1.1 {
execsql { DELETE FROM t1 }
sqlite3session S db main
S attach t1
execsql { INSERT INTO t1 VALUES(1, 'Sukhothai') }
execsql { INSERT INTO t1 VALUES(2, 'Ayutthaya') }
execsql { INSERT INTO t1 VALUES(3, 'Thonburi') }
} {}
do_changeset_test $tn.2.1.2 S {
{INSERT t1 0 X. {} {i 1 t Sukhothai}}
{INSERT t1 0 X. {} {i 2 t Ayutthaya}}
{INSERT t1 0 X. {} {i 3 t Thonburi}}
}
do_changeset_invert_test $tn.2.1.3 S {
{DELETE t1 0 X. {i 1 t Sukhothai} {}}
{DELETE t1 0 X. {i 2 t Ayutthaya} {}}
{DELETE t1 0 X. {i 3 t Thonburi} {}}
}
do_test $tn.2.1.4 { S delete } {}
do_test $tn.2.2.1 {
sqlite3session S db main
S attach t1
execsql { DELETE FROM t1 WHERE 1 }
} {}
do_changeset_test $tn.2.2.2 S {
{DELETE t1 0 X. {i 1 t Sukhothai} {}}
{DELETE t1 0 X. {i 2 t Ayutthaya} {}}
{DELETE t1 0 X. {i 3 t Thonburi} {}}
}
do_changeset_invert_test $tn.2.2.3 S {
{INSERT t1 0 X. {} {i 1 t Sukhothai}}
{INSERT t1 0 X. {} {i 2 t Ayutthaya}}
{INSERT t1 0 X. {} {i 3 t Thonburi}}
}
do_test $tn.2.2.4 { S delete } {}
do_test $tn.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') }
execsql { INSERT INTO t1 VALUES(3, 'Thonburi') }
S attach t1
execsql {
UPDATE t1 SET x = 10 WHERE x = 1;
UPDATE t1 SET y = 'Surin' WHERE x = 2;
UPDATE t1 SET x = 20, y = 'Thapae' WHERE x = 3;
}
} {}
do_changeset_test $tn.2.3.2 S {
{INSERT t1 0 X. {} {i 10 t Sukhothai}}
{DELETE t1 0 X. {i 1 t Sukhothai} {}}
{UPDATE t1 0 X. {i 2 t Ayutthaya} {{} {} t Surin}}
{DELETE t1 0 X. {i 3 t Thonburi} {}}
{INSERT t1 0 X. {} {i 20 t Thapae}}
}
do_changeset_invert_test $tn.2.3.3 S {
{DELETE t1 0 X. {i 10 t Sukhothai} {}}
{INSERT t1 0 X. {} {i 1 t Sukhothai}}
{UPDATE t1 0 X. {i 2 t Surin} {{} {} t Ayutthaya}}
{INSERT t1 0 X. {} {i 3 t Thonburi}}
{DELETE t1 0 X. {i 20 t Thapae} {}}
}
do_test $tn.2.3.4 { S delete } {}
do_test $tn.2.4.1 {
sqlite3session S db main
S attach t1
execsql { INSERT INTO t1 VALUES(100, 'Bangkok') }
execsql { DELETE FROM t1 WHERE x = 100 }
} {}
do_changeset_test $tn.2.4.2 S {}
do_changeset_invert_test $tn.2.4.3 S {}
do_test $tn.2.4.4 { S delete } {}
do_execsql_test $tn.2.5.0 {
SELECT * FROM t1 ORDER BY x
} {
2 Surin
10 Sukhothai
20 Thapae
}
do_test $tn.2.5.1 {
sqlite3session S db main
S attach t1
execsql { DELETE FROM t1 }
} {}
do_changeset_test $tn.2.5.2 S {
{DELETE t1 0 X. {i 10 t Sukhothai} {}}
{DELETE t1 0 X. {i 2 t Surin} {}}
{DELETE t1 0 X. {i 20 t Thapae} {}}
}
do_test $tn.2.5.3 { S delete } {}
#-------------------------------------------------------------------------
# Test the application of simple changesets. These tests also test that
# the conflict callback is invoked correctly. For these tests, the
# conflict callback always returns OMIT.
#
db close
forcedelete test.db test.db2
sqlite3 db test.db
sqlite3 db2 test.db2
proc xConflict {args} {
lappend ::xConflict $args
return ""
}
proc bgerror {args} { set ::background_error $args }
proc do_conflict_test {tn args} {
set O(-tables) [list]
set O(-sql) [list]
set O(-conflicts) [list]
array set V $args
foreach key [array names V] {
if {![info exists O($key)]} {error "no such option: $key"}
}
array set O $args
sqlite3session S db main
foreach t $O(-tables) { S attach $t }
execsql $O(-sql)
set ::xConflict [list]
catch {
sqlite3changeset_apply db2 [S changeset] xConflict
}
set conflicts [list]
foreach c $O(-conflicts) {
lappend conflicts $c
}
after 1 {set go 1}
vwait go
uplevel do_test $tn [list { set ::xConflict }] [list $conflicts]
S delete
}
proc do_db2_test {testname sql {result {}}} {
uplevel do_test $testname [list "execsql {$sql} db2"] [list [list {*}$result]]
}
# Test INSERT changesets.
#
do_test $tn.3.1.0 {
execsql { CREATE TABLE t1(a PRIMARY KEY, b NOT NULL) %WR% } db2
execsql {
CREATE TABLE t1(a PRIMARY KEY, b) %WR%;
INSERT INTO t1 VALUES(1, 'one');
INSERT INTO t1 VALUES(2, 'two');
} db
} {}
do_db2_test $tn.3.1.1 "INSERT INTO t1 VALUES(6, 'VI')"
do_conflict_test $tn.3.1.2 -tables t1 -sql {
INSERT INTO t1 VALUES(3, 'three');
INSERT INTO t1 VALUES(4, 'four');
INSERT INTO t1 VALUES(5, 'five');
INSERT INTO t1 VALUES(6, 'six');
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 {}}}
}
do_db2_test $tn.3.1.3 "SELECT * FROM t1 ORDER BY a" {
3 three 4 four 5 five 6 VI 7 seven
}
do_execsql_test $tn.3.1.4 "SELECT * FROM t1" {
1 one 2 two 3 three 4 four 5 five 6 six 7 seven 8 {}
}
# Test DELETE changesets.
#
do_execsql_test $tn.3.2.1 {
PRAGMA foreign_keys = on;
CREATE TABLE t2(a PRIMARY KEY, b)%WR%;
CREATE TABLE t3(c, d REFERENCES t2);
INSERT INTO t2 VALUES(1, 'one');
INSERT INTO t2 VALUES(2, 'two');
INSERT INTO t2 VALUES(3, 'three');
INSERT INTO t2 VALUES(4, 'four');
}
do_db2_test $tn.3.2.2 {
PRAGMA foreign_keys = on;
CREATE TABLE t2(a PRIMARY KEY, b)%WR%;
CREATE TABLE t3(c, d REFERENCES t2);
INSERT INTO t2 VALUES(1, 'one');
INSERT INTO t2 VALUES(2, 'two');
INSERT INTO t2 VALUES(4, 'five');
INSERT INTO t3 VALUES('i', 1);
}
do_conflict_test $tn.3.2.3 -tables t2 -sql {
DELETE FROM t2 WHERE a = 1;
DELETE FROM t2 WHERE a = 2;
DELETE FROM t2 WHERE a = 3;
DELETE FROM t2 WHERE a = 4;
} -conflicts {
{DELETE t2 NOTFOUND {i 3 t three}}
{DELETE t2 DATA {i 4 t four} {i 4 t five}}
{FOREIGN_KEY 1}
}
do_execsql_test $tn.3.2.4 "SELECT * FROM t2" {}
do_db2_test $tn.3.2.5 "SELECT * FROM t2" {4 five}
# Test UPDATE changesets.
#
do_execsql_test $tn.3.3.1 {
CREATE TABLE t4(a, b, c, PRIMARY KEY(b, c))%WR%;
INSERT INTO t4 VALUES(1, 2, 3);
INSERT INTO t4 VALUES(4, 5, 6);
INSERT INTO t4 VALUES(7, 8, 9);
INSERT INTO t4 VALUES(10, 11, 12);
}
do_db2_test $tn.3.3.2 {
CREATE TABLE t4(a NOT NULL, b, c, PRIMARY KEY(b, c))%WR%;
INSERT INTO t4 VALUES(0, 2, 3);
INSERT INTO t4 VALUES(4, 5, 7);
INSERT INTO t4 VALUES(7, 8, 9);
INSERT INTO t4 VALUES(10, 11, 12);
}
do_conflict_test $tn.3.3.3 -tables t4 -sql {
UPDATE t4 SET a = -1 WHERE b = 2;
UPDATE t4 SET a = -1 WHERE b = 5;
UPDATE t4 SET a = NULL WHERE c = 9;
UPDATE t4 SET a = 'x' WHERE b = 11;
} -conflicts {
{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 $tn.3.3.4 { SELECT * FROM t4 } {0 2 3 4 5 7 7 8 9 x 11 12}
do_execsql_test $tn.3.3.5 { SELECT * FROM t4 } {-1 2 3 -1 5 6 {} 8 9 x 11 12}
#-------------------------------------------------------------------------
# This next block of tests verifies that values returned by the conflict
# handler are intepreted correctly.
#
proc test_reset {} {
db close
db2 close
forcedelete test.db test.db2
sqlite3 db test.db
sqlite3 db2 test.db2
}
proc xConflict {args} {
lappend ::xConflict $args
return $::conflict_return
}
foreach {tn2 conflict_return after} {
1 OMIT {1 2 value1 4 5 7 10 x x}
2 REPLACE {1 2 value1 4 5 value2 10 8 9}
} {
test_reset
do_test $tn.4.$tn2.1 {
foreach db {db db2} {
execsql {
CREATE TABLE t1(a, b, c, PRIMARY KEY(a))%WR%;
INSERT INTO t1 VALUES(1, 2, 3);
INSERT INTO t1 VALUES(4, 5, 6);
INSERT INTO t1 VALUES(7, 8, 9);
} $db
}
execsql {
REPLACE INTO t1 VALUES(4, 5, 7);
REPLACE INTO t1 VALUES(10, 'x', 'x');
} db2
} {}
do_conflict_test $tn.4.$tn2.2 -tables t1 -sql {
UPDATE t1 SET c = 'value1' WHERE a = 1; -- no conflict
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}}
}
do_db2_test $tn.4.$tn2.3 "SELECT * FROM t1 ORDER BY a" $after
}
foreach {tn2 conflict_return} {
1 OMIT
2 REPLACE
} {
test_reset
do_test $tn.5.$tn2.1 {
# Create an identical schema in both databases.
set schema {
CREATE TABLE "'foolish name'"(x, y, z, PRIMARY KEY(x, y))%WR%;
}
execsql $schema db
execsql $schema db2
# Add some rows to [db2]. These rows will cause conflicts later
# on when the changeset from [db] is applied to it.
execsql {
INSERT INTO "'foolish name'" VALUES('one', 'one', 'ii');
INSERT INTO "'foolish name'" VALUES('one', 'two', 'i');
INSERT INTO "'foolish name'" VALUES('two', 'two', 'ii');
} db2
} {}
do_conflict_test $tn.5.$tn2.2 -tables {{'foolish name'}} -sql {
INSERT INTO "'foolish name'" VALUES('one', 'two', 2);
} -conflicts {
{INSERT {'foolish name'} CONFLICT {t one t two i 2} {t one t two t i}}
}
set res(REPLACE) {one one ii one two 2 two two ii}
set res(OMIT) {one one ii one two i two two ii}
do_db2_test $tn.5.$tn2.3 {
SELECT * FROM "'foolish name'" ORDER BY x, y
} $res($conflict_return)
do_test $tn.5.$tn2.1 {
set schema {
CREATE TABLE d1("z""z" PRIMARY KEY, y)%WR%;
INSERT INTO d1 VALUES(1, 'one');
INSERT INTO d1 VALUES(2, 'two');
}
execsql $schema db
execsql $schema db2
execsql {
UPDATE d1 SET y = 'TWO' WHERE "z""z" = 2;
} db2
} {}
do_conflict_test $tn.5.$tn2.2 -tables d1 -sql {
DELETE FROM d1 WHERE "z""z" = 2;
} -conflicts {
{DELETE d1 DATA {i 2 t two} {i 2 t TWO}}
}
set res(REPLACE) {1 one}
set res(OMIT) {1 one 2 TWO}
do_db2_test $tn.5.$tn2.3 "SELECT * FROM d1" $res($conflict_return)
}
#-------------------------------------------------------------------------
# Test that two tables can be monitored by a single session object.
#
test_reset
set schema {
CREATE TABLE t1(a COLLATE nocase PRIMARY KEY, b)%WR%;
CREATE TABLE t2(a, b PRIMARY KEY)%WR%;
}
do_test $tn.6.0 {
execsql $schema db
execsql $schema db2
execsql {
INSERT INTO t1 VALUES('a', 'b');
INSERT INTO t2 VALUES('a', 'b');
} db2
} {}
set conflict_return ""
do_conflict_test $tn.6.1 -tables {t1 t2} -sql {
INSERT INTO t1 VALUES('1', '2');
INSERT INTO t1 VALUES('A', 'B');
INSERT INTO t2 VALUES('A', 'B');
} -conflicts {
{INSERT t1 CONFLICT {t A t B} {t a t b}}
}
do_db2_test $tn.6.2 "SELECT * FROM t1 ORDER BY a" {1 2 a b}
do_db2_test $tn.6.3 "SELECT * FROM t2 ORDER BY a" {A B a b}
#-------------------------------------------------------------------------
# Test that session objects are not confused by changes to table in
# other databases.
#
catch { db2 close }
drop_all_tables
forcedelete test.db2
do_iterator_test $tn.7.1 * {
ATTACH 'test.db2' AS aux;
CREATE TABLE main.t1(x PRIMARY KEY, y)%WR%;
CREATE TABLE aux.t1(x PRIMARY KEY, y)%WR%;
INSERT INTO main.t1 VALUES('one', 1);
INSERT INTO main.t1 VALUES('two', 2);
INSERT INTO aux.t1 VALUES('three', 3);
INSERT INTO aux.t1 VALUES('four', 4);
} {
{INSERT t1 0 X. {} {t two i 2}}
{INSERT t1 0 X. {} {t one i 1}}
}
#-------------------------------------------------------------------------
# Test the sqlite3session_isempty() function.
#
do_test $tn.8.1 {
execsql {
CREATE TABLE t5(x PRIMARY KEY, y)%WR%;
CREATE TABLE t6(x PRIMARY KEY, y)%WR%;
INSERT INTO t5 VALUES('a', 'b');
INSERT INTO t6 VALUES('a', 'b');
}
sqlite3session S db main
S attach *
S isempty
} {1}
do_test $tn.8.2 {
execsql { DELETE FROM t5 }
S isempty
} {0}
do_test $tn.8.3 {
S delete
sqlite3session S db main
S attach t5
execsql { DELETE FROM t5 }
S isempty
} {1}
do_test $tn.8.4 { S delete } {}
do_test $tn.8.5 {
sqlite3session S db main
S attach t5
S attach t6
execsql { INSERT INTO t5 VALUES(1, 2) }
S isempty
} {0}
do_test $tn.8.6 {
S delete
sqlite3session S db main
S attach t5
S attach t6
execsql { INSERT INTO t6 VALUES(1, 2) }
S isempty
} {0}
do_test $tn.8.7 { S delete } {}
#-------------------------------------------------------------------------
#
do_execsql_test $tn.9.1 {
CREATE TABLE t7(a, b, c, d, e PRIMARY KEY, f, g)%WR%;
INSERT INTO t7 VALUES(1, 1, 1, 1, 1, 1, 1);
}
do_test $tn.9.2 {
sqlite3session S db main
S attach *
execsql { UPDATE t7 SET b=2, d=2 }
} {}
do_changeset_test $tn.9.2 S {{UPDATE t7 0 ....X.. {{} {} i 1 {} {} i 1 i 1 {} {} {} {}} {{} {} i 2 {} {} i 2 {} {} {} {} {} {}}}}
S delete
catch { db2 close }
#-------------------------------------------------------------------------
# Test a really long table name.
#
reset_db
set tblname [string repeat tblname123 100]
do_test $tn.10.1.1 {
execsql "
CREATE TABLE $tblname (a PRIMARY KEY, b)%WR%;
INSERT INTO $tblname VALUES('xyz', 'def');
"
sqlite3session S db main
S attach $tblname
execsql "
INSERT INTO $tblname VALUES('uvw', 'abc');
DELETE FROM $tblname WHERE a = 'xyz';
"
} {}
do_changeset_test $tn.10.1.2 S "
{INSERT $tblname 0 X. {} {t uvw t abc}}
{DELETE $tblname 0 X. {t xyz t def} {}}
"
do_test $tn.10.1.4 { S delete } {}
#---------------------------------------------------------------
reset_db
do_execsql_test $tn.11.1 {
CREATE TABLE t1(a, b);
}
do_test $tn.11.2 {
sqlite3session S db main
S attach t1
execsql {
INSERT INTO t1 VALUES(1, 2);
}
S changeset
} {}
S delete
#-------------------------------------------------------------------------
# Test a really long table name.
#
reset_db
set tblname [string repeat tblname123 100]
do_test $tn.10.1.1 {
execsql "
CREATE TABLE $tblname (a PRIMARY KEY, b)%WR%;
INSERT INTO $tblname VALUES('xyz', 'def');
"
sqlite3session S db main
S attach $tblname
execsql "
INSERT INTO $tblname VALUES('uvw', 'abc');
DELETE FROM $tblname WHERE a = 'xyz';
"
} {}
do_changeset_test $tn.10.1.2 S "
{INSERT $tblname 0 X. {} {t uvw t abc}}
{DELETE $tblname 0 X. {t xyz t def} {}}
"
do_test $tn.10.1.4 { S delete } {}
#-------------------------------------------------------------------------
# Test the effect of updating a column from 0.0 to 0.0.
#
reset_db
do_execsql_test $tn.11.1 {
CREATE TABLE t1(a INTEGER PRIMARY KEY, b REAL)%WR%;
INSERT INTO t1 VALUES(1, 0.0);
}
do_iterator_test $tn.11.2 * {
UPDATE t1 SET b = 0.0;
} {
}
reset_db
do_execsql_test $tn.12.1 {
CREATE TABLE t1(r INTEGER PRIMARY KEY, a, b)%WR%;
CREATE INDEX i1 ON t1(a);
INSERT INTO t1 VALUES(1, 1, 1);
INSERT INTO t1 VALUES(2, 1, 2);
INSERT INTO t1 VALUES(3, 1, 3);
}
do_iterator_test $tn.12.2 * {
UPDATE t1 SET b='one' WHERE a=1;
} {
{UPDATE t1 0 X.. {i 1 {} {} i 1} {{} {} {} {} t one}}
{UPDATE t1 0 X.. {i 2 {} {} i 2} {{} {} {} {} t one}}
{UPDATE t1 0 X.. {i 3 {} {} i 3} {{} {} {} {} t one}}
}
#-------------------------------------------------------------------------
# Test that no savepoint is used if -nosavepoint is specified.
#
do_execsql_test $tn.13.1 {
CREATE TABLE x1(a INTEGER PRIMARY KEY, b)%WR%;
}
do_test $tn.13.2 {
execsql BEGIN
set C [changeset_from_sql {
INSERT INTO x1 VALUES(1, 'one');
INSERT INTO x1 VALUES(2, 'two');
INSERT INTO x1 VALUES(3, 'three');
}]
execsql ROLLBACK
execsql {
INSERT INTO x1 VALUES(1, 'i');
INSERT INTO x1 VALUES(2, 'ii');
INSERT INTO x1 VALUES(3, 'iii');
}
} {}
proc xConflict {args} {
set ret [lindex $::CONFLICT_HANDLERS 0]
set ::CONFLICT_HANDLERS [lrange $::CONFLICT_HANDLERS 1 end]
set ret
}
do_test $tn.13.3 {
set CONFLICT_HANDLERS [list REPLACE REPLACE ABORT]
execsql BEGIN
catch { sqlite3changeset_apply_v2 db $C xConflict } msg
execsql {
SELECT * FROM x1
}
} {1 i 2 ii 3 iii}
do_test $tn.13.3 {
set CONFLICT_HANDLERS [list REPLACE REPLACE ABORT]
execsql ROLLBACK
execsql BEGIN
catch { sqlite3changeset_apply_v2 -nosavepoint db $C xConflict } msg
execsql { SELECT * FROM x1 }
} {1 one 2 two 3 iii}
execsql ROLLBACK
do_test $tn.14.1 { sqlite3session_config strm_size -1 } 64
do_test $tn.14.2 { sqlite3session_config strm_size 65536 } 65536
do_test $tn.14.3 { sqlite3session_config strm_size 64 } 64
do_test $tn.14.4 {
list [catch {sqlite3session_config invalid 123} msg] $msg
} {1 SQLITE_MISUSE}
#-------------------------------------------------------------------------
reset_db
do_execsql_test $tn.15.0 {
CREATE TABLE records(
idx INTEGER PRIMARY KEY AUTOINCREMENT,
tbl TEXT NOT NULL,
pk TEXT NOT NULL,
value TEXT,
UNIQUE(tbl, pk)
);
CREATE TABLE changes(
idx INTEGER PRIMARY KEY AUTOINCREMENT,
tbl TEXT NOT NULL,
pk TEXT NOT NULL,
op TEXT DEFAULT ('='),
path TEXT DEFAULT ('$'),
value TEXT,
UNIQUE(tbl, pk)
);
CREATE TRIGGER changes_after_insert
AFTER INSERT ON changes
FOR EACH ROW
BEGIN
INSERT INTO records(tbl, pk, value)
VALUES (NEW.tbl, NEW.pk, NEW.value)
ON CONFLICT(tbl, pk) DO UPDATE SET value = NEW.value;
END;
CREATE VIEW data_view AS
SELECT *, '=' AS op, '$' AS path FROM records
WHERE tbl = 'data';
CREATE TRIGGER data_insert
INSTEAD OF INSERT ON data_view
BEGIN
INSERT INTO changes(tbl, pk, op, path, value)
VALUES (NEW.tbl, NEW.pk, NEW.op, NEW.path, NEW.value);
END;
CREATE TRIGGER data_update
INSTEAD OF UPDATE ON data_view
BEGIN
INSERT INTO changes(tbl, pk, op, path, value)
VALUES (NEW.tbl, NEW.pk, NEW.op, NEW.path, NEW.value);
END;
CREATE TRIGGER data_delete
INSTEAD OF DELETE ON data_view
BEGIN
INSERT INTO changes(tbl, pk, op, path, value)
VALUES (NEW.tbl, NEW.pk, NEW.op, NEW.path, NEW.value);
END;
}
do_test $tn.15.1 {
set C [changeset_from_sql {
INSERT INTO data_view (tbl, pk, value) VALUES
('data', '1', 'First'),
('data', '2', 'Second');
}]
changeset_to_list $C
} [list \
{INSERT changes 1 X..... {} {i 1 t data t 1 n {} n {} t First}} \
{INSERT changes 1 X..... {} {i 2 t data t 2 n {} n {} t Second}} \
{INSERT records 1 X... {} {i 1 t data t 1 t First}} \
{INSERT records 1 X... {} {i 2 t data t 2 t Second}} \
]
forcedelete test.db2
sqlite3 db2 test.db2
do_test $tn.15.2 {
db2 eval {
CREATE TABLE records(
idx INTEGER PRIMARY KEY AUTOINCREMENT,
tbl TEXT NOT NULL,
pk TEXT NOT NULL,
value TEXT,
UNIQUE(tbl, pk)
);
CREATE TABLE changes(
idx INTEGER PRIMARY KEY AUTOINCREMENT,
tbl TEXT NOT NULL,
pk TEXT NOT NULL,
op TEXT DEFAULT ('='),
path TEXT DEFAULT ('$'),
value TEXT,
UNIQUE(tbl, pk)
);
CREATE TRIGGER changes_after_insert
AFTER INSERT ON changes
FOR EACH ROW
BEGIN
INSERT INTO records(tbl, pk, value)
VALUES (NEW.tbl, NEW.pk, NEW.value)
ON CONFLICT(tbl, pk) DO UPDATE SET value = NEW.value;
END;
}
sqlite3changeset_apply db2 $C ""
db2 eval {
SELECT * FROM records;
}
} [list 1 data 1 First 2 data 2 Second]
do_test $tn.15.3 {
db2 eval {
SELECT * FROM changes;
}
} {1 data 1 {} {} First 2 data 2 {} {} Second}
db2 close
}]
}
finish_test
+639
View File
@@ -0,0 +1,639 @@
# 2011 Mar 16
#
# 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.
#
#***********************************************************************
#
# The focus of this file is testing the session module.
#
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 session2
proc test_reset {} {
catch { db close }
catch { db2 close }
forcedelete test.db test.db2
sqlite3 db test.db
sqlite3 db2 test.db2
}
##########################################################################
# End of proc definitions. Start of tests.
##########################################################################
test_reset
do_execsql_test 1.0 {
CREATE TABLE t1(a INT PRIMARY KEY, b);
INSERT INTO t1 VALUES('i', 'one');
}
do_iterator_test 1.1 t1 {
DELETE FROM t1 WHERE a = 'i';
INSERT INTO t1 VALUES('ii', 'two');
} {
{DELETE t1 0 X. {t i t one} {}}
{INSERT t1 0 X. {} {t ii t two}}
}
do_iterator_test 1.2 t1 {
INSERT INTO t1 VALUES(1.5, 99.9)
} {
{INSERT t1 0 X. {} {f 1.5 f 99.9}}
}
do_iterator_test 1.3 t1 {
UPDATE t1 SET b = 100.1 WHERE a = 1.5;
UPDATE t1 SET b = 99.9 WHERE a = 1.5;
} { }
do_iterator_test 1.4 t1 {
UPDATE t1 SET b = 100.1 WHERE a = 1.5;
} {
{UPDATE t1 0 X. {f 1.5 f 99.9} {{} {} f 100.1}}
}
# Execute each of the following blocks of SQL on database [db1]. Collect
# changes using a session object. Apply the resulting changeset to
# database [db2]. Then check that the contents of the two databases are
# identical.
#
set set_of_tests {
1 { INSERT INTO %T1% VALUES(1, 2) }
2 {
INSERT INTO %T2% VALUES(1, NULL);
INSERT INTO %T2% VALUES(2, NULL);
INSERT INTO %T2% VALUES(3, NULL);
DELETE FROM %T2% WHERE a = 2;
INSERT INTO %T2% VALUES(4, NULL);
UPDATE %T2% SET b=0 WHERE b=1;
}
3 { INSERT INTO %T3% SELECT *, NULL FROM %T2% }
4 {
INSERT INTO %T3% SELECT a||a, b||b, NULL FROM %T3%;
DELETE FROM %T3% WHERE rowid%2;
}
5 { UPDATE %T3% SET c = a||b }
6 { UPDATE %T1% SET a = 32 }
7 {
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
DELETE FROM %T1% WHERE (rowid%3)==0;
}
8 {
BEGIN;
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
ROLLBACK;
}
9 {
BEGIN;
UPDATE %T1% SET b = 'xxx';
ROLLBACK;
}
10 {
BEGIN;
DELETE FROM %T1% WHERE 1;
ROLLBACK;
}
11 {
INSERT INTO %T1% VALUES(randomblob(21000), randomblob(0));
INSERT INTO %T1% VALUES(1.5, 1.5);
INSERT INTO %T1% VALUES(4.56, -99.999999999999999999999);
}
12 {
INSERT INTO %T2% VALUES(NULL, NULL);
}
13 {
DELETE FROM %T1% WHERE 1;
-- Insert many rows with real primary keys. Enough to force the session
-- objects hash table to resize.
INSERT INTO %T1% VALUES(0.1, 0.1);
INSERT INTO %T1% SELECT a+0.1, b+0.1 FROM %T1%;
INSERT INTO %T1% SELECT a+0.2, b+0.2 FROM %T1%;
INSERT INTO %T1% SELECT a+0.4, b+0.4 FROM %T1%;
INSERT INTO %T1% SELECT a+0.8, b+0.8 FROM %T1%;
INSERT INTO %T1% SELECT a+1.6, b+1.6 FROM %T1%;
INSERT INTO %T1% SELECT a+3.2, b+3.2 FROM %T1%;
INSERT INTO %T1% SELECT a+6.4, b+6.4 FROM %T1%;
INSERT INTO %T1% SELECT a+12.8, b+12.8 FROM %T1%;
INSERT INTO %T1% SELECT a+25.6, b+25.6 FROM %T1%;
INSERT INTO %T1% SELECT a+51.2, b+51.2 FROM %T1%;
INSERT INTO %T1% SELECT a+102.4, b+102.4 FROM %T1%;
INSERT INTO %T1% SELECT a+204.8, b+204.8 FROM %T1%;
}
14 {
DELETE FROM %T1% WHERE 1;
}
15 {
INSERT INTO %T1% VALUES(1, 1);
INSERT INTO %T1% SELECT a+2, b+2 FROM %T1%;
INSERT INTO %T1% SELECT a+4, b+4 FROM %T1%;
INSERT INTO %T1% SELECT a+8, b+8 FROM %T1%;
INSERT INTO %T1% SELECT a+256, b+256 FROM %T1%;
}
16 {
INSERT INTO %T4% VALUES('abc', 'def');
INSERT INTO %T4% VALUES('def', 'abc');
}
17 { UPDATE %T4% SET b = 1 }
18 { DELETE FROM %T4% WHERE 1 }
19 {
INSERT INTO t1 VALUES('', '');
INSERT INTO t1 VALUES(X'', X'');
}
20 {
DELETE FROM t1;
INSERT INTO t1 VALUES('', NULL);
}
}
test_reset
do_common_sql {
CREATE TABLE t1(a int PRIMARY KEY, b);
CREATE TABLE t2(a, b INTEGER PRIMARY KEY);
CREATE TABLE t3(a, b, c, PRIMARY KEY(a, b));
CREATE TABLE t4(a, b, PRIMARY KEY(b, a));
}
foreach {tn sql} [string map {%T1% t1 %T2% t2 %T3% t3 %T4% t4} $set_of_tests] {
do_then_apply_sql -ignorenoop $sql
do_test 2.$tn { compare_db db db2 } {}
}
# The following block of tests is similar to the last, except that the
# session object is recording changes made to an attached database. The
# main database contains a table of the same name as the table being
# modified within the attached db.
#
test_reset
forcedelete test.db3
sqlite3 db3 test.db3
do_test 3.0 {
execsql {
ATTACH 'test.db3' AS 'aux';
CREATE TABLE t1(a int, b PRIMARY KEY);
CREATE TABLE t2(x, y, z);
CREATE TABLE t3(a);
CREATE TABLE aux.t1(a int PRIMARY KEY, b);
CREATE TABLE aux.t2(a, b INTEGER PRIMARY KEY);
CREATE TABLE aux.t3(a, b, c, PRIMARY KEY(a, b));
CREATE TABLE aux.t4(a, b, PRIMARY KEY(b, a));
}
execsql {
CREATE TABLE t1(a int PRIMARY KEY, b);
CREATE TABLE t2(a, b INTEGER PRIMARY KEY);
CREATE TABLE t3(a, b, c, PRIMARY KEY(a, b));
CREATE TABLE t4(a, b, PRIMARY KEY(b, a));
} db2
} {}
proc xTrace {args} { puts $args }
foreach {tn sql} [
string map {%T1% aux.t1 %T2% aux.t2 %T3% aux.t3 %T4% aux.t4} $set_of_tests
] {
do_then_apply_sql $sql aux
do_test 3.$tn { compare_db db2 db3 } {}
}
catch {db3 close}
#-------------------------------------------------------------------------
# The following tests verify that NULL values in primary key columns are
# handled correctly by the session module.
#
test_reset
do_execsql_test 4.0 {
CREATE TABLE t1(a PRIMARY KEY);
CREATE TABLE t2(a, b, c, PRIMARY KEY(c, b));
CREATE TABLE t3(a, b INTEGER PRIMARY KEY);
}
foreach {tn sql changeset} {
1 {
INSERT INTO t1 VALUES(123);
INSERT INTO t1 VALUES(NULL);
INSERT INTO t1 VALUES(456);
} {
{INSERT t1 0 X {} {i 456}}
{INSERT t1 0 X {} {i 123}}
}
2 {
UPDATE t1 SET a = NULL;
} {
{DELETE t1 0 X {i 456} {}}
{DELETE t1 0 X {i 123} {}}
}
3 { DELETE FROM t1 } { }
4 {
INSERT INTO t3 VALUES(NULL, NULL)
} {
{INSERT t3 0 .X {} {n {} i 1}}
}
5 { INSERT INTO t2 VALUES(1, 2, NULL) } { }
6 { INSERT INTO t2 VALUES(1, NULL, 3) } { }
7 { INSERT INTO t2 VALUES(1, NULL, NULL) } { }
8 { INSERT INTO t2 VALUES(1, 2, 3) } { {INSERT t2 0 .XX {} {i 1 i 2 i 3}} }
9 { DELETE FROM t2 WHERE 1 } { {DELETE t2 0 .XX {i 1 i 2 i 3} {}} }
} {
do_iterator_test 4.$tn {t1 t2 t3} $sql $changeset
}
#-------------------------------------------------------------------------
# Test that if NULL is passed to sqlite3session_attach(), all database
# tables are attached to the session object.
#
test_reset
do_execsql_test 5.0 {
CREATE TABLE t1(a PRIMARY KEY);
CREATE TABLE t2(x, y PRIMARY KEY);
}
foreach {tn sql changeset} {
1 { INSERT INTO t1 VALUES(35) } { {INSERT t1 0 X {} {i 35}} }
2 { INSERT INTO t2 VALUES(36, 37) } { {INSERT t2 0 .X {} {i 36 i 37}} }
3 {
DELETE FROM t1 WHERE 1;
UPDATE t2 SET x = 34;
} {
{DELETE t1 0 X {i 35} {}}
{UPDATE t2 0 .X {i 36 i 37} {i 34 {} {}}}
}
} {
do_iterator_test 5.$tn * $sql $changeset
}
#-------------------------------------------------------------------------
# The next block of tests verify that the "indirect" flag is set
# correctly within changesets. The indirect flag is set for a change
# if either of the following are true:
#
# * The sqlite3session_indirect() API has been used to set the session
# indirect flag to true, or
# * The change was made by a trigger.
#
# If the same row is updated more than once during a session, then the
# change is considered indirect only if all changes meet the criteria
# above.
#
test_reset
db function indirect [list S indirect]
do_execsql_test 6.0 {
CREATE TABLE t1(a PRIMARY KEY, b, c);
CREATE TABLE t2(x PRIMARY KEY, y);
CREATE TRIGGER AFTER INSERT ON t2 WHEN new.x%2 BEGIN
INSERT INTO t2 VALUES(new.x+1, NULL);
END;
}
do_iterator_test 6.1.1 * {
INSERT INTO t1 VALUES(1, 'one', 'i');
SELECT indirect(1);
INSERT INTO t1 VALUES(2, 'two', 'ii');
SELECT indirect(0);
INSERT INTO t1 VALUES(3, 'three', 'iii');
} {
{INSERT t1 0 X.. {} {i 1 t one t i}}
{INSERT t1 1 X.. {} {i 2 t two t ii}}
{INSERT t1 0 X.. {} {i 3 t three t iii}}
}
do_iterator_test 6.1.2 * {
SELECT indirect(1);
UPDATE t1 SET c = 'I' WHERE a = 1;
SELECT indirect(0);
} {
{UPDATE t1 1 X.. {i 1 {} {} t i} {{} {} {} {} t I}}
}
do_iterator_test 6.1.3 * {
SELECT indirect(1);
UPDATE t1 SET c = '.' WHERE a = 1;
SELECT indirect(0);
UPDATE t1 SET c = 'o' WHERE a = 1;
} {
{UPDATE t1 0 X.. {i 1 {} {} t I} {{} {} {} {} t o}}
}
do_iterator_test 6.1.4 * {
SELECT indirect(0);
UPDATE t1 SET c = 'x' WHERE a = 1;
SELECT indirect(1);
UPDATE t1 SET c = 'i' WHERE a = 1;
} {
{UPDATE t1 0 X.. {i 1 {} {} t o} {{} {} {} {} t i}}
}
do_iterator_test 6.1.4 * {
SELECT indirect(1);
UPDATE t1 SET c = 'y' WHERE a = 1;
SELECT indirect(1);
UPDATE t1 SET c = 'I' WHERE a = 1;
} {
{UPDATE t1 1 X.. {i 1 {} {} t i} {{} {} {} {} t I}}
}
do_iterator_test 6.1.5 * {
INSERT INTO t2 VALUES(1, 'x');
} {
{INSERT t2 0 X. {} {i 1 t x}}
{INSERT t2 1 X. {} {i 2 n {}}}
}
do_iterator_test 6.1.6 * {
SELECT indirect(1);
INSERT INTO t2 VALUES(3, 'x');
SELECT indirect(0);
UPDATE t2 SET y = 'y' WHERE x>2;
} {
{INSERT t2 0 X. {} {i 3 t y}}
{INSERT t2 0 X. {} {i 4 t y}}
}
do_iterator_test 6.1.7 * {
SELECT indirect(1);
DELETE FROM t2 WHERE x = 4;
SELECT indirect(0);
INSERT INTO t2 VALUES(4, 'new');
} {
{UPDATE t2 0 X. {i 4 t y} {{} {} t new}}
}
do_iterator_test 6.1.8 * {
CREATE TABLE t3(a, b PRIMARY KEY);
CREATE TABLE t4(a, b PRIMARY KEY);
CREATE TRIGGER t4t AFTER UPDATE ON t4 BEGIN
UPDATE t3 SET a = new.a WHERE b = new.b;
END;
SELECT indirect(1);
INSERT INTO t3 VALUES('one', 1);
INSERT INTO t4 VALUES('one', 1);
SELECT indirect(0);
UPDATE t4 SET a = 'two' WHERE b = 1;
} {
{INSERT t3 1 .X {} {t two i 1}}
{INSERT t4 0 .X {} {t two i 1}}
}
sqlite3session S db main
do_execsql_test 6.2.1 {
SELECT indirect(0);
SELECT indirect(-1);
SELECT indirect(45);
SELECT indirect(-100);
} {0 0 1 1}
S delete
#-------------------------------------------------------------------------
# Test that if a conflict-handler that has been passed either NOTFOUND or
# CONSTRAINT returns REPLACE - the sqlite3changeset_apply() call returns
# MISUSE and rolls back any changes made so far.
#
# 7.1.*: NOTFOUND conflict-callback.
# 7.2.*: CONSTRAINT conflict-callback.
#
proc xConflict {args} {return REPLACE}
test_reset
do_execsql_test 7.1.1 {
CREATE TABLE t1(a PRIMARY KEY, b);
INSERT INTO t1 VALUES(1, 'one');
INSERT INTO t1 VALUES(2, 'two');
}
do_test 7.1.2 {
execsql {
CREATE TABLE t1(a PRIMARY KEY, b NOT NULL);
INSERT INTO t1 VALUES(1, 'one');
} db2
} {}
do_test 7.1.3 {
set changeset [changeset_from_sql {
UPDATE t1 SET b = 'five' WHERE a = 1;
UPDATE t1 SET b = 'six' WHERE a = 2;
}]
set x [list]
sqlite3session_foreach c $changeset { lappend x $c }
set x
} [list \
{UPDATE t1 0 X. {i 1 t one} {{} {} t five}} \
{UPDATE t1 0 X. {i 2 t two} {{} {} t six}} \
]
do_test 7.1.4 {
list [catch {sqlite3changeset_apply db2 $changeset xConflict} msg] $msg
} {1 SQLITE_MISUSE}
do_test 7.1.5 { execsql { SELECT * FROM t1 } db2 } {1 one}
do_test 7.2.1 {
set changeset [changeset_from_sql { UPDATE t1 SET b = NULL WHERE a = 1 }]
set x [list]
sqlite3session_foreach c $changeset { lappend x $c }
set x
} [list \
{UPDATE t1 0 X. {i 1 t five} {{} {} n {}}} \
]
do_test 7.2.2 {
list [catch {sqlite3changeset_apply db2 $changeset xConflict} msg] $msg
} {1 SQLITE_MISUSE}
do_test 7.2.3 { execsql { SELECT * FROM t1 } db2 } {1 one}
#-------------------------------------------------------------------------
# Test that if a conflict-handler returns ABORT, application of the
# changeset is rolled back and the sqlite3changeset_apply() method returns
# SQLITE_ABORT.
#
# Also test that the same thing happens if a conflict handler returns an
# unrecognized integer value. Except, in this case SQLITE_MISUSE is returned
# instead of SQLITE_ABORT.
#
foreach {tn conflict_return apply_return} {
1 ABORT SQLITE_ABORT
2 567 SQLITE_MISUSE
} {
test_reset
proc xConflict {args} [list return $conflict_return]
do_test 8.$tn.0 {
do_common_sql {
CREATE TABLE t1(x, y, PRIMARY KEY(x, y));
INSERT INTO t1 VALUES('x', 'y');
}
execsql { INSERT INTO t1 VALUES('w', 'w') }
set changeset [changeset_from_sql { DELETE FROM t1 WHERE 1 }]
set x [list]
sqlite3session_foreach c $changeset { lappend x $c }
set x
} [list \
{DELETE t1 0 XX {t w t w} {}} \
{DELETE t1 0 XX {t x t y} {}} \
]
do_test 8.$tn.1 {
list [catch {sqlite3changeset_apply db2 $changeset xConflict} msg] $msg
} [list 1 $apply_return]
do_test 8.$tn.2 {
execsql {SELECT * FROM t1} db2
} {x y}
}
#-------------------------------------------------------------------------
# Try to cause an infinite loop as follows:
#
# 1. Have a changeset insert a row that causes a CONFLICT callback,
# 2. Have the conflict handler return REPLACE,
# 3. After the session module deletes the conflicting row, have a trigger
# re-insert it.
# 4. Goto step 1...
#
# This doesn't work, as the second invocation of the conflict handler is a
# CONSTRAINT, not a CONFLICT. There is at most one CONFLICT callback for
# each change in the changeset.
#
test_reset
proc xConflict {type args} {
if {$type == "CONFLICT"} { return REPLACE }
return OMIT
}
do_test 9.1 {
execsql {
CREATE TABLE t1(a PRIMARY KEY, b);
}
execsql {
CREATE TABLE t1(a PRIMARY KEY, b);
INSERT INTO t1 VALUES('x', 2);
CREATE TRIGGER tr1 AFTER DELETE ON t1 BEGIN
INSERT INTO t1 VALUES(old.a, old.b);
END;
} db2
} {}
do_test 9.2 {
set changeset [changeset_from_sql { INSERT INTO t1 VALUES('x', 1) }]
sqlite3changeset_apply db2 $changeset xConflict
} {}
do_test 9.3 {
execsql { SELECT * FROM t1 } db2
} {x 2}
#-------------------------------------------------------------------------
#
test_reset
db function enable [list S enable]
do_common_sql {
CREATE TABLE t1(a PRIMARY KEY, b);
INSERT INTO t1 VALUES('x', 'X');
}
do_iterator_test 10.1 t1 {
INSERT INTO t1 VALUES('y', 'Y');
SELECT enable(0);
INSERT INTO t1 VALUES('z', 'Z');
SELECT enable(1);
} {
{INSERT t1 0 X. {} {t y t Y}}
}
sqlite3session S db main
do_execsql_test 10.2 {
SELECT enable(0);
SELECT enable(-1);
SELECT enable(1);
SELECT enable(-1);
} {0 0 1 1}
S delete
#-------------------------------------------------------------------------
test_reset
do_common_sql {
CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c, d, e, f);
WITH s(i) AS (
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<32
)
INSERT INTO t1 SELECT NULL, 0, 0, 0, 0, 0 FROM s
}
do_then_apply_sql -ignorenoop {
UPDATE t1 SET f=f+1 WHERE a=1;
UPDATE t1 SET e=e+1 WHERE a=2;
UPDATE t1 SET e=e+1, f=f+1 WHERE a=3;
UPDATE t1 SET d=d+1 WHERE a=4;
UPDATE t1 SET d=d+1, f=f+1 WHERE a=5;
UPDATE t1 SET d=d+1, e=e+1 WHERE a=6;
UPDATE t1 SET d=d+1, e=e+1, f=f+1 WHERE a=7;
UPDATE t1 SET c=c+1 WHERE a=8;
UPDATE t1 SET c=c+1, f=f+1 WHERE a=9;
UPDATE t1 SET c=c+1, e=e+1 WHERE a=10;
UPDATE t1 SET c=c+1, e=e+1, f=f+1 WHERE a=11;
UPDATE t1 SET c=c+1, d=d+1 WHERE a=12;
UPDATE t1 SET c=c+1, d=d+1, f=f+1 WHERE a=13;
UPDATE t1 SET c=c+1, d=d+1, e=e+1 WHERE a=14;
UPDATE t1 SET c=c+1, d=d+1, e=e+1, f=f+1 WHERE a=15;
UPDATE t1 SET d=d+1 WHERE a=16;
UPDATE t1 SET d=d+1, f=f+1 WHERE a=17;
UPDATE t1 SET d=d+1, e=e+1 WHERE a=18;
UPDATE t1 SET d=d+1, e=e+1, f=f+1 WHERE a=19;
UPDATE t1 SET d=d+1, d=d+1 WHERE a=20;
UPDATE t1 SET d=d+1, d=d+1, f=f+1 WHERE a=21;
UPDATE t1 SET d=d+1, d=d+1, e=e+1 WHERE a=22;
UPDATE t1 SET d=d+1, d=d+1, e=e+1, f=f+1 WHERE a=23;
UPDATE t1 SET d=d+1, c=c+1 WHERE a=24;
UPDATE t1 SET d=d+1, c=c+1, f=f+1 WHERE a=25;
UPDATE t1 SET d=d+1, c=c+1, e=e+1 WHERE a=26;
UPDATE t1 SET d=d+1, c=c+1, e=e+1, f=f+1 WHERE a=27;
UPDATE t1 SET d=d+1, c=c+1, d=d+1 WHERE a=28;
UPDATE t1 SET d=d+1, c=c+1, d=d+1, f=f+1 WHERE a=29;
UPDATE t1 SET d=d+1, c=c+1, d=d+1, e=e+1 WHERE a=30;
UPDATE t1 SET d=d+1, c=c+1, d=d+1, e=e+1, f=f+1 WHERE a=31;
}
do_test 11.0 {
compare_db db db2
} {}
finish_test
+214
View File
@@ -0,0 +1,214 @@
# 2011 March 24
#
# 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. More
# specifically, it focuses on testing the session modules response to
# database schema modifications and mismatches.
#
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 session3
#-------------------------------------------------------------------------
# These tests - session3-1.* - verify that the session module behaves
# correctly when confronted with a schema mismatch when applying a
# changeset (in function sqlite3changeset_apply()).
#
# session3-1.1.*: Table does not exist in target db.
# session3-1.2.*: Table has wrong number of columns in target db.
# session3-1.3.*: Table has wrong PK columns in target db.
#
db close
sqlite3_shutdown
test_sqlite3_log log
sqlite3 db test.db
proc log {code msg} { lappend ::log $code $msg }
forcedelete test.db2
sqlite3 db2 test.db2
do_execsql_test 1.0 {
CREATE TABLE t1(a PRIMARY KEY, b);
}
do_test 1.1 {
set ::log {}
do_then_apply_sql {
INSERT INTO t1 VALUES(1, 2);
INSERT INTO t1 VALUES(3, 4);
}
set ::log
} {SQLITE_SCHEMA {sqlite3changeset_apply(): no such table: t1}}
do_test 1.2.0 {
execsql { CREATE TABLE t1(a PRIMARY KEY, b, c) } db2
} {}
do_test 1.2.1 {
set ::log {}
do_then_apply_sql {
INSERT INTO t1 VALUES(5, 6);
INSERT INTO t1 VALUES(7, 8);
}
set ::log
} {}
do_test 1.2.2 {
db2 eval { SELECT * FROM t1 }
} {5 6 {} 7 8 {}}
do_test 1.3.0 {
execsql {
DROP TABLE t1;
CREATE TABLE t1(a, b PRIMARY KEY);
} db2
} {}
do_test 1.3.1 {
set ::log {}
do_then_apply_sql {
INSERT INTO t1 VALUES(9, 10);
INSERT INTO t1 VALUES(11, 12);
}
set ::log
} {SQLITE_SCHEMA {sqlite3changeset_apply(): primary key mismatch for table t1}}
#-------------------------------------------------------------------------
# These tests - session3-2.* - verify that the session module behaves
# correctly when the schema of an attached table is modified during the
# session.
#
# session3-2.1.*: Table is dropped midway through the session.
# session3-2.2.*: Table is dropped and recreated with a different # cols.
# session3-2.3.*: Table is dropped and recreated with a different PK.
#
# In all of these scenarios, the call to sqlite3session_changeset() will
# return SQLITE_SCHEMA. Also:
#
# session3-2.4.*: Table is dropped and recreated with an identical schema.
# In this case sqlite3session_changeset() returns SQLITE_OK.
#
do_test 2.1 {
execsql { CREATE TABLE t2(a, b PRIMARY KEY) }
sqlite3session S db main
S attach t2
execsql {
INSERT INTO t2 VALUES(1, 2);
DROP TABLE t2;
}
list [catch { S changeset } msg] $msg
} {1 SQLITE_SCHEMA}
do_test 2.2.1 {
S delete
sqlite3session S db main
execsql { CREATE TABLE t2(a, b PRIMARY KEY, c) }
S attach t2
execsql {
INSERT INTO t2 VALUES(1, 2, 3);
DROP TABLE t2;
CREATE TABLE t2(a, b PRIMARY KEY);
}
list [catch { S changeset } msg] $msg
} {1 SQLITE_SCHEMA}
do_test 2.2.2 {
S delete
sqlite3session S db main
execsql {
DROP TABLE t2;
CREATE TABLE t2(a, b PRIMARY KEY, c);
}
S attach t2
execsql {
INSERT INTO t2 VALUES(1, 2, 3);
DROP TABLE t2;
CREATE TABLE t2(a, b PRIMARY KEY, c, d);
}
catch { S changeset }
} {0}
do_test 2.2.3 {
S delete
sqlite3session S db main
execsql {
DROP TABLE t2;
CREATE TABLE t2(a, b PRIMARY KEY, c);
}
S attach t2
execsql {
INSERT INTO t2 VALUES(1, 2, 3);
DROP TABLE t2;
CREATE TABLE t2(a, b PRIMARY KEY);
INSERT INTO t2 VALUES(4, 5);
}
list [catch { S changeset } msg] $msg
} {1 SQLITE_SCHEMA}
do_test 2.2.4 {
S delete
sqlite3session S db main
execsql {
DROP TABLE t2;
CREATE TABLE t2(a, b PRIMARY KEY, c);
}
S attach t2
execsql {
INSERT INTO t2 VALUES(1, 2, 3);
DROP TABLE t2;
CREATE TABLE t2(a, b PRIMARY KEY, c, d);
INSERT INTO t2 VALUES(4, 5, 6, 7);
}
catch { S changeset }
} {0}
do_test 2.3 {
S delete
sqlite3session S db main
execsql {
DROP TABLE t2;
CREATE TABLE t2(a, b PRIMARY KEY);
}
S attach t2
execsql {
INSERT INTO t2 VALUES(1, 2);
DROP TABLE t2;
CREATE TABLE t2(a PRIMARY KEY, b);
}
list [catch { S changeset } msg] $msg
} {1 SQLITE_SCHEMA}
do_test 2.4 {
S delete
sqlite3session S db main
execsql {
DROP TABLE t2;
CREATE TABLE t2(a, b PRIMARY KEY);
}
S attach t2
execsql {
INSERT INTO t2 VALUES(1, 2);
DROP TABLE t2;
CREATE TABLE t2(a, b PRIMARY KEY);
}
list [catch { S changeset } msg] $msg
} {0 {}}
S delete
catch { db close }
catch { db2 close }
sqlite3_shutdown
test_sqlite3_log
sqlite3_initialize
finish_test
+149
View File
@@ -0,0 +1,149 @@
# 2011 March 25
#
# 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.
#
if {$tcl_version<8.6} {
puts "This module requires Tcl 8.6 or later"
return
}
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 session4
do_test 1.0 {
execsql {
CREATE TABLE x(a, b, c, d, e, PRIMARY KEY(c, e));
INSERT INTO x VALUES(65.21, X'28B0', 16.35, NULL, 'doers');
INSERT INTO x VALUES(NULL, 78.49, 2, X'60', -66);
INSERT INTO x VALUES('cathedral', NULL, 35, NULL, X'B220937E80A2D8');
INSERT INTO x VALUES(NULL, 'masking', -91.37, NULL, X'596D');
INSERT INTO x VALUES(19, 'domains', 'espouse', -94, 'throw');
}
set changeset [changeset_from_sql {
DELETE FROM x WHERE e = -66;
UPDATE x SET a = 'parameterizable', b = 31.8 WHERE c = 35;
INSERT INTO x VALUES(-75.61, -17, 16.85, NULL, X'D73DB02678');
}]
set {} {}
} {}
# This currently causes crashes. sqlite3changeset_invert() does not handle
# corrupt changesets well.
if 0 {
do_test 1.1 {
for {set i 0} {$i < [string length $changeset]} {incr i} {
set before [string range $changeset 0 [expr $i-1]]
set after [string range $changeset [expr $i+1] end]
for {set j 10} {$j < 260} {incr j} {
set x [binary format "a*ca*" $before $j $after]
catch { sqlite3changeset_invert $x }
}
}
} {}
}
do_test 1.2 {
set x [binary format "ca*" 0 [string range $changeset 1 end]]
list [catch { sqlite3changeset_invert $x } msg] $msg
} {1 SQLITE_CORRUPT}
do_test 1.3 {
set x [binary format "ca*" 0 [string range $changeset 1 end]]
list [catch { sqlite3changeset_apply db $x xConflict } msg] $msg
} {1 SQLITE_CORRUPT}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 2.0 {
CREATE TABLE t1(a INTEGER PRIMARY KEY,b,c,d);
CREATE TABLE t2(e TEXT PRIMARY KEY NOT NULL,f,g);
CREATE TABLE t3(w REAL PRIMARY KEY NOT NULL,x,y);
CREATE TABLE t4(z PRIMARY KEY) WITHOUT ROWID;
}
foreach {tn blob} {
1 54010174340012000000
2 54fefe8bcb0012000300
3 5480809280808001017434001200fb
4 50af9c939c9c9cb09c9c6400b09c9c6400
5 12000300
6 09847304
7 5401017434001208
8 54010174340012fc0386868600
9 54010174340012FC0386868600
10 548894FEFE
11 54010171340012E703ABFA7433FD1200
12 540101743400120003FFED00010000000000000002120002400C00000000000054040100000074310017000100000000000000050100000000000000030100000000000000040000010000000000000004010000000000000003001700010000000000000007030378797A01000000000000000F000001000000000000000F030378797A005403010000743200090003037838790100000000800000000200000000000000000900030378327902400C0000000000000304666F7572
13 540101743400120003001200010000000000000002120002400C0000000000005404010000007431001700010000000000000005010000000000000003010000000000000004000001000000000000000401000000000000000300170001000000000000000703FC87797A01000000000000000F000001000000000000000F030378797A005403010000743200090003037838790100000000800000000200000000000000000900030378327902400C0000000000000304666F7572
14 540101743400120003001200010000000000000002120002400C00000000000054040100000074310017000100000000000000050100000000000000030100000000000000040000010000000000000004010000000000000003001700010000000000000007030378797A01000000000000000F000001000000000000000F03FC87797A005403010000743200090003037838790100000000800000000200000000000000000900030378327902400C0000000000000304666F7572
15 540101743400120003001200010000000000000002120002400C00000000000054040100000074310017000100000000000000050100000000000000030100000000000000040000010000000000000004010000000000000003001700010000000000000007030378797A01000000000000000F000001000000000000000F030378797A005403010000743200090003FC8738790100000000800000000200000000000000000900030378327902400C0000000000000304666F7572
16 540101743400120003001200010000000000000002120002400C00000000000054040100000074310017000100000000000000050100000000000000030100000000000000040000010000000000000004010000000000000003001700010000000000000007030378797A01000000000000000F000001000000000000000F030378797A00540301000074320009000303783879010000000080000000020000000000000000090003FC87327902400C0000000000000304666F7572
17 540101743400120003FFE3000412F7010000E600000000021202120002400C0000000000005B0401000000743100171C0304646F750002400C000000000000540401000000D3310017000100000000000000050100000000000378797A405403000002F10100000100000000000004090001000100000007030378797A0100000000000D0007000001000000002300000F1B0378797A405403013900743200090003038C3879010000000000000000000002120002400C0000000000005B0401000000743117170003047C5E00FF
18 54010174340012000300120001000000E6FF100000120002401E00000000000054040100000074310017000100040000010000000000000004FFFF7FFF0000000000010000010000001000000007030378797A01000000000000000F000000000000FA0304666F7572
19 540101743400120003001200010000000000000002121B02400C00000000000054040000000074310017000100000000000000050100000000000000030100000000000000040000010000000000000004010000000000000003001700010000000000000007030378817A01000000000000000F000001000000000100000F030378797A005403010000743200090003FFE809000303780000000000000304666F7572
20 5401017D3400120003001200010000000000000002120002400CFC00000000005404010000007431001700010000000000000005010000000000000003010000000000000004000001000000000000000401000000000000000300170001000000000000000703FFFF797A01000000000000000F000001000000000000000F030378797A005403010000743200090003037838790100000000800000000200000000000000000900030378326C02400C0000000000000304666F7572
21 5401017434001200030012000100FFE20000000002120002400C00000000000054040100E0007431001700010000E99D000000020000000003FFE70009000303783279020004000001030000000000002117000003001700012701000100000000743100000100000000008000090003037F387901000000008000000002000000000400000009005303010A00FF7FFFFF00000000000304664F6572
22 540101743400120003FFFF7FFF0000000000000002120002400C00000000000054040100000074310017000100000000000000050100000000000000030100010000000000000000040000010000000000000004010000000000000003001700010000000000000007030378797A01000000000000000F000001000000000000000F030378797A005403010000743200090003037838790100000000800000000200000000000000000900030378327902400C0000000000000304666F7572
23 540101742700120100120003F5FF0300
24 5401017434E312540101743400120003FFFC00
25 540101743400540101743D3D3D3D3D3D3D3D3D3D3D3D3D3400120003FFED000300
26 5401017446EA5301743D1D3D3D01743D1D3D3DCF3D3D3D1A3D3D3D3D3400120003FFFF000000
27 540101743400540101743D3D3D3D3D3D3D3D3D3D251000120003FF81000000000000
28 540101340012000397FF3D7F3D3400120003001200540101743D3D3D3D3D3D393D3D3D12000300
29 500174340050010F74340012000300120003FFE5
30 5004007233E900177FEF0054257F0002EF001200031E12000300
31 5001015001015252525250010174340012EF039A9A0100E351525D52525252525252525252525252525252525250010174340012EF039A0100009A9A9A9A9A9BA3B200120003010040743400
32 5401017400123400120003FFFC00
33 540101743400120003001200010000000000004002120002400C0000000000005404010000007431001700010000000000000005010000000000000003010000000000000004000001000000000000000401000000000000000300170001000000000000000703FC87797A01000000000000000F000001000000000000000F030378797A005403010000743200090003037838790100000000800000000200000000000000000900030378327902400C0000000000000304666F7572
34 54040100000074310017000100000002000015050100000000000000030100000000140000040000010000000000000004010000000000000003001700010000000000000007030378797A01000000000000000F000001000000000000000F030378797A0054030100007432000900030378387901000000008E000000020000000000000000090003FFFF000002400C0000000000000304666F7572
35 540101743400120003001200010000000000000002120002400C00000000000050060100000074310017000100000000000000050100000000000000030100000003001700010000666F7572
36 540101743400120003001200010000000000000002120002400C00000000000050050100000074310017000100000000000000050100000000000000030100000003001700010000666F7572
37 540101743400120003001200010000000000000002120002400C00000000000050040100008074310017000100000000000000050100000000000000030100000003001700010000666F7572
38 540101743400120003001200010000000000000002120002400C00000000000050040100000074310017000000000000000000050100000000000000030100000003001700010000666F7572
39 540101743400120003001200010000000000000002120002400C00000000000050040100018074310017000100000000000000050100000000000000030100000003001700010000666F7572
40 540101743400120003001200010000000000000002120002400C0000000000005004FEFFFFFF74310017000100000000000000050100000000000000030100000003001700010000666F7572
41 540101743400120003001200010000000000000002120002400C00000000000050040100000074310017000004000000000000050100000000000000030100000003001700010000666F7572
42 540101743400120003001200010000000000000002120002400C0000000000005005FFFF050074310017000100000000000000050100000000000000030100000003001700010000666F7572
43 540101743400120003001200010000000000000002120002400C000000000000500401006E0074310017000300000000001221050100000000000000030100000003001700010000666F7572
44 540101743400120003001200010000000000020000120002400C00000000000050050100000074310017000100000000000000050100004000000000030100000025001700010000666F7572
45 540101743400120003001200010000000000ECFF02120002400C000000000000500401F9FF00743100170001000000000000000500E1000000000000030100000003000000000000666F7572
46 54010174340B0B0B0B0B0B0B0B0B0B0B0B0B0B0B00120003001200010000000000000002120002400C00000000000050040100000074310017010000000000000000050100FFE900000000030100000003007F00000000666F7572
47 54010103001200010000000000020002120002400C0000000000005004010000F374310017000100000000000000050100000000000000030100000003001700010000666F8E72
48 540101743400120003001200010000000000000002120002400C00000000000050030012000174310017000700000000000000050100002000000001000000000003001700010000666F7572
49 540101743400120004001200010000000000000002120002400C0000000000005004010000FC733100170001000000000000000501000000000000000301000000F6FF17000100007C6F7572
50 54010174FFDDFF8003001200010000100000000002120002400C000000000000500401000000743100170000000005010000000000000000000003010072
51 540101743200120003001200010000000000000002120002400C00000000000050040100001074310017000000000003010000120300170100000000000000050100000000000000030100000003001700010000666F7572
52 540101745401017434001200010000000000001702120002400C00000000000050040100001A74310017000100000000000100000100000000000000030100000003001700010000666F7572
53 540101743400120003001200010000000000000002120002400C000000000000500401000000743100170001000002400C00000000000050040110000074310017000000000000050100000000000000030100000003001700010000666F7572
54 540101743400120003001200010000000000000002120002400C000000000002120002400C00000000000050040100000074310017FF0050040100000074310017FF7F00000000000000050100000000000000030100000003001700010000666F7572
55 540101743400120003001200010000000000000002120002400C00000000000050040100000074310017000100010080000001000000020003010100000300170100000003001700010000666F7572
56 5487ffffff7f
} {
do_test 2.$tn {
set changeset [binary decode hex $blob]
#set fd [open x.change w+]
#fconfigure $fd -translation binary
#puts -nonewline $fd $changeset
#close $fd
list [catch { sqlite3changeset_apply db $changeset xConflict } msg] $msg
} {1 SQLITE_CORRUPT}
}
finish_test
+408
View File
@@ -0,0 +1,408 @@
# 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
# Organization of tests:
#
# session5-1.*: Simple tests to check the concat() function produces
# correct results.
#
# session5-2.*: More complicated tests.
#
# session5-3.*: Schema mismatch errors.
#
# session5-4.*: Test the concat cases that indicate that the database
# was modified in between recording of the two changesets
# being concatenated (i.e. two changesets that INSERT rows
# with the same PK values).
#
proc do_concat_test {tn args} {
set subtest 0
foreach sql $args {
incr subtest
sqlite3session S db main ; S attach *
execsql $sql
set c [S changeset]
if {[info commands s_prev] != ""} {
set c_concat [sqlite3changeset_concat $c_prev $c]
set c_two [s_prev changeset]
s_prev delete
set h_concat [changeset_to_list $c_concat]
set h_two [changeset_to_list $c_two]
do_test $tn.$subtest [list set {} $h_concat] $h_two
}
set c_prev $c
rename S s_prev
}
catch { s_prev delete }
}
#-------------------------------------------------------------------------
# Test cases session5-1.* - simple tests.
#
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';
}
#-------------------------------------------------------------------------
# Test cases session5-2.* - more complex tests.
#
db function indirect indirect
proc indirect {{x -1}} {
S indirect $x
s_prev indirect $x
}
do_concat_test 2.1 {
CREATE TABLE abc(a, b, c PRIMARY KEY);
INSERT INTO abc VALUES(NULL, NULL, 1);
INSERT INTO abc VALUES('abcdefghijkl', NULL, 2);
} {
DELETE FROM abc WHERE c = 1;
UPDATE abc SET c = 1 WHERE c = 2;
} {
INSERT INTO abc VALUES('abcdefghijkl', NULL, 2);
INSERT INTO abc VALUES(1.0, 2.0, 3);
} {
UPDATE abc SET a = a-1;
} {
CREATE TABLE def(d, e, f, PRIMARY KEY(e, f));
INSERT INTO def VALUES('x', randomblob(11000), 67);
INSERT INTO def SELECT d, e, f+1 FROM def;
INSERT INTO def SELECT d, e, f+2 FROM def;
INSERT INTO def SELECT d, e, f+4 FROM def;
} {
DELETE FROM def WHERE rowid>4;
} {
INSERT INTO def SELECT d, e, f+4 FROM def;
} {
INSERT INTO abc VALUES(22, 44, -1);
} {
UPDATE abc SET c=-2 WHERE c=-1;
UPDATE abc SET c=-3 WHERE c=-2;
} {
UPDATE abc SET c=-4 WHERE c=-3;
} {
UPDATE abc SET a=a+1 WHERE c=-3;
UPDATE abc SET a=a+1 WHERE c=-3;
} {
UPDATE abc SET a=a+1 WHERE c=-3;
UPDATE abc SET a=a+1 WHERE c=-3;
} {
INSERT INTO abc VALUES('one', 'two', 'three');
} {
SELECT indirect(1);
UPDATE abc SET a='one point five' WHERE c = 'three';
} {
SELECT indirect(0);
UPDATE abc SET a='one point six' WHERE c = 'three';
} {
CREATE TABLE x1(a, b, PRIMARY KEY(a));
SELECT indirect(1);
INSERT INTO x1 VALUES(1, 2);
} {
SELECT indirect(1);
UPDATE x1 SET b = 3 WHERE a = 1;
}
catch {db close}
forcedelete test.db
sqlite3 db test.db
do_concat_test 2.2 {
CREATE TABLE t1(a, b, PRIMARY KEY(b));
CREATE TABLE t2(a PRIMARY KEY, b);
INSERT INTO t1 VALUES('string', 1);
INSERT INTO t1 VALUES(4, 2);
INSERT INTO t1 VALUES(X'FFAAFFAAFFAA', 3);
} {
INSERT INTO t2 VALUES('one', 'two');
INSERT INTO t2 VALUES(1, NULL);
UPDATE t1 SET a = 5 WHERE a = 2;
} {
DELETE FROM t2 WHERE a = 1;
UPDATE t1 SET a = 4 WHERE a = 2;
INSERT INTO t2 VALUES('x', 'y');
}
do_test 2.3.0 {
catch {db close}
forcedelete test.db
sqlite3 db test.db
set sql1 ""
set sql2 ""
for {set i 1} {$i < 120} {incr i} {
append sql1 "INSERT INTO x1 VALUES($i*4, $i);"
}
for {set i 1} {$i < 120} {incr i} {
append sql2 "DELETE FROM x1 WHERE a = $i*4;"
}
set {} {}
} {}
do_concat_test 2.3 {
CREATE TABLE x1(a PRIMARY KEY, b)
} $sql1 $sql2 $sql1 $sql2
do_concat_test 2.4 {
CREATE TABLE x2(a PRIMARY KEY, b);
CREATE TABLE x3(a PRIMARY KEY, b);
INSERT INTO x2 VALUES('a', 'b');
INSERT INTO x2 VALUES('x', 'y');
INSERT INTO x3 VALUES('a', 'b');
} {
INSERT INTO x2 VALUES('c', 'd');
INSERT INTO x3 VALUES('e', 'f');
INSERT INTO x3 VALUES('x', 'y');
}
do_concat_test 2.5 {
UPDATE x3 SET b = 'Y' WHERE a = 'x'
} {
DELETE FROM x3 WHERE a = 'x'
} {
DELETE FROM x2 WHERE a = 'a'
} {
INSERT INTO x2 VALUES('a', 'B');
}
for {set k 1} {$k <=10} {incr k} {
do_test 2.6.$k.1 {
drop_all_tables
set sql1 ""
set sql2 ""
for {set i 1} {$i < 120} {incr i} {
append sql1 "INSERT INTO x1 VALUES(randomblob(20+(random()%10)), $i);"
}
for {set i 1} {$i < 120} {incr i} {
append sql2 "DELETE FROM x1 WHERE rowid = $i;"
}
set {} {}
} {}
do_concat_test 2.6.$k {
CREATE TABLE x1(a PRIMARY KEY, b)
} $sql1 $sql2 $sql1 $sql2
}
for {set k 1} {$k <=10} {incr k} {
do_test 2.7.$k.1 {
drop_all_tables
set sql1 ""
set sql2 ""
for {set i 1} {$i < 120} {incr i} {
append sql1 {
INSERT INTO x1 VALUES(
CASE WHEN random()%2 THEN random() ELSE randomblob(20+random()%10) END,
CASE WHEN random()%2 THEN random() ELSE randomblob(20+random()%10) END
);
}
}
for {set i 1} {$i < 120} {incr i} {
append sql2 "DELETE FROM x1 WHERE rowid = $i;"
}
set {} {}
} {}
do_concat_test 2.7.$k {
CREATE TABLE x1(a PRIMARY KEY, b)
} $sql1 $sql2 $sql1 $sql2
}
#-------------------------------------------------------------------------
# Test that schema incompatibilities are detected correctly.
#
# session5-3.1: Incompatible number of columns.
# session5-3.2: Incompatible PK definition.
#
do_test 3.1 {
db close
forcedelete test.db
sqlite3 db test.db
execsql { CREATE TABLE t1(a PRIMARY KEY, b) }
set c1 [changeset_from_sql { INSERT INTO t1 VALUES(1, 2) }]
execsql {
DROP TABLE t1;
CREATE TABLE t1(a PRIMARY KEY, b, c);
}
set c2 [changeset_from_sql { INSERT INTO t1 VALUES(2, 3, 4) }]
list [catch { sqlite3changeset_concat $c1 $c2 } msg] $msg
} {1 SQLITE_SCHEMA}
do_test 3.2 {
db close
forcedelete test.db
sqlite3 db test.db
execsql { CREATE TABLE t1(a PRIMARY KEY, b) }
set c1 [changeset_from_sql { INSERT INTO t1 VALUES(1, 2) }]
execsql {
DROP TABLE t1;
CREATE TABLE t1(a, b PRIMARY KEY);
}
set c2 [changeset_from_sql { INSERT INTO t1 VALUES(2, 3) }]
list [catch { sqlite3changeset_concat $c1 $c2 } msg] $msg
} {1 SQLITE_SCHEMA}
#-------------------------------------------------------------------------
# Test that concat() handles these properly:
#
# session5-4.1: INSERT + INSERT
# session5-4.2: UPDATE + INSERT
# session5-4.3: DELETE + UPDATE
# session5-4.4: DELETE + DELETE
#
proc do_concat_test2 {tn sql1 sqlX sql2 expected} {
sqlite3session S db main ; S attach *
execsql $sql1
set ::c1 [S changeset]
S delete
execsql $sqlX
sqlite3session S db main ; S attach *
execsql $sql2
set ::c2 [S changeset]
S delete
uplevel do_test $tn [list {
changeset_to_list [sqlite3changeset_concat $::c1 $::c2]
}] [list [normalize_list $expected]]
}
drop_all_tables db
do_concat_test2 4.1 {
CREATE TABLE t1(a PRIMARY KEY, b);
INSERT INTO t1 VALUES('key', 'value');
} {
DELETE FROM t1 WHERE a = 'key';
} {
INSERT INTO t1 VALUES('key', 'xxx');
} {
{INSERT t1 0 X. {} {t key t value}}
}
do_concat_test2 4.2 {
UPDATE t1 SET b = 'yyy';
} {
DELETE FROM t1 WHERE a = 'key';
} {
INSERT INTO t1 VALUES('key', 'value');
} {
{UPDATE t1 0 X. {t key t xxx} {{} {} t yyy}}
}
do_concat_test2 4.3 {
DELETE FROM t1 WHERE a = 'key';
} {
INSERT INTO t1 VALUES('key', 'www');
} {
UPDATE t1 SET b = 'valueX' WHERE a = 'key';
} {
{DELETE t1 0 X. {t key t value} {}}
}
do_concat_test2 4.4 {
DELETE FROM t1 WHERE a = 'key';
} {
INSERT INTO t1 VALUES('key', 'ttt');
} {
DELETE FROM t1 WHERE a = 'key';
} {
{DELETE t1 0 X. {t key t valueX} {}}
}
finish_test
+91
View File
@@ -0,0 +1,91 @@
# 2011 July 11
#
# 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 sessions extension.
# Specifically, it tests that sessions work when the database is modified
# using incremental blob handles.
#
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}
ifcapable !incrblob {finish_test; return}
set testprefix session6
proc do_then_apply_tcl {tcl {dbname main}} {
proc xConflict args { return "OMIT" }
set rc [catch {
sqlite3session S db $dbname
db eval "SELECT name FROM $dbname.sqlite_master WHERE type = 'table'" {
S attach $name
}
eval $tcl
sqlite3changeset_apply db2 [S changeset] xConflict
} msg]
catch { S delete }
if {$rc} {error $msg}
}
test_sqlite3_log x
proc x {args} {puts $args}
forcedelete test.db2
sqlite3 db2 test.db2
do_common_sql {
CREATE TABLE t1(a PRIMARY KEY, b);
CREATE TABLE t2(c PRIMARY KEY, d);
}
# Test a blob update.
#
do_test 1.1 {
do_then_apply_tcl {
db eval { INSERT INTO t1 VALUES(1, 'helloworld') }
db eval { INSERT INTO t2 VALUES(2, 'onetwothree') }
}
compare_db db db2
} {}
do_test 1.2 {
do_then_apply_tcl {
set fd [db incrblob t1 b 1]
puts -nonewline $fd 1234567890
close $fd
}
compare_db db db2
} {}
# Test an attached database.
#
do_test 2.1 {
forcedelete test.db3
file copy test.db2 test.db3
execsql { ATTACH 'test.db3' AS aux; }
do_then_apply_tcl {
set fd [db incrblob aux t2 d 1]
puts -nonewline $fd fourfivesix
close $fd
} aux
sqlite3 db3 test.db3
compare_db db2 db3
} {}
db3 close
db2 close
finish_test
+91
View File
@@ -0,0 +1,91 @@
# 2011 July 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 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 session8
proc noop {args} {}
# Like [dbcksum] in tester.tcl. Except this version is not sensitive
# to changes in the value of implicit IPK columns.
#
proc udbcksum {db dbname} {
if {$dbname=="temp"} {
set master sqlite_temp_master
} else {
set master $dbname.sqlite_master
}
set alltab [$db eval "SELECT name FROM $master WHERE type='table'"]
set txt [$db eval "SELECT * FROM $master"]\n
foreach tab $alltab {
append txt [lsort [$db eval "SELECT * FROM $dbname.$tab"]]\n
}
return [md5 $txt]
}
proc do_then_undo {tn sql} {
set ck1 [udbcksum db main]
sqlite3session S db main
S attach *
db eval $sql
set ck2 [udbcksum db main]
set invert [sqlite3changeset_invert [S changeset]]
S delete
sqlite3changeset_apply db $invert noop
set ck3 [udbcksum db main]
set a [expr {$ck1==$ck2}]
set b [expr {$ck1==$ck3}]
uplevel [list do_test $tn.1 "set {} $a" 0]
uplevel [list do_test $tn.2 "set {} $b" 1]
}
do_execsql_test 1.1 {
CREATE TABLE t1(a PRIMARY KEY, b);
INSERT INTO t1 VALUES(1, 2);
INSERT INTO t1 VALUES('abc', 'xyz');
}
do_then_undo 1.2 { INSERT INTO t1 VALUES(3, 4); }
do_then_undo 1.3 { DELETE FROM t1 WHERE b=2; }
do_then_undo 1.4 { UPDATE t1 SET b = 3 WHERE a = 1; }
do_execsql_test 2.1 {
CREATE TABLE t2(a, b PRIMARY KEY);
INSERT INTO t2 VALUES(1, 2);
INSERT INTO t2 VALUES('abc', 'xyz');
}
do_then_undo 1.2 { INSERT INTO t2 VALUES(3, 4); }
do_then_undo 1.3 { DELETE FROM t2 WHERE b=2; }
do_then_undo 1.4 { UPDATE t1 SET a = '123' WHERE b = 'xyz'; }
do_execsql_test 3.1 {
CREATE TABLE t3(a, b, c, d, e, PRIMARY KEY(c, e));
INSERT INTO t3 VALUES('x', 45, 0.0, 'abcdef', 12);
INSERT INTO t3 VALUES(45, 0.0, 'abcdef', 12, 'x');
INSERT INTO t3 VALUES(0.0, 'abcdef', 12, 'x', 45);
}
do_then_undo 3.2 { UPDATE t3 SET b=b||b WHERE e!='x' }
do_then_undo 3.3 { UPDATE t3 SET a = 46 }
finish_test
+293
View File
@@ -0,0 +1,293 @@
# 2013 July 04
#
# 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 tests that the sessions module handles foreign key constraint
# violations when applying changesets as required.
#
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 session9
#--------------------------------------------------------------------
# Basic tests.
#
proc populate_db {} {
drop_all_tables
execsql {
PRAGMA foreign_keys = 1;
CREATE TABLE p1(a PRIMARY KEY, b);
CREATE TABLE c1(a PRIMARY KEY, b REFERENCES p1);
CREATE TABLE c2(a PRIMARY KEY,
b REFERENCES p1 DEFERRABLE INITIALLY DEFERRED
);
INSERT INTO p1 VALUES(1, 'one');
INSERT INTO p1 VALUES(2, 'two');
INSERT INTO p1 VALUES(3, 'three');
INSERT INTO p1 VALUES(4, 'four');
}
}
proc capture_changeset {sql} {
sqlite3session S db main
foreach t [db eval {SELECT name FROM sqlite_master WHERE type='table'}] {
S attach $t
}
execsql $sql
set ret [S changeset]
S delete
return $ret
}
do_test 1.1 {
populate_db
set cc [capture_changeset {
INSERT INTO c1 VALUES('ii', 2);
INSERT INTO c2 VALUES('iii', 3);
}]
set {} {}
} {}
proc xConflict {args} {
lappend ::xConflict {*}$args
return $::conflictret
}
foreach {tn delrow trans conflictargs conflictret} {
1 2 0 {FOREIGN_KEY 1} OMIT
2 3 0 {FOREIGN_KEY 1} OMIT
3 2 1 {FOREIGN_KEY 1} OMIT
4 3 1 {FOREIGN_KEY 1} OMIT
5 2 0 {FOREIGN_KEY 1} ABORT
6 3 0 {FOREIGN_KEY 1} ABORT
7 2 1 {FOREIGN_KEY 1} ABORT
8 3 1 {FOREIGN_KEY 1} ABORT
} {
set A(OMIT,0) {0 {}}
set A(OMIT,1) {0 {}}
set A(ABORT,0) {1 SQLITE_CONSTRAINT}
set A(ABORT,1) {1 SQLITE_CONSTRAINT}
do_test 1.2.$tn.1 {
populate_db
execsql { DELETE FROM p1 WHERE a=($delrow+0) }
if {$trans} { execsql BEGIN }
set ::xConflict [list]
list [catch {sqlite3changeset_apply db $::cc xConflict} msg] $msg
} $A($conflictret,$trans)
do_test 1.2.$tn.2 { set ::xConflict } $conflictargs
set A(OMIT,0) {1 1}
set A(OMIT,1) {1 1}
set A(ABORT,0) {0 0}
set A(ABORT,1) {0 0}
do_test 1.2.$tn.3 {
execsql { SELECT count(*) FROM c1 UNION ALL SELECT count(*) FROM c2 }
} $A($conflictret,$trans)
do_test 1.2.$tn.4 { expr ![sqlite3_get_autocommit db] } $trans
do_test 1.2.$tn.5 {
if { $trans && $conflictret=="ABORT" } { execsql COMMIT }
} {}
catchsql ROLLBACK
}
#--------------------------------------------------------------------
# Test that closing a transaction clears the defer_foreign_keys flag.
#
foreach {tn open noclose close} {
1 BEGIN {} COMMIT
2 BEGIN {} ROLLBACK
3 {SAVEPOINT one} {} {RELEASE one}
4 {SAVEPOINT one} {ROLLBACK TO one} {RELEASE one}
} {
execsql $open
do_execsql_test 2.$tn.1 { PRAGMA defer_foreign_keys } {0}
do_execsql_test 2.$tn.2 {
PRAGMA defer_foreign_keys = 1;
PRAGMA defer_foreign_keys;
} {1}
execsql $noclose
do_execsql_test 2.$tn.3 { PRAGMA defer_foreign_keys } {1}
execsql $close
do_execsql_test 2.$tn.4 { PRAGMA defer_foreign_keys } {0}
}
#--------------------------------------------------------------------
# Test that a cyclic relationship can be inserted and deleted.
#
# This situation does not come up in practice, but testing it serves to
# show that it does not matter which order parent and child keys
# are processed in internally when applying a changeset.
#
drop_all_tables
do_execsql_test 3.1 {
CREATE TABLE t1(a PRIMARY KEY, b);
CREATE TABLE t2(x PRIMARY KEY, y);
}
# Create changesets as follows:
#
# $cc1 - Insert a row into t1.
# $cc2 - Insert a row into t2.
# $cc - Combination of $cc1 and $cc2.
#
# $ccdel1 - Delete the row from t1.
# $ccdel2 - Delete the row from t2.
# $ccdel - Combination of $cc1 and $cc2.
#
do_test 3.2 {
set cc1 [capture_changeset {
INSERT INTO t1 VALUES('one', 'value one');
}]
set ccdel1 [capture_changeset { DELETE FROM t1; }]
set cc2 [capture_changeset {
INSERT INTO t2 VALUES('value one', 'one');
}]
set ccdel2 [capture_changeset { DELETE FROM t2; }]
set cc [capture_changeset {
INSERT INTO t1 VALUES('one', 'value one');
INSERT INTO t2 VALUES('value one', 'one');
}]
set ccdel [capture_changeset {
DELETE FROM t1;
DELETE FROM t2;
}]
set {} {}
} {}
# Now modify the database schema to create a cyclic foreign key dependency
# between tables t1 and t2. This means that although changesets $cc and
# $ccdel can be applied, none of the others may without violating the
# foreign key constraints.
#
do_test 3.3 {
drop_all_tables
execsql {
CREATE TABLE t1(a PRIMARY KEY, b REFERENCES t2);
CREATE TABLE t2(x PRIMARY KEY, y REFERENCES t1);
}
proc conflict_handler {args} { return "ABORT" }
sqlite3changeset_apply db $cc conflict_handler
execsql {
SELECT * FROM t1;
SELECT * FROM t2;
}
} {one {value one} {value one} one}
do_test 3.3.1 {
list [catch {sqlite3changeset_apply db $::ccdel1 conflict_handler} msg] $msg
} {1 SQLITE_CONSTRAINT}
do_test 3.3.2 {
list [catch {sqlite3changeset_apply db $::ccdel2 conflict_handler} msg] $msg
} {1 SQLITE_CONSTRAINT}
do_test 3.3.4.1 {
list [catch {sqlite3changeset_apply db $::ccdel conflict_handler} msg] $msg
} {0 {}}
do_execsql_test 3.3.4.2 {
SELECT * FROM t1;
SELECT * FROM t2;
} {}
do_test 3.5.1 {
list [catch {sqlite3changeset_apply db $::cc1 conflict_handler} msg] $msg
} {1 SQLITE_CONSTRAINT}
do_test 3.5.2 {
list [catch {sqlite3changeset_apply db $::cc2 conflict_handler} msg] $msg
} {1 SQLITE_CONSTRAINT}
#--------------------------------------------------------------------
# Test that if a change that affects FK processing is not applied
# due to a separate constraint, SQLite does not get confused and
# increment FK counters anyway.
#
drop_all_tables
do_execsql_test 4.1 {
CREATE TABLE p1(x PRIMARY KEY, y);
CREATE TABLE c1(a PRIMARY KEY, b REFERENCES p1);
INSERT INTO p1 VALUES(1,1);
}
do_execsql_test 4.2.1 {
BEGIN;
PRAGMA defer_foreign_keys = 1;
INSERT INTO c1 VALUES('x', 'x');
}
do_catchsql_test 4.2.2 { COMMIT } {1 {FOREIGN KEY constraint failed}}
do_catchsql_test 4.2.3 { ROLLBACK } {0 {}}
do_execsql_test 4.3.1 {
BEGIN;
PRAGMA defer_foreign_keys = 1;
INSERT INTO c1 VALUES(1, 1);
}
do_catchsql_test 4.3.2 {
INSERT INTO c1 VALUES(1, 'x')
} {1 {UNIQUE constraint failed: c1.a}}
do_catchsql_test 4.3.3 { COMMIT } {0 {}}
do_catchsql_test 4.3.4 { BEGIN ; COMMIT } {0 {}}
#--------------------------------------------------------------------
# Test that if a DELETE change cannot be applied due to an
# SQLITE_CONSTRAINT error thrown by a trigger program, things do not
# go awry.
drop_all_tables
reset_db
do_execsql_test 5.1 {
CREATE TABLE x1(x PRIMARY KEY, y);
CREATE TABLE x2(x PRIMARY KEY, y);
INSERT INTO x2 VALUES(1, 1);
INSERT INTO x1 VALUES(1, 1);
}
set ::cc [changeset_from_sql { DELETE FROM x1; }]
do_execsql_test 5.2 {
INSERT INTO x1 VALUES(1, 1);
CREATE TRIGGER tr1 AFTER DELETE ON x1 BEGIN
INSERT INTO x2 VALUES(old.x, old.y);
END;
} {}
proc conflict_handler {args} { return "ABORT" }
do_test 5.3 {
list [catch {sqlite3changeset_apply db $::cc conflict_handler} msg] $msg
} {1 SQLITE_ABORT}
do_execsql_test 5.4 {
SELECT * FROM X1;
} {1 1}
finish_test
+106
View File
@@ -0,0 +1,106 @@
# 2013 July 04
#
# 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 tests that filter callbacks work as required.
#
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 sessionA
forcedelete test.db2
sqlite3 db2 test.db2
foreach {tn db} {1 db 2 db2} {
do_test 1.$tn.1 {
execsql {
CREATE TABLE t1(a PRIMARY KEY, b);
CREATE TABLE t2(a PRIMARY KEY, b);
CREATE TABLE t3(a PRIMARY KEY, b);
} $db
} {}
}
proc tbl_filter {zTbl} {
return $::table_filter($zTbl)
}
do_test 2.1 {
set ::table_filter(t1) 1
set ::table_filter(t2) 0
set ::table_filter(t3) 1
sqlite3session S db main
S table_filter tbl_filter
execsql {
INSERT INTO t1 VALUES('a', 'b');
INSERT INTO t2 VALUES('c', 'd');
INSERT INTO t3 VALUES('e', 'f');
}
set changeset [S changeset]
S delete
sqlite3changeset_apply db2 $changeset xConflict
execsql {
SELECT * FROM t1;
SELECT * FROM t2;
SELECT * FROM t3;
} db2
} {a b e f}
#-------------------------------------------------------------------------
# Test that filter callbacks passed to sqlite3changeset_apply() are
# invoked correctly.
#
reset_db
do_execsql_test 3.1 {
CREATE TABLE t1(a PRIMARY KEY, b);
CREATE TABLE t2(x PRIMARY KEY, y);
}
do_test 3.2 {
execsql BEGIN
set ::cs [changeset_from_sql {
INSERT INTO t1 VALUES(1, 2);
INSERT INTO t2 VALUES('x', 'y');
}]
execsql ROLLBACK
set {} {}
} {}
proc filter {x y} {
return [string equal $x $y]
}
do_test 3.3 {
sqlite3changeset_apply db $::cs {} [list filter t1]
execsql {
SELECT * FROM t1;
SELECT * FROM t2;
}
} {1 2}
do_test 3.4 {
execsql { DELETE FROM t1 }
sqlite3changeset_apply db $::cs {} [list filter t2]
execsql {
SELECT * FROM t1;
SELECT * FROM t2;
}
} {x y}
finish_test
+507
View File
@@ -0,0 +1,507 @@
# 2014 August 16
#
# 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 sessions SQLite extension.
# Specifically, this file contains tests for "patchset" changes.
#
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 sessionB
#
# 1.*: Test that the blobs returned by the session_patchset() API are
# as expected. Also the sqlite3_changeset_iter functions.
#
# 2.*: Test that patchset blobs are handled by sqlite3changeset_apply().
#
# 3.*: Test that sqlite3changeset_invert() works with patchset blobs.
# Correct behaviour is to return SQLITE_CORRUPT.
proc do_sql2patchset_test {tn sql res} {
sqlite3session S db main
S attach *
execsql $sql
uplevel [list do_patchset_test $tn S $res]
S delete
}
#-------------------------------------------------------------------------
# Run simple tests of the _patchset() API.
#
do_execsql_test 1.0 {
CREATE TABLE t1(a, b, c, d, PRIMARY KEY(d, a));
INSERT INTO t1 VALUES(1, 2, 3, 4);
INSERT INTO t1 VALUES(5, 6, 7, 8);
INSERT INTO t1 VALUES(9, 10, 11, 12);
}
do_test 1.1 {
sqlite3session S db main
S attach t1
execsql {
INSERT INTO t1 VALUES('w', 'x', 'y', 'z');
DELETE FROM t1 WHERE d=4;
UPDATE t1 SET c = 14 WHERE a=5;
}
} {}
do_patchset_test 1.2 S {
{UPDATE t1 0 X..X {i 5 {} {} {} {} i 8} {{} {} {} {} i 14 {} {}}}
{INSERT t1 0 X..X {} {t w t x t y t z}}
{DELETE t1 0 X..X {i 1 {} {} {} {} i 4} {}}
}
do_test 1.3 {
S delete
} {}
do_sql2patchset_test 1.4 {
DELETE FROM t1;
} {
{DELETE t1 0 X..X {i 5 {} {} {} {} i 8} {}}
{DELETE t1 0 X..X {t w {} {} {} {} t z} {}}
{DELETE t1 0 X..X {i 9 {} {} {} {} i 12} {}}
}
do_sql2patchset_test 1.5 {
INSERT INTO t1 VALUES(X'61626364', NULL, NULL, 4.2);
INSERT INTO t1 VALUES(4.2, NULL, NULL, X'61626364');
} {
{INSERT t1 0 X..X {} {f 4.2 n {} n {} b abcd}}
{INSERT t1 0 X..X {} {b abcd n {} n {} f 4.2}}
}
do_sql2patchset_test 1.6 {
UPDATE t1 SET b=45 WHERE typeof(a)=='blob';
UPDATE t1 SET c='zzzz' WHERE typeof(a)!='blob';
} {
{UPDATE t1 0 X..X {f 4.2 {} {} {} {} b abcd} {{} {} {} {} t zzzz {} {}}}
{UPDATE t1 0 X..X {b abcd {} {} {} {} f 4.2} {{} {} i 45 {} {} {} {}}}
}
do_sql2patchset_test 1.7 {
UPDATE t1 SET b='xyz' WHERE typeof(a)=='blob';
UPDATE t1 SET c='xyz' WHERE typeof(a)!='blob';
UPDATE t1 SET b=45 WHERE typeof(a)=='blob';
UPDATE t1 SET c='zzzz' WHERE typeof(a)!='blob';
} {
}
do_sql2patchset_test 1.8 {
DELETE FROM t1;
} {
{DELETE t1 0 X..X {f 4.2 {} {} {} {} b abcd} {}}
{DELETE t1 0 X..X {b abcd {} {} {} {} f 4.2} {}}
}
#-------------------------------------------------------------------------
# Run simple tests of _apply() with patchset objects.
#
reset_db
proc noop {args} { error $args }
proc exec_rollback_replay {sql} {
sqlite3session S db main
S attach *
execsql BEGIN
execsql $sql
set patchset [S patchset]
S delete
execsql ROLLBACK
sqlite3changeset_apply db $patchset noop
}
do_execsql_test 2.0 {
CREATE TABLE t2(a, b, c, d, PRIMARY KEY(b,c));
CREATE TABLE t3(w, x, y, z, PRIMARY KEY(w));
}
do_test 2.1 {
exec_rollback_replay {
INSERT INTO t2 VALUES(1, 2, 3, 4);
INSERT INTO t2 VALUES('w', 'x', 'y', 'z');
}
execsql { SELECT * FROM t2 }
} {1 2 3 4 w x y z}
do_test 2.2 {
exec_rollback_replay {
DELETE FROM t2 WHERE a=1;
UPDATE t2 SET d = 'a';
}
execsql { SELECT * FROM t2 }
} {w x y a}
#-------------------------------------------------------------------------
# sqlite3changeset_invert()
#
reset_db
do_execsql_test 3.1 { CREATE TABLE t1(x PRIMARY KEY, y) }
do_test 3.2 {
sqlite3session S db main
S attach *
execsql { INSERT INTO t1 VALUES(1, 2) }
set patchset [S patchset]
S delete
list [catch { sqlite3changeset_invert $patchset } msg] [set msg]
} {1 SQLITE_CORRUPT}
#-------------------------------------------------------------------------
# sqlite3changeset_concat()
#
reset_db
proc do_patchconcat_test {tn args} {
set bRevert 0
if {[lindex $args 0] == "-revert"} {
set bRevert 1
set args [lrange $args 1 end]
}
set nSql [expr [llength $args]-1]
set res [lindex $args $nSql]
set patchlist [list]
execsql BEGIN
if {$bRevert} { execsql { SAVEPOINT x } }
foreach sql [lrange $args 0 end-1] {
sqlite3session S db main
S attach *
execsql $sql
lappend patchlist [S patchset]
S delete
if {$bRevert} { execsql { ROLLBACK TO x } }
}
execsql ROLLBACK
set patch [lindex $patchlist 0]
foreach p [lrange $patchlist 1 end] {
set patch [sqlite3changeset_concat $patch $p]
}
set x [list]
sqlite3session_foreach c $patch { lappend x $c }
uplevel [list do_test $tn [list set {} $x] [list {*}$res]]
}
do_execsql_test 4.1.1 {
CREATE TABLE t1(x PRIMARY KEY, y, z);
}
do_patchconcat_test 4.1.2 {
INSERT INTO t1 VALUES(1, 2, 3);
} {
INSERT INTO t1 VALUES(4, 5, 6);
} {
{INSERT t1 0 X.. {} {i 1 i 2 i 3}}
{INSERT t1 0 X.. {} {i 4 i 5 i 6}}
}
do_execsql_test 4.2.1 {
INSERT INTO t1 VALUES(1, 2, 3);
INSERT INTO t1 VALUES(4, 5, 6);
}
do_patchconcat_test 4.2.2 {
UPDATE t1 SET z = 'abc' WHERE x=1
} {
UPDATE t1 SET z = 'def' WHERE x=4
} {
{UPDATE t1 0 X.. {i 1 {} {} {} {}} {{} {} {} {} t abc}}
{UPDATE t1 0 X.. {i 4 {} {} {} {}} {{} {} {} {} t def}}
}
do_patchconcat_test 4.2.3 {
DELETE FROM t1 WHERE x=1;
} {
DELETE FROM t1 WHERE x=4;
} {
{DELETE t1 0 X.. {i 1 {} {} {} {}} {}}
{DELETE t1 0 X.. {i 4 {} {} {} {}} {}}
}
do_execsql_test 4.3.1 {
CREATE TABLE t2(a, b, c, d, PRIMARY KEY(c, b));
INSERT INTO t2 VALUES('.', 1, 1, '.');
INSERT INTO t2 VALUES('.', 1, 2, '.');
INSERT INTO t2 VALUES('.', 2, 1, '.');
INSERT INTO t2 VALUES('.', 2, 2, '.');
}
# INSERT + INSERT
do_patchconcat_test 4.3.2 -revert {
INSERT INTO t2 VALUES('a', 'a', 'a', 'a');
} {
INSERT INTO t2 VALUES('b', 'a', 'a', 'b');
} {
{INSERT t2 0 .XX. {} {t a t a t a t a}}
}
# INSERT + DELETE
do_patchconcat_test 4.3.3 {
INSERT INTO t2 VALUES('a', 'a', 'a', 'a');
} {
DELETE FROM t2 WHERE c = 'a';
} {}
# INSERT + UPDATE
do_patchconcat_test 4.3.4 {
INSERT INTO t2 VALUES('a', 'a', 'a', 'a');
} {
UPDATE t2 SET d = 'b' WHERE c='a';
} {
{INSERT t2 0 .XX. {} {t a t a t a t b}}
}
# UPDATE + UPDATE
do_patchconcat_test 4.3.5 {
UPDATE t2 SET a = 'a' WHERE c=1 AND b=2;
} {
UPDATE t2 SET d = 'd' WHERE c=1 AND b=2;
} {
{UPDATE t2 0 .XX. {{} {} i 2 i 1 {} {}} {t a {} {} {} {} t d}}
}
# UPDATE + DELETE
do_patchconcat_test 4.3.6 {
UPDATE t2 SET a = 'a' WHERE c=1 AND b=2;
} {
DELETE FROM t2 WHERE c=1 AND b=2;
} {
{DELETE t2 0 .XX. {{} {} i 2 i 1 {} {}} {}}
}
# DELETE + INSERT
do_patchconcat_test 4.3.7 {
DELETE FROM t2 WHERE b=1;
} {
INSERT INTO t2 VALUES('x', 1, 2, '.');
} {
{DELETE t2 0 .XX. {{} {} i 1 i 1 {} {}} {}}
{UPDATE t2 0 .XX. {{} {} i 1 i 2 {} {}} {t x {} {} {} {} t .}}
}
# DELETE + UPDATE
do_patchconcat_test 4.3.8 -revert {
DELETE FROM t2 WHERE b=1 AND c=2;
} {
UPDATE t2 SET a=5 WHERE b=1 AND c=2;
} {
{DELETE t2 0 .XX. {{} {} i 1 i 2 {} {}} {}}
}
# DELETE + UPDATE
do_patchconcat_test 4.3.9 -revert {
DELETE FROM t2 WHERE b=1 AND c=2;
} {
DELETE FROM t2 WHERE b=1;
} {
{DELETE t2 0 .XX. {{} {} i 1 i 1 {} {}} {}}
{DELETE t2 0 .XX. {{} {} i 1 i 2 {} {}} {}}
}
#-------------------------------------------------------------------------
# More rigorous testing of the _patchset(), _apply and _concat() APIs.
#
# The inputs to each test are a populate database and a list of DML
# statements. This test determines that the final database is the same
# if:
#
# 1) the statements are executed directly on the database.
#
# 2) a single patchset is collected while executing the statements and
# then applied to a copy of the original database file.
#
# 3) individual patchsets are collected for statement while executing
# them and concatenated together before being applied to a copy of
# the original database. The concatenation is done in a couple of
# different ways - linear, pairwise etc.
#
# All tests, as it happens, are run with both changesets and patchsets.
# But the focus is on patchset capabilities.
#
# Return a checksum of the contents of the database file. Implicit IPK
# columns are not included in the checksum - just modifying rowids does
# not change the database checksum.
#
proc databasecksum {db} {
set alltab [$db eval {SELECT name FROM sqlite_master WHERE type='table'}]
foreach tab $alltab {
$db eval "SELECT * FROM $tab LIMIT 1" res { }
set slist [list]
foreach col [lsort $res(*)] {
lappend slist "quote($col)"
}
set sql "SELECT [join $slist ,] FROM $tab"
append txt "[lsort [$db eval $sql]]\n"
}
return [md5 $txt]
}
proc do_patchset_test {tn tstcmd lSql} {
if {$tstcmd != "patchset" && $tstcmd != "changeset"} {
error "have $tstcmd: must be patchset or changeset"
}
foreach fname {test.db2 test.db3 test.db4 test.db5} {
forcedelete $fname
forcecopy test.db $fname
}
# Execute the SQL statements on [db]. Collect a patchset for each
# individual statement, as well as a single patchset for the entire
# operation.
sqlite3session S db main
S attach *
foreach sql $lSql {
sqlite3session T db main
T attach *
db eval $sql
lappend lPatch [T $tstcmd]
T delete
}
set patchset [S $tstcmd]
S delete
# Calculate a checksum for the final database.
set cksum [databasecksum db]
# 1. Apply the single large patchset to test.db2
sqlite3 db2 test.db2
sqlite3changeset_apply db2 $patchset noop
uplevel [list do_test $tn.1 { databasecksum db2 } $cksum ]
db2 close
# 2. Apply each of the single-statement patchsets to test.db3
sqlite3 db2 test.db3
foreach p $lPatch {
sqlite3changeset_apply db2 $p noop
}
uplevel [list do_test $tn.2 { databasecksum db2 } $cksum ]
db2 close
# 3. Concatenate all single-statement patchsets into a single large
# patchset, then apply it to test.db4.
#
sqlite3 db2 test.db4
set big ""
foreach p $lPatch {
set big [sqlite3changeset_concat $big $p]
}
sqlite3changeset_apply db2 $big noop
uplevel [list do_test $tn.3 { databasecksum db2 } $cksum ]
db2 close
# 4. Concatenate all single-statement patchsets pairwise into a single
# large patchset, then apply it to test.db5. Pairwise concatenation:
#
# a b c d e f g h i j k
# -> {a b} {c d} {e f} {g h} {i j} k
# -> {a b c d} {e f g h} {i j k}
# -> {a b c d e f g h} {i j k}
# -> {a b c d e f g h i j k}
# -> APPLY!
#
sqlite3 db2 test.db5
set L $lPatch
while {[llength $L] > 1} {
set O [list]
for {set i 0} {$i < [llength $L]} {incr i 2} {
if {$i==[llength $L]-1} {
lappend O [lindex $L $i]
} else {
set i1 [expr $i+1]
lappend O [sqlite3changeset_concat [lindex $L $i] [lindex $L $i1]]
}
}
set L $O
}
sqlite3changeset_apply db2 [lindex $L 0] noop
uplevel [list do_test $tn.4 { databasecksum db2 } $cksum ]
db2 close
}
proc do_patchset_changeset_test {tn initsql args} {
foreach tstcmd {patchset changeset} {
reset_db
execsql $initsql
set x 0
foreach sql $args {
incr x
set lSql [split $sql ";"]
uplevel [list do_patchset_test $tn.$tstcmd.$x $tstcmd $lSql]
}
}
}
do_patchset_changeset_test 5.1 {
CREATE TABLE t1(a PRIMARY KEY, b, c);
INSERT INTO t1 VALUES(1, 2, 3);
} {
INSERT INTO t1 VALUES(4, 5, 6);
DELETE FROM t1 WHERE a=1;
} {
INSERT INTO t1 VALUES(7, 8, 9);
UPDATE t1 SET c = 5;
INSERT INTO t1 VALUES(10, 11, 12);
UPDATE t1 SET c = 6;
INSERT INTO t1 VALUES(13, 14, 15);
} {
UPDATE t1 SET c=c+1;
DELETE FROM t1 WHERE (a%2);
}
do_patchset_changeset_test 5.2 {
CREATE TABLE t1(a PRIMARY KEY, b, c);
CREATE TABLE t2(a, b, c, d, PRIMARY KEY(c, b));
} {
INSERT INTO t1 VALUES(x'00', 0, 'zero');
INSERT INTO t1 VALUES(x'01', 1, 'one');
INSERT INTO t1 VALUES(x'02', 4, 'four');
INSERT INTO t1 VALUES(x'03', 9, 'nine');
INSERT INTO t1 VALUES(x'04', 16, 'sixteen');
INSERT INTO t1 VALUES(x'05', 25, 'twenty-five');
} {
UPDATE t1 SET a = b WHERE b<=4;
INSERT INTO t2 SELECT NULL, * FROM t1;
DELETE FROM t1 WHERE b=25;
} {
DELETE FROM t2;
INSERT INTO t2 SELECT NULL, * FROM t1;
DELETE FROM t1;
INSERT INTO t1 SELECT b, c, d FROM t2;
UPDATE t1 SET b = b+1;
UPDATE t1 SET b = b+1;
UPDATE t1 SET b = b+1;
}
set initsql { CREATE TABLE t1(a, b, c, PRIMARY KEY(c, b)); }
for {set i 0} {$i < 1000} {incr i} {
append insert "INSERT INTO t1 VALUES($i, $i, $i);"
append delete "DELETE FROM t1 WHERE b=$i;"
}
do_patchset_changeset_test 5.3 \
$initsql $insert $delete \
$insert $delete \
"$insert $delete" \
$delete
finish_test
+197
View File
@@ -0,0 +1,197 @@
# 2014 August 16
#
# 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.
#
#***********************************************************************
#
#
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 sessionC
#-------------------------------------------------------------------------
# Test the outcome of a DELETE operation made as part of applying a
# changeset failing with SQLITE_CONSTRAINT. This may happen if an
# ON DELETE RESTRICT foreign key action is triggered, or if a trigger
# program raises a constraint somehow.
#
# UPDATE: The above is no longer true, as "PRAGMA defer_foreign_keys"
# now disables "RESTRICT" processing. The test below has been rewritten
# to use a trigger instead of a foreign key to test this case.
#
do_execsql_test 1.0 {
PRAGMA foreign_keys = 1;
CREATE TABLE p(a PRIMARY KEY, b, c);
CREATE TABLE c(d PRIMARY KEY, e /* REFERENCES p ON DELETE RESTRICT */);
CREATE TRIGGER restrict_trig BEFORE DELETE ON p BEGIN
SELECT raise(ABORT, 'error!') FROM c WHERE e=old.a;
END;
INSERT INTO p VALUES('one', 1, 1);
INSERT INTO p VALUES('two', 2, 2);
INSERT INTO p VALUES('three', 3, 3);
INSERT INTO c VALUES(1, 'one');
INSERT INTO c VALUES(3, 'three');
}
do_test 1.1 {
execsql BEGIN
set C [changeset_from_sql {
INSERT INTO c VALUES(4, 'one');
DELETE FROM p WHERE a='two';
}]
execsql ROLLBACK
execsql {
INSERT INTO c VALUES(2, 'two');
}
} {}
do_test 1.2.1 {
proc xConflict {args} { return "ABORT" }
catch { sqlite3changeset_apply db $C xConflict } msg
set msg
} {SQLITE_ABORT}
do_execsql_test 1.2.2 { SELECT * FROM c } {1 one 3 three 2 two}
do_test 1.3.1 {
proc xConflict {args} { return "OMIT" }
catch { sqlite3changeset_apply db $C xConflict } msg
set msg
} {}
do_execsql_test 1.3.2 { SELECT * FROM c } {1 one 3 three 2 two 4 one}
do_execsql_test 1.3.3 {
SELECT * FROM p;
} {one 1 1 two 2 2 three 3 3}
#-------------------------------------------------------------------------
# Test that concatenating a changeset with a patchset does not work.
# Any attempt to do so returns SQLITE_ERROR.
#
reset_db
do_execsql_test 2.0 {
CREATE TABLE x1(t, v PRIMARY KEY);
INSERT INTO x1 VALUES(12, 55);
INSERT INTO x1 VALUES(55, 14);
}
do_test 2.1 {
execsql BEGIN
sqlite3session S1 db main
S1 attach *
execsql {
UPDATE x1 SET t=13 WHERE v=55;
INSERT INTO x1 VALUES(99, 123);
}
set patchset [S1 patchset]
S1 delete
sqlite3session S1 db main
S1 attach *
execsql {
UPDATE x1 SET t=56 WHERE v=14;
INSERT INTO x1 VALUES(22, 998);
}
set changeset [S1 changeset]
S1 delete
execsql ROLLBACK
} {}
do_test 2.2 {
set rc [catch { sqlite3changeset_concat $patchset $changeset } msg]
list $rc $msg
} {1 SQLITE_ERROR}
do_test 2.3 {
set rc [catch { sqlite3changeset_concat $changeset $patchset } msg]
list $rc $msg
} {1 SQLITE_ERROR}
do_test 2.4 {
set rc [catch { sqlite3changeset_concat {} $patchset } msg]
list $rc $msg
} [list 0 $patchset]
do_test 2.5 {
set rc [catch { sqlite3changeset_concat $patchset {} } msg]
list $rc $msg
} [list 0 $patchset]
do_test 2.6 {
set rc [catch { sqlite3changeset_concat {} $changeset } msg]
list $rc $msg
} [list 0 $changeset]
do_test 2.7 {
set rc [catch { sqlite3changeset_concat $changeset {} } msg]
list $rc $msg
} [list 0 $changeset]
do_test 2.8 {
set rc [catch { sqlite3changeset_concat {} {} } msg]
list $rc $msg
} [list 0 {}]
#-------------------------------------------------------------------------
# Test that the xFilter argument to sqlite3changeset_apply() works.
#
reset_db
do_execsql_test 3.0 {
CREATE TABLE t1(a PRIMARY KEY, b);
CREATE TABLE t2(a PRIMARY KEY, b);
CREATE TABLE t3(a PRIMARY KEY, b);
}
do_test 3.1 {
execsql BEGIN
set changeset [changeset_from_sql {
INSERT INTO t1 VALUES(1, 1);
INSERT INTO t2 VALUES(2, 2);
INSERT INTO t3 VALUES(3, 3);
}]
execsql ROLLBACK
} {}
do_test 3.2 {
proc xFilter {zName} {
if {$zName == "t1"} { return 1 }
return 0
}
sqlite3changeset_apply db $changeset noop xFilter
execsql {
SELECT * FROM t1;
SELECT * FROM t2;
SELECT * FROM t3;
}
} {1 1}
do_test 3.3 {
proc xFilter {zName} {
if {$zName == "t3"} { return 1 }
return 0
}
sqlite3changeset_apply db $changeset noop xFilter
execsql {
SELECT * FROM t1;
SELECT * FROM t2;
SELECT * FROM t3;
}
} {1 1 3 3}
finish_test
+308
View File
@@ -0,0 +1,308 @@
# 2014 August 16
#
# 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 focuses on the sqlite3session_diff() function.
#
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 sessionD
proc scksum {db dbname} {
if {$dbname=="temp"} {
set master sqlite_temp_master
} else {
set master $dbname.sqlite_master
}
set alltab [$db eval "SELECT name FROM $master WHERE type='table'"]
set txt [$db eval "SELECT * FROM $master ORDER BY type,name,sql"]
foreach tab $alltab {
set cols [list]
db eval "PRAGMA $dbname.table_info = $tab" x {
lappend cols "quote($x(name))"
}
set cols [join $cols ,]
append txt [db eval "SELECT $cols FROM $tab ORDER BY $cols"]
}
return [md5 $txt]
}
# Ensure that the diff produced by comparing the current contents of [db]
# with itself is empty.
proc do_empty_diff_test {tn} {
forcedelete test.db2
forcecopy test.db test.db2
execsql { ATTACH 'test.db2' AS aux }
sqlite3session S db main
foreach tbl [db eval {SELECT name FROM sqlite_master WHERE type='table'}] {
S attach $tbl
S diff aux $tbl
}
set ::C [S changeset]
S delete
uplevel [list do_test $tn {string length $::C} 0]
}
forcedelete test.db2
do_execsql_test 1.0 {
CREATE TABLE t2(a PRIMARY KEY, b);
INSERT INTO t2 VALUES(1, 'one');
INSERT INTO t2 VALUES(2, 'two');
ATTACH 'test.db2' AS aux;
CREATE TABLE aux.t2(a PRIMARY KEY, b);
}
do_test 1.1 {
sqlite3session S db main
S attach t2
S diff aux t2
set C [S changeset]
S delete
} {}
do_test 1.2 {
sqlite3 db2 test.db2
sqlite3changeset_apply db2 $C ""
db2 close
db eval { SELECT * FROM aux.t2 }
} {1 one 2 two}
do_diff_test 2.1 {
CREATE TABLE aux.t1(x, y, PRIMARY KEY(y));
CREATE TABLE t1(x, y, PRIMARY KEY(y));
INSERT INTO t1 VALUES(1, 2);
INSERT INTO t1 VALUES(NULL, 'xyz');
INSERT INTO t1 VALUES(4.5, 5.5);
}
do_diff_test 2.2 {
CREATE TABLE aux.t1(x, y, PRIMARY KEY(y));
CREATE TABLE t1(x, y, PRIMARY KEY(y));
INSERT INTO aux.t1 VALUES(1, 2);
INSERT INTO aux.t1 VALUES(NULL, 'xyz');
INSERT INTO aux.t1 VALUES(4.5, 5.5);
}
do_diff_test 2.3 {
CREATE TABLE aux.t1(a PRIMARY KEY, b TEXT);
CREATE TABLE t1(a PRIMARY KEY, b TEXT);
INSERT INTO aux.t1 VALUES(1, 'one');
INSERT INTO aux.t1 VALUES(2, 'two');
INSERT INTO aux.t1 VALUES(3, 'three');
INSERT INTO t1 VALUES(1, 'I');
INSERT INTO t1 VALUES(2, 'two');
INSERT INTO t1 VALUES(3, 'III');
}
do_diff_test 2.4 {
CREATE TABLE aux.t1(a, b, c, d, PRIMARY KEY(c, b, a));
CREATE TABLE t1(a, b, c, d, PRIMARY KEY(c, b, a));
INSERT INTO t1 VALUES('hvkzyipambwdqlvwv','',-458331.50,X'DA51ED5E84');
INSERT INTO t1 VALUES(X'C5C6B5DD','jjxrath',40917,830244);
INSERT INTO t1 VALUES(-204877.54,X'1704C253D5F3AFA8',155120.88,NULL);
INSERT INTO t1
VALUES('ckmqmzoeuvxisxqy',X'EB5A5D3A1DD22FD1','tidhjcbvbppdt',-642987.37);
INSERT INTO t1 VALUES(-851726,-161992,-469943,-159541);
INSERT INTO t1 VALUES(X'4A6A667F858938',185083,X'7A',NULL);
INSERT INTO aux.t1 VALUES(415075.74,'auawczkb',X'',X'57B4FAAF2595');
INSERT INTO aux.t1 VALUES(727637,711560,-181340,'hphuo');
INSERT INTO aux.t1
VALUES(-921322.81,662959,'lvlgwdgxaurr','ajjrzrbhqflsutnymgc');
INSERT INTO aux.t1 VALUES(-146061,-377892,X'4E','gepvpvvuhszpxabbb');
INSERT INTO aux.t1 VALUES(-851726,-161992,-469943,-159541);
INSERT INTO aux.t1 VALUES(X'4A6A667F858938',185083,X'7A',NULL);
INSERT INTO aux.t1 VALUES(-204877.54,X'1704C253D5F3AFA8',155120.88, 4);
INSERT INTO aux.t1
VALUES('ckmqmzoeuvxisxqy',X'EB5A5D3A1DD22FD1','tidgtsplhjcbvbppdt',-642987.3);
}
reset_db
do_execsql_test 3.0 {
CREATE TABLE t1(a, b, c, PRIMARY KEY(a));
INSERT INTO t1 VALUES(1, 2, 3);
INSERT INTO t1 VALUES(4, 5, 6);
INSERT INTO t1 VALUES(7, 8, 9);
CREATE TABLE t2(a, b, c, PRIMARY KEY(a, b));
INSERT INTO t2 VALUES(1, 2, 3);
INSERT INTO t2 VALUES(4, 5, 6);
INSERT INTO t2 VALUES(7, 8, 9);
CREATE TABLE t3(a, b, c, PRIMARY KEY(a, b, c));
INSERT INTO t3 VALUES(1, 2, 3);
INSERT INTO t3 VALUES(4, 5, 6);
INSERT INTO t3 VALUES(7, 8, 9);
}
do_empty_diff_test 3.1
#-------------------------------------------------------------------------
# Test some error cases:
#
# 1) schema mismatches between the two dbs, and
# 2) tables with no primary keys. This is not actually an error, but
# should not add any changes to the session object.
#
reset_db
forcedelete test.db2
do_execsql_test 4.0 {
ATTACH 'test.db2' AS ixua;
CREATE TABLE ixua.t1(a, b, c);
CREATE TABLE main.t1(a, b, c);
INSERT INTO main.t1 VALUES(1, 2, 3);
CREATE TABLE ixua.t2(a PRIMARY KEY, b, c);
CREATE TABLE main.t2(a PRIMARY KEY, b, x);
}
do_test 4.1.1 {
sqlite3session S db main
S attach t1
list [catch { S diff ixua t1 } msg] $msg
} {0 {}}
do_test 4.1.2 {
string length [S changeset]
} {0}
S delete
do_test 4.2.2 {
sqlite3session S db main
S attach t2
list [catch { S diff ixua t2 } msg] $msg
} {1 {SQLITE_SCHEMA - table schemas do not match}}
S delete
do_test 4.3.1 {
sqlite3session S db main
S attach t4
execsql { CREATE TABLE t4(i PRIMARY KEY, b) }
list [catch { S diff ixua t4 } msg] $msg
} {1 {SQLITE_SCHEMA - no such table: ixua.t4}}
S delete
do_catchsql_test 4.3.2 {
SELECT * FROM ixua.t4;
} {1 {no such table: ixua.t4}}
do_test 4.4.1 {
sqlite3session S db main
S attach sqlite_stat1
execsql { ANALYZE }
execsql { DROP TABLE ixua.sqlite_stat1 }
list [catch { S diff ixua sqlite_stat1 } msg] $msg
} {1 {SQLITE_SCHEMA - no such table: ixua.sqlite_stat1}}
S delete
do_catchsql_test 4.4.2 {
SELECT * FROM ixua.sqlite_stat1;
} {1 {no such table: ixua.sqlite_stat1}}
do_test 4.5.1 {
sqlite3session S db main
S attach t8
list [catch { S diff ixua t8 } msg] $msg
} {0 {}}
S delete
do_catchsql_test 4.5.2 {
SELECT * FROM ixua.i8;
} {1 {no such table: ixua.i8}}
#-------------------------------------------------------------------------
# Test that sqlite3session_diff() really does automatically attach tables,
# as documented.
#
reset_db
forcedelete test.db2
do_execsql_test 4.0 {
ATTACH 'test.db2' AS two;
CREATE TABLE two.t1(a PRIMARY KEY, b, c);
CREATE TABLE t1(a PRIMARY KEY, b, c);
INSERT INTO two.t1 VALUES(1, 2, 3), (4, 5, 6), (7, 8, 9);
}
do_test 4.1 {
sqlite3session S db main
S attach t8
S diff two t1
} {}
do_changeset_test 4.2 S {
{DELETE t1 0 X.. {i 1 i 2 i 3} {}}
{DELETE t1 0 X.. {i 4 i 5 i 6} {}}
{DELETE t1 0 X.. {i 7 i 8 i 9} {}}
}
S delete
#-------------------------------------------------------------------------
# Test that sqlite3session_diff() really does return errors if the named
# table or database do not exist.
#
reset_db
forcedelete test.db2
do_execsql_test 5.0 {
ATTACH 'test.db2' AS two;
CREATE TABLE main.t1(a INTEGER PRIMARY KEY, b);
CREATE TABLE main.t2(a INTEGER PRIMARY KEY, b);
CREATE TABLE two.t1(a, b INTEGER PRIMARY KEY);
}
proc do_sessions_diff_error {tn db tbl err} {
sqlite3session S db main
set rc [catch {S diff $db $tbl} msg]
set ::sdgot [list $rc $msg]
do_test $tn [list set sdgot] [list {*}$err]
S delete
}
# Test that it is an error if the named db is missing.
do_sessions_diff_error 5.1 nosuchdb t1 {
1 {SQLITE_SCHEMA - no such table: nosuchdb.t1}
}
# Test that it is an error if the named db is present, but named table is not.
do_sessions_diff_error 5.2 two t2 {
1 {SQLITE_SCHEMA - no such table: two.t2}
}
# Test that it is an error if the tables are present, but schemas do not match.
do_sessions_diff_error 5.3 two t1 {
1 {SQLITE_SCHEMA - table schemas do not match}
}
do_test 5.4 {
sqlite3session S db main
catch {S diff two blue}
catch {S diff two blue}
S delete
} {}
finish_test
+113
View File
@@ -0,0 +1,113 @@
# 2015 June 02
#
# 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 sessions module.
# Specifically, it tests that operations on tables without primary keys
# are ignored.
#
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 sessionE
#
# Test plan:
#
# 1.*: Test that non-PK tables are not auto-attached.
# 2.*: Test that explicitly attaching a non-PK table is a no-op.
# 3.*: Test that sqlite3session_diff() on a non-PK table is a no-op.
#
#--------------------------------------------------------------------------
reset_db
do_execsql_test 1.0 {
CREATE TABLE t1(a, b);
CREATE TABLE t2(a PRIMARY KEY, b);
}
do_test 1.1 {
sqlite3session S db main
S attach *
execsql {
INSERT INTO t1 VALUES(1, 2);
INSERT INTO t2 VALUES(1, 2);
}
} {}
do_changeset_test 1.2 S {
{INSERT t2 0 X. {} {i 1 i 2}}
}
S delete
reset_db
do_execsql_test 2.0 {
CREATE TABLE t1(a, b);
CREATE TABLE t2(a PRIMARY KEY, b);
}
do_test 2.1 {
sqlite3session S db main
S attach t1
S attach t2
execsql {
INSERT INTO t1 VALUES(3, 4);
INSERT INTO t2 VALUES(3, 4);
INSERT INTO t1 VALUES(5, 6);
INSERT INTO t2 VALUES(5, 6);
}
} {}
do_changeset_test 2.2 S {
{INSERT t2 0 X. {} {i 3 i 4}}
{INSERT t2 0 X. {} {i 5 i 6}}
}
S delete
reset_db
forcedelete test.db2
do_execsql_test 3.0 {
ATTACH 'test.db2' AS aux;
CREATE TABLE aux.t1(a, b);
CREATE TABLE aux.t2(a PRIMARY KEY, b);
CREATE TABLE t1(a, b);
CREATE TABLE t2(a PRIMARY KEY, b);
INSERT INTO t1 VALUES(1, 2);
INSERT INTO t2 VALUES(3, 4);
}
do_test 3.1 {
sqlite3session S db main
S attach t1
S diff aux t1
S attach t2
S diff aux t2
} {}
do_changeset_test 3.2 S {
{INSERT t2 0 X. {} {i 3 i 4}}
}
do_execsql_test 3.3 {
INSERT INTO t1 VALUES(5, 6);
INSERT INTO t2 VALUES(7, 8);
}
do_changeset_test 3.4 S {
{INSERT t2 0 X. {} {i 3 i 4}}
{INSERT t2 0 X. {} {i 7 i 8}}
}
S delete
finish_test
+294
View File
@@ -0,0 +1,294 @@
# 2015 June 02
#
# 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 sessions module.
# Specifically, it tests that tables appear in the correct order
# within changesets and patchsets.
#
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 sessionF
#
# Test plan:
#
# 1.*: Test that sqlite3session_changeset() and sqlite3session_patchset()
# output tables in the right order.
#
# 2.*: Test that sqlite3session_invert() does not modify the order of
# tables within a changeset.
#
# 3.*: Test that sqlite3session_concat outputs tables in the right order.
#
# Create a db schema to use.
#
do_execsql_test 1.0 {
CREATE TABLE t3(e PRIMARY KEY, f);
CREATE TABLE t1(a PRIMARY KEY, b);
CREATE TABLE t2(c PRIMARY KEY, d);
}
#-----------------------------------------------------------------------
# 1.* - changeset() and patchset().
#
foreach {tn setup result} {
1 {
S attach *
} {
{INSERT t2 0 X. {} {i 2 t two}}
{INSERT t1 0 X. {} {i 1 t one}}
{INSERT t3 0 X. {} {i 3 t three}}
}
2 {
S attach t1
S attach *
} {
{INSERT t1 0 X. {} {i 1 t one}}
{INSERT t2 0 X. {} {i 2 t two}}
{INSERT t3 0 X. {} {i 3 t three}}
}
3 {
S attach t3
S attach t2
S attach t1
} {
{INSERT t3 0 X. {} {i 3 t three}}
{INSERT t2 0 X. {} {i 2 t two}}
{INSERT t1 0 X. {} {i 1 t one}}
}
} {
execsql {
DELETE FROM t1;
DELETE FROM t2;
DELETE FROM t3;
}
sqlite3session S db main
eval $setup
do_execsql_test 1.$tn.1 {
INSERT INTO t2 VALUES(2, 'two');
INSERT INTO t1 VALUES(1, 'one');
INSERT INTO t3 VALUES(3, 'three');
}
do_changeset_test 1.1.$tn.2 S $result
do_patchset_test 1.1.$tn.3 S $result
S delete
}
foreach {tn setup result} {
1 {
S attach *
} {
{INSERT t2 0 X. {} {i 4 t four}}
{INSERT t2 0 X. {} {i 5 t five}}
{INSERT t1 0 X. {} {i 1 t one}}
{INSERT t3 0 X. {} {i 6 t six}}
}
2 {
S attach t1
S attach *
} {
{INSERT t1 0 X. {} {i 1 t one}}
{INSERT t2 0 X. {} {i 4 t four}}
{INSERT t2 0 X. {} {i 5 t five}}
{INSERT t3 0 X. {} {i 6 t six}}
}
3 {
S attach t3
S attach t2
S attach t1
} {
{INSERT t3 0 X. {} {i 6 t six}}
{INSERT t2 0 X. {} {i 4 t four}}
{INSERT t2 0 X. {} {i 5 t five}}
{INSERT t1 0 X. {} {i 1 t one}}
}
} {
execsql {
DELETE FROM t1;
DELETE FROM t2;
DELETE FROM t3;
}
sqlite3session S db main
eval $setup
do_execsql_test 1.$tn.1 {
INSERT INTO t2 VALUES(2, 'two');
INSERT INTO t1 VALUES(1, 'one');
DELETE FROM t2;
INSERT INTO t2 VALUES(4, 'four');
INSERT INTO t2 VALUES(5, 'five');
INSERT INTO t3 VALUES(6, 'six');
}
do_changeset_test 1.2.$tn.2 S $result
do_patchset_test 1.2.$tn.2 S $result
S delete
}
#-------------------------------------------------------------------------
# 2.* - invert()
#
foreach {tn setup result} {
1 {
S attach *
} {
{DELETE t2 0 X. {i 4 t four} {}}
{DELETE t2 0 X. {i 5 t five} {}}
{DELETE t1 0 X. {i 1 t one} {}}
{DELETE t3 0 X. {i 6 t six} {}}
}
2 {
S attach t1
S attach *
} {
{DELETE t1 0 X. {i 1 t one} {}}
{DELETE t2 0 X. {i 4 t four} {}}
{DELETE t2 0 X. {i 5 t five} {}}
{DELETE t3 0 X. {i 6 t six} {}}
}
3 {
S attach t3
S attach t2
S attach t1
} {
{DELETE t3 0 X. {i 6 t six} {}}
{DELETE t2 0 X. {i 4 t four} {}}
{DELETE t2 0 X. {i 5 t five} {}}
{DELETE t1 0 X. {i 1 t one} {}}
}
} {
execsql {
DELETE FROM t1;
DELETE FROM t2;
DELETE FROM t3;
}
sqlite3session S db main
eval $setup
do_execsql_test 1.$tn.1 {
INSERT INTO t2 VALUES(2, 'two');
INSERT INTO t1 VALUES(1, 'one');
DELETE FROM t2;
INSERT INTO t2 VALUES(4, 'four');
INSERT INTO t2 VALUES(5, 'five');
INSERT INTO t3 VALUES(6, 'six');
}
do_changeset_invert_test 2.$tn.2 S $result
S delete
}
#-------------------------------------------------------------------------
# 3.* - concat()
#
foreach {tn setup1 sql1 setup2 sql2 result} {
1 {
S attach *
} {
INSERT INTO t1 VALUES(1, 'one');
INSERT INTO t2 VALUES(2, 'two');
} {
S attach t2
S attach t1
} {
INSERT INTO t1 VALUES(3, 'three');
INSERT INTO t2 VALUES(4, 'four');
} {
{INSERT t1 0 X. {} {i 1 t one}}
{INSERT t1 0 X. {} {i 3 t three}}
{INSERT t2 0 X. {} {i 2 t two}}
{INSERT t2 0 X. {} {i 4 t four}}
}
1 {
S attach t2
S attach t1
} {
INSERT INTO t1 VALUES(1, 'one');
INSERT INTO t2 VALUES(2, 'two');
} {
S attach *
} {
INSERT INTO t1 VALUES(3, 'three');
INSERT INTO t2 VALUES(4, 'four');
} {
{INSERT t2 0 X. {} {i 2 t two}}
{INSERT t2 0 X. {} {i 4 t four}}
{INSERT t1 0 X. {} {i 1 t one}}
{INSERT t1 0 X. {} {i 3 t three}}
}
1 {
S attach *
} {
INSERT INTO t2 VALUES(2, 'two');
} {
S attach *
} {
INSERT INTO t1 VALUES(3, 'three');
INSERT INTO t2 VALUES(4, 'four');
INSERT INTO t3 VALUES(5, 'five');
} {
{INSERT t2 0 X. {} {i 2 t two}}
{INSERT t2 0 X. {} {i 4 t four}}
{INSERT t1 0 X. {} {i 3 t three}}
{INSERT t3 0 X. {} {i 5 t five}}
}
} {
execsql {
DELETE FROM t1;
DELETE FROM t2;
DELETE FROM t3;
}
sqlite3session S db main
eval $setup1
execsql $sql1
set c1 [S changeset]
S delete
sqlite3session S db main
eval $setup2
execsql $sql2
set c2 [S changeset]
S delete
set res [list]
sqlite3session_foreach x [sqlite3changeset_concat $c1 $c2] {
lappend res $x
}
do_test 3.$tn { set res } [list {*}$result]
}
finish_test
+250
View File
@@ -0,0 +1,250 @@
# 2016 March 30
#
# 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 sessions module.
# Specifically, it tests that UNIQUE constraints are dealt with correctly.
#
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 sessionG
forcedelete test.db2
sqlite3 db2 test.db2
do_test 1.0 {
do_common_sql {
CREATE TABLE t1(a PRIMARY KEY, b UNIQUE);
INSERT INTO t1 VALUES(1, 'one');
INSERT INTO t1 VALUES(2, 'two');
INSERT INTO t1 VALUES(3, 'three');
}
do_then_apply_sql -ignorenoop {
DELETE FROM t1 WHERE a=1;
INSERT INTO t1 VALUES(4, 'one');
}
compare_db db db2
} {}
do_test 1.1 {
do_then_apply_sql -ignorenoop {
DELETE FROM t1 WHERE a=4;
INSERT INTO t1 VALUES(1, 'one');
}
compare_db db db2
} {}
do_test 1.2 {
execsql { INSERT INTO t1 VALUES(5, 'five') } db2
do_then_apply_sql -ignorenoop {
INSERT INTO t1 VALUES(11, 'eleven');
INSERT INTO t1 VALUES(12, 'five');
}
execsql { SELECT * FROM t1 } db2
} {2 two 3 three 1 one 5 five 11 eleven}
do_test 1.3 {
execsql { SELECT * FROM t1 }
} {2 two 3 three 1 one 11 eleven 12 five}
#-------------------------------------------------------------------------
#
reset_db
db2 close
forcedelete test.db2
sqlite3 db2 test.db2
do_test 2.1 {
do_common_sql {
CREATE TABLE t1(a PRIMARY KEY, b UNIQUE, c UNIQUE);
INSERT INTO t1 VALUES(1, 1, 1);
INSERT INTO t1 VALUES(2, 2, 2);
INSERT INTO t1 VALUES(3, 3, 3);
}
} {}
do_test 2.2.1 {
# It is not possible to apply the changeset generated by the following
# SQL, as none of the three updated rows may be updated as part of the
# first pass.
do_then_apply_sql -ignorenoop {
UPDATE t1 SET b=0 WHERE a=1;
UPDATE t1 SET b=1 WHERE a=2;
UPDATE t1 SET b=2 WHERE a=3;
UPDATE t1 SET b=3 WHERE a=1;
}
db2 eval { SELECT a, b FROM t1 }
} {1 1 2 2 3 3}
do_test 2.2.2 { db eval { SELECT a, b FROM t1 } } {1 3 2 1 3 2}
#-------------------------------------------------------------------------
#
reset_db
db2 close
forcedelete test.db2
sqlite3 db2 test.db2
do_test 3.1 {
do_common_sql {
CREATE TABLE t1(a PRIMARY KEY, b UNIQUE, c UNIQUE);
INSERT INTO t1 VALUES(1, 1, 1);
INSERT INTO t1 VALUES(2, 2, 2);
INSERT INTO t1 VALUES(3, 3, 3);
}
} {}
do_test 3.3 {
do_then_apply_sql -ignorenoop {
UPDATE t1 SET b=4 WHERE a=3;
UPDATE t1 SET b=3 WHERE a=2;
UPDATE t1 SET b=2 WHERE a=1;
}
compare_db db db2
} {}
do_test 3.4 {
do_then_apply_sql -ignorenoop {
UPDATE t1 SET b=1 WHERE a=1;
UPDATE t1 SET b=2 WHERE a=2;
UPDATE t1 SET b=3 WHERE a=3;
}
compare_db db db2
} {}
#-------------------------------------------------------------------------
#
reset_db
db2 close
forcedelete test.db2
sqlite3 db2 test.db2
do_test 4.1 {
do_common_sql {
CREATE TABLE t1(a PRIMARY KEY, b UNIQUE);
INSERT INTO t1 VALUES(1, 1);
INSERT INTO t1 VALUES(2, 2);
INSERT INTO t1 VALUES(3, 3);
CREATE TABLE t2(a PRIMARY KEY, b UNIQUE);
INSERT INTO t2 VALUES(1, 1);
INSERT INTO t2 VALUES(2, 2);
INSERT INTO t2 VALUES(3, 3);
}
} {}
do_test 4.2 {
do_then_apply_sql -ignorenoop {
UPDATE t1 SET b=4 WHERE a=3;
UPDATE t1 SET b=3 WHERE a=2;
UPDATE t1 SET b=2 WHERE a=1;
UPDATE t2 SET b=0 WHERE a=1;
UPDATE t2 SET b=1 WHERE a=2;
UPDATE t2 SET b=2 WHERE a=3;
}
compare_db db db2
} {}
do_test 4.3 {
do_then_apply_sql -ignorenoop {
UPDATE t1 SET b=1 WHERE a=1;
UPDATE t1 SET b=2 WHERE a=2;
UPDATE t1 SET b=3 WHERE a=3;
UPDATE t2 SET b=3 WHERE a=3;
UPDATE t2 SET b=2 WHERE a=2;
UPDATE t2 SET b=1 WHERE a=1;
}
compare_db db db2
} {}
#-------------------------------------------------------------------------
reset_db
catch { db2 close }
forcedelete test.db2
sqlite3 db2 test.db2
do_execsql_test 5.0.1 {
CREATE TABLE t1(a PRIMARY KEY, b, c);
CREATE TABLE t2(a, b, c PRIMARY KEY);
CREATE TABLE t3(a, b PRIMARY KEY, c);
}
do_execsql_test -db db2 5.0.2 {
CREATE TABLE t1(a PRIMARY KEY, b, c);
CREATE TABLE t2(a, b, c);
CREATE TABLE t3(a, b PRIMARY KEY, c);
}
do_test 5.1 {
do_then_apply_sql -ignorenoop {
INSERT INTO t1 VALUES(1, 2, 3);
INSERT INTO t2 VALUES(4, 5, 6);
INSERT INTO t3 VALUES(7, 8, 9);
}
db2 eval {
SELECT * FROM t1;
SELECT * FROM t2;
SELECT * FROM t3;
}
} {1 2 3 7 8 9}
#-------------------------------------------------------------------------
reset_db
db func number_name number_name
do_execsql_test 6.0 {
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
CREATE UNIQUE INDEX t1b ON t1(b);
WITH s(i) AS (
SELECT 1
UNION ALL
SELECT i+1 FROM s WHERE i<1000
)
INSERT INTO t1 SELECT i, number_name(i) FROM s;
}
do_test 6.1 {
db eval BEGIN
set ::C [changeset_from_sql {
DELETE FROM t1;
WITH s(i) AS (
SELECT 1
UNION ALL
SELECT i+1 FROM s WHERE i<1000
)
INSERT INTO t1 SELECT i, number_name(i+1) FROM s;
}]
db eval ROLLBACK
execsql { SELECT count(*) FROM t1 WHERE number_name(a) IS NOT b }
} {0}
proc xConflict {args} { exit ; return "OMIT" }
do_test 6.2 {
sqlite3changeset_apply db $C xConflict
} {}
do_execsql_test 6.3 { SELECT count(*) FROM t1; } {1000}
do_execsql_test 6.4 {
SELECT count(*) FROM t1 WHERE number_name(a+1) IS NOT b;
} {0}
# db eval { SELECT * FROM t1 } { puts "$a || $b" }
finish_test
+84
View File
@@ -0,0 +1,84 @@
# 2018 January 18
#
# 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.
#
#***********************************************************************
#
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 sessionH
forcedelete test.db2
sqlite3 db2 test.db2
do_test 1.0 {
do_common_sql {
CREATE TABLE t1(a, b, c, PRIMARY KEY(a, b));
}
do_then_apply_sql -ignorenoop {
WITH s(i) AS (
VALUES(1) UNION ALL SELECT i+1 FROM s WHERe i<10000
)
INSERT INTO t1 SELECT 'abcde', randomblob(16), i FROM s;
}
compare_db db db2
} {}
#------------------------------------------------------------------------
db2 close
reset_db
do_execsql_test 2.0 {
CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
INSERT INTO main.t1 VALUES(1, 2, 3), (4, 5, 6), (7, 8, 9);
}
do_test 2.1 {
sqlite3session S db main
S attach *
db eval {
BEGIN;
INSERT INTO t1 VALUES(10, 11, 12);
DELETE FROM t1 WHERE a=1;
UPDATE t1 SET b='five', c='six' WHERE a=4;
}
set C [S changeset]
db eval ROLLBACK
S delete
set {} {}
} {}
do_execsql_test 2.2 {
CREATE TEMP TABLE t1(a INTEGER PRIMARY KEY, b, c);
INSERT INTO temp.t1 VALUES(1, 2, 3), (4, 5, 6), (7, 8, 9);
}
set ::conflict [list]
proc xConflict {args} { lappend ::conflict $args ; return "" }
do_test 2.3 {
sqlite3changeset_apply db $C xConflict
set ::conflict
} {}
do_execsql_test 2.4 {
SELECT * FROM main.t1;
SELECT '****';
SELECT * FROM temp.t1;
} {
4 five six 7 8 9 10 11 12
****
1 2 3 4 5 6 7 8 9
}
finish_test
+303
View File
@@ -0,0 +1,303 @@
proc do_changeset_test {tn session res} {
set r [list]
foreach x $res {lappend r $x}
uplevel do_test $tn [list [subst -nocommands {
set x [list]
sqlite3session_foreach c [$session changeset] { lappend x [set c] }
set x
}]] [list $r]
}
proc do_patchset_test {tn session res} {
set r [list]
foreach x $res {lappend r $x}
uplevel do_test $tn [list [subst -nocommands {
set x [list]
sqlite3session_foreach c [$session patchset] { lappend x [set c] }
set x
}]] [list $r]
}
proc do_changeset_invert_test {tn session res} {
set r [list]
foreach x $res {lappend r $x}
uplevel do_test $tn [list [subst -nocommands {
set x [list]
set changeset [sqlite3changeset_invert [$session changeset]]
sqlite3session_foreach c [set changeset] { lappend x [set c] }
set x
}]] [list $r]
}
proc do_conflict_test {tn args} {
set O(-tables) [list]
set O(-sql) [list]
set O(-conflicts) [list]
set O(-policy) "OMIT"
array set V $args
foreach key [array names V] {
if {![info exists O($key)]} {error "no such option: $key"}
}
array set O $args
proc xConflict {args} [subst -nocommands {
lappend ::xConflict [set args]
return $O(-policy)
}]
proc bgerror {args} { set ::background_error $args }
sqlite3session S db main
S object_config rowid 1
foreach t $O(-tables) { S attach $t }
execsql $O(-sql)
set ::xConflict [list]
sqlite3changeset_apply db2 [S changeset] xConflict
set conflicts [list]
foreach c $O(-conflicts) {
lappend conflicts $c
}
after 1 {set go 1}
vwait go
uplevel do_test $tn [list { set ::xConflict }] [list $conflicts]
S delete
}
proc do_common_sql {sql} {
execsql $sql db
execsql $sql db2
}
proc changeset_from_sql {sql {dbname main}} {
if {$dbname == "main"} {
return [sql_exec_changeset db $sql]
}
set rc [catch {
sqlite3session S db $dbname
S object_config rowid 1
db eval "SELECT name FROM $dbname.sqlite_master WHERE type = 'table'" {
S attach $name
}
db eval $sql
S changeset
} changeset]
catch { S delete }
if {$rc} {
error $changeset
}
return $changeset
}
proc patchset_from_sql {sql {dbname main}} {
set rc [catch {
sqlite3session S db $dbname
db eval "SELECT name FROM $dbname.sqlite_master WHERE type = 'table'" {
S attach $name
}
db eval $sql
S patchset
} patchset]
catch { S delete }
if {$rc} {
error $patchset
}
return $patchset
}
# Usage: do_then_apply_sql ?-ignorenoop? SQL ?DBNAME?
#
proc do_then_apply_sql {args} {
set bIgnoreNoop 0
set a1 [lindex $args 0]
if {[string length $a1]>1 && [string first $a1 -ignorenoop]==0} {
set bIgnoreNoop 1
set args [lrange $args 1 end]
}
if {[llength $args]!=1 && [llength $args]!=2} {
error "usage: do_then_apply_sql ?-ignorenoop? SQL ?DBNAME?"
}
set sql [lindex $args 0]
if {[llength $args]==1} {
set dbname main
} else {
set dbname [lindex $args 1]
}
set ::n_conflict 0
proc xConflict args { incr ::n_conflict ; return "OMIT" }
set rc [catch {
sqlite3session S db $dbname
S object_config rowid 1
db eval "SELECT name FROM $dbname.sqlite_master WHERE type = 'table'" {
S attach $name
}
db eval $sql
set ::changeset [S changeset]
sqlite3changeset_apply db2 $::changeset xConflict
} msg]
catch { S delete }
if {$rc} {error $msg}
if {$bIgnoreNoop} {
set nSave $::n_conflict
set ::n_conflict 0
proc xConflict args { incr ::n_conflict ; return "OMIT" }
sqlite3changeset_apply_v2 -ignorenoop db2 $::changeset xConflict
if {$::n_conflict!=$nSave} {
error "-ignorenoop problem ($::n_conflict $nSave)..."
}
}
}
proc do_iterator_test {tn tbl_list sql res} {
sqlite3session S db main
S object_config rowid 1
if {[llength $tbl_list]==0} { S attach * }
foreach t $tbl_list {S attach $t}
execsql $sql
set r [list]
foreach v $res { lappend r $v }
set x [list]
# set ::c [S changeset] ; execsql_pp { SELECT quote($::c) }
sqlite3session_foreach c [S changeset] { lappend x $c }
uplevel do_test $tn [list [list set {} $x]] [list $r]
S delete
}
# Compare the contents of all tables in [db1] and [db2]. Throw an error if
# they are not identical, or return an empty string if they are.
#
proc compare_db {db1 db2} {
set sql {SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name}
set lot1 [$db1 eval $sql]
set lot2 [$db2 eval $sql]
if {$lot1 != $lot2} {
puts $lot1
puts $lot2
error "databases contain different tables"
}
foreach tbl $lot1 {
set col1 [list]
set col2 [list]
set quoted [list]
$db1 eval "PRAGMA table_info = $tbl" { lappend col1 $name }
$db2 eval "PRAGMA table_info = $tbl" {
lappend col2 $name
lappend quoted "\"[string map {\" \"\"} $name]\""
}
if {$col1 != $col2} { error "table $tbl schema mismatch" }
set sql "SELECT * FROM $tbl ORDER BY [join $quoted ,]"
set data1 [$db1 eval $sql]
set data2 [$db2 eval $sql]
if {$data1 != $data2} {
puts "$db1: $data1"
puts "$db2: $data2"
error "table $tbl data mismatch"
}
}
return ""
}
proc changeset_to_list {c} {
set list [list]
sqlite3session_foreach elem $c { lappend list $elem }
lsort $list
}
set ones {zero one two three four five six seven eight nine
ten eleven twelve thirteen fourteen fifteen sixteen seventeen
eighteen nineteen}
set tens {{} ten twenty thirty forty fifty sixty seventy eighty ninety}
proc number_name {n} {
if {$n>=1000} {
set txt "[number_name [expr {$n/1000}]] thousand"
set n [expr {$n%1000}]
} else {
set txt {}
}
if {$n>=100} {
append txt " [lindex $::ones [expr {$n/100}]] hundred"
set n [expr {$n%100}]
}
if {$n>=20} {
append txt " [lindex $::tens [expr {$n/10}]]"
set n [expr {$n%10}]
}
if {$n>0} {
append txt " [lindex $::ones $n]"
}
set txt [string trim $txt]
if {$txt==""} {set txt zero}
return $txt
}
proc scksum {db dbname} {
if {$dbname=="temp"} {
set master sqlite_temp_master
} else {
set master $dbname.sqlite_master
}
set alltab [$db eval "SELECT name FROM $master WHERE type='table'"]
set txt [$db eval "SELECT * FROM $master ORDER BY type,name,sql"]
foreach tab $alltab {
set cols [list]
db eval "PRAGMA $dbname.table_info = $tab" x {
lappend cols "quote($x(name))"
}
set cols [join $cols ,]
append txt [db eval "SELECT $cols FROM $dbname.$tab ORDER BY $cols"]
}
return [md5 $txt]
}
proc do_diff_test {tn setup} {
reset_db
forcedelete test.db2
execsql { ATTACH 'test.db2' AS aux }
execsql $setup
sqlite3session S db main
S object_config rowid 1
foreach tbl [db eval {SELECT name FROM sqlite_master WHERE type='table'}] {
S attach $tbl
S diff aux $tbl
}
set C [S changeset]
S delete
sqlite3 db2 test.db2
sqlite3changeset_apply db2 $C ""
uplevel do_test $tn.1 [list {execsql { PRAGMA integrity_check } db2}] ok
db2 close
set cksum [scksum db main]
uplevel do_test $tn.2 [list {scksum db aux}] [list $cksum]
}
+191
View File
@@ -0,0 +1,191 @@
# 2025 Jan 28
#
# 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 session_gen
foreach {otn sct} {
1 VIRTUAL
2 STORED
} {
eval [string map [list %TYPE% $sct] {
reset_db
set testprefix $testprefix-$otn
do_execsql_test 1.0 {
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
CREATE TABLE t2(a INTEGER PRIMARY KEY, b AS (c+1) %TYPE%, c);
CREATE TABLE t3(
a,
b AS (a+10) %TYPE%,
c,
d AS (c+1) %TYPE%,
e,
PRIMARY KEY(c, e)
) WITHOUT ROWID;
CREATE TABLE t4(a AS (c*100) %TYPE%, b INTEGER PRIMARY KEY, c);
CREATE TABLE t5(x, y);
}
foreach {tn sql changeset} {
0.1 {
INSERT INTO t5 VALUES('abc', 'def');
} {
{INSERT t5 0 X.. {} {i 1 t abc t def}}
}
0.2 {
UPDATE t5 SET y='xyz' WHERE rowid=1;
} {
{UPDATE t5 0 X.. {i 1 {} {} t def} {{} {} {} {} t xyz}}
}
0.3 {
DELETE FROM t5;
} {
{DELETE t5 0 X.. {i 1 t abc t xyz} {}}
}
1.1 {
INSERT INTO t2 VALUES(1, 2);
INSERT INTO t2 VALUES(2, 123);
} {
{INSERT t2 0 X. {} {i 1 i 2}}
{INSERT t2 0 X. {} {i 2 i 123}}
}
1.2 {
UPDATE t2 SET c=456 WHERE a=1
} {
{UPDATE t2 0 X. {i 1 i 2} {{} {} i 456}}
}
1.3 {
DELETE FROM t2 WHERE a=2
} {
{DELETE t2 0 X. {i 2 i 123} {}}
}
1.4 {
UPDATE t2 SET a=15
} {
{INSERT t2 0 X. {} {i 15 i 456}}
{DELETE t2 0 X. {i 1 i 456} {}}
}
2.1 {
INSERT INTO t3 VALUES(5, 6, 7);
INSERT INTO t3 VALUES(8, 9, 10);
} {
{INSERT t3 0 .XX {} {i 8 i 9 i 10}}
{INSERT t3 0 .XX {} {i 5 i 6 i 7}}
}
2.2 {
UPDATE t3 SET a = 505 WHERE (c, e) = (6, 7);
} {
{UPDATE t3 0 .XX {i 5 i 6 i 7} {i 505 {} {} {} {}}}
}
2.3 {
DELETE FROM t3 WHERE (c, e) = (9, 10);
} {
{DELETE t3 0 .XX {i 8 i 9 i 10} {}}
}
2.4 {
UPDATE t3 SET c=1000
} {
{DELETE t3 0 .XX {i 505 i 6 i 7} {}}
{INSERT t3 0 .XX {} {i 505 i 1000 i 7}}
}
3.1 {
INSERT INTO t4 VALUES(100, 100);
} {
{INSERT t4 0 X. {} {i 100 i 100}}
}
} {
do_test 1.$tn.1 {
sqlite3session S db main
S object_config rowid 1
S attach *
execsql $sql
} {}
do_changeset_test 1.$tn.2 S $changeset
S delete
}
#-------------------------------------------------------------------------
reset_db
forcedelete test.db2
sqlite3 db2 test.db2
do_common_sql {
CREATE TABLE t0(x INTEGER PRIMARY KEY, y);
INSERT INTO t0 VALUES(1, 'one');
INSERT INTO t0 VALUES(2, 'two');
CREATE TABLE t1(a AS (c*10) %TYPE%, b INTEGER PRIMARY KEY, c);
INSERT INTO t1 VALUES(1, 5);
INSERT INTO t1 VALUES(2, 10);
INSERT INTO t1 VALUES(3, 5);
CREATE TABLE t2(
a, b, c AS (a*b) %TYPE%,
'k 1', 'k 2', PRIMARY KEY('k 1', 'k 2')
) WITHOUT ROWID;
INSERT INTO t2 VALUES('a', 'b', 1, 11);
INSERT INTO t2 VALUES('A', 'B', 2, 22);
INSERT INTO t2 VALUES('Aa', 'Bb', 3, 33);
}
foreach {tn sql} {
1.1 { INSERT INTO t0 VALUES(4, 15) }
1.2 { INSERT INTO t1 VALUES(4, 15) }
1.3 { INSERT INTO t2 VALUES(1, 2, 3, 4) }
2.1 { UPDATE t1 SET c=100 WHERE b=2 }
2.2 { UPDATE t2 SET a=11 }
3.1 { DELETE FROM t2 WHERE (t2.'k 1') = 2 }
3.2 { DELETE FROM t1 }
} {
do_test 2.$tn.1 {
# execsql { PRAGMA vdbe_listing = 1 } db2
do_then_apply_sql $sql
} {}
do_test 2.$tn.2 {
compare_db db db2
} {}
}
db2 close
}]}
finish_test
+358
View File
@@ -0,0 +1,358 @@
/*
** 2017 January 31
**
** 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 contains the source code for a standalone program used to
** test the performance of the sessions module. Compile and run:
**
** ./session_speed_test -help
**
** for details.
*/
#include "sqlite3.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <unistd.h>
/*************************************************************************
** Start of generic command line parser.
*/
#define CMDLINE_BARE 0
#define CMDLINE_INTEGER 1
#define CMDLINE_STRING 2
#define CMDLINE_BOOLEAN 3
typedef struct CmdLineOption CmdLineOption;
struct CmdLineOption {
const char *zText; /* Name of command line option */
const char *zHelp; /* Help text for option */
int eType; /* One of the CMDLINE_* values */
int iOff; /* Offset of output variable */
};
#define CMDLINE_INT32(x,y,z) {x, y, CMDLINE_INTEGER, z}
#define CMDLINE_BOOL(x,y,z) {x, y, CMDLINE_BOOLEAN, z}
#define CMDLINE_TEXT(x,y,z) {x, y, CMDLINE_STRING, z}
#define CMDLINE_NONE(x,y,z) {x, y, CMDLINE_BARE, z}
static void option_requires_argument_error(CmdLineOption *pOpt){
fprintf(stderr, "Option requires a%s argument: %s\n",
pOpt->eType==CMDLINE_INTEGER ? "n integer" :
pOpt->eType==CMDLINE_STRING ? " string" : " boolean",
pOpt->zText
);
exit(1);
}
static void ambiguous_option_error(const char *zArg){
fprintf(stderr, "Option is ambiguous: %s\n", zArg);
exit(1);
}
static void unknown_option_error(
const char *zArg,
CmdLineOption *aOpt,
const char *zHelp
){
int i;
fprintf(stderr, "Unknown option: %s\n", zArg);
fprintf(stderr, "\nOptions are:\n");
fprintf(stderr, " % -30sEcho command line options\n", "-cmdline:verbose");
for(i=0; aOpt[i].zText; i++){
int eType = aOpt[i].eType;
char *zOpt = sqlite3_mprintf("%s %s", aOpt[i].zText,
eType==CMDLINE_BARE ? "" :
eType==CMDLINE_INTEGER ? "N" :
eType==CMDLINE_BOOLEAN ? "BOOLEAN" : "TEXT"
);
fprintf(stderr, " % -30s%s\n", zOpt, aOpt[i].zHelp);
sqlite3_free(zOpt);
}
if( zHelp ){
fprintf(stderr, "\n%s\n", zHelp);
}
exit(1);
}
static int get_integer_option(CmdLineOption *pOpt, const char *zArg){
int i = 0;
int iRet = 0;
int bSign = 1;
if( zArg[0]=='-' ){
bSign = -1;
i = 1;
}
while( zArg[i] ){
if( zArg[i]<'0' || zArg[i]>'9' ) option_requires_argument_error(pOpt);
iRet = iRet*10 + (zArg[i] - '0');
i++;
}
return (iRet*bSign);
}
static int get_boolean_option(CmdLineOption *pOpt, const char *zArg){
if( 0==sqlite3_stricmp(zArg, "true") ) return 1;
if( 0==sqlite3_stricmp(zArg, "1") ) return 1;
if( 0==sqlite3_stricmp(zArg, "0") ) return 0;
if( 0==sqlite3_stricmp(zArg, "false") ) return 0;
option_requires_argument_error(pOpt);
return 0;
}
static void parse_command_line(
int argc,
char **argv,
int iStart,
CmdLineOption *aOpt,
void *pStruct,
const char *zHelp
){
char *pOut = (char*)pStruct;
int bVerbose = 0;
int iArg;
for(iArg=iStart; iArg<argc; iArg++){
const char *zArg = argv[iArg];
int nArg = strlen(zArg);
int nMatch = 0;
int iOpt;
for(iOpt=0; aOpt[iOpt].zText; iOpt++){
CmdLineOption *pOpt = &aOpt[iOpt];
if( 0==sqlite3_strnicmp(pOpt->zText, zArg, nArg) ){
if( nMatch ){
ambiguous_option_error(zArg);
}
nMatch++;
if( pOpt->eType==CMDLINE_BARE ){
*(int*)(&pOut[pOpt->iOff]) = 1;
}else{
iArg++;
if( iArg==argc ){
option_requires_argument_error(pOpt);
}
switch( pOpt->eType ){
case CMDLINE_INTEGER:
*(int*)(&pOut[pOpt->iOff]) = get_integer_option(pOpt, argv[iArg]);
break;
case CMDLINE_STRING:
*(const char**)(&pOut[pOpt->iOff]) = argv[iArg];
break;
case CMDLINE_BOOLEAN:
*(int*)(&pOut[pOpt->iOff]) = get_boolean_option(pOpt, argv[iArg]);
break;
}
}
}
}
if( nMatch==0 && 0==sqlite3_strnicmp("-cmdline:verbose", zArg, nArg) ){
bVerbose = 1;
nMatch = 1;
}
if( nMatch==0 ){
unknown_option_error(zArg, aOpt, zHelp);
}
}
if( bVerbose ){
int iOpt;
fprintf(stdout, "Options are: ");
for(iOpt=0; aOpt[iOpt].zText; iOpt++){
CmdLineOption *pOpt = &aOpt[iOpt];
if( pOpt->eType!=CMDLINE_BARE || *(int*)(&pOut[pOpt->iOff]) ){
fprintf(stdout, "%s ", pOpt->zText);
}
switch( pOpt->eType ){
case CMDLINE_INTEGER:
fprintf(stdout, "%d ", *(int*)(&pOut[pOpt->iOff]));
break;
case CMDLINE_BOOLEAN:
fprintf(stdout, "%d ", *(int*)(&pOut[pOpt->iOff]));
break;
case CMDLINE_STRING:
fprintf(stdout, "%s ", *(const char**)(&pOut[pOpt->iOff]));
break;
}
}
fprintf(stdout, "\n");
}
}
/*
** End of generic command line parser.
*************************************************************************/
static void abort_due_to_error(int rc){
fprintf(stderr, "Error: %d\n");
exit(-1);
}
static void execsql(sqlite3 *db, const char *zSql){
int rc = sqlite3_exec(db, zSql, 0, 0, 0);
if( rc!=SQLITE_OK ) abort_due_to_error(rc);
}
static int xConflict(void *pCtx, int eConflict, sqlite3_changeset_iter *p){
return SQLITE_CHANGESET_ABORT;
}
static void run_test(
sqlite3 *db,
sqlite3 *db2,
int nRow,
const char *zSql
){
sqlite3_session *pSession = 0;
sqlite3_stmt *pStmt = 0;
int rc;
int i;
int nChangeset;
void *pChangeset;
/* Attach a session object to database db */
rc = sqlite3session_create(db, "main", &pSession);
if( rc!=SQLITE_OK ) abort_due_to_error(rc);
/* Configure the session to capture changes on all tables */
rc = sqlite3session_attach(pSession, 0);
if( rc!=SQLITE_OK ) abort_due_to_error(rc);
/* Prepare the SQL statement */
rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0);
if( rc!=SQLITE_OK ) abort_due_to_error(rc);
/* Open a transaction */
execsql(db, "BEGIN");
/* Execute the SQL statement nRow times */
for(i=0; i<nRow; i++){
sqlite3_bind_int(pStmt, 1, i);
sqlite3_step(pStmt);
rc = sqlite3_reset(pStmt);
if( rc!=SQLITE_OK ) abort_due_to_error(rc);
}
sqlite3_finalize(pStmt);
/* Extract a changeset from the sessions object */
rc = sqlite3session_changeset(pSession, &nChangeset, &pChangeset);
if( rc!=SQLITE_OK ) abort_due_to_error(rc);
execsql(db, "COMMIT");
/* Apply the changeset to the second db */
rc = sqlite3changeset_apply(db2, nChangeset, pChangeset, 0, xConflict, 0);
if( rc!=SQLITE_OK ) abort_due_to_error(rc);
/* Cleanup */
sqlite3_free(pChangeset);
sqlite3session_delete(pSession);
}
int main(int argc, char **argv){
struct Options {
int nRow;
int bWithoutRowid;
int bInteger;
int bAll;
const char *zDb;
};
struct Options o = { 2500, 0, 0, 0, "session_speed_test.db" };
CmdLineOption aOpt[] = {
CMDLINE_INT32( "-rows", "number of rows in test",
offsetof(struct Options, nRow) ),
CMDLINE_BOOL("-without-rowid", "use WITHOUT ROWID tables",
offsetof(struct Options, bWithoutRowid) ),
CMDLINE_BOOL("-integer", "use integer data (instead of text/blobs)",
offsetof(struct Options, bInteger) ),
CMDLINE_NONE("-all", "Run all 4 combos of -without-rowid and -integer",
offsetof(struct Options, bAll) ),
CMDLINE_TEXT("-database", "prefix for database files to use",
offsetof(struct Options, zDb) ),
{0, 0, 0, 0}
};
const char *azCreate[] = {
"CREATE TABLE t1(a PRIMARY KEY, b, c, d)",
"CREATE TABLE t1(a PRIMARY KEY, b, c, d) WITHOUT ROWID",
};
const char *azInsert[] = {
"INSERT INTO t1 VALUES("
"printf('%.8d',?), randomblob(50), randomblob(50), randomblob(50))",
"INSERT INTO t1 VALUES(?, random(), random(), random())"
};
const char *azUpdate[] = {
"UPDATE t1 SET d = randomblob(50) WHERE a = printf('%.8d',?)",
"UPDATE t1 SET d = random() WHERE a = ?"
};
const char *azDelete[] = {
"DELETE FROM t1 WHERE a = printf('%.8d',?)",
"DELETE FROM t1 WHERE a = ?"
};
int rc;
sqlite3 *db;
sqlite3 *db2;
char *zDb2;
int bWithoutRowid;
int bInteger;
parse_command_line(argc, argv, 1, aOpt, (void*)&o,
"This program creates two new, empty, databases each containing a single\n"
"table. It then does the following:\n\n"
" 1. Inserts -rows rows into the first database\n"
" 2. Updates each row in the first db\n"
" 3. Delete each row from the first db\n\n"
"The modifications made by each step are captured in a changeset and\n"
"applied to the second database.\n"
);
zDb2 = sqlite3_mprintf("%s2", o.zDb);
for(bWithoutRowid=0; bWithoutRowid<2; bWithoutRowid++){
for(bInteger=0; bInteger<2; bInteger++){
if( o.bAll || (o.bWithoutRowid==bWithoutRowid && o.bInteger==bInteger) ){
fprintf(stdout, "Testing %s data with %s table\n",
bInteger ? "integer" : "blob/text",
bWithoutRowid ? "WITHOUT ROWID" : "rowid"
);
/* Open new database handles on two empty databases */
unlink(o.zDb);
rc = sqlite3_open(o.zDb, &db);
if( rc!=SQLITE_OK ) abort_due_to_error(rc);
unlink(zDb2);
rc = sqlite3_open(zDb2, &db2);
if( rc!=SQLITE_OK ) abort_due_to_error(rc);
/* Create the schema in both databases. */
execsql(db, azCreate[o.bWithoutRowid]);
execsql(db2, azCreate[o.bWithoutRowid]);
/* Run the three tests */
run_test(db, db2, o.nRow, azInsert[o.bInteger]);
run_test(db, db2, o.nRow, azUpdate[o.bInteger]);
run_test(db, db2, o.nRow, azDelete[o.bInteger]);
/* Close the db handles */
sqlite3_close(db);
sqlite3_close(db2);
}
}
}
return 0;
}
+239
View File
@@ -0,0 +1,239 @@
# 2023 October 02
#
# 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 that the sessions module interacts well with
# the ALTER TABLE ADD COLUMN 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 sessionalter
forcedelete test.db2
sqlite3 db2 test.db2
do_execsql_test 1.0 {
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
}
do_execsql_test -db db2 1.1 {
CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c DEFAULT 1234);
}
do_then_apply_sql {
INSERT INTO t1 VALUES(1, 'one');
INSERT INTO t1 VALUES(2, 'two');
}
do_execsql_test -db db2 1.2 {
SELECT * FROM t1
} {
1 one 1234
2 two 1234
}
do_then_apply_sql {
UPDATE t1 SET b='four' WHERE a=2;
}
do_execsql_test -db db2 1.3 {
SELECT * FROM t1
} {
1 one 1234
2 four 1234
}
do_then_apply_sql {
DELETE FROM t1 WHERE a=1;
}
do_execsql_test -db db2 1.4 {
SELECT * FROM t1
} {
2 four 1234
}
db2 close
#--------------------------------------------------------------------------
reset_db
do_execsql_test 2.0 {
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
}
do_test 2.1 {
sqlite3session S db main
S attach t1
set {} {}
} {}
do_execsql_test 2.2 {
INSERT INTO t1 VALUES(1, 2);
ALTER TABLE t1 ADD COLUMN c DEFAULT 'abcd';
INSERT INTO t1 VALUES(2, 3, 4);
}
do_changeset_test 2.3 S {
{INSERT t1 0 X.. {} {i 1 i 2 t abcd}}
{INSERT t1 0 X.. {} {i 2 i 3 i 4}}
}
do_iterator_test 2.4 {} {
DELETE FROM t1 WHERE a=2;
ALTER TABLE t1 ADD COLUMN d DEFAULT 'abcd';
ALTER TABLE t1 ADD COLUMN e DEFAULT 5;
ALTER TABLE t1 ADD COLUMN f DEFAULT 7.2;
-- INSERT INTO t1 VALUES(9, 9, 9, 9);
} {
{DELETE t1 0 X..... {i 2 i 3 i 4 t abcd i 5 f 7.2} {}}
}
#-------------------------------------------------------------------------
# Tests of the sqlite3changegroup_xxx() APIs.
#
reset_db
do_execsql_test 3.0 {
CREATE TABLE t1(x INTEGER PRIMARY KEY, y);
CREATE TABLE t2(x PRIMARY KEY, y);
CREATE TABLE t3(x, y);
CREATE TABLE t4(y PRIMARY KEY, x) WITHOUT ROWID;
INSERT INTO t1 VALUES(1, 2), (3, 4), (5, 6);
INSERT INTO t2 VALUES('one', 'two'), ('three', 'four'), ('five', 'six');
INSERT INTO t3 VALUES(1, 2), (3, 4), (5, 6);
INSERT INTO t4(x, y) VALUES(1, 2), (3, 4), (5, 6);
}
db_save_and_close
foreach {tn sql1 at sql2} {
1 {
INSERT INTO t1(x, y) VALUES(7, 8);
} {
ALTER TABLE t1 ADD COLUMN z DEFAULT 10;
} {
UPDATE t1 SET y=11 WHERE x=7;
}
2 {
UPDATE t2 SET y='two.two' WHERE x='one';
DELETE FROM t2 WHERE x='five';
INSERT INTO t2(x, y) VALUES('seven', 'eight');
} {
ALTER TABLE t2 ADD COLUMN z;
ALTER TABLE t2 ADD COLUMN zz;
} {
}
3 {
DELETE FROM t2 WHERE x='five';
} {
ALTER TABLE t2 ADD COLUMN z DEFAULT 'xyz';
} {
}
4 {
UPDATE t2 SET y='two.two' WHERE x='three';
} {
ALTER TABLE t2 ADD COLUMN z;
} {
UPDATE t2 SET z='abc' WHERE x='one';
}
5* {
UPDATE t2 SET y='two.two' WHERE x='three';
} {
ALTER TABLE t2 ADD COLUMN z DEFAULT 'defu1';
} {
}
6* {
INSERT INTO t2(x, y) VALUES('nine', 'ten');
} {
ALTER TABLE t2 ADD COLUMN z;
ALTER TABLE t2 ADD COLUMN a DEFAULT 'eelve';
ALTER TABLE t2 ADD COLUMN b DEFAULT x'1234abcd';
ALTER TABLE t2 ADD COLUMN c DEFAULT 4.2;
ALTER TABLE t2 ADD COLUMN d DEFAULT NULL;
} {
}
7 {
INSERT INTO t3(x, y) VALUES(7, 8);
UPDATE t3 SET y='fourteen' WHERE x=1;
DELETE FROM t3 WHERE x=3;
} {
ALTER TABLE t3 ADD COLUMN c;
} {
INSERT INTO t3(x, y, c) VALUES(9, 10, 11);
}
8 {
INSERT INTO t4(x, y) VALUES(7, 8);
UPDATE t4 SET y='fourteen' WHERE x=1;
DELETE FROM t4 WHERE x=3;
} {
ALTER TABLE t4 ADD COLUMN c;
} {
INSERT INTO t4(x, y, c) VALUES(9, 10, 11);
}
} {
foreach {tn2 cmd} {
1 changeset_from_sql
2 patchset_from_sql
} {
db_restore_and_reopen
set C1 [$cmd $sql1]
execsql $at
set C2 [$cmd $sql2]
sqlite3changegroup grp
grp schema db main
breakpoint
grp add $C1
grp add $C2
set T1 [grp output]
grp delete
db_restore_and_reopen
execsql $at
set T2 [$cmd "$sql1 ; $sql2"]
if {[string range $tn end end]!="*"} {
do_test 3.1.$tn.$tn2.1 { changeset_to_list $T1 } [changeset_to_list $T2]
set testname "$tn.$tn2"
} else {
set testname "[string range $tn 0 end-1].$tn2"
}
db_restore_and_reopen
proc xConflict {args} { return "REPLACE" }
sqlite3changeset_apply_v2 db $T1 xConflict
set S1 [scksum db main]
db_restore_and_reopen
sqlite3changeset_apply_v2 db $T2 xConflict
set S2 [scksum db main]
# if { $tn==7 } { puts [changeset_to_list $T1] }
do_test 3.1.$tn.2 { set S1 } $S2
}
}
finish_test
+308
View File
@@ -0,0 +1,308 @@
# 2017 February 04
#
# 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.
#
#***********************************************************************
#
# Tests for the sessions module. Specifically, that a changeset can
# be applied after ALTER TABLE ADD COLUMN has been used to add
# columns to tables.
#
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 sessionat
# If SQLITE_OMIT_ALTERTABLE is defined, omit this file.
ifcapable !altertable {
finish_test
return
}
db close
sqlite3_shutdown
test_sqlite3_log log
proc log {code msg} { lappend ::log $code $msg }
proc reset_test {} {
catch { db close }
catch { db2 close }
forcedelete test.db test.db2
sqlite3 db test.db
sqlite3 db2 test.db2
}
# Run all tests in this file twice. Once with "WITHOUT ROWID", and once
# with regular rowid tables.
#
# ?.1.*: Test that PK inconsistencies are detected if one or more of the PK
# columns are not present in the changeset.
#
# ?.2.*: Test that it is not possible to apply a changeset with N columns
# to a db with fewer than N columns.
#
# ?.3.*: Test some INSERT, UPDATE and DELETE operations that do not
# require conflict handling.
#
# ?.4.*: Test some INSERT, UPDATE and DELETE operations that do require
# conflict handling.
#
# ?.5.*: Test that attempting to concat two changesets with different
# numbers of columns for the same table is an error.
#
foreach {tn trailing} {
sessionat-ipk ""
sessionat-wor " WITHOUT ROWID "
} {
eval [string map [list %WR% $trailing] {
reset_test
#-----------------------------------------------------------------------
do_execsql_test $tn.1.0 {
CREATE TABLE t1(a, b, PRIMARY KEY(a)) %WR%;
}
do_execsql_test -db db2 $tn.1.1 {
CREATE TABLE t1(a, b, c, PRIMARY KEY(a, c)) %WR%;
}
do_test $tn.1.2 {
set ::log {}
do_then_apply_sql { INSERT INTO t1 VALUES('one', 'two') }
set ::log
} [list \
SQLITE_SCHEMA {sqlite3changeset_apply(): primary key mismatch for table t1}
]
do_execsql_test $tn.1.3 { SELECT * FROM t1 } {one two}
do_execsql_test -db db2 $tn.1.4 { SELECT * FROM t1 } {}
#-----------------------------------------------------------------------
do_execsql_test $tn.2.0 {
CREATE TABLE t2(x, y, z, PRIMARY KEY(x)) %WR%;
}
do_execsql_test -db db2 $tn.2.1 {
CREATE TABLE t2(x, y, PRIMARY KEY(x)) %WR%;
}
do_test $tn.2.2 {
db cache flush
set ::log {}
do_then_apply_sql { INSERT INTO t2 VALUES(1, 2, 3) }
set ::log
} [list SQLITE_SCHEMA \
{sqlite3changeset_apply(): table t2 has 2 columns, expected 3 or more}
]
do_execsql_test $tn.2.3 { SELECT * FROM t2 } {1 2 3}
do_execsql_test -db db2 $tn.2.4 { SELECT * FROM t2 } {}
#-----------------------------------------------------------------------
do_execsql_test $tn.3.0 {
CREATE TABLE t3(a, b, PRIMARY KEY(b)) %WR%;
}
do_execsql_test -db db2 $tn.3.1 {
CREATE TABLE t3(a, b, c DEFAULT 'D', PRIMARY KEY(b)) %WR%;
}
do_test $tn.3.2 {
do_then_apply_sql -ignorenoop {
INSERT INTO t3 VALUES(1, 2);
INSERT INTO t3 VALUES(3, 4);
INSERT INTO t3 VALUES(5, 6);
};
db2 eval {SELECT * FROM t3}
} {1 2 D 3 4 D 5 6 D}
do_test $tn.3.3 {
do_then_apply_sql -ignorenoop {
UPDATE t3 SET a=45 WHERE b=4;
DELETE FROM t3 WHERE a=5;
};
db2 eval {SELECT * FROM t3}
} {1 2 D 45 4 D}
#-----------------------------------------------------------------------
# 4.1: INSERT statements
# 4.2: DELETE statements
# 4.3: UPDATE statements
#
do_execsql_test $tn.4.1.0 {
CREATE TABLE t4(x INTEGER PRIMARY KEY, y) %WR%;
}
do_execsql_test -db db2 $tn.4.1.1 {
CREATE TABLE t4(x INTEGER PRIMARY KEY, y, z) %WR%;
INSERT INTO t4 VALUES(1, 2, 3);
INSERT INTO t4 VALUES(4, 5, 6);
}
do_conflict_test $tn.4.1.2 -tables t4 -sql {
INSERT INTO t4 VALUES(10, 20);
INSERT INTO t4 VALUES(4, 11);
} -conflicts {
{INSERT t4 CONFLICT {i 4 i 11} {i 4 i 5}}
}
do_execsql_test -db db2 $tn.4.1.3 {
SELECT * FROM t4 ORDER BY x
} {1 2 3 4 5 6 10 20 {}}
do_conflict_test $tn.4.1.4 -policy REPLACE -tables t4 -sql {
INSERT INTO t4 VALUES(1, 11);
} -conflicts {
{INSERT t4 CONFLICT {i 1 i 11} {i 1 i 2}}
}
do_execsql_test -db db2 $tn.4.1.5 {
SELECT * FROM t4 ORDER BY x
} {1 11 {} 4 5 6 10 20 {}}
do_execsql_test $tn.4.2.0 {
DELETE FROM t4;
INSERT INTO t4 VALUES(1, 'A');
INSERT INTO t4 VALUES(2, 'B');
INSERT INTO t4 VALUES(3, 'C');
INSERT INTO t4 VALUES(4, 'D');
}
do_execsql_test -db db2 $tn.4.2.1 {
DELETE FROM t4;
INSERT INTO t4 VALUES(1, 'A', 'a');
INSERT INTO t4 VALUES(3, 'C', 'c');
INSERT INTO t4 VALUES(4, 'E', 'd');
}
do_conflict_test $tn.4.2.2 -tables t4 -sql {
DELETE FROM t4 WHERE x=2;
DELETE FROM t4 WHERE x=4;
} -conflicts {
{DELETE t4 NOTFOUND {i 2 t B}}
{DELETE t4 DATA {i 4 t D} {i 4 t E}}
}
do_execsql_test $tn.4.3.0 {
CREATE TABLE t5(a, b, c PRIMARY KEY) %WR%;
INSERT INTO t5 VALUES(1,1,1), (2,2,2), (3,3,3), (4,4,4);
}
do_execsql_test -db db2 $tn.4.3.1 {
CREATE TABLE t5(a, b, c PRIMARY KEY, d CHECK(b!=10)) %WR%;
INSERT INTO t5 VALUES (2,2,2,2), (3,8,3,3), (4,4,4,4);
}
do_conflict_test $tn.4.3.2 -tables t5 -sql {
UPDATE t5 SET a=4 WHERE c=1;
UPDATE t5 SET b=9 WHERE c=3;
UPDATE t5 SET b=10 WHERE c=2;
} -conflicts {
{UPDATE t5 NOTFOUND {i 1 {} {} i 1} {i 4 {} {} {} {}}}
{UPDATE t5 DATA {{} {} i 3 i 3} {{} {} i 9 {} {}} {i 3 i 8 i 3}}
{UPDATE t5 CONSTRAINT {{} {} i 2 i 2} {{} {} i 10 {} {}}}
}
#-----------------------------------------------------------------------
do_execsql_test $tn.5.0 {
CREATE TABLE t6(a, b, c, PRIMARY KEY(a, b)) %WR%;
}
do_execsql_test -db db2 $tn.5.1 {
CREATE TABLE t6(a, b, c, d, e, PRIMARY KEY(a, b)) %WR%;
}
do_test $tn.5.2 {
set c1 [sql_exec_changeset db {
INSERT INTO t6 VALUES(1, 1, 1);
INSERT INTO t6 VALUES(2, 2, 2);
}]
set c2 [sql_exec_changeset db2 {
INSERT INTO t6 VALUES(3, 3, 3, 3, 3);
INSERT INTO t6 VALUES(4, 4, 4, 4, 4);
}]
list [catch { sqlite3changeset_concat $c1 $c2} msg] $msg
} {1 SQLITE_SCHEMA}
#-----------------------------------------------------------------------
db2 close
sqlite3 db2 test.db
do_execsql_test $tn.6.0 {
CREATE TABLE t7(a INTEGER PRIMARY KEY, b) %WR%;
INSERT INTO t7 VALUES(1, 1);
INSERT INTO t7 VALUES(2, 2);
INSERT INTO t7 VALUES(3, 3);
}
do_test $tn.6.1 {
set c1 [sql_exec_changeset db {
INSERT INTO t7 VALUES(4, 4);
DELETE FROM t7 WHERE a=1;
UPDATE t7 SET b=222 WHERE a=2;
}]
set cinv [sqlite3changeset_invert $c1]
execsql { SELECT * FROM t7 }
} {2 222 3 3 4 4}
do_execsql_test -db db2 $tn.6.2 {
ALTER TABLE t7 ADD COLUMN c DEFAULT 'ccc'
}
proc xConfict {args} { return "OMIT" }
do_test $tn.6.3 {
sqlite3changeset_apply db $cinv xConflict
execsql { SELECT * FROM t7 }
} {1 1 ccc 2 2 ccc 3 3 ccc}
#-----------------------------------------------------------------------
reset_test
do_execsql_test $tn.7.0 {
CREATE TABLE t8(a PRIMARY KEY, b, c);
}
do_execsql_test -db db2 $tn.7.1 {
CREATE TABLE t8(a PRIMARY KEY, b, c, d DEFAULT 'D', e DEFAULT 'E');
}
do_then_apply_sql -ignorenoop {
INSERT INTO t8 VALUES(1, 2, 3);
INSERT INTO t8 VALUES(4, 5, 6);
}
do_execsql_test $tn.7.2.1 {
SELECT * FROM t8
} {1 2 3 4 5 6}
do_execsql_test -db db2 $tn.7.2.2 {
SELECT * FROM t8
} {1 2 3 D E 4 5 6 D E}
do_then_apply_sql -ignorenoop {
UPDATE t8 SET c=45 WHERE a=4;
}
do_execsql_test $tn.7.3.1 {
SELECT * FROM t8
} {1 2 3 4 5 45}
do_execsql_test -db db2 $tn.7.3.2 {
SELECT * FROM t8
} {1 2 3 D E 4 5 45 D E}
#-----------------------------------------------------------------------
reset_test
do_execsql_test $tn.8.0 {
CREATE TABLE t9(a PRIMARY KEY, b, c, d, e, f, g, h);
}
do_execsql_test -db db2 $tn.8.1 {
CREATE TABLE t9(a PRIMARY KEY, b, c, d, e, f, g, h, i, j, k, l);
}
do_then_apply_sql -ignorenoop {
INSERT INTO t9 VALUES(1, 2, 3, 4, 5, 6, 7, 8);
}
do_then_apply_sql {
UPDATE t9 SET h=450 WHERE a=1
}
do_execsql_test -db db2 $tn.8.2 {
SELECT * FROM t9
} {1 2 3 4 5 6 7 450 {} {} {} {}}
do_then_apply_sql -ignorenoop {
UPDATE t9 SET h=NULL
}
do_execsql_test -db db2 $tn.8.2 {
SELECT * FROM t9
} {1 2 3 4 5 6 7 {} {} {} {} {}}
}]
}
catch { db close }
catch { db2 close }
sqlite3_shutdown
test_sqlite3_log
finish_test
+108
View File
@@ -0,0 +1,108 @@
# 2014 August 16
#
# 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 sessions SQLite extension.
# Specifically, this file contains tests for "patchset" changes.
#
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}
if {[permutation]=="session_strm" || [permutation]=="session_eec"} {
finish_test
return
}
if {$::tcl_platform(pointerSize)<8} {
finish_test
return
}
set testprefix sessionbig
forcedelete test.db2
sqlite3 db2 test.db2
do_execsql_test 1.0 {
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
}
do_execsql_test -db db2 1.1 {
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
}
do_test 1.2 {
do_then_apply_sql -ignorenoop {
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
}
} {}
do_test 1.3 {
execsql { DELETE FROM t1 }
execsql2 { DELETE FROM t1 }
} {}
do_test 1.4 {
set rc [catch {
do_then_apply_sql -ignorenoop {
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) );
}
} msg]
list $rc $msg
} {1 SQLITE_NOMEM}
finish_test
+55
View File
@@ -0,0 +1,55 @@
# 2024 November 04
#
# 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.
#
#***********************************************************************
#
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}
if {$::tcl_platform(pointerSize)<8} {
finish_test
return
}
set testprefix sessionblob
forcedelete test.db2
sqlite3 db2 test.db2
set NBLOB 2000000
do_execsql_test 1.0 {
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
INSERT INTO t1 VALUES(123, zeroblob($NBLOB));
}
do_test 1.1 {
sqlite3session S db main
S attach t1
} {}
set b2 [string repeat x 1000]
do_test 1.2 {
set ::blob [db incrblob t1 b 123]
for {set ii 0} {$ii < $NBLOB} {incr ii [string length $b2]} {
seek $::blob $ii
puts -nonewline $::blob $b2
}
close $::blob
} {}
S delete
finish_test
+117
View File
@@ -0,0 +1,117 @@
# 2011 March 07
#
# 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 sessionchange
do_execsql_test 1.0 {
CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
}
do_test 1.1 {
set C [changeset_from_sql {
INSERT INTO t1 VALUES(1, 2, 3);
INSERT INTO t1 VALUES(10, 20, 30);
}]
set res [list]
set iter [sqlite3changeset_start $C]
while {[$iter next]=="SQLITE_ROW"} {
lappend res [$iter data]
}
$iter finalize
set res
} [list \
{INSERT t1 0 X.. {} {i 10 i 20 i 30}} \
{INSERT t1 0 X.. {} {i 1 i 2 i 3}} \
]
do_test 1.2 {
sqlite3changegroup grp
set iter [sqlite3changeset_start $C]
while {[$iter next]=="SQLITE_ROW"} {
grp add_change $iter
}
$iter finalize
set res [list]
grp output
sqlite3session_foreach c [grp output] { lappend res $c }
grp delete
set res
} [list \
{INSERT t1 0 X.. {} {i 10 i 20 i 30}} \
{INSERT t1 0 X.. {} {i 1 i 2 i 3}} \
]
do_test 1.3.1 {
set iter [sqlite3changeset_start $C]
sqlite3changegroup grp
list [catch { grp add_change $iter } msg] $msg
} {1 SQLITE_ERROR}
do_test 1.3.2 {
while {[$iter next]=="SQLITE_ROW"} { }
list [catch { grp add_change $iter } msg] $msg
} {1 SQLITE_ERROR}
grp delete
$iter finalize
do_test 1.4 {
set res [list]
set iter [sqlite3changeset_start -invert $C]
while {[$iter next]=="SQLITE_ROW"} {
lappend res [$iter data]
}
$iter finalize
set res
} [list \
{DELETE t1 0 X.. {i 10 i 20 i 30} {}} \
{DELETE t1 0 X.. {i 1 i 2 i 3} {}} \
]
do_test 1.5 {
sqlite3changegroup grp
set iter [sqlite3changeset_start -invert $C]
$iter next
list [catch { grp add_change $iter } msg] $msg
} {1 SQLITE_ERROR}
$iter finalize
grp delete
do_test 1.6 {
set C [changeset_from_sql {
INSERT INTO t1 VALUES(1001, hex(randomblob(10000)), hex(randomblob(10000)));
INSERT INTO t1 VALUES(2001, hex(randomblob(10000)), hex(randomblob(10000)));
INSERT INTO t1 VALUES(3001, hex(randomblob(10000)), hex(randomblob(10000)));
}]
sqlite3changegroup grp
set iter [sqlite3changeset_start $C]
while {[$iter next]=="SQLITE_ROW"} {
grp add_change $iter
}
$iter finalize
} SQLITE_OK
grp delete
finish_test
+76
View File
@@ -0,0 +1,76 @@
# 2011 March 07
#
# 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 sessionconflict
db close
sqlite3_shutdown
test_sqlite3_log log
proc log {code msg} { puts "LOG $code $msg" }
sqlite3 db test.db
forcedelete test.db2
sqlite3 db2 test.db2
do_test 1.0 {
do_common_sql {
CREATE TABLE t1(a PRIMARY KEY, b, c UNIQUE);
INSERT INTO t1 VALUES(1, 1, 1);
INSERT INTO t1 VALUES(2, 2, 2);
INSERT INTO t1 VALUES(3, 3, 3);
}
} {}
do_execsql_test -db db2 1.1 {
INSERT INTO t1 VALUES(6, 6, 6);
}
proc xConflict {args} {
return "ABORT"
}
do_test 1.2 {
set chng [changeset_from_sql {
UPDATE t1 SET b=10, c=10 WHERE a=1;
UPDATE t1 SET b=444 WHERE a=2;
INSERT INTO t1 VALUES(4, 4, 4);
INSERT INTO t1 VALUES(5, 5, 5);
INSERT INTO t1 VALUES(6, 6, 6);
}]
execsql BEGIN db2
set res [list [catch { sqlite3changeset_apply db2 $chng xConflict } msg] $msg]
execsql ROLLBACK db2
set res
} {1 SQLITE_ABORT}
do_execsql_test -db db2 1.3 {
SELECT * FROM t1;
} {
1 1 1
2 2 2
3 3 3
6 6 6
}
db2 close
finish_test
+114
View File
@@ -0,0 +1,114 @@
# 2015-07-31
#
# 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.
#
#***********************************************************************
#
# Tests for the [sqldiff --changeset] command.
#
#
if {![info exists testdir]} {
set testdir [file join [file dirname [info script]] .. .. test]
}
source $testdir/tester.tcl
ifcapable !session {finish_test; return}
set testprefix sessiondiff
set PROG [test_find_sqldiff]
db close
proc sqlesc {id} {
set ret "'[string map {' ''} $id]'"
set ret
}
proc database_cksum {db1} {
set txt ""
sqlite3 dbtmp $db1
foreach tbl [dbtmp eval {SELECT name FROM sqlite_master WHERE type='table'}] {
set cols [list]
dbtmp eval "PRAGMA table_info = [sqlesc $tbl]" {
lappend cols "quote( $name )"
}
append txt [dbtmp eval \
"SELECT [join $cols {||'.'||}] FROM [sqlesc $tbl] ORDER BY 1"
]
}
dbtmp close
md5 $txt
}
proc readfile {filename} {
set fd [open $filename]
fconfigure $fd -translation binary
set data [read $fd]
close $fd
set data
}
proc get_changeset {db1 db2} {
exec $::PROG --changeset changeset.bin $db1 $db2
set bin [readfile changeset.bin]
return $bin
}
proc xConflict {args} {
return ""
}
proc do_changeset_test {tn sql1 sql2} {
forcedelete test.db123 test.db124
sqlite3 db test.db123
db eval $sql1
db close
sqlite3 db test.db124
db eval $sql2
set cs [get_changeset test.db124 test.db123]
sqlite3changeset_apply db $cs xConflict
db close
set database_cksum1 [database_cksum test.db123]
set database_cksum2 [database_cksum test.db124]
uplevel [list \
do_test $tn [list string compare $database_cksum1 $database_cksum2] 0
]
}
do_changeset_test 1.0 {
CREATE TABLE t1(x PRIMARY KEY);
} {
CREATE TABLE t1(x PRIMARY KEY);
}
do_changeset_test 1.1 {
CREATE TABLE t1(x PRIMARY KEY);
CREATE TABLE t2(x PRIMARY KEY, y);
INSERT INTO t2 VALUES(1, 2);
} {
CREATE TABLE t1(x PRIMARY KEY);
CREATE TABLE t2(x PRIMARY KEY, y);
INSERT INTO t2 VALUES(3, 4);
}
do_changeset_test 1.2 {
CREATE TABLE t2(a, b, c, PRIMARY KEY(b, c));
INSERT INTO t2 VALUES(1, 2, 3);
INSERT INTO t2 VALUES(4, 5, 6);
} {
CREATE TABLE t2(a, b, c, PRIMARY KEY(b, c));
INSERT INTO t2 VALUES(1, 2, 11);
INSERT INTO t2 VALUES(7, 8, 9);
}
finish_test
+590
View File
@@ -0,0 +1,590 @@
# 2011 Mar 21
#
# 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.
#
#***********************************************************************
#
# The focus of this file is testing the session module.
#
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 sessionfault
forcedelete test.db2
sqlite3 db2 test.db2
do_common_sql {
CREATE TABLE t1(a, b, c, PRIMARY KEY(a, b));
INSERT INTO t1 VALUES(1, 2, 3);
INSERT INTO t1 VALUES(4, 5, 6);
}
faultsim_save_and_close
db2 close
#-------------------------------------------------------------------------
# Test OOM error handling when collecting and applying a simple changeset.
#
# Test 1.1 attaches tables individually by name to the session object.
# Whereas test 1.2 passes NULL to sqlite3session_attach() to attach all
# tables.
#
do_faultsim_test 1.1 -faults oom-* -prep {
catch {db2 close}
catch {db close}
faultsim_restore_and_reopen
sqlite3 db2 test.db2
} -body {
do_then_apply_sql -ignorenoop {
INSERT INTO t1 VALUES('a string value', 8, 9);
UPDATE t1 SET c = 10 WHERE a = 1;
DELETE FROM t1 WHERE a = 4;
}
} -test {
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
faultsim_integrity_check
if {$testrc==0} { compare_db db db2 }
}
do_faultsim_test 1.2 -faults oom-* -prep {
catch {db2 close}
catch {db close}
faultsim_restore_and_reopen
} -body {
sqlite3session S db main
S attach *
execsql {
INSERT INTO t1 VALUES('a string value', 8, 9);
UPDATE t1 SET c = 10 WHERE a = 1;
DELETE FROM t1 WHERE a = 4;
}
set ::changeset [S changeset]
set {} {}
} -test {
catch { S delete }
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
faultsim_integrity_check
if {$testrc==0} {
proc xConflict {args} { return "OMIT" }
sqlite3 db2 test.db2
sqlite3changeset_apply db2 $::changeset xConflict
compare_db db db2
}
}
#-------------------------------------------------------------------------
# The following block of tests - 2.* - are designed to check
# the handling of faults in the sqlite3changeset_apply() function.
#
catch {db close}
catch {db2 close}
forcedelete test.db2 test.db
sqlite3 db2 test.db2
sqlite3 db test.db
do_common_sql {
CREATE TABLE t1(a, b, c, PRIMARY KEY(a, b));
INSERT INTO t1 VALUES('apple', 'orange', 'pear');
CREATE TABLE t2(x PRIMARY KEY, y);
}
db2 close
faultsim_save_and_close
foreach {tn conflict_policy sql sql2} {
1 OMIT { INSERT INTO t1 VALUES('one text', 'two text', X'00ff00') } {}
2 OMIT { DELETE FROM t1 WHERE a = 'apple' } {}
3 OMIT { UPDATE t1 SET c = 'banana' WHERE b = 'orange' } {}
4 REPLACE { INSERT INTO t2 VALUES('keyvalue', 'value 1') } {
INSERT INTO t2 VALUES('keyvalue', 'value 2');
}
} {
proc xConflict args [list return $conflict_policy]
do_faultsim_test 2.$tn -faults oom-transient -prep {
catch {db2 close}
catch {db close}
faultsim_restore_and_reopen
set ::changeset [changeset_from_sql $::sql]
sqlite3 db2 test.db2
sqlite3_db_config_lookaside db2 0 0 0
execsql $::sql2 db2
} -body {
sqlite3changeset_apply db2 $::changeset xConflict
} -test {
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
faultsim_integrity_check
if {$testrc==0} { compare_db db db2 }
}
}
#-------------------------------------------------------------------------
# This test case is designed so that a malloc() failure occurs while
# resizing the session object hash-table from 256 to 512 buckets. This
# is not an error, just a sub-optimal condition.
#
do_faultsim_test 3 -faults oom-* -prep {
catch {db2 close}
catch {db close}
faultsim_restore_and_reopen
sqlite3 db2 test.db2
sqlite3session S db main
S attach t1
execsql { BEGIN }
for {set i 0} {$i < 125} {incr i} {
execsql {INSERT INTO t1 VALUES(10+$i, 10+$i, 10+$i)}
}
} -body {
for {set i 125} {$i < 133} {incr i} {
execsql {INSERT INTO t1 VALUES(10+$i, 10+$i, 1-+$i)}
}
S changeset
set {} {}
} -test {
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
if {$testrc==0} {
sqlite3changeset_apply db2 [S changeset] xConflict
compare_db db db2
}
catch { S delete }
faultsim_integrity_check
}
catch { db close }
catch { db2 close }
forcedelete test.db2 test.db
sqlite3 db2 test.db2
sqlite3 db test.db
proc xConflict {op tbl type args} {
if { $type=="CONFLICT" || $type=="DATA" } {
return "REPLACE"
}
return "OMIT"
}
do_test 4.0 {
execsql {
PRAGMA encoding = 'utf16';
CREATE TABLE t1(a PRIMARY KEY, b);
INSERT INTO t1 VALUES(5, 32);
}
execsql {
PRAGMA encoding = 'utf16';
CREATE TABLE t1(a PRIMARY KEY, b NOT NULL);
INSERT INTO t1 VALUES(1, 2);
INSERT INTO t1 VALUES(2, 4);
INSERT INTO t1 VALUES(4, 16);
} db2
} {}
faultsim_save_and_close
db2 close
do_faultsim_test 4 -faults oom-* -prep {
catch {db2 close}
catch {db close}
faultsim_restore_and_reopen
sqlite3 db2 test.db2
sqlite3session S db main
S attach t1
execsql {
INSERT INTO t1 VALUES(1, 45);
INSERT INTO t1 VALUES(2, 55);
INSERT INTO t1 VALUES(3, 55);
UPDATE t1 SET a = 4 WHERE a = 5;
}
} -body {
sqlite3changeset_apply db2 [S changeset] xConflict
} -test {
catch { S delete }
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
if {$testrc==0} { compare_db db db2 }
}
#-------------------------------------------------------------------------
# This block of tests verifies that OOM faults in the
# sqlite3changeset_invert() function are handled correctly.
#
catch {db close}
catch {db2 close}
forcedelete test.db
sqlite3 db test.db
execsql {
CREATE TABLE t1(a, b, PRIMARY KEY(b));
CREATE TABLE t2(a PRIMARY KEY, b);
INSERT INTO t1 VALUES('string', 1);
INSERT INTO t1 VALUES(4, 2);
INSERT INTO t1 VALUES(X'FFAAFFAAFFAA', 3);
}
set changeset [changeset_from_sql {
INSERT INTO t1 VALUES('xxx', 'yyy');
DELETE FROM t1 WHERE a = 'string';
UPDATE t1 SET a = 20 WHERE b = 2;
}]
db close
do_faultsim_test 5.1 -faults oom* -body {
set ::inverse [sqlite3changeset_invert $::changeset]
set {} {}
} -test {
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
if {$testrc==0} {
set x [list]
sqlite3session_foreach c $::inverse { lappend x $c }
foreach c {
{DELETE t1 0 .X {t xxx t yyy} {}}
{INSERT t1 0 .X {} {t string i 1}}
{UPDATE t1 0 .X {i 20 i 2} {i 4 {} {}}}
} { lappend y $c }
if {$x != $y} { error "changeset no good" }
}
}
catch {db close}
catch {db2 close}
forcedelete test.db
sqlite3 db test.db
execsql {
CREATE TABLE t2(a PRIMARY KEY, b);
INSERT INTO t2 VALUES(1, 'abc');
INSERT INTO t2 VALUES(2, 'def');
}
set changeset [changeset_from_sql {
UPDATE t2 SET b = (b || b || b || b);
UPDATE t2 SET b = (b || b || b || b);
UPDATE t2 SET b = (b || b || b || b);
UPDATE t2 SET b = (b || b || b || b);
}]
db close
set abc [string repeat abc 256]
set def [string repeat def 256]
do_faultsim_test 5.2 -faults oom-tra* -body {
set ::inverse [sqlite3changeset_invert $::changeset]
set {} {}
} -test {
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
if {$testrc==0} {
set x [list]
sqlite3session_foreach c $::inverse { lappend x $c }
foreach c "
{UPDATE t2 0 X. {i 1 t $::abc} {{} {} t abc}}
{UPDATE t2 0 X. {i 2 t $::def} {{} {} t def}}
" { lappend y $c }
if {$x != $y} { error "changeset no good" }
}
}
catch {db close}
catch {db2 close}
forcedelete test.db
sqlite3 db test.db
set abc [string repeat abc 256]
set def [string repeat def 256]
execsql "
CREATE TABLE t2(a PRIMARY KEY, b);
INSERT INTO t2 VALUES(1, '$abc');
"
set changeset [changeset_from_sql "
INSERT INTO t2 VALUES(2, '$def');
DELETE FROM t2 WHERE a = 1;
"]
db close
do_faultsim_test 5.3 -faults oom-tra* -body {
set ::inverse [sqlite3changeset_invert $::changeset]
set {} {}
} -test {
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
if {$testrc==0} {
set x [list]
sqlite3session_foreach c $::inverse { lappend x $c }
foreach c "
{INSERT t2 0 X. {} {i 1 t $::abc}}
{DELETE t2 0 X. {i 2 t $::def} {}}
" { lappend y $c }
if {$x != $y} { error "changeset no good" }
}
}
#-------------------------------------------------------------------------
# Test that OOM errors in sqlite3changeset_concat() are handled correctly.
#
catch {db close}
forcedelete test.db
sqlite3 db test.db
do_execsql_test 5.prep1 {
CREATE TABLE t1(a, b, PRIMARY KEY(b));
CREATE TABLE t2(a PRIMARY KEY, b);
INSERT INTO t1 VALUES('string', 1);
INSERT INTO t1 VALUES(4, 2);
INSERT INTO t1 VALUES(X'FFAAFFAAFFAA', 3);
}
do_test 6.prep2 {
sqlite3session M db main
M attach *
set ::c2 [changeset_from_sql {
INSERT INTO t2 VALUES(randomblob(1000), randomblob(1000));
INSERT INTO t2 VALUES('one', 'two');
INSERT INTO t2 VALUES(1, NULL);
UPDATE t1 SET a = 5 WHERE a = 2;
}]
set ::c1 [changeset_from_sql {
DELETE FROM t2 WHERE a = 1;
UPDATE t1 SET a = 4 WHERE a = 2;
INSERT INTO t2 VALUES('x', 'y');
}]
set ::total [changeset_to_list [M changeset]]
M delete
} {}
do_faultsim_test 6 -faults oom-* -body {
set ::result [sqlite3changeset_concat $::c1 $::c2]
set {} {}
} -test {
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
if {$testrc==0} {
set v [changeset_to_list $::result]
if {$v != $::total} { error "result no good" }
}
}
faultsim_delete_and_reopen
do_execsql_test 7.prep1 {
CREATE TABLE t1(a, b, PRIMARY KEY(a));
}
faultsim_save_and_close
unset -nocomplain res
set res [list]
for {set ::i 0} {$::i < 480} {incr ::i 4} {
lappend res "INSERT t1 0 X. {} {i $::i i $::i}"
}
set res [lsort $res]
do_faultsim_test 7 -faults oom-transient -prep {
catch { S delete }
faultsim_restore_and_reopen
sqlite3session S db main
S attach *
} -body {
for {set ::i 0} {$::i < 480} {incr ::i 4} {
execsql {INSERT INTO t1 VALUES($::i, $::i)}
}
} -test {
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
if {$testrc==0} {
set cres [list [catch {changeset_to_list [S changeset]} msg] $msg]
S delete
if {$cres != "1 SQLITE_NOMEM" && $cres != "0 {$::res}"} {
error "Expected {0 $::res} Got {$cres}"
}
} else {
catch { S changeset }
catch { S delete }
}
}
faultsim_delete_and_reopen
do_test 8.prep {
sqlite3session S db main
S attach *
execsql {
CREATE TABLE t1(a, b, PRIMARY KEY(a));
INSERT INTO t1 VALUES(1, 2);
INSERT INTO t1 VALUES(3, 4);
INSERT INTO t1 VALUES(5, 6);
}
set ::changeset [S changeset]
S delete
} {}
set expected [normalize_list {
{INSERT t1 0 X. {} {i 1 i 2}}
{INSERT t1 0 X. {} {i 3 i 4}}
{INSERT t1 0 X. {} {i 5 i 6}}
}]
do_faultsim_test 8.1 -faults oom* -body {
set ::res [list]
sqlite3session_foreach -next v $::changeset { lappend ::res $v }
normalize_list $::res
} -test {
faultsim_test_result [list 0 $::expected] {1 SQLITE_NOMEM}
}
do_faultsim_test 8.2 -faults oom* -body {
set ::res [list]
sqlite3session_foreach v $::changeset { lappend ::res $v }
normalize_list $::res
} -test {
faultsim_test_result [list 0 $::expected] {1 SQLITE_NOMEM}
}
faultsim_delete_and_reopen
do_test 9.1.prep {
execsql {
PRAGMA encoding = 'utf16';
CREATE TABLE t1(a PRIMARY KEY, b);
}
} {}
faultsim_save_and_close
set answers [list {0 {}} {1 SQLITE_NOMEM} \
{1 {callback requested query abort}} \
{1 {abort due to ROLLBACK}}]
do_faultsim_test 9.1 -faults oom-transient -prep {
catch { unset ::c }
faultsim_restore_and_reopen
sqlite3session S db main
S attach *
} -body {
execsql {
INSERT INTO t1 VALUES('abcdefghijklmnopqrstuv', 'ABCDEFGHIJKLMNOPQRSTUV');
}
set ::c [S changeset]
set {} {}
} -test {
S delete
eval faultsim_test_result $::answers
if {[info exists ::c]} {
set expected [normalize_list {
{INSERT t1 0 X. {} {t abcdefghijklmnopqrstuv t ABCDEFGHIJKLMNOPQRSTUV}}
}]
if { [changeset_to_list $::c] != $expected } {
error "changeset mismatch"
}
}
}
faultsim_delete_and_reopen
do_test 9.2.prep {
execsql {
PRAGMA encoding = 'utf16';
CREATE TABLE t1(a PRIMARY KEY, b);
INSERT INTO t1 VALUES('abcdefghij', 'ABCDEFGHIJKLMNOPQRSTUV');
}
} {}
faultsim_save_and_close
set answers [list {0 {}} {1 SQLITE_NOMEM} \
{1 {callback requested query abort}} \
{1 {abort due to ROLLBACK}}]
do_faultsim_test 9.2 -faults oom-transient -prep {
catch { unset ::c }
faultsim_restore_and_reopen
sqlite3session S db main
S attach *
} -body {
execsql {
UPDATE t1 SET b = 'xyz';
}
set ::c [S changeset]
set {} {}
} -test {
S delete
eval faultsim_test_result $::answers
if {[info exists ::c]} {
set expected [normalize_list {
{UPDATE t1 0 X. {t abcdefghij t ABCDEFGHIJKLMNOPQRSTUV} {{} {} t xyz}}
}]
if { [changeset_to_list $::c] != $expected } {
error "changeset mismatch"
}
}
}
#-------------------------------------------------------------------------
# Test that if a conflict-handler encounters an OOM in
# sqlite3_value_text() but goes on to return SQLITE_CHANGESET_REPLACE
# anyway, the OOM is picked up by the sessions module.
set bigstr [string repeat abcdefghij 100]
faultsim_delete_and_reopen
do_test 10.prep.1 {
execsql {
CREATE TABLE t1(a PRIMARY KEY, b);
INSERT INTO t1 VALUES($bigstr, $bigstr);
}
sqlite3session S db main
S attach *
execsql { UPDATE t1 SET b = b||'x' }
set C [S changeset]
S delete
execsql { UPDATE t1 SET b = b||'xyz' }
} {}
faultsim_save_and_close
faultsim_restore_and_reopen
do_test 10.prep.2 {
proc xConflict {args} { return "ABORT" }
list [catch { sqlite3changeset_apply db $C xConflict } msg] $msg
} {1 SQLITE_ABORT}
do_execsql_test 10.prep.3 { SELECT b=$bigstr||'x' FROM t1 } 0
do_test 10.prep.4 {
proc xConflict {args} { return "REPLACE" }
list [catch { sqlite3changeset_apply db $C xConflict } msg] $msg
} {0 {}}
do_execsql_test 10.prep.5 { SELECT b=$bigstr||'x' FROM t1 } 1
db close
do_faultsim_test 10 -faults oom-tra* -prep {
faultsim_restore_and_reopen
} -body {
sqlite3changeset_apply_replace_all db $::C
} -test {
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
if {$testrc==0} {
if {[db one {SELECT b=$bigstr||'x' FROM t1}]==0} {
error "data does not look right"
}
}
}
#-------------------------------------------------------------------------
# Test an OOM with an sqlite3changeset_apply() filter callback.
#
reset_db
do_test 11.prep {
execsql {
CREATE TABLE t1(a PRIMARY KEY, b);
CREATE TABLE t2(x PRIMARY KEY, y);
BEGIN;
}
set ::cs [changeset_from_sql {
INSERT INTO t1 VALUES(1, 2);
INSERT INTO t2 VALUES('x', 'y');
}]
execsql ROLLBACK
set {} {}
} {}
proc filter {x} { return [string equal t1 $x] }
faultsim_save_and_close
do_faultsim_test 11 -faults oom-tra* -prep {
faultsim_restore_and_reopen
} -body {
sqlite3changeset_apply db $::cs {} filter
} -test {
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
if {$testrc==0} {
if {[db eval {SELECT * FROM t1 UNION ALL SELECT * FROM t2}] != "1 2"} {
error "data does not look right"
}
}
}
finish_test
+284
View File
@@ -0,0 +1,284 @@
# 2016 March 31
#
# 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.
#
#***********************************************************************
#
# The focus of this file is testing the session module.
#
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 sessionfault2
if 1 {
do_execsql_test 1.0.0 {
CREATE TABLE t1(a PRIMARY KEY, b UNIQUE);
INSERT INTO t1 VALUES(1, 1);
INSERT INTO t1 VALUES(2, 2);
INSERT INTO t1 VALUES(3, 3);
CREATE TABLE t2(a PRIMARY KEY, b UNIQUE);
INSERT INTO t2 VALUES(1, 1);
INSERT INTO t2 VALUES(2, 2);
INSERT INTO t2 VALUES(3, 3);
}
faultsim_save_and_close
faultsim_restore_and_reopen
do_test 1.0.1 {
set ::C [changeset_from_sql {
UPDATE t1 SET b=4 WHERE a=3;
UPDATE t1 SET b=3 WHERE a=2;
UPDATE t1 SET b=2 WHERE a=1;
UPDATE t2 SET b=0 WHERE a=1;
UPDATE t2 SET b=1 WHERE a=2;
UPDATE t2 SET b=2 WHERE a=3;
}]
set {} {}
} {}
proc xConflict args { return "OMIT" }
do_faultsim_test 1 -faults oom-p* -prep {
faultsim_restore_and_reopen
} -body {
sqlite3changeset_apply db $::C xConflict
} -test {
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
faultsim_integrity_check
catch { db eval ROLLBACK }
set res [db eval {
SELECT * FROM t1;
SELECT * FROM t2;
}]
if {$testrc==0} {
if {$res != "1 2 2 3 3 4 1 0 2 1 3 2"} { error "data error" }
} else {
if {
$res != "1 2 2 3 3 4 1 0 2 1 3 2"
&& $res != "1 1 2 2 3 3 1 1 2 2 3 3"
} { error "data error!! $res" }
}
}
#-------------------------------------------------------------------------
# OOM when applying a changeset for which one of the tables has a name
# 99 bytes in size. This happens to cause an extra malloc in within the
# sessions_strm permutation.
#
reset_db
set nm [string repeat t 99]
do_execsql_test 2.0.0 [string map "%TBL% $nm" {
CREATE TABLE %TBL%(a PRIMARY KEY, b UNIQUE);
}]
faultsim_save_and_close
faultsim_restore_and_reopen
do_test 1.0.1 {
set ::C [changeset_from_sql [string map "%TBL% $nm" {
INSERT INTO %TBL% VALUES(1, 2);
INSERT INTO %TBL% VALUES(3, 4);
}]]
set {} {}
} {}
proc xConflict args { return "OMIT" }
do_faultsim_test 2 -faults oom-p* -prep {
faultsim_restore_and_reopen
} -body {
sqlite3changeset_apply db $::C xConflict
} -test {
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
faultsim_integrity_check
}
#-------------------------------------------------------------------------
# OOM when collecting and apply a changeset that uses sqlite_stat1.
#
reset_db
forcedelete test.db2
sqlite3 db2 test.db2
do_common_sql {
CREATE TABLE t1(a PRIMARY KEY, b UNIQUE, c);
CREATE INDEX i1 ON t1(c);
INSERT INTO t1 VALUES(1, 2, 3);
INSERT INTO t1 VALUES(4, 5, 6);
INSERT INTO t1 VALUES(7, 8, 9);
CREATE TABLE t2(a, b, c);
INSERT INTO t2 VALUES(1, 2, 3);
INSERT INTO t2 VALUES(4, 5, 6);
INSERT INTO t2 VALUES(7, 8, 9);
ANALYZE;
}
faultsim_save_and_close
db2 close
do_faultsim_test 1.1 -faults oom-* -prep {
catch {db2 close}
catch {db close}
faultsim_restore_and_reopen
sqlite3 db2 test.db2
} -body {
do_then_apply_sql -ignorenoop {
INSERT INTO sqlite_stat1 VALUES('x', 'y', 45);
UPDATE sqlite_stat1 SET stat = 123 WHERE tbl='t1' AND idx='i1';
UPDATE sqlite_stat1 SET stat = 456 WHERE tbl='t2';
}
} -test {
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
faultsim_integrity_check
if {$testrc==0} { compare_db db db2 }
}
#-------------------------------------------------------------------------
# OOM when collecting and using a rebase changeset.
#
reset_db
do_execsql_test 2.0 {
CREATE TABLE t3(a, b, c, PRIMARY KEY(b, c));
CREATE TABLE t4(x PRIMARY KEY, y, z);
INSERT INTO t3 VALUES(1, 2, 3);
INSERT INTO t3 VALUES(4, 2, 5);
INSERT INTO t3 VALUES(7, 2, 9);
INSERT INTO t4 VALUES('a', 'b', 'c');
INSERT INTO t4 VALUES('d', 'e', 'f');
INSERT INTO t4 VALUES('g', 'h', 'i');
}
faultsim_save_and_close
db2 close
proc xConflict {ret args} { return $ret }
do_test 2.1 {
faultsim_restore_and_reopen
set C1 [changeset_from_sql {
INSERT INTO t3 VALUES(10, 11, 12);
UPDATE t4 SET y='j' WHERE x='g';
DELETE FROM t4 WHERE x='a';
}]
faultsim_restore_and_reopen
set C2 [changeset_from_sql {
INSERT INTO t3 VALUES(1000, 11, 12);
DELETE FROM t4 WHERE x='g';
}]
faultsim_restore_and_reopen
sqlite3changeset_apply db $C1 [list xConflict OMIT]
faultsim_save_and_close
} {}
do_faultsim_test 2.2 -faults oom* -prep {
catch {db2 close}
catch {db close}
faultsim_restore_and_reopen
sqlite3 db2 test.db2
} -body {
set rebase [sqlite3changeset_apply_v2 db $::C2 [list xConflict OMIT]]
set {} {}
} -test {
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
}
do_faultsim_test 2.3 -faults oom* -prep {
catch {db2 close}
catch {db close}
faultsim_restore_and_reopen
sqlite3 db2 test.db2
} -body {
set rebase [sqlite3changeset_apply_v2 db $::C2 [list xConflict REPLACE]]
set {} {}
} -test {
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
}
do_faultsim_test 2.4 -faults oom* -prep {
catch {db2 close}
catch {db close}
faultsim_restore_and_reopen
set ::rebase [sqlite3changeset_apply_v2 db $::C2 [list xConflict REPLACE]]
} -body {
sqlite3rebaser_create R
R configure $::rebase
R rebase $::C1
set {} {}
} -test {
catch { R delete }
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
}
do_faultsim_test 2.5 -faults oom* -prep {
catch {db2 close}
catch {db close}
faultsim_restore_and_reopen
set ::rebase [sqlite3changeset_apply_v2 db $::C2 [list xConflict OMIT]]
} -body {
sqlite3rebaser_create R
R configure $::rebase
R rebase $::C1
set {} {}
} -test {
catch { R delete }
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
}
}
reset_db
do_execsql_test 3.0 {
CREATE TABLE t1(x PRIMARY KEY, y, z);
INSERT INTO t1 VALUES(3, 1, 4);
INSERT INTO t1 VALUES(1, 5, 9);
}
faultsim_save_and_close
proc xConflict {ret args} { return $ret }
do_test 3.1 {
faultsim_restore_and_reopen
execsql { BEGIN; UPDATE t1 SET z=11; }
set C1 [changeset_from_sql {
UPDATE t1 SET z=10 WHERE x=1;
}]
execsql { ROLLBACK }
execsql { BEGIN; UPDATE t1 SET z=11; }
set C2 [changeset_from_sql {
UPDATE t1 SET z=55 WHERE x=1;
}]
execsql { ROLLBACK }
set ::rebase1 [sqlite3changeset_apply_v2 db $::C1 [list xConflict OMIT]]
set ::rebase2 [sqlite3changeset_apply_v2 db $::C2 [list xConflict OMIT]]
set {} {}
execsql { SELECT * FROM t1 }
} {3 1 4 1 5 9}
do_faultsim_test 3.2 -faults oom* -prep {
faultsim_restore_and_reopen
} -body {
sqlite3rebaser_create R
R configure $::rebase1
R configure $::rebase2
set {} {}
} -test {
catch { R delete }
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
}
finish_test
+83
View File
@@ -0,0 +1,83 @@
# 2016 October 6
#
# 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.
#
#***********************************************************************
#
# The focus of this file is testing the session module.
#
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 sessionfault3
do_execsql_test 1.0 {
CREATE TABLE t1(a, b, PRIMARY KEY(a));
INSERT INTO t1 VALUES(1, 2);
INSERT INTO t1 VALUES(3, 4);
INSERT INTO t1 VALUES('five', 'six');
}
set C1 [changeset_from_sql {
INSERT INTO t1 VALUES('seven', 'eight');
UPDATE t1 SET b=6 WHERE a='five';
DELETE FROM t1 WHERE a=1;
}]
do_execsql_test 1.1 {
ALTER TABLE t1 ADD COLUMN d DEFAULT 123;
ALTER TABLE t1 ADD COLUMN e DEFAULT 'string';
}
set C2 [changeset_from_sql {
UPDATE t1 SET e='new value' WHERE a='seven';
INSERT INTO t1 VALUES(0, 0, 0, 0);
}]
do_faultsim_test 1 -faults oom* -prep {
sqlite3changegroup G
} -body {
G schema db main
G add $::C1
G add $::C2
G output
set {} {}
} -test {
catch { G delete }
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 2.0 {
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
INSERT INTO t1 VALUES(1, 'one');
INSERT INTO t1 VALUES(2, 'two');
ALTER TABLE t1 ADD COLUMN c DEFAULT 'abcdefghijklmnopqrstuvwxyz';
}
faultsim_save_and_close
do_faultsim_test 2 -faults oom-t* -prep {
faultsim_restore_and_reopen
db eval {SELECT * FROM sqlite_schema}
} -body {
sqlite3session S db main
S attach *
execsql {
DELETE FROM t1 WHERE a = 1;
}
} -test {
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
catch { S delete }
}
finish_test
+185
View File
@@ -0,0 +1,185 @@
# 2018 October 18
#
# 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 sessioninvert
proc iter_invert {C} {
set x [list]
sqlite3session_foreach -invert c $C { lappend x $c }
set x
}
proc do_invert_test {tn sql {iter {}}} {
forcecopy test.db test.db2
sqlite3 db2 test.db2
set C [changeset_from_sql $sql]
forcecopy test.db test.db3
sqlite3 db3 test.db3
uplevel [list do_test $tn.1 [list compare_db db db3] {}]
set I [sqlite3changeset_invert $C]
sqlite3changeset_apply db $I {}
uplevel [list do_test $tn.2 [list compare_db db db2] {}]
sqlite3changeset_apply_v2 -invert db3 $C {}
uplevel [list do_test $tn.3 [list compare_db db db3] {}]
if {$iter!=""} {
uplevel [list do_test $tn.4 [list iter_invert $C] [list {*}$iter]]
}
catch { db2 close }
catch { db3 close }
}
do_execsql_test 1.0 {
CREATE TABLE t1(a PRIMARY KEY, b, c);
CREATE TABLE t2(d, e, f, PRIMARY KEY(e, f));
INSERT INTO t1 VALUES(1, 'one', 'i');
INSERT INTO t1 VALUES(2, 'two', 'ii');
INSERT INTO t1 VALUES(3, 'three', 'iii');
INSERT INTO t1 VALUES(4, 'four', 'iv');
INSERT INTO t1 VALUES(5, 'five', 'v');
INSERT INTO t1 VALUES(6, 'six', 'vi');
INSERT INTO t2 SELECT * FROM t1;
}
do_invert_test 1.1 {
INSERT INTO t1 VALUES(7, 'seven', 'vii');
} {
{DELETE t1 0 X.. {i 7 t seven t vii} {}}
}
do_invert_test 1.2 {
DELETE FROM t1 WHERE a<4;
} {
{INSERT t1 0 X.. {} {i 1 t one t i}}
{INSERT t1 0 X.. {} {i 2 t two t ii}}
{INSERT t1 0 X.. {} {i 3 t three t iii}}
}
do_invert_test 1.3 {
UPDATE t1 SET c=5;
} {
{UPDATE t1 0 X.. {i 1 {} {} i 5} {{} {} {} {} t i}}
{UPDATE t1 0 X.. {i 2 {} {} i 5} {{} {} {} {} t ii}}
{UPDATE t1 0 X.. {i 3 {} {} i 5} {{} {} {} {} t iii}}
{UPDATE t1 0 X.. {i 4 {} {} i 5} {{} {} {} {} t iv}}
{UPDATE t1 0 X.. {i 5 {} {} i 5} {{} {} {} {} t v}}
{UPDATE t1 0 X.. {i 6 {} {} i 5} {{} {} {} {} t vi}}
}
do_invert_test 1.4 {
UPDATE t1 SET b = a+1 WHERE a%2;
DELETE FROM t2;
INSERT INTO t1 VALUES(10, 'ten', NULL);
}
do_invert_test 1.5 {
UPDATE t2 SET d = d-1;
} {
{UPDATE t2 0 .XX {i 2 t three t iii} {i 3 {} {} {} {}}}
{UPDATE t2 0 .XX {i 1 t two t ii} {i 2 {} {} {} {}}}
{UPDATE t2 0 .XX {i 5 t six t vi} {i 6 {} {} {} {}}}
{UPDATE t2 0 .XX {i 3 t four t iv} {i 4 {} {} {} {}}}
{UPDATE t2 0 .XX {i 0 t one t i} {i 1 {} {} {} {}}}
{UPDATE t2 0 .XX {i 4 t five t v} {i 5 {} {} {} {}}}
}
do_execsql_test 2.0 {
ANALYZE;
PRAGMA writable_schema = 1;
DROP TABLE IF EXISTS sqlite_stat4;
SELECT * FROM sqlite_stat1;
} {
t2 sqlite_autoindex_t2_1 {6 1 1}
t1 sqlite_autoindex_t1_1 {6 1}
}
do_invert_test 2.1 {
INSERT INTO sqlite_stat1 VALUES('t3', 'idx2', '1 2 3');
} {
{DELETE sqlite_stat1 0 XX. {t t3 t idx2 t {1 2 3}} {}}
}
do_invert_test 2.2 {
DELETE FROM sqlite_stat1;
} {
{INSERT sqlite_stat1 0 XX. {} {t t1 t sqlite_autoindex_t1_1 t {6 1}}}
{INSERT sqlite_stat1 0 XX. {} {t t2 t sqlite_autoindex_t2_1 t {6 1 1}}}
}
do_invert_test 2.3 {
UPDATE sqlite_stat1 SET stat = 'hello world';
}
do_test 3.0 {
forcecopy test.db test.db2
sqlite3 db2 test.db2
set P [patchset_from_sql {
INSERT INTO t2 VALUES(1, 2, 3);
DELETE FROM t2 WHERE d = 3;
}]
list [catch { sqlite3changeset_apply_v2 -invert db2 $P {} } msg] $msg
} {1 SQLITE_CORRUPT}
do_test 3.1 {
list [catch { sqlite3session_foreach -invert db2 $P {} } msg] $msg
} {1 SQLITE_CORRUPT}
do_test 3.2 {
sqlite3changeset_apply_v2 db2 $P {}
compare_db db db2
} {}
db2 close
#-------------------------------------------------------------------------
#
reset_db
do_execsql_test 4.0 {
CREATE TABLE t1(a INTEGER PRIMARY KEY, b UNIQUE);
INSERT INTO t1 VALUES(1, 'one');
INSERT INTO t1 VALUES(2, 'two');
INSERT INTO t1 VALUES(3, 'three');
INSERT INTO t1 VALUES(4, 'four');
}
do_invert_test 4.1 {
DELETE FROM t1;
INSERT INTO t1 VALUES(1, 'two');
INSERT INTO t1 VALUES(2, 'five');
INSERT INTO t1 VALUES(3, 'one');
INSERT INTO t1 VALUES(4, 'three');
} {
{UPDATE t1 0 X. {i 1 t two} {{} {} t one}}
{UPDATE t1 0 X. {i 2 t five} {{} {} t two}}
{UPDATE t1 0 X. {i 3 t one} {{} {} t three}}
{UPDATE t1 0 X. {i 4 t three} {{} {} t four}}
}
finish_test
+57
View File
@@ -0,0 +1,57 @@
# 2020 December 23
#
# 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 SQLite sessions module
# Specifically, for the sqlite3session_memory_used() API.
#
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 sessionmem
do_execsql_test 1.0 {
CREATE TABLE t1(i INTEGER PRIMARY KEY, x, y);
CREATE TABLE t2(i INTEGER, x, y, PRIMARY KEY(x, y));
}
do_test 1.1 {
sqlite3session S db main
S attach *
} {}
foreach {tn sql eRes} {
1 { INSERT INTO t1 VALUES(1, 2, 3) } 1
2 { UPDATE t1 SET x=5 } 0
3 { UPDATE t1 SET i=5 } 1
4 { DELETE FROM t1 } 0
5 { INSERT INTO t1 VALUES(1, 2, 3) } 0
6 { INSERT INTO t1 VALUES(5, 2, 3) } 0
7 { INSERT INTO t2 VALUES('a', 'b', 'c') } 1
8 { INSERT INTO t2 VALUES('d', 'e', 'f') } 1
9 { UPDATE t2 SET i='e' } 0
} {
set mem1 [S memory_used]
do_test 1.2.$tn.(mu=$mem1) {
execsql $sql
set mem2 [S memory_used]
expr {$mem2 > $mem1}
} $eRes
}
do_test 1.3 {
S delete
} {}
finish_test
+232
View File
@@ -0,0 +1,232 @@
# 2023 October 20
#
# 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 sessionnoact
do_execsql_test 1.0 {
CREATE TABLE p1(a INTEGER PRIMARY KEY, b, c UNIQUE);
INSERT INTO p1 VALUES(1, 1, 'one');
INSERT INTO p1 VALUES(2, 2, 'two');
INSERT INTO p1 VALUES(3, 3, 'three');
INSERT INTO p1 VALUES(4, 4, 'four');
}
db_save
set C [changeset_from_sql {
DELETE FROM p1 WHERE a=2;
UPDATE p1 SET c='six' WHERE a=3;
INSERT INTO p1 VALUES(5, 5, 'two');
INSERT INTO p1 VALUES(6, 6, 'three');
}]
db_restore_and_reopen
do_execsql_test 1.1 {
CREATE TABLE c1(x INTEGER PRIMARY KEY, y,
FOREIGN KEY(y) REFERENCES p1(c) ON DELETE CASCADE ON UPDATE SET NULL
);
INSERT INTO c1 VALUES(10, 'one');
INSERT INTO c1 VALUES(20, 'two');
INSERT INTO c1 VALUES(30, 'three');
INSERT INTO c1 VALUES(40, 'four');
}
db_save
do_execsql_test 1.2 {
PRAGMA foreign_keys = 1;
}
set ::nConflict 0
proc conflict {args} {
incr ::nConflict
return "ABORT"
}
sqlite3changeset_apply_v2 db $C conflict
do_execsql_test 1.3 {
SELECT * FROM c1
} {
10 one
30 {}
40 four
}
db_restore_and_reopen
do_execsql_test 1.4 {
PRAGMA foreign_keys = 1;
}
do_execsql_test 1.5 {
UPDATE p1 SET c=12345 WHERE a = 45;
}
sqlite3changeset_apply_v2 -noaction db $C conflict
do_execsql_test 1.6 {
SELECT * FROM c1
} {
10 one
20 two
30 three
40 four
}
do_execsql_test 1.7 {
PRAGMA foreign_keys = 1;
UPDATE p1 SET c = 'ten' WHERE c='two';
SELECT * FROM c1;
} {
10 one
20 {}
30 three
40 four
}
do_execsql_test 1.8 {
PRAGMA foreign_key_check
}
#-------------------------------------------------------------------------
# Check that a changeset that causes an FK violation may not be applied,
# even if SQLITE_CHANGESETAPPLY_FKNOACTION is specified.
#
# UPDATE: Unless the conflict-handler returns OMIT. In that case it can
# be committed. See test cases 3.* in this file.
#
reset_db
do_execsql_test 2.0 {
CREATE TABLE p1(a INTEGER PRIMARY KEY, b, c UNIQUE);
INSERT INTO p1 VALUES(1, 1, 'one');
INSERT INTO p1 VALUES(2, 2, 'two');
CREATE TABLE c1(x REFERENCES p1(c) ON DELETE CASCADE);
INSERT INTO c1 VALUES('two');
}
db_save
set C [changeset_from_sql {
DELETE FROM p1 WHERE a=2;
}]
db_restore_and_reopen
do_test 2.1 {
sqlite3changeset_apply_v2 -noaction db $C conflict
} {}
do_execsql_test 2.2 {
SELECT * FROM p1
} {1 1 one}
db_restore_and_reopen
db eval { PRAGMA foreign_keys = 1 }
do_test 2.3 {
list [catch { sqlite3changeset_apply_v2 -noaction db $C conflict } msg] $msg
} {1 SQLITE_CONSTRAINT}
do_execsql_test 2.4 {
SELECT * FROM p1;
} {1 1 one 2 2 two}
do_execsql_test 2.5 {
SELECT * FROM c1;
} {two}
db_restore_and_reopen
db eval { PRAGMA foreign_keys = 1 }
do_test 2.6 {
list [catch {
sqlite3changeset_apply_v2 -ignorenoop -noaction db $C conflict
} msg] $msg
} {1 SQLITE_CONSTRAINT}
do_execsql_test 2.7 {
SELECT * FROM p1;
} {1 1 one 2 2 two}
do_execsql_test 2.8 {
SELECT * FROM c1;
} {two}
#-------------------------------------------------------------------------
#
reset_db
do_execsql_test 3.0 {
CREATE TABLE p1(a INTEGER PRIMARY KEY, b, c UNIQUE);
INSERT INTO p1 VALUES(1, 1, 'one');
INSERT INTO p1 VALUES(2, 2, 'two');
CREATE TABLE c1(x REFERENCES p1(c) ON DELETE CASCADE);
INSERT INTO c1 VALUES('two');
}
set ::nConflict 0
proc conflict {args} {
incr ::nConflict
return "OMIT"
}
db_save
set C [changeset_from_sql {
DELETE FROM p1 WHERE a=2;
}]
db_restore_and_reopen
do_test 3.1 {
sqlite3changeset_apply_v2 -noaction db $C conflict
} {}
do_execsql_test 3.2 {
SELECT * FROM p1
} {1 1 one}
db_restore_and_reopen
db eval { PRAGMA foreign_keys = 1 }
do_test 3.3 {
list [catch { sqlite3changeset_apply_v2 -noaction db $C conflict } msg] $msg
} {0 {}}
do_execsql_test 3.4 {
SELECT * FROM p1;
} {1 1 one}
do_execsql_test 3.5 {
SELECT * FROM c1;
} {two}
db_restore_and_reopen
db eval { PRAGMA foreign_keys = 1 }
do_test 3.6 {
list [catch {
sqlite3changeset_apply_v2 -ignorenoop -noaction db $C conflict
} msg] $msg
} {0 {}}
do_execsql_test 3.7 {
SELECT * FROM p1;
} {1 1 one}
do_execsql_test 3.8 {
SELECT * FROM c1;
} {two}
finish_test
+187
View File
@@ -0,0 +1,187 @@
# 2021 Februar 20
#
# 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 sessionnoop
#-------------------------------------------------------------------------
# Test plan:
#
# 1.*: Test that concatenating changesets cannot produce a noop UPDATE.
# 2.*: Test that rebasing changesets cannot produce a noop UPDATE.
# 3.*: Test that sqlite3changeset_apply() ignores noop UPDATE changes.
#
do_execsql_test 1.0 {
CREATE TABLE t1(a PRIMARY KEY, b, c, d);
INSERT INTO t1 VALUES(1, 1, 1, 1);
INSERT INTO t1 VALUES(2, 2, 2, 2);
INSERT INTO t1 VALUES(3, 3, 3, 3);
}
proc do_concat_test {tn sql1 sql2 res} {
uplevel [list do_test $tn [subst -nocommands {
set C1 [changeset_from_sql {$sql1}]
set C2 [changeset_from_sql {$sql2}]
set C3 [sqlite3changeset_concat [set C1] [set C2]]
set got [list]
sqlite3session_foreach elem [set C3] { lappend got [set elem] }
set got
}] [list {*}$res]]
}
do_concat_test 1.1 {
UPDATE t1 SET c=c+1;
} {
UPDATE t1 SET c=c-1;
} {
}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 2.0 {
CREATE TABLE t1(a PRIMARY KEY, b, c);
INSERT INTO t1 VALUES(1, 1, 1);
INSERT INTO t1 VALUES(2, 2, 2);
INSERT INTO t1 VALUES(3, 3, 3);
}
proc do_rebase_test {tn sql_local sql_remote conflict_res expected} {
proc xConflict {args} [list return $conflict_res]
uplevel [list \
do_test $tn [subst -nocommands {
execsql BEGIN
set c_remote [changeset_from_sql {$sql_remote}]
execsql ROLLBACK
execsql BEGIN
set c_local [changeset_from_sql {$sql_local}]
set base [sqlite3changeset_apply_v2 db [set c_remote] xConflict]
execsql ROLLBACK
sqlite3rebaser_create R
R config [set base]
set res [list]
sqlite3session_foreach elem [R rebase [set c_local]] {
lappend res [set elem]
}
R delete
set res
}] [list {*}$expected]
]
}
do_rebase_test 2.1 {
UPDATE t1 SET c=2 WHERE a=1; -- local
} {
UPDATE t1 SET c=3 WHERE a=1; -- remote
} OMIT {
{UPDATE t1 0 X.. {i 1 {} {} i 3} {{} {} {} {} i 2}}
}
do_rebase_test 2.2 {
UPDATE t1 SET c=2 WHERE a=1; -- local
} {
UPDATE t1 SET c=3 WHERE a=1; -- remote
} REPLACE {
}
do_rebase_test 2.3.1 {
UPDATE t1 SET c=4 WHERE a=1; -- local
} {
UPDATE t1 SET c=4 WHERE a=1 -- remote
} OMIT {
{UPDATE t1 0 X.. {i 1 {} {} i 4} {{} {} {} {} i 4}}
}
do_rebase_test 2.3.2 {
UPDATE t1 SET c=5 WHERE a=1; -- local
} {
UPDATE t1 SET c=5 WHERE a=1 -- remote
} REPLACE {
}
#-------------------------------------------------------------------------
#
reset_db
do_execsql_test 3.0 {
CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
INSERT INTO t1 VALUES(1, 1, 1);
INSERT INTO t1 VALUES(2, 2, 2);
INSERT INTO t1 VALUES(3, 3, 3);
INSERT INTO t1 VALUES(4, 4, 4);
}
# Arg $pkstr contains one character for each column in the table. An
# "X" for PK column, or a "." for a non-PK.
#
proc mk_tbl_header {name pkstr} {
set ret [binary format H2c 54 [string length $pkstr]]
foreach i [split $pkstr {}] {
if {$i=="X"} {
append ret [binary format H2 01]
} else {
if {$i!="."} {error "bad pkstr: $pkstr ($i)"}
append ret [binary format H2 00]
}
}
append ret $name
append ret [binary format H2 00]
set ret
}
proc mk_update_change {args} {
set ret [binary format H2H2 17 00]
foreach a $args {
if {$a==""} {
append ret [binary format H2 00]
} else {
append ret [binary format H2W 01 $a]
}
}
set ret
}
proc xConflict {args} { return "ABORT" }
do_test 3.1 {
set C [mk_tbl_header t1 X..]
append C [mk_update_change 1 {} 1 {} {} 500]
append C [mk_update_change 2 {} {} {} {} {}]
append C [mk_update_change 3 3 {} {} 600 {}]
append C [mk_update_change 4 {} {} {} {} {}]
sqlite3changeset_apply_v2 db $C xConflict
} {}
do_execsql_test 3.2 {
SELECT * FROM t1
} {
1 1 500
2 2 2
3 600 3
4 4 4
}
finish_test
+180
View File
@@ -0,0 +1,180 @@
# 2011 March 07
#
# 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 sessionnoop2
foreach {tn wo} {
1 ""
2 " WITHOUT ROWID "
} {
reset_db
eval [string map [list %WO% $wo] {
do_execsql_test $tn.1.0 {
CREATE TABLE t1(a PRIMARY KEY, b, c) %WO%;
INSERT INTO t1 VALUES('a', 'A', 'AAA');
INSERT INTO t1 VALUES('b', 'B', 'BBB');
INSERT INTO t1 VALUES('c', 'C', 'CCC');
INSERT INTO t1 VALUES('d', 'D', 'DDD');
INSERT INTO t1 VALUES('e', 'E', 'EEE');
}
forcedelete test.db2
sqlite3 db2 test.db2
do_execsql_test -db db2 $tn.1.1 {
CREATE TABLE t1(a PRIMARY KEY, b, c) %WO%;
INSERT INTO t1 VALUES('a', 'A', 'AAA');
INSERT INTO t1 VALUES('b', 'B', '123');
INSERT INTO t1 VALUES('c', 'C', 'CCC');
INSERT INTO t1 VALUES('e', 'E', 'EEE');
INSERT INTO t1 VALUES('f', 'F', 'FFF');
}
set C [changeset_from_sql {
UPDATE t1 SET c='123' WHERE a='b';
DELETE FROM t1 WHERE a='d';
INSERT INTO t1 VALUES('f', 'F', 'FFF');
}]
set ::conflict_list [list]
proc xConflict {args} {
lappend ::conflict_list $args
return "OMIT"
}
do_test $tn.1.2 {
sqlite3changeset_apply_v2 db2 $C xConflict
set ::conflict_list
} [list {*}{
{UPDATE t1 DATA {t b {} {} t BBB} {{} {} {} {} t 123} {t b t B t 123}}
{INSERT t1 CONFLICT {t f t F t FFF} {t f t F t FFF}}
{DELETE t1 NOTFOUND {t d t D t DDD}}
}]
do_test $tn.1.3 {
set ::conflict_list [list]
sqlite3changeset_apply_v2 db2 $C xConflict
set ::conflict_list
} [list {*}{
{UPDATE t1 DATA {t b {} {} t BBB} {{} {} {} {} t 123} {t b t B t 123}}
{INSERT t1 CONFLICT {t f t F t FFF} {t f t F t FFF}}
{DELETE t1 NOTFOUND {t d t D t DDD}}
}]
do_test $tn.1.4 {
set ::conflict_list [list]
sqlite3changeset_apply_v2 -ignorenoop db2 $C xConflict
set ::conflict_list
} {}
do_execsql_test -db db2 1.5 {
UPDATE t1 SET b='G' WHERE a='f';
UPDATE t1 SET c='456' WHERE a='b';
}
do_test $tn.1.6 {
set ::conflict_list [list]
sqlite3changeset_apply_v2 -ignorenoop db2 $C xConflict
set ::conflict_list
} [list {*}{
{UPDATE t1 DATA {t b {} {} t BBB} {{} {} {} {} t 123} {t b t B t 456}}
{INSERT t1 CONFLICT {t f t F t FFF} {t f t G t FFF}}
}]
db2 close
#--------------------------------------------------------------------------
reset_db
forcedelete test.db2
sqlite3 db2 test.db2
do_execsql_test $tn.2.0 {
CREATE TABLE t1(a PRIMARY KEY, b) %WO%;
}
do_execsql_test -db db2 $tn.2.1 {
CREATE TABLE t1(a PRIMARY KEY, b, c DEFAULT 'val') %WO%;
}
do_test $tn.2.2 {
do_then_apply_sql -ignorenoop {
INSERT INTO t1 VALUES(1, 2);
}
do_then_apply_sql -ignorenoop {
UPDATE t1 SET b=2 WHERE a=1
}
} {}
db2 close
}]
}
#-------------------------------------------------------------------------
reset_db
forcedelete test.db2
do_execsql_test 3.0 {
CREATE TABLE xyz(a, b, c, PRIMARY KEY(a, b), UNIQUE(c));
ANALYZE;
WITH s(i) AS (
VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<100
)
INSERT INTO xyz SELECT i, i, i FROM s;
VACUUM INTO 'test.db2';
}
set C [changeset_from_sql { ANALYZE }]
sqlite3 db2 test.db2
set ::conflict_list [list]
proc xConflict {args} { lappend ::conflict_list $args ; return "OMIT" }
do_test 3.1 {
sqlite3changeset_apply_v2 db2 $C xConflict
set ::conflict_list
} {}
do_test 3.2 {
sqlite3changeset_apply_v2 -ignorenoop db2 $C xConflict
set ::conflict_list
} {}
do_test 3.3 {
sqlite3changeset_apply_v2 db2 $C xConflict
set ::conflict_list
} [list {*}{
{INSERT sqlite_stat1 CONFLICT {t xyz t sqlite_autoindex_xyz_1 t {100 1 1}} {t xyz t sqlite_autoindex_xyz_1 t {100 1 1}}}
{INSERT sqlite_stat1 CONFLICT {t xyz t sqlite_autoindex_xyz_2 t {100 1}} {t xyz t sqlite_autoindex_xyz_2 t {100 1}}}
}]
do_execsql_test -db db2 3.4 {
UPDATE sqlite_stat1 SET stat='200 1 1' WHERE idx='sqlite_autoindex_xyz_1';
}
do_test 3.5 {
set ::conflict_list [list]
sqlite3changeset_apply_v2 -ignorenoop db2 $C xConflict
set ::conflict_list
} [list {*}{
{INSERT sqlite_stat1 CONFLICT {t xyz t sqlite_autoindex_xyz_1 t {100 1 1}} {t xyz t sqlite_autoindex_xyz_1 t {200 1 1}}}
}]
finish_test
+552
View File
@@ -0,0 +1,552 @@
# 2018 March 14
#
# 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 sessionrebase
set ::lConflict [list]
proc xConflict {args} {
set res [lindex $::lConflict 0]
set ::lConflict [lrange $::lConflict 1 end]
return $res
}
#-------------------------------------------------------------------------
# The following test cases - 1.* - test that the rebase blobs output by
# sqlite3_changeset_apply_v2 look correct in some simple cases. The blob
# is itself a changeset, containing records determined as follows:
#
# * For each conflict resolved with REPLACE, the rebase blob contains
# a DELETE record. All fields other than the PK fields are undefined.
#
# * For each conflict resolved with OMIT, the rebase blob contains an
# INSERT record. For an INSERT or UPDATE operation, the indirect flag
# is clear and all updated fields are defined. For a DELETE operation,
# the indirect flag is set and all non-PK fields left undefined.
#
proc do_apply_v2_test {tn sql modsql conflict_handler res} {
execsql BEGIN
sqlite3session S db main
S attach *
execsql $sql
set changeset [S changeset]
S delete
execsql ROLLBACK
execsql BEGIN
execsql $modsql
set ::lConflict $conflict_handler
set blob [sqlite3changeset_apply_v2 db $changeset xConflict]
execsql ROLLBACK
uplevel [list do_test $tn [list changeset_to_list $blob] [list {*}$res]]
}
set ::lConflict [list]
proc xConflict {args} {
set res [lindex $::lConflict 0]
set ::lConflict [lrange $::lConflict 1 end]
return $res
}
# Take a copy of database test.db in file test.db2. Execute $sql1
# against test.db and $sql2 against test.db2. Capture a changeset
# for each. Then send the test.db2 changeset to test.db and apply
# it with the conflict handlers in $conflict_handler. Patch the
# test.db changeset and then execute it against test.db2. Test that
# the two databases come out the same.
#
proc do_rebase_test {tn sql1 sql2 conflict_handler {testsql ""} {testres ""}} {
for {set i 1} {$i <= 2} {incr i} {
forcedelete test.db2 test.db2-journal test.db2-wal
forcecopy test.db test.db2
sqlite3 db2 test.db2
db eval BEGIN
sqlite3session S1 db main
S1 object_config rowid 1
S1 attach *
execsql $sql1 db
set c1 [S1 changeset]
S1 delete
if {$i==1} {
sqlite3session S2 db2 main
S2 object_config rowid 1
S2 attach *
execsql $sql2 db2
set c2 [S2 changeset]
S2 delete
} else {
set c2 [list]
foreach sql [split $sql2 ";"] {
if {[string is space $sql]} continue
sqlite3session S2 db2 main
S2 object_config rowid 1
S2 attach *
execsql $sql db2
lappend c2 [S2 changeset]
S2 delete
}
}
set ::lConflict $conflict_handler
set rebase [list]
if {$i==1} {
lappend rebase [sqlite3changeset_apply_v2 db $c2 xConflict]
} else {
foreach c $c2 {
#puts "apply_v2: [changeset_to_list $c]"
lappend rebase [sqlite3changeset_apply_v2 db $c xConflict]
}
#puts "llength: [llength $rebase]"
}
#if {$tn=="2.1.4"} { puts [changeset_to_list $rebase] ; breakpoint }
#puts [changeset_to_list [lindex $rebase 0]] ; breakpoint
#puts [llength $rebase]
sqlite3rebaser_create R
foreach r $rebase {
#puts [changeset_to_list $r]
R configure $r
}
set c1r [R rebase $c1]
R delete
#if {$tn=="2.1.4"} { puts [changeset_to_list $c1r] }
sqlite3changeset_apply_v2 db2 $c1r xConflictAbort
if {[string range $tn end end]!="*"} {
uplevel [list do_test $tn.$i.1 [list compare_db db db2] {}]
}
db2 close
if {$testsql!=""} {
uplevel [list do_execsql_test $tn.$i.2 $testsql $testres]
}
db eval ROLLBACK
}
}
do_execsql_test 1.0 {
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
INSERT INTO t1 VALUES(1, 'value A');
}
do_apply_v2_test 1.1.1 {
UPDATE t1 SET b = 'value B' WHERE a=1;
} {
UPDATE t1 SET b = 'value C' WHERE a=1;
} {
OMIT
} {
{INSERT t1 0 X. {} {i 1 t {value B}}}
}
do_apply_v2_test 1.1.2 {
UPDATE t1 SET b = 'value B' WHERE a=1;
} {
UPDATE t1 SET b = 'value C' WHERE a=1;
} {
REPLACE
} {
{INSERT t1 1 X. {} {i 1 t {value B}}}
}
do_apply_v2_test 1.2.1 {
INSERT INTO t1 VALUES(2, 'first');
} {
INSERT INTO t1 VALUES(2, 'second');
} {
OMIT
} {
{INSERT t1 0 X. {} {i 2 t first}}
}
do_apply_v2_test 1.2.2 {
INSERT INTO t1 VALUES(2, 'first');
} {
INSERT INTO t1 VALUES(2, 'second');
} {
REPLACE
} {
{INSERT t1 1 X. {} {i 2 t first}}
}
do_apply_v2_test 1.3.1 {
DELETE FROM t1 WHERE a=1;
} {
UPDATE t1 SET b='value D' WHERE a=1;
} {
OMIT
} {
{DELETE t1 0 X. {i 1 t {value A}} {}}
}
do_apply_v2_test 1.3.2 {
DELETE FROM t1 WHERE a=1;
} {
UPDATE t1 SET b='value D' WHERE a=1;
} {
REPLACE
} {
{DELETE t1 1 X. {i 1 t {value A}} {}}
}
#-------------------------------------------------------------------------
# Test cases 2.* - simple tests of rebasing actual changesets.
#
# 2.1.1 - 1u2u1r
# 2.1.2 - 1u2u2r
# 2.1.3 - 1d2d
# 2.1.4 - 1d2u1r
# 2.1.5 - 1d2u2r !!
# 2.1.6 - 1u2d1r
# 2.1.7 - 1u2d2r
#
# 2.1.8 - 1i2i2r
# 2.1.9 - 1i2i1r
#
proc xConflictAbort {args} {
return "ABORT"
}
reset_db
do_execsql_test 2.1.0 {
CREATE TABLE t1 (a INTEGER PRIMARY KEY, b TEXT);
INSERT INTO t1 VALUES(1, 'one');
INSERT INTO t1 VALUES(2, 'two');
INSERT INTO t1 VALUES(3, 'three');
}
do_rebase_test 2.1.1 {
UPDATE t1 SET b = 'two.1' WHERE a=2
} {
UPDATE t1 SET b = 'two.2' WHERE a=2;
} {
OMIT
} { SELECT * FROM t1 } {1 one 2 two.1 3 three}
do_rebase_test 2.1.2 {
UPDATE t1 SET b = 'two.1' WHERE a=2
} {
UPDATE t1 SET b = 'two.2' WHERE a=2;
} {
REPLACE
} { SELECT * FROM t1 } {1 one 2 two.2 3 three}
do_rebase_test 2.1.3 {
DELETE FROM t1 WHERE a=3
} {
DELETE FROM t1 WHERE a=3;
} {
OMIT
} { SELECT * FROM t1 } {1 one 2 two}
do_rebase_test 2.1.4 {
DELETE FROM t1 WHERE a=1
} {
UPDATE t1 SET b='one.2' WHERE a=1
} {
OMIT
} { SELECT * FROM t1 } {2 two 3 three}
#do_rebase_test 2.1.5 {
# DELETE FROM t1 WHERE a=1;
#} {
# UPDATE t1 SET b='one.2' WHERE a=1
#} {
# REPLACE
#} { SELECT * FROM t1 } {2 two 3 three}
do_rebase_test 2.1.6 {
UPDATE t1 SET b='three.1' WHERE a=3
} {
DELETE FROM t1 WHERE a=3;
} {
OMIT
} { SELECT * FROM t1 } {1 one 2 two 3 three.1}
do_rebase_test 2.1.7 {
UPDATE t1 SET b='three.1' WHERE a=3
} {
DELETE FROM t1 WHERE a=3;
} {
REPLACE
} { SELECT * FROM t1 } {1 one 2 two}
do_rebase_test 2.1.8 {
INSERT INTO t1 VALUES(4, 'four.1')
} {
INSERT INTO t1 VALUES(4, 'four.2');
} {
REPLACE
} { SELECT * FROM t1 } {1 one 2 two 3 three 4 four.2}
do_rebase_test 2.1.9 {
INSERT INTO t1 VALUES(4, 'four.1')
} {
INSERT INTO t1 VALUES(4, 'four.2');
} {
OMIT
} { SELECT * FROM t1 } {1 one 2 two 3 three 4 four.1}
do_execsql_test 2.2.0 {
CREATE TABLE t2(x, y, z PRIMARY KEY);
INSERT INTO t2 VALUES('i', 'a', 'A');
INSERT INTO t2 VALUES('ii', 'b', 'B');
INSERT INTO t2 VALUES('iii', 'c', 'C');
CREATE TABLE t3(a INTEGER PRIMARY KEY, b, c);
INSERT INTO t3 VALUES(-1, 'z', 'Z');
INSERT INTO t3 VALUES(-2, 'y', 'Y');
}
do_rebase_test 2.2.1 {
UPDATE t2 SET x=1 WHERE z='A'
} {
UPDATE t2 SET y='one' WHERE z='A';
} {
} { SELECT * FROM t2 WHERE z='A' } { 1 one A }
do_rebase_test 2.2.2 {
UPDATE t2 SET x=1, y='one' WHERE z='B'
} {
UPDATE t2 SET y='two' WHERE z='B';
} {
REPLACE
} { SELECT * FROM t2 WHERE z='B' } { 1 two B }
do_rebase_test 2.2.3 {
UPDATE t2 SET x=1, y='one' WHERE z='B'
} {
UPDATE t2 SET y='two' WHERE z='B';
} {
OMIT
} { SELECT * FROM t2 WHERE z='B' } { 1 one B }
reset_db
do_execsql_test 2.3.0 {
CREATE TABLE t1 (b TEXT);
INSERT INTO t1(rowid, b) VALUES(1, 'one');
INSERT INTO t1(rowid, b) VALUES(2, 'two');
INSERT INTO t1(rowid, b) VALUES(3, 'three');
}
do_rebase_test 2.3.1 {
UPDATE t1 SET b = 'two.1' WHERE rowid=2
} {
UPDATE t1 SET b = 'two.2' WHERE rowid=2;
} {
OMIT
} { SELECT rowid, * FROM t1 } {1 one 2 two.1 3 three}
do_rebase_test 2.3.2 {
UPDATE t1 SET b = 'two.1' WHERE rowid=2
} {
UPDATE t1 SET b = 'two.2' WHERE rowid=2;
} {
REPLACE
} { SELECT rowid, * FROM t1 } {1 one 2 two.2 3 three}
do_rebase_test 2.3.3 {
DELETE FROM t1 WHERE rowid=3
} {
DELETE FROM t1 WHERE rowid=3;
} {
OMIT
} { SELECT rowid, * FROM t1 } {1 one 2 two}
do_rebase_test 2.3.4 {
DELETE FROM t1 WHERE rowid=1
} {
UPDATE t1 SET b='one.2' WHERE rowid=1
} {
OMIT
} { SELECT rowid, * FROM t1 } {2 two 3 three}
do_rebase_test 2.3.6 {
UPDATE t1 SET b='three.1' WHERE rowid=3
} {
DELETE FROM t1 WHERE rowid=3;
} {
OMIT
} { SELECT rowid, * FROM t1 } {1 one 2 two 3 three.1}
do_rebase_test 2.3.7 {
UPDATE t1 SET b='three.1' WHERE rowid=3
} {
DELETE FROM t1 WHERE rowid=3;
} {
REPLACE
} { SELECT rowid, * FROM t1 } {1 one 2 two}
do_rebase_test 2.3.8 {
INSERT INTO t1(rowid, b) VALUES(4, 'four.1')
} {
INSERT INTO t1(rowid, b) VALUES(4, 'four.2');
} {
REPLACE
} { SELECT rowid, * FROM t1 } {1 one 2 two 3 three 4 four.2}
do_rebase_test 2.3.9 {
INSERT INTO t1(rowid, b) VALUES(4, 'four.1')
} {
INSERT INTO t1(rowid, b) VALUES(4, 'four.2');
} {
OMIT
} { SELECT rowid, * FROM t1 } {1 one 2 two 3 three 4 four.1}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 3.0 {
CREATE TABLE t3(a, b, c, PRIMARY KEY(b, c));
CREATE TABLE abcdefghijkl(x PRIMARY KEY, y, z);
INSERT INTO t3 VALUES(1, 2, 3);
INSERT INTO t3 VALUES(4, 2, 5);
INSERT INTO t3 VALUES(7, 2, 9);
INSERT INTO abcdefghijkl VALUES('a', 'b', 'c');
INSERT INTO abcdefghijkl VALUES('d', 'e', 'f');
INSERT INTO abcdefghijkl VALUES('g', 'h', 'i');
}
breakpoint
# do_rebase_test 3.6.tn {
# UPDATE abcdefghijkl SET z='X', y='X' WHERE x='d';
# } {
# UPDATE abcdefghijkl SET y=1 WHERE x='d';
# UPDATE abcdefghijkl SET z=1 WHERE x='d';
# } [list REPLACE REPLACE REPLACE]
foreach {tn p} {
1 OMIT 2 REPLACE
} {
do_rebase_test 3.1.$tn {
INSERT INTO t3 VALUES(1, 1, 1);
UPDATE abcdefghijkl SET y=2;
} {
INSERT INTO t3 VALUES(4, 1, 1);
DELETE FROM abcdefghijkl;
} [list $p $p $p $p $p $p $p $p]
do_rebase_test 3.2.$tn {
INSERT INTO abcdefghijkl SELECT * FROM t3;
UPDATE t3 SET b=b+1;
} {
INSERT INTO t3 VALUES(3, 3, 3);
INSERT INTO abcdefghijkl SELECT * FROM t3;
} [list $p $p $p $p $p $p $p $p]
do_rebase_test 3.3.$tn {
INSERT INTO abcdefghijkl VALUES(22, 23, 24);
} {
INSERT INTO abcdefghijkl VALUES(22, 25, 26);
UPDATE abcdefghijkl SET y=400 WHERE x=22;
} [list $p $p $p $p $p $p $p $p]
do_rebase_test 3.4.$tn {
INSERT INTO abcdefghijkl VALUES(22, 23, 24);
} {
INSERT INTO abcdefghijkl VALUES(22, 25, 26);
UPDATE abcdefghijkl SET y=400 WHERE x=22;
} [list REPLACE $p]
do_rebase_test 3.5.$tn* {
UPDATE abcdefghijkl SET y='X' WHERE x='d';
} {
DELETE FROM abcdefghijkl WHERE x='d';
INSERT INTO abcdefghijkl VALUES('d', NULL, NULL);
} [list $p $p $p]
do_rebase_test 3.5.$tn {
UPDATE abcdefghijkl SET y='X' WHERE x='d';
} {
DELETE FROM abcdefghijkl WHERE x='d';
INSERT INTO abcdefghijkl VALUES('d', NULL, NULL);
} [list REPLACE $p $p]
do_rebase_test 3.6.$tn {
UPDATE abcdefghijkl SET z='X', y='X' WHERE x='d';
} {
UPDATE abcdefghijkl SET y=1 WHERE x='d';
UPDATE abcdefghijkl SET z=1 WHERE x='d';
} [list REPLACE $p $p]
}
#-------------------------------------------------------------------------
# Check that apply_v2() does not create a rebase buffer for a patchset.
# And that it is not possible to rebase a patchset.
#
do_execsql_test 4.0 {
CREATE TABLE t5(o PRIMARY KEY, p, q);
INSERT INTO t5 VALUES(1, 2, 3);
INSERT INTO t5 VALUES(4, 5, 6);
}
foreach {tn cmd rebasable} {
1 patchset 0
2 changeset 1
} {
proc xConflict {args} { return "OMIT" }
do_test 4.1.$tn {
execsql {
BEGIN;
DELETE FROM t5 WHERE o=4;
}
sqlite3session S db main
S attach *
execsql {
INSERT INTO t5 VALUES(4, 'five', 'six');
}
set P [S $cmd]
S delete
execsql ROLLBACK;
set ::rebase [sqlite3changeset_apply_v2 db $P xConflict]
expr [llength $::rebase]>0
} $rebasable
}
foreach {tn cmd rebasable} {
1 patchset 0
2 changeset 1
} {
do_test 4.2.$tn {
sqlite3session S db main
S attach *
execsql {
INSERT INTO t5 VALUES(5+$tn, 'five', 'six');
}
set P [S $cmd]
S delete
sqlite3rebaser_create R
R configure $::rebase
expr [catch {R rebase $P}]==0
} $rebasable
catch { R delete }
}
finish_test
+282
View File
@@ -0,0 +1,282 @@
# 2011 Mar 16
#
# 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.
#
#***********************************************************************
#
# The focus of this file is testing the session module.
#
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 sessionrowid
do_execsql_test 0.0 {
CREATE TABLE t1(a, b);
}
foreach {tn rowid bEmpty} {
1 0 1
2 1 0
3 -1 1
} {
do_test 0.$tn {
sqlite3session S db main
if {$rowid>=0} { S object_config rowid $rowid }
S attach t1
execsql { INSERT INTO t1 VALUES(1, 2); }
expr [string length [S changeset]]==0
} $bEmpty
S delete
}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 1.0 {
CREATE TABLE t1(a, b);
}
do_iterator_test 1.1 t1 {
INSERT INTO t1 VALUES('i', 'one');
} {
{INSERT t1 0 X.. {} {i 1 t i t one}}
}
do_execsql_test 1.2 {
SELECT rowid, * FROM t1
} {1 i one}
do_iterator_test 1.3 t1 {
UPDATE t1 SET b='two'
} {
{UPDATE t1 0 X.. {i 1 {} {} t one} {{} {} {} {} t two}}
}
do_iterator_test 1.4 t1 {
DELETE FROM t1;
} {
{DELETE t1 0 X.. {i 1 t i t two} {}}
}
do_iterator_test 1.5 t1 {
INSERT INTO t1(rowid, a, b) VALUES(14, 'hello', 'world');
INSERT INTO t1(rowid, a, b) VALUES(NULL, 'yes', 'no');
INSERT INTO t1(rowid, a, b) VALUES(-123, 'ii', 'iii');
} {
{INSERT t1 0 X.. {} {i -123 t ii t iii}}
{INSERT t1 0 X.. {} {i 15 t yes t no}}
{INSERT t1 0 X.. {} {i 14 t hello t world}}
}
do_iterator_test 1.6 t1 {
UPDATE t1 SET a='deluxe' WHERE rowid=14;
DELETE FROM t1 WHERE rowid=-123;
INSERT INTO t1 VALUES('x', 'xi');
} {
{DELETE t1 0 X.. {i -123 t ii t iii} {}}
{UPDATE t1 0 X.. {i 14 t hello {} {}} {{} {} t deluxe {} {}}}
{INSERT t1 0 X.. {} {i 16 t x t xi}}
}
#-------------------------------------------------------------------------
reset_db
forcedelete test.db2
sqlite3 db2 test.db2
do_execsql_test 2.0 {
CREATE TABLE t1(a, b);
}
do_execsql_test -db db2 2.0.1 {
CREATE TABLE t1(a, b);
}
proc xConflict {args} {
puts "CONFLICT!"
return "OMIT"
}
do_test 2.1 {
set C [changeset_from_sql {
INSERT INTO t1 VALUES('abc', 'def');
}]
sqlite3changeset_apply db2 $C xConflict
execsql { SELECT * FROM t1 } db2
} {abc def}
do_test 2.2 {
set C [changeset_from_sql {
UPDATE t1 SET b='hello'
}]
sqlite3changeset_apply db2 $C xConflict
execsql { SELECT * FROM t1 } db2
} {abc hello}
do_test 2.3 {
set C [changeset_from_sql {
DELETE FROM t1 WHERE b='hello'
}]
sqlite3changeset_apply db2 $C xConflict
execsql { SELECT * FROM t1 } db2
} {}
do_test 2.4 {
do_then_apply_sql {
INSERT INTO t1 VALUES('i', 'one');
INSERT INTO t1 VALUES('ii', 'two');
INSERT INTO t1 VALUES('iii', 'three');
INSERT INTO t1 VALUES('iv', 'four');
}
compare_db db db2
} {}
do_test 2.5 {
do_then_apply_sql {
DELETE FROM t1 WHERE a='ii';
UPDATE t1 SET b='THREE' WHERE a='iii';
UPDATE t1 SET a='III' WHERE a='iii';
INSERT INTO t1 VALUES('v', 'five');
}
compare_db db db2
} {}
do_execsql_test 2.6 {SELECT * FROM t1} {i one III THREE iv four v five}
do_execsql_test -db db2 2.7 {SELECT * FROM t1} {i one III THREE iv four v five}
#-------------------------------------------------------------------------
db2 close
reset_db
forcedelete test.db2
sqlite3 db2 test.db2
set init_sql {
CREATE TABlE t4(a, b);
CREATE INDEX t4a ON t4(a);
CREATE UNIQUE INDEX t4b ON t4(b);
}
do_execsql_test 3.0 $init_sql
do_execsql_test -db db2 3.0a $init_sql
do_execsql_test -db db2 3.1 {
INSERT INTO t4(rowid, a, b) VALUES(43, 'hello', 'world');
}
do_conflict_test 3.2 -sql {
INSERT INTO t4(rowid, a, b) VALUES(43, 'abc', 'def');
} -tables t4 -conflicts {
{INSERT t4 CONFLICT {i 43 t abc t def} {i 43 t hello t world}}
}
do_execsql_test -db db2 3.3 {
SELECT * FROM t4
} {hello world}
do_execsql_test 3.4 { DELETE FROM t4 }
do_conflict_test 3.5 -sql {
INSERT INTO t4(rowid, a, b) VALUES(43, 'abc', 'def');
} -tables t4 -conflicts {
{INSERT t4 CONFLICT {i 43 t abc t def} {i 43 t hello t world}}
} -policy REPLACE
do_execsql_test -db db2 3.6 {
SELECT * FROM t4
} {abc def}
do_execsql_test 3.7 { DELETE FROM t4 }
do_conflict_test 3.8 -sql {
INSERT INTO t4(rowid, a, b) VALUES(45, 'xyz', 'def');
} -tables t4 -conflicts {
{INSERT t4 CONSTRAINT {i 45 t xyz t def}}
}
do_execsql_test -db db2 3.9 {
SELECT * FROM t4
} {abc def}
do_execsql_test -db db 3.10a { DELETE FROM t4 }
do_execsql_test -db db2 3.10b { DELETE FROM t4 }
do_execsql_test -db db 3.11a {
INSERT INTO t4(rowid, a, b) VALUES(111, 'one', 'one');
INSERT INTO t4(rowid, a, b) VALUES(222, 'two', 'two');
}
do_execsql_test -db db2 3.11b {
INSERT INTO t4(rowid, a, b) VALUES(111, 'one', 'blip');
}
do_conflict_test 3.12 -sql {
DELETE FROM t4 WHERE a='one';
} -tables t4 -conflicts {
{DELETE t4 DATA {i 111 t one t one} {i 111 t one t blip}}
}
do_execsql_test -db db2 3.13 {
SELECT * FROM t4
} {one blip}
do_conflict_test 3.14 -sql {
DELETE FROM t4 WHERE a='two';
} -tables t4 -conflicts {
{DELETE t4 NOTFOUND {i 222 t two t two}}
}
do_execsql_test -db db2 3.15 {
SELECT * FROM t4
} {one blip}
do_execsql_test -db db 3.16a { DELETE FROM t4 }
do_execsql_test -db db2 3.16b { DELETE FROM t4 }
do_execsql_test -db db 3.17a {
INSERT INTO t4(rowid, a, b) VALUES(111, 'one', 'one');
INSERT INTO t4(rowid, a, b) VALUES(222, 'two', 'two');
}
do_execsql_test -db db2 3.17b {
INSERT INTO t4(rowid, a, b) VALUES(111, 'one', 'blip');
}
do_conflict_test 3.18 -sql {
UPDATE t4 SET b='xyz' WHERE a='one'
} -tables t4 -conflicts {
{UPDATE t4 DATA {i 111 {} {} t one} {{} {} {} {} t xyz} {i 111 t one t blip}}
}
do_execsql_test -db db2 3.19 {
SELECT * FROM t4
} {one blip}
do_conflict_test 3.20 -sql {
UPDATE t4 SET b='123' WHERE a='two'
} -tables t4 -conflicts {
{UPDATE t4 NOTFOUND {i 222 {} {} t two} {{} {} {} {} t 123}}
}
do_execsql_test -db db2 3.21 {
SELECT * FROM t4
} {one blip}
db2 close
#--------------------------------------------------------------------------
breakpoint
do_diff_test 4.0 {
CREATE TABLE t1(x, y);
CREATE TABLE aux.t1(x, y);
INSERT INTO t1 VALUES(1, 2);
}
do_diff_test 4.1 {
CREATE TABLE t1(x, y);
CREATE TABLE aux.t1(x, y);
INSERT INTO aux.t1 VALUES(1, 2);
}
do_diff_test 4.2 {
CREATE TABLE t1(x, y);
CREATE TABLE aux.t1(x, y);
INSERT INTO t1(rowid, x, y) VALUES(413, 'hello', 'there');
INSERT INTO aux.t1(rowid, x, y) VALUES(413, 'hello', 'world');
}
finish_test
+131
View File
@@ -0,0 +1,131 @@
# 2021 April 22
#
# 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 sessionsize
proc do_changeset_size_test {tn sql} {
sqlite3session S db main
S attach *
db eval $sql
set sz [S changeset_size]
set C [S changeset]
set szC [string length $C]
S delete
do_test $tn "expr $sz" $szC
}
do_execsql_test 1.0 {
CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
INSERT INTO t1 VALUES(1, 'abc', 'def');
INSERT INTO t1 VALUES(2, 'ghi', 'jkl');
}
do_changeset_size_test 1.1 {
INSERT INTO t1 VALUES(3, 'hello', 'world');
}
do_changeset_size_test 1.2 {
DELETE FROM t1 WHERE a=2;
}
do_changeset_size_test 1.3 {
DELETE FROM t1 WHERE a=3;
INSERT INTO t1 VALUES(3, 1, 2);
}
do_changeset_size_test 1.4 {
UPDATE t1 SET c='hello world' WHERE a=3;
}
#-------------------------------------------------------------------------
do_execsql_test 2.0 {
CREATE TABlE t2(a, b, c, d, PRIMARY KEY(a, b)) WITHOUT ROWID;
CREATE TABlE t3(a, b, c, d PRIMARY KEY);
}
do_changeset_size_test 2.1 {
WITH s(i) AS (
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<50
)
INSERT INTO t2 SELECT i, i+1, i+2, i+3 FROM s;
UPDATE t2 SET c=randomblob(a) WHERE a>10
}
do_changeset_size_test 2.2 {
DELETE FROM t2 WHERE a=1;
INSERT INTO t2 VALUES(1, 4, 3, 4);
}
do_changeset_size_test 2.2 {
UPDATE t2 SET b=4 WHERE a=2
}
do_changeset_size_test 2.3 {
INSERT INTO t2 VALUES('a', 'b', 'c', 'd');
UPDATE t2 SET c='qwertyuiop' WHERE a='a';
}
do_changeset_size_test 2.4 {
DELETE FROM t2 WHERE a='a';
INSERT INTO t2 VALUES('a', 'b', 'c', 'd');
}
do_changeset_size_test 2.5 {
UPDATE t2 SET a='aa', b='bb' WHERE (a, b) = ('a', 'b');
}
do_changeset_size_test 2.6 {
UPDATE t2 SET a='a', b='b' WHERE (a, b) = ('aa', 'bb');
}
do_changeset_size_test 2.7 {
INSERT INTO t3 DEFAULT VALUES;
INSERT INTO t3 VALUES(1,2,3,4);
}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 3.0 {
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
}
do_test 3.1 {
sqlite3session S db main
S object_config size -1
} 1
do_test 3.2.1 { S object_config size 0 } 0
do_test 3.2.2 { S object_config size -1 } 0
do_test 3.2.3 { S object_config size 1 } 1
do_test 3.2.4 { S object_config size -1 } 1
do_test 3.3 { S attach t1 } {}
do_test 3.4 { S object_config size 1 } {SQLITE_MISUSE}
do_test 3.4 { S object_config size -1 } {1}
S delete
finish_test
+310
View File
@@ -0,0 +1,310 @@
# 2018 January 12
#
# 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.
#
#***********************************************************************
#
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 sessionstat1
do_execsql_test 1.0 {
CREATE TABLE t1(a PRIMARY KEY, b, c);
CREATE INDEX t1b ON t1(b);
CREATE INDEX t1c ON t1(c);
WITH s(i) AS (
SELECT 0 UNION ALL SELECT i+1 FROM s WHERE (i+1)<32
)
INSERT INTO t1 SELECT i, i%8, i%2 FROM s;
}
do_iterator_test 1.1 {} {
ANALYZE
} {
{INSERT sqlite_stat1 0 XX. {} {t t1 t sqlite_autoindex_t1_1 t {32 1}}}
{INSERT sqlite_stat1 0 XX. {} {t t1 t t1b t {32 4}}}
{INSERT sqlite_stat1 0 XX. {} {t t1 t t1c t {32 16}}}
}
do_execsql_test 1.2 {
WITH s(i) AS (
SELECT 32 UNION ALL SELECT i+1 FROM s WHERE (i+1)<64
)
INSERT INTO t1 SELECT i, i%8, i%2 FROM s;
}
do_iterator_test 1.3 {} {
ANALYZE
} {
{UPDATE sqlite_stat1 0 XX. {t t1 t sqlite_autoindex_t1_1 t {32 1}} {{} {} {} {} t {64 1}}}
{UPDATE sqlite_stat1 0 XX. {t t1 t t1b t {32 4}} {{} {} {} {} t {64 8}}}
{UPDATE sqlite_stat1 0 XX. {t t1 t t1c t {32 16}} {{} {} {} {} t {64 32}}}
}
do_iterator_test 1.5 {} {
DROP INDEX t1b;
} {
{DELETE sqlite_stat1 0 XX. {t t1 t t1b t {64 8}} {}}
}
do_iterator_test 1.6 {} {
DROP TABLE t1;
} {
{DELETE sqlite_stat1 0 XX. {t t1 t sqlite_autoindex_t1_1 t {64 1}} {}}
{DELETE sqlite_stat1 0 XX. {t t1 t t1c t {64 32}} {}}
}
#-------------------------------------------------------------------------
#
catch { db2 close }
forcedelete test.db2
sqlite3 db2 test.db2
do_test 2.0 {
do_common_sql {
CREATE TABLE t1(a PRIMARY KEY, b, c);
CREATE INDEX t1b ON t1(b);
CREATE INDEX t1c ON t1(c);
ANALYZE;
}
} {}
do_test 2.1 {
do_then_apply_sql -ignorenoop {
WITH s(i) AS (
SELECT 0 UNION ALL SELECT i+1 FROM s WHERE (i+1)<32
)
INSERT INTO t1 SELECT i, i%8, i%2 FROM s;
ANALYZE;
}
} {}
do_execsql_test -db db2 2.2 {
SELECT * FROM sqlite_stat1 ORDER BY tbl, idx
} {
t1 sqlite_autoindex_t1_1 {32 1}
t1 t1b {32 4}
t1 t1c {32 16}
}
do_test 2.3 {
do_then_apply_sql -ignorenoop { DROP INDEX t1c }
} {}
do_execsql_test -db db2 2.4 {
SELECT * FROM sqlite_stat1 ORDER BY tbl, idx;
} {
t1 sqlite_autoindex_t1_1 {32 1}
t1 t1b {32 4}
}
do_test 2.3 {
do_then_apply_sql -ignorenoop { DROP TABLE t1 }
} {}
do_execsql_test -db db2 2.4 {
SELECT * FROM sqlite_stat1
} {
}
do_execsql_test -db db2 2.5 { SELECT count(*) FROM t1 } 32
#-------------------------------------------------------------------------
db2 close
forcedelete test.db2
reset_db
sqlite3 db2 test.db2
do_test 3.0 {
do_common_sql {
CREATE TABLE t1(a, b, c);
ANALYZE;
DELETE FROM sqlite_stat1;
}
execsql {
INSERT INTO t1 VALUES(1, 1, 1);
INSERT INTO t1 VALUES(2, 2, 2);
INSERT INTO t1 VALUES(3, 3, 3);
INSERT INTO t1 VALUES(4, 4, 4);
}
} {}
do_iterator_test 3.1 {} {
ANALYZE
} {
{INSERT sqlite_stat1 0 XX. {} {t t1 b {} t 4}}
}
db null null
db2 null null
do_execsql_test 3.2 {
SELECT * FROM sqlite_stat1;
} {t1 null 4}
do_test 3.3 {
execsql { DELETE FROM sqlite_stat1 }
do_then_apply_sql -ignorenoop { ANALYZE }
execsql { SELECT * FROM sqlite_stat1 } db2
} {t1 null 4}
do_test 3.4 {
execsql { INSERT INTO t1 VALUES(5,5,5) }
do_then_apply_sql -ignorenoop { ANALYZE }
execsql { SELECT * FROM sqlite_stat1 } db2
} {t1 null 5}
do_test 3.5 {
do_then_apply_sql -ignorenoop { DROP TABLE t1 }
execsql { SELECT * FROM sqlite_stat1 } db2
} {}
do_test 3.6.1 {
execsql {
CREATE TABLE t1(a, b, c);
CREATE TABLE t2(x, y, z);
INSERT INTO t1 VALUES(1,1,1), (2,2,2), (3,3,3), (4,4,4), (5,5,5);
INSERT INTO t2 SELECT * FROM t1;
DELETE FROM sqlite_stat1;
}
sqlite3session S db main
S attach sqlite_stat1
execsql { ANALYZE }
} {}
do_changeset_test 3.6.2 S {
{INSERT sqlite_stat1 0 XX. {} {t t2 b {} t 5}}
{INSERT sqlite_stat1 0 XX. {} {t t1 b {} t 5}}
}
do_changeset_invert_test 3.6.3 S {
{DELETE sqlite_stat1 0 XX. {t t2 b {} t 5} {}}
{DELETE sqlite_stat1 0 XX. {t t1 b {} t 5} {}}
}
do_test 3.6.4 { S delete } {}
proc sql_changeset_concat {args} {
foreach sql $args {
sqlite3session S db main
S attach sqlite_stat1
execsql $sql
set change [S changeset]
S delete
if {[info vars ret]!=""} {
set ret [sqlite3changeset_concat $ret $change]
} else {
set ret $change
}
}
changeset_to_list $ret
}
proc do_scc_test {tn args} {
uplevel [list \
do_test $tn [concat sql_changeset_concat [lrange $args 0 end-1]] \
[list {*}[ lindex $args end ]]
]
}
do_execsql_test 3.7.0 {
DELETE FROM sqlite_stat1;
}
do_scc_test 3.7.1 {
ANALYZE;
} {
INSERT INTO t2 VALUES(6,6,6);
ANALYZE;
} {
{INSERT sqlite_stat1 0 XX. {} {t t1 b {} t 5}}
{INSERT sqlite_stat1 0 XX. {} {t t2 b {} t 6}}
}
#-------------------------------------------------------------------------
catch { db2 close }
reset_db
forcedelete test.db2
sqlite3 db2 test.db2
do_test 4.1.0 {
do_common_sql {
CREATE TABLE t1(a, b);
CREATE INDEX i1 ON t1(a);
CREATE INDEX i2 ON t1(b);
INSERT INTO t1 VALUES(1,1), (2,2);
ANALYZE;
}
execsql { DELETE FROM sqlite_stat1 }
} {}
do_test 4.1.1 {
execsql { INSERT INTO t1 VALUES(3,3); }
set C [changeset_from_sql {ANALYZE}]
set ::c [list]
proc xConflict {args} {
lappend ::c $args
return "OMIT"
}
sqlite3changeset_apply db2 $C xConflict
set ::c
} [list {*}{
{INSERT sqlite_stat1 CONFLICT {t t1 t i1 t {3 1}} {t t1 t i1 t {2 1}}}
{INSERT sqlite_stat1 CONFLICT {t t1 t i2 t {3 1}} {t t1 t i2 t {2 1}}}
}]
do_execsql_test -db db2 4.1.2 {
SELECT * FROM sqlite_stat1 ORDER BY 1,2;
} {t1 i1 {2 1} t1 i2 {2 1}}
do_test 4.1.3 {
proc xConflict {args} {
return "REPLACE"
}
sqlite3changeset_apply db2 $C xConflict
execsql { SELECT * FROM sqlite_stat1 ORDER BY 1,2 } db2
} {t1 i1 {3 1} t1 i2 {3 1}}
do_test 4.2.0 {
do_common_sql {
DROP TABLE t1;
CREATE TABLE t3(x,y);
INSERT INTO t3 VALUES('a','a');
INSERT INTO t3 VALUES('b','b');
ANALYZE;
}
execsql { DELETE FROM sqlite_stat1 }
} {}
do_test 4.2.1 {
execsql { INSERT INTO t3 VALUES('c','c'); }
set C [changeset_from_sql {ANALYZE}]
set ::c [list]
proc xConflict {args} {
lappend ::c $args
return "OMIT"
}
sqlite3changeset_apply db2 $C xConflict
set ::c
} [list {*}{
{INSERT sqlite_stat1 CONFLICT {t t3 b {} t 3} {t t3 b {} t 2}}
}]
db2 null null
do_execsql_test -db db2 4.2.2 {
SELECT * FROM sqlite_stat1 ORDER BY 1,2;
} {t3 null 2}
do_test 4.2.3 {
proc xConflict {args} {
return "REPLACE"
}
sqlite3changeset_apply db2 $C xConflict
execsql { SELECT * FROM sqlite_stat1 ORDER BY 1,2 } db2
} {t3 null 3}
finish_test
+123
View File
@@ -0,0 +1,123 @@
# 2017 Jan 31
#
# 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.
#
#***********************************************************************
#
# The focus of this file is testing the session module. Specifically,
# testing support for WITHOUT ROWID tables.
#
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 sessionwor
proc test_reset {} {
catch { db close }
catch { db2 close }
forcedelete test.db test.db2
sqlite3 db test.db
sqlite3 db2 test.db2
}
foreach {tn wo} {
1 ""
2 "WITHOUT ROWID"
} {
reset_db
do_execsql_test 1.$tn.0 "CREATE TABLE t1(a PRIMARY KEY, b) $wo ;"
do_iterator_test 1.$tn.1 t1 {
INSERT INTO t1 VALUES('one', 'two');
} {
{INSERT t1 0 X. {} {t one t two}}
}
do_iterator_test 1.$tn.2 t1 {
UPDATE t1 SET b='three'
} {
{UPDATE t1 0 X. {t one t two} {{} {} t three}}
}
do_iterator_test 1.$tn.3 t1 {
REPLACE INTO t1 VALUES('one', 'four');
} {
{UPDATE t1 0 X. {t one t three} {{} {} t four}}
}
do_iterator_test 1.$tn.4 t1 {
DELETE FROM t1;
} {
{DELETE t1 0 X. {t one t four} {}}
}
}
foreach {tn wo} {
1 ""
2 "WITHOUT ROWID"
} {
reset_db
do_execsql_test 2.$tn.0.1 "CREATE TABLE t1(a INTEGER PRIMARY KEY, b) $wo ;"
do_execsql_test 2.$tn.0.2 "CREATE TABLE t2(a INTEGER PRIMARY KEY, b) $wo ;"
do_execsql_test 2.$tn.0.3 "CREATE TABLE t3(a INTEGER PRIMARY KEY, b) $wo ;"
do_iterator_test 1.1 t1 {
INSERT INTO t1 VALUES(1, 'two');
} {
{INSERT t1 0 X. {} {i 1 t two}}
}
do_iterator_test 2.$tn.2 t1 {
UPDATE t1 SET b='three'
} {
{UPDATE t1 0 X. {i 1 t two} {{} {} t three}}
}
do_iterator_test 2.$tn.3 t1 {
REPLACE INTO t1 VALUES(1, 'four');
} {
{UPDATE t1 0 X. {i 1 t three} {{} {} t four}}
}
do_iterator_test 2.$tn.4 t1 {
DELETE FROM t1;
} {
{DELETE t1 0 X. {i 1 t four} {}}
}
do_execsql_test 2.$tn.5 {
INSERT INTO t1 VALUES(1, 'one');
INSERT INTO t1 VALUES(2, 'two');
INSERT INTO t1 VALUES(3, 'three');
}
do_iterator_test 2.$tn.6 t2 {
INSERT INTO t2 SELECT a, b FROM t1
} {
{INSERT t2 0 X. {} {i 1 t one}}
{INSERT t2 0 X. {} {i 2 t two}}
{INSERT t2 0 X. {} {i 3 t three}}
}
do_iterator_test 2.$tn.7 t3 {
INSERT INTO t3 SELECT * FROM t1
} {
{INSERT t3 0 X. {} {i 1 t one}}
{INSERT t3 0 X. {} {i 2 t two}}
{INSERT t3 0 X. {} {i 3 t three}}
}
}
finish_test
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff