Adding the sqlite sourcecode
This commit is contained in:
1 parent
bdb54a1f8f
commit
406b584760
2208 files changed
+1172785
-1
No files matched your search
@@ -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
@@ -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
|
||||
Executable
+807
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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]
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
Executable
+76
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Executable
+185
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
Reference in new issue
Block a user