mirror of
https://github.com/sqlite/sqlite.git
synced 2025-07-30 19:03:16 +03:00
Experimental change to the handling of foreign key constraint violations when applying a changeset: all foreign keys, immediate and deferred, are deferred until the end of the transaction (or sub-transaction) opened by the sqlite3changeset_apply(). A single call to the conflict-handler (if any) is made if any FK constraint violations are still present in the database at this point. The conflict-handler may choose to rollback the changeset, or to apply it, constraint violations and all.
FossilOrigin-Name: 1d44e5d3c2b1dc958442f9114a960b256e002ed3
This commit is contained in:
@ -264,12 +264,12 @@ do_conflict_test 3.2.3 -tables t2 -sql {
|
||||
DELETE FROM t2 WHERE a = 3;
|
||||
DELETE FROM t2 WHERE a = 4;
|
||||
} -conflicts {
|
||||
{DELETE t2 CONSTRAINT {i 1 t one}}
|
||||
{DELETE t2 NOTFOUND {i 3 t three}}
|
||||
{DELETE t2 DATA {i 4 t four} {i 4 t five}}
|
||||
{FOREIGN_KEY 1}
|
||||
}
|
||||
do_execsql_test 3.2.4 "SELECT * FROM t2" {}
|
||||
do_db2_test 3.2.5 "SELECT * FROM t2" {1 one 4 five}
|
||||
do_db2_test 3.2.5 "SELECT * FROM t2" {4 five}
|
||||
|
||||
# Test UPDATE changesets.
|
||||
#
|
||||
|
134
ext/session/session9.test
Normal file
134
ext/session/session9.test
Normal file
@ -0,0 +1,134 @@
|
||||
# 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
|
||||
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
#
|
||||
|
||||
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 {}}
|
||||
set A(ABORT) {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)
|
||||
|
||||
do_test 1.2.$tn.2 { set ::xConflict } $conflictargs
|
||||
|
||||
set A(OMIT) {1 1}
|
||||
set A(ABORT) {0 0}
|
||||
do_test 1.2.$tn.3 {
|
||||
execsql { SELECT count(*) FROM c1 UNION ALL SELECT count(*) FROM c2 }
|
||||
} $A($conflictret)
|
||||
|
||||
do_test 1.2.$tn.4 { expr ![sqlite3_get_autocommit db] } $trans
|
||||
do_test 1.2.$tn.5 {
|
||||
if { $trans } { execsql COMMIT }
|
||||
} {}
|
||||
}
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# 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}
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
@ -1,6 +1,5 @@
|
||||
|
||||
#if defined(SQLITE_ENABLE_SESSION) && defined(SQLITE_ENABLE_PREUPDATE_HOOK)
|
||||
|
||||
#include "sqlite3session.h"
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
@ -2110,6 +2109,26 @@ int sqlite3changeset_conflict(
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** This function may only be called with an iterator passed to an
|
||||
** SQLITE_CHANGESET_FOREIGN_KEY conflict handler callback. In this case
|
||||
** it sets the output variable to the total number of known foreign key
|
||||
** violations in the destination database and returns SQLITE_OK.
|
||||
**
|
||||
** In all other cases this function returns SQLITE_MISUSE.
|
||||
*/
|
||||
int sqlite3changeset_fk_conflicts(
|
||||
sqlite3_changeset_iter *pIter, /* Changeset iterator */
|
||||
int *pnOut /* OUT: Number of FK violations */
|
||||
){
|
||||
if( pIter->pConflict || pIter->apValue ){
|
||||
return SQLITE_MISUSE;
|
||||
}
|
||||
*pnOut = pIter->nCol;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Finalize an iterator allocated with sqlite3changeset_start().
|
||||
**
|
||||
@ -2845,6 +2864,9 @@ int sqlite3changeset_apply(
|
||||
|
||||
sqlite3_mutex_enter(sqlite3_db_mutex(db));
|
||||
rc = sqlite3_exec(db, "SAVEPOINT changeset_apply", 0, 0, 0);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_exec(db, "PRAGMA defer_foreign_keys = 1", 0, 0, 0);
|
||||
}
|
||||
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3changeset_next(pIter) ){
|
||||
int nCol;
|
||||
int op;
|
||||
@ -2948,6 +2970,23 @@ int sqlite3changeset_apply(
|
||||
sqlite3changeset_finalize(pIter);
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
int nFk = sqlite3_foreign_key_check(db);
|
||||
if( nFk>0 ){
|
||||
int res = SQLITE_CHANGESET_ABORT;
|
||||
if( xConflict ){
|
||||
sqlite3_changeset_iter sIter;
|
||||
memset(&sIter, 0, sizeof(sIter));
|
||||
sIter.nCol = nFk;
|
||||
res = xConflict(pCtx, SQLITE_CHANGESET_FOREIGN_KEY, &sIter);
|
||||
}
|
||||
if( res!=SQLITE_CHANGESET_OMIT ){
|
||||
rc = SQLITE_CONSTRAINT;
|
||||
}
|
||||
}
|
||||
}
|
||||
sqlite3_exec(db, "PRAGMA defer_foreign_keys = 0", 0, 0, 0);
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_exec(db, "RELEASE changeset_apply", 0, 0, 0);
|
||||
}else{
|
||||
|
@ -489,6 +489,21 @@ int sqlite3changeset_conflict(
|
||||
sqlite3_value **ppValue /* OUT: Value from conflicting row */
|
||||
);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Determine The Number Of Foreign Key Constraint Violations
|
||||
**
|
||||
** This function may only be called with an iterator passed to an
|
||||
** SQLITE_CHANGESET_FOREIGN_KEY conflict handler callback. In this case
|
||||
** it sets the output variable to the total number of known foreign key
|
||||
** violations in the destination database and returns SQLITE_OK.
|
||||
**
|
||||
** In all other cases this function returns SQLITE_MISUSE.
|
||||
*/
|
||||
int sqlite3changeset_fk_conflicts(
|
||||
sqlite3_changeset_iter *pIter, /* Changeset iterator */
|
||||
int *pnOut /* OUT: Number of FK violations */
|
||||
);
|
||||
|
||||
|
||||
/*
|
||||
** CAPI3REF: Finalize A Changeset Iterator
|
||||
@ -810,19 +825,34 @@ int sqlite3changeset_apply(
|
||||
** The conflicting row in this case is the database row with the matching
|
||||
** primary key.
|
||||
**
|
||||
** <dt>SQLITE_CHANGESET_FOREIGN_KEY<dd>
|
||||
** If foreign key handling is enabled, and applying a changeset leaves the
|
||||
** database in a state containing foreign key violations, the conflict
|
||||
** handler is invoked with CHANGESET_FOREIGN_KEY as the second argument
|
||||
** exactly once before the changeset is committed. If the conflict handler
|
||||
** returns CHANGESET_OMIT, the changes, including those that caused the
|
||||
** foreign key constraint violation, are committed. Or, if it returns
|
||||
** CHANGESET_ABORT, the changeset is rolled back.
|
||||
**
|
||||
** No current or conflicting row information is provided. The only function
|
||||
** it is possible to call on the supplied sqlite3_changeset_iter handle
|
||||
** is sqlite3changeset_fk_conflicts().
|
||||
**
|
||||
** <dt>SQLITE_CHANGESET_CONSTRAINT<dd>
|
||||
** If any other constraint violation occurs while applying a change (i.e.
|
||||
** a FOREIGN KEY, UNIQUE, CHECK or NOT NULL constraint), the conflict
|
||||
** handler is invoked with CHANGESET_CONSTRAINT as the second argument.
|
||||
** a UNIQUE, CHECK or NOT NULL constraint), the conflict handler is
|
||||
** invoked with CHANGESET_CONSTRAINT as the second argument.
|
||||
**
|
||||
** There is no conflicting row in this case. The results of invoking the
|
||||
** sqlite3changeset_conflict() API are undefined.
|
||||
**
|
||||
** </dl>
|
||||
*/
|
||||
#define SQLITE_CHANGESET_DATA 1
|
||||
#define SQLITE_CHANGESET_NOTFOUND 2
|
||||
#define SQLITE_CHANGESET_CONFLICT 3
|
||||
#define SQLITE_CHANGESET_CONSTRAINT 4
|
||||
#define SQLITE_CHANGESET_DATA 1
|
||||
#define SQLITE_CHANGESET_NOTFOUND 2
|
||||
#define SQLITE_CHANGESET_CONFLICT 3
|
||||
#define SQLITE_CHANGESET_CONSTRAINT 4
|
||||
#define SQLITE_CHANGESET_FOREIGN_KEY 5
|
||||
|
||||
/*
|
||||
** CAPI3REF: Constants Returned By The Conflict Handler
|
||||
|
@ -250,109 +250,119 @@ static int test_conflict_handler(
|
||||
|
||||
sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0);
|
||||
|
||||
/* Append the operation type. */
|
||||
Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(
|
||||
op==SQLITE_INSERT ? "INSERT" :
|
||||
op==SQLITE_UPDATE ? "UPDATE" :
|
||||
"DELETE", -1
|
||||
));
|
||||
|
||||
/* Append the table name. */
|
||||
Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zTab, -1));
|
||||
|
||||
/* Append the conflict type. */
|
||||
switch( eConf ){
|
||||
case SQLITE_CHANGESET_DATA:
|
||||
Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("DATA",-1));
|
||||
break;
|
||||
case SQLITE_CHANGESET_NOTFOUND:
|
||||
Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("NOTFOUND",-1));
|
||||
break;
|
||||
case SQLITE_CHANGESET_CONFLICT:
|
||||
Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("CONFLICT",-1));
|
||||
break;
|
||||
case SQLITE_CHANGESET_CONSTRAINT:
|
||||
Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("CONSTRAINT",-1));
|
||||
break;
|
||||
}
|
||||
|
||||
/* If this is not an INSERT, append the old row */
|
||||
if( op!=SQLITE_INSERT ){
|
||||
int i;
|
||||
Tcl_Obj *pOld = Tcl_NewObj();
|
||||
for(i=0; i<nCol; i++){
|
||||
sqlite3_value *pVal;
|
||||
sqlite3changeset_old(pIter, i, &pVal);
|
||||
test_append_value(pOld, pVal);
|
||||
}
|
||||
Tcl_ListObjAppendElement(0, pEval, pOld);
|
||||
}
|
||||
|
||||
/* If this is not a DELETE, append the new row */
|
||||
if( op!=SQLITE_DELETE ){
|
||||
int i;
|
||||
Tcl_Obj *pNew = Tcl_NewObj();
|
||||
for(i=0; i<nCol; i++){
|
||||
sqlite3_value *pVal;
|
||||
sqlite3changeset_new(pIter, i, &pVal);
|
||||
test_append_value(pNew, pVal);
|
||||
}
|
||||
Tcl_ListObjAppendElement(0, pEval, pNew);
|
||||
}
|
||||
|
||||
/* If this is a CHANGESET_DATA or CHANGESET_CONFLICT conflict, append
|
||||
** the conflicting row. */
|
||||
if( eConf==SQLITE_CHANGESET_DATA || eConf==SQLITE_CHANGESET_CONFLICT ){
|
||||
int i;
|
||||
Tcl_Obj *pConflict = Tcl_NewObj();
|
||||
for(i=0; i<nCol; i++){
|
||||
int rc;
|
||||
sqlite3_value *pVal;
|
||||
rc = sqlite3changeset_conflict(pIter, i, &pVal);
|
||||
assert( rc==SQLITE_OK );
|
||||
test_append_value(pConflict, pVal);
|
||||
}
|
||||
Tcl_ListObjAppendElement(0, pEval, pConflict);
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
** This block is purely for testing some error conditions.
|
||||
*/
|
||||
if( eConf==SQLITE_CHANGESET_CONSTRAINT || eConf==SQLITE_CHANGESET_NOTFOUND ){
|
||||
sqlite3_value *pVal;
|
||||
int rc = sqlite3changeset_conflict(pIter, 0, &pVal);
|
||||
assert( rc==SQLITE_MISUSE );
|
||||
if( eConf==SQLITE_CHANGESET_FOREIGN_KEY ){
|
||||
int nFk;
|
||||
sqlite3changeset_fk_conflicts(pIter, &nFk);
|
||||
Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj("FOREIGN_KEY", -1));
|
||||
Tcl_ListObjAppendElement(0, pEval, Tcl_NewIntObj(nFk));
|
||||
}else{
|
||||
sqlite3_value *pVal;
|
||||
int rc = sqlite3changeset_conflict(pIter, -1, &pVal);
|
||||
assert( rc==SQLITE_RANGE );
|
||||
rc = sqlite3changeset_conflict(pIter, nCol, &pVal);
|
||||
assert( rc==SQLITE_RANGE );
|
||||
|
||||
/* Append the operation type. */
|
||||
Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(
|
||||
op==SQLITE_INSERT ? "INSERT" :
|
||||
op==SQLITE_UPDATE ? "UPDATE" :
|
||||
"DELETE", -1
|
||||
));
|
||||
|
||||
/* Append the table name. */
|
||||
Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zTab, -1));
|
||||
|
||||
/* Append the conflict type. */
|
||||
switch( eConf ){
|
||||
case SQLITE_CHANGESET_DATA:
|
||||
Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("DATA",-1));
|
||||
break;
|
||||
case SQLITE_CHANGESET_NOTFOUND:
|
||||
Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("NOTFOUND",-1));
|
||||
break;
|
||||
case SQLITE_CHANGESET_CONFLICT:
|
||||
Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("CONFLICT",-1));
|
||||
break;
|
||||
case SQLITE_CHANGESET_CONSTRAINT:
|
||||
Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("CONSTRAINT",-1));
|
||||
break;
|
||||
}
|
||||
|
||||
/* If this is not an INSERT, append the old row */
|
||||
if( op!=SQLITE_INSERT ){
|
||||
int i;
|
||||
Tcl_Obj *pOld = Tcl_NewObj();
|
||||
for(i=0; i<nCol; i++){
|
||||
sqlite3_value *pVal;
|
||||
sqlite3changeset_old(pIter, i, &pVal);
|
||||
test_append_value(pOld, pVal);
|
||||
}
|
||||
Tcl_ListObjAppendElement(0, pEval, pOld);
|
||||
}
|
||||
|
||||
/* If this is not a DELETE, append the new row */
|
||||
if( op!=SQLITE_DELETE ){
|
||||
int i;
|
||||
Tcl_Obj *pNew = Tcl_NewObj();
|
||||
for(i=0; i<nCol; i++){
|
||||
sqlite3_value *pVal;
|
||||
sqlite3changeset_new(pIter, i, &pVal);
|
||||
test_append_value(pNew, pVal);
|
||||
}
|
||||
Tcl_ListObjAppendElement(0, pEval, pNew);
|
||||
}
|
||||
|
||||
/* If this is a CHANGESET_DATA or CHANGESET_CONFLICT conflict, append
|
||||
** the conflicting row. */
|
||||
if( eConf==SQLITE_CHANGESET_DATA || eConf==SQLITE_CHANGESET_CONFLICT ){
|
||||
int i;
|
||||
Tcl_Obj *pConflict = Tcl_NewObj();
|
||||
for(i=0; i<nCol; i++){
|
||||
int rc;
|
||||
sqlite3_value *pVal;
|
||||
rc = sqlite3changeset_conflict(pIter, i, &pVal);
|
||||
assert( rc==SQLITE_OK );
|
||||
test_append_value(pConflict, pVal);
|
||||
}
|
||||
Tcl_ListObjAppendElement(0, pEval, pConflict);
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
** This block is purely for testing some error conditions.
|
||||
*/
|
||||
if( eConf==SQLITE_CHANGESET_CONSTRAINT
|
||||
|| eConf==SQLITE_CHANGESET_NOTFOUND
|
||||
){
|
||||
sqlite3_value *pVal;
|
||||
int rc = sqlite3changeset_conflict(pIter, 0, &pVal);
|
||||
assert( rc==SQLITE_MISUSE );
|
||||
}else{
|
||||
sqlite3_value *pVal;
|
||||
int rc = sqlite3changeset_conflict(pIter, -1, &pVal);
|
||||
assert( rc==SQLITE_RANGE );
|
||||
rc = sqlite3changeset_conflict(pIter, nCol, &pVal);
|
||||
assert( rc==SQLITE_RANGE );
|
||||
}
|
||||
if( op==SQLITE_DELETE ){
|
||||
sqlite3_value *pVal;
|
||||
int rc = sqlite3changeset_new(pIter, 0, &pVal);
|
||||
assert( rc==SQLITE_MISUSE );
|
||||
}else{
|
||||
sqlite3_value *pVal;
|
||||
int rc = sqlite3changeset_new(pIter, -1, &pVal);
|
||||
assert( rc==SQLITE_RANGE );
|
||||
rc = sqlite3changeset_new(pIter, nCol, &pVal);
|
||||
assert( rc==SQLITE_RANGE );
|
||||
}
|
||||
if( op==SQLITE_INSERT ){
|
||||
sqlite3_value *pVal;
|
||||
int rc = sqlite3changeset_old(pIter, 0, &pVal);
|
||||
assert( rc==SQLITE_MISUSE );
|
||||
}else{
|
||||
sqlite3_value *pVal;
|
||||
int rc = sqlite3changeset_old(pIter, -1, &pVal);
|
||||
assert( rc==SQLITE_RANGE );
|
||||
rc = sqlite3changeset_old(pIter, nCol, &pVal);
|
||||
assert( rc==SQLITE_RANGE );
|
||||
}
|
||||
/* End of testing block
|
||||
***********************************************************************/
|
||||
}
|
||||
if( op==SQLITE_DELETE ){
|
||||
sqlite3_value *pVal;
|
||||
int rc = sqlite3changeset_new(pIter, 0, &pVal);
|
||||
assert( rc==SQLITE_MISUSE );
|
||||
}else{
|
||||
sqlite3_value *pVal;
|
||||
int rc = sqlite3changeset_new(pIter, -1, &pVal);
|
||||
assert( rc==SQLITE_RANGE );
|
||||
rc = sqlite3changeset_new(pIter, nCol, &pVal);
|
||||
assert( rc==SQLITE_RANGE );
|
||||
}
|
||||
if( op==SQLITE_INSERT ){
|
||||
sqlite3_value *pVal;
|
||||
int rc = sqlite3changeset_old(pIter, 0, &pVal);
|
||||
assert( rc==SQLITE_MISUSE );
|
||||
}else{
|
||||
sqlite3_value *pVal;
|
||||
int rc = sqlite3changeset_old(pIter, -1, &pVal);
|
||||
assert( rc==SQLITE_RANGE );
|
||||
rc = sqlite3changeset_old(pIter, nCol, &pVal);
|
||||
assert( rc==SQLITE_RANGE );
|
||||
}
|
||||
/* End of testing block
|
||||
***********************************************************************/
|
||||
|
||||
if( TCL_OK!=Tcl_EvalObjEx(interp, pEval, TCL_EVAL_GLOBAL) ){
|
||||
Tcl_BackgroundError(interp);
|
||||
|
37
manifest
37
manifest
@ -1,5 +1,5 @@
|
||||
C Fixes\sfor\sthe\ssessions\smodule\sso\sthat\sit\sworks\swith\ssqlite3_extended_error_codes()\sset.
|
||||
D 2013-07-02T20:23:40.233
|
||||
C Experimental\schange\sto\sthe\shandling\sof\sforeign\skey\sconstraint\sviolations\swhen\sapplying\sa\schangeset:\sall\sforeign\skeys,\simmediate\sand\sdeferred,\sare\sdeferred\suntil\sthe\send\sof\sthe\stransaction\s(or\ssub-transaction)\sopened\sby\sthe\ssqlite3changeset_apply().\sA\ssingle\scall\sto\sthe\sconflict-handler\s(if\sany)\sis\smade\sif\sany\sFK\sconstraint\sviolations\sare\sstill\spresent\sin\sthe\sdatabase\sat\sthis\spoint.\sThe\sconflict-handler\smay\schoose\sto\srollback\sthe\schangeset,\sor\sto\sapply\sit,\sconstraint\sviolations\sand\sall.
|
||||
D 2013-07-03T19:53:05.693
|
||||
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
|
||||
F Makefile.in aff38bc64c582dd147f18739532198372587b0f0
|
||||
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
|
||||
@ -135,18 +135,19 @@ F ext/rtree/rtree_util.tcl 06aab2ed5b826545bf215fff90ecb9255a8647ea
|
||||
F ext/rtree/sqlite3rtree.h c34c1e41d1ab80bb8ad09aae402c9c956871a765
|
||||
F ext/rtree/tkt3363.test 142ab96eded44a3615ec79fba98c7bde7d0f96de
|
||||
F ext/rtree/viewrtree.tcl eea6224b3553599ae665b239bd827e182b466024
|
||||
F ext/session/session1.test 502086908e4144dfaccb1baa77bc29d75a9daace
|
||||
F ext/session/session1.test 894e3bc9f497c4fa07a2aa3271e3911f3670c3d8
|
||||
F ext/session/session2.test 99ca0da7ddb617d42bafd83adccf99f18ae0384b
|
||||
F ext/session/session3.test a7a9ce59b8d1e49e2cc23d81421ac485be0eea01
|
||||
F ext/session/session4.test a6ed685da7a5293c5d6f99855bcf41dbc352ca84
|
||||
F ext/session/session5.test 8fdfaf9dba28a2f1c6b89b06168bdab1fef2d478
|
||||
F ext/session/session6.test 443789bc2fca12e4f7075cf692c60b8a2bea1a26
|
||||
F ext/session/session8.test 7d35947ad329b8966f095d34f9617a9eff52dc65
|
||||
F ext/session/session9.test 3378ceace4e291dda8512b83d256be51aac5344e
|
||||
F ext/session/session_common.tcl 1539d8973b2aea0025c133eb0cc4c89fcef541a5
|
||||
F ext/session/sessionfault.test 496291b287ba3c0b14ca2e074425e29cc92a64a6
|
||||
F ext/session/sqlite3session.c c0867804cc86b219c3905c0cd313408d11f7a409
|
||||
F ext/session/sqlite3session.h f374c9c4c96e08f67ac418871c29d423245c7673
|
||||
F ext/session/test_session.c 23eddaf713708ae063d278ec6297652e3672dc38
|
||||
F ext/session/sqlite3session.c 80903fe2c24c8a9e7ccacebca1855a31ebebbbc3
|
||||
F ext/session/sqlite3session.h c7db3d8515eba7f41eeb8698a25e58d24cd384bf
|
||||
F ext/session/test_session.c 12053e9190653164fa624427cf90d1f46ca7f179
|
||||
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
|
||||
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
|
||||
F magic.txt f2b23a6bde8f1c6e86b957e4d94eab0add520b0d
|
||||
@ -185,7 +186,7 @@ F src/date.c 067a81c9942c497aafd2c260e13add8a7d0c7dd4
|
||||
F src/delete.c 39a770e9729b1acd2de347f8f614584841d0083e
|
||||
F src/expr.c 2b47ae9da6c9f34eff6736962ea2e102c6c4a755
|
||||
F src/fault.c 160a0c015b6c2629d3899ed2daf63d75754a32bb
|
||||
F src/fkey.c e16942bd5c8a868ac53287886464a5ed0e72b179
|
||||
F src/fkey.c ddd160ce0b81f092165f72d84c356d49c903a3ad
|
||||
F src/func.c 5c50c1ea31fd864b0fe921fe1a8d4c55acd609ef
|
||||
F src/global.c 5caf4deab621abb45b4c607aad1bd21c20aac759
|
||||
F src/hash.c ac3470bbf1ca4ae4e306a8ecb0fdf1731810ffe4
|
||||
@ -196,7 +197,7 @@ F src/journal.c b4124532212b6952f42eb2c12fa3c25701d8ba8d
|
||||
F src/legacy.c 0df0b1550b9cc1f58229644735e317ac89131f12
|
||||
F src/lempar.c cdf0a000315332fc9b50b62f3b5e22e080a0952b
|
||||
F src/loadext.c c48f7f3f170e502fe0cc20748e03c6e0b5a016c2
|
||||
F src/main.c 55347b3785f59939ddccfa22ea2bd5bd4acc7a13
|
||||
F src/main.c 893986530b7ea4607643675babf08888cb63e48e
|
||||
F src/malloc.c fe085aa851b666b7c375c1ff957643dc20a04bf6
|
||||
F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
|
||||
F src/mem1.c 437c7c4af964895d4650f29881df63535caaa1fa
|
||||
@ -221,7 +222,7 @@ F src/parse.y 9acfcc83ddbf0cf82f0ed9582ccf0ad6c366ff37
|
||||
F src/pcache.c f8043b433a57aba85384a531e3937a804432a346
|
||||
F src/pcache.h a5e4f5d9f5d592051d91212c5949517971ae6222
|
||||
F src/pcache1.c d23d07716de96c7c0c2503ec5051a4384c3fb938
|
||||
F src/pragma.c 67a611bd4be0754f27ee13eb87932c3b14415862
|
||||
F src/pragma.c 057f5b1343c9a79e3e6c0c542a3a08b85849ee61
|
||||
F src/prepare.c 2306be166bbeddf454e18bf8b21dba8388d05328
|
||||
F src/printf.c bff529ed47657098c55c9910b9c69b1b3b1a1353
|
||||
F src/random.c cd4a67b3953b88019f8cd4ccd81394a8ddfaba50
|
||||
@ -229,10 +230,10 @@ F src/resolve.c 89f9003e8316ee3a172795459efc2a0274e1d5a8
|
||||
F src/rowset.c 64655f1a627c9c212d9ab497899e7424a34222e0
|
||||
F src/select.c 91b62654caf8dfe292fb8882715e575d34ad3874
|
||||
F src/shell.c a02544af6697c5782d29ec3204616f35ed9e8458
|
||||
F src/sqlite.h.in d4abdfeea75b91d505998e14c6b98b240ce4eb31
|
||||
F src/sqlite.h.in 0693f95792b64cca4d1780c082d7b96fd32aa1c3
|
||||
F src/sqlite3.rc fea433eb0a59f4c9393c8e6d76a6e2596b1fe0c0
|
||||
F src/sqlite3ext.h d936f797812c28b81b26ed18345baf8db28a21a5
|
||||
F src/sqliteInt.h e92a2e1b8154da9c5fc4b677a3e46644e5885b2b
|
||||
F src/sqliteInt.h 5a005fde923b3755fa3184e60028c582a8efe01d
|
||||
F src/sqliteLimit.h 164b0e6749d31e0daa1a4589a169d31c0dec7b3d
|
||||
F src/status.c bedc37ec1a6bb9399944024d63f4c769971955a9
|
||||
F src/table.c 2cd62736f845d82200acfa1287e33feb3c15d62e
|
||||
@ -288,11 +289,11 @@ F src/update.c 19daebf6a0a67af5524913e93498d08388792128
|
||||
F src/utf.c 8d819e2e5104a430fc2005f018db14347c95a38f
|
||||
F src/util.c f566b5138099a2df8533b190d0dcc74b7dfbe0c9
|
||||
F src/vacuum.c ddf21cc9577c4cb459d08bee9863a78ec000c5bb
|
||||
F src/vdbe.c 45a342e1bf84cab8f6c42effeb97a11c9e58cfe4
|
||||
F src/vdbe.c e1782e46404dd5aeb26d49e2180be6efcb6a2334
|
||||
F src/vdbe.h 1223e2548e0970cf96f573ff6b99f804a36ad683
|
||||
F src/vdbeInt.h cbecb801377541cd84f7720c77ed28e38e454204
|
||||
F src/vdbeapi.c 4c5da2026196e49dbeb6f9d2aa37ab0165887617
|
||||
F src/vdbeaux.c a0418e1eefa31cd52af0e2a1b1444cc01c65356e
|
||||
F src/vdbeInt.h 1ca0f9ae9e9c28823647749edb767ea9ef2176d1
|
||||
F src/vdbeapi.c c45805f7acd2a07444b3d3b63853eb96545ec5f0
|
||||
F src/vdbeaux.c 2e82e249a0b72e9c2b63d16ec7801a966ff6a182
|
||||
F src/vdbeblob.c 1268e0bcb8e21fa32520b0fc376e1bcdfaa0c642
|
||||
F src/vdbemem.c 833005f1cbbf447289f1973dba2a0c2228c7b8ab
|
||||
F src/vdbesort.c 3937e06b2a0e354500e17dc206ef4c35770a5017
|
||||
@ -1110,7 +1111,7 @@ F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
|
||||
F tool/warnings.sh fbc018d67fd7395f440c28f33ef0f94420226381
|
||||
F tool/wherecosttest.c f407dc4c79786982a475261866a161cd007947ae
|
||||
F tool/win/sqlite.vsix 97894c2790eda7b5bce3cc79cb2a8ec2fde9b3ac
|
||||
P 086a127236ee99d67513490fb7b5549e8b752c44
|
||||
R 2763382f3d6cfe76b83308c186ebd5fb
|
||||
P c2972b6aed23f6c76a289534de9ea4732a48f40e
|
||||
R 1e1bb18e922bc4e9bb6b2e837b9cd1c8
|
||||
U dan
|
||||
Z 3d139fb0da074f52216dcf16de48044b
|
||||
Z 8c44f70721e5b391cc002040c9d1eb51
|
||||
|
@ -1 +1 @@
|
||||
c2972b6aed23f6c76a289534de9ea4732a48f40e
|
||||
1d44e5d3c2b1dc958442f9114a960b256e002ed3
|
@ -422,7 +422,11 @@ static void fkLookupParent(
|
||||
}
|
||||
}
|
||||
|
||||
if( !pFKey->isDeferred && !pParse->pToplevel && !pParse->isMultiWrite ){
|
||||
if( !pFKey->isDeferred
|
||||
&& !pParse->pToplevel
|
||||
&& !pParse->isMultiWrite
|
||||
&& !(pParse->db->flags & SQLITE_DeferForeignKeys)
|
||||
){
|
||||
/* Special case: If this is an INSERT statement that will insert exactly
|
||||
** one row into the table, raise a constraint immediately instead of
|
||||
** incrementing a counter. This is necessary as the VM code is being
|
||||
|
@ -1037,6 +1037,8 @@ void sqlite3RollbackAll(sqlite3 *db, int tripCode){
|
||||
|
||||
/* Any deferred constraint violations have now been resolved. */
|
||||
db->nDeferredCons = 0;
|
||||
db->nDeferredImmCons = 0;
|
||||
db->flags &= ~SQLITE_DeferForeignKeys;
|
||||
|
||||
/* If one has been configured, invoke the rollback-hook callback */
|
||||
if( db->xRollbackCallback && (inTrans || !db->autoCommit) ){
|
||||
|
15
src/pragma.c
15
src/pragma.c
@ -1169,6 +1169,21 @@ void sqlite3Pragma(
|
||||
}
|
||||
}
|
||||
}else
|
||||
|
||||
if( sqlite3StrICmp(zLeft, "defer_foreign_keys")==0 ){
|
||||
if( zRight ){
|
||||
if( sqlite3GetBoolean(zRight, 0) ){
|
||||
db->flags |= SQLITE_DeferForeignKeys;
|
||||
}else{
|
||||
db->flags &= ~SQLITE_DeferForeignKeys;
|
||||
db->nDeferredImmCons = 0;
|
||||
}
|
||||
sqlite3VdbeAddOp2(v, OP_Expire, 0, 0);
|
||||
}else{
|
||||
int bVal = !!(db->flags & SQLITE_DeferForeignKeys);
|
||||
returnSingleInt(pParse, "defer_foreign_keys", bVal);
|
||||
}
|
||||
}
|
||||
#endif /* !defined(SQLITE_OMIT_FOREIGN_KEY) */
|
||||
|
||||
#ifndef SQLITE_OMIT_FOREIGN_KEY
|
||||
|
@ -7280,6 +7280,8 @@ SQLITE_EXPERIMENTAL int sqlite3_preupdate_count(sqlite3 *);
|
||||
SQLITE_EXPERIMENTAL int sqlite3_preupdate_depth(sqlite3 *);
|
||||
SQLITE_EXPERIMENTAL int sqlite3_preupdate_new(sqlite3 *, int, sqlite3_value **);
|
||||
|
||||
int sqlite3_foreign_key_check(sqlite3 *db);
|
||||
|
||||
/*
|
||||
** Undo the hack that converts floating point types to integer for
|
||||
** builds on processors without floating point support.
|
||||
|
@ -961,6 +961,7 @@ struct sqlite3 {
|
||||
int nSavepoint; /* Number of non-transaction savepoints */
|
||||
int nStatement; /* Number of nested statement-transactions */
|
||||
i64 nDeferredCons; /* Net deferred constraints this transaction. */
|
||||
i64 nDeferredImmCons; /* Net deferred immediate constraints */
|
||||
int *pnBytesFreed; /* If not NULL, increment this in DbFree() */
|
||||
|
||||
#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
|
||||
@ -1016,6 +1017,7 @@ struct sqlite3 {
|
||||
#define SQLITE_PreferBuiltin 0x00100000 /* Preference to built-in funcs */
|
||||
#define SQLITE_LoadExtension 0x00200000 /* Enable load_extension */
|
||||
#define SQLITE_EnableTrigger 0x00400000 /* True to enable triggers */
|
||||
#define SQLITE_DeferForeignKeys 0x00800000
|
||||
|
||||
/*
|
||||
** Bits of the sqlite3.dbOptFlags field that are used by the
|
||||
@ -1161,6 +1163,7 @@ struct FuncDestructor {
|
||||
struct Savepoint {
|
||||
char *zName; /* Savepoint name (nul-terminated) */
|
||||
i64 nDeferredCons; /* Number of deferred fk violations */
|
||||
i64 nDeferredImmCons; /* Number of deferred imm fk. */
|
||||
Savepoint *pNext; /* Parent savepoint (if any) */
|
||||
};
|
||||
|
||||
|
13
src/vdbe.c
13
src/vdbe.c
@ -878,7 +878,7 @@ case OP_Halt: {
|
||||
p->rc = rc = SQLITE_BUSY;
|
||||
}else{
|
||||
assert( rc==SQLITE_OK || (p->rc&0xff)==SQLITE_CONSTRAINT );
|
||||
assert( rc==SQLITE_OK || db->nDeferredCons>0 );
|
||||
assert( rc==SQLITE_OK || db->nDeferredCons>0 || db->nDeferredImmCons>0 );
|
||||
rc = p->rc ? SQLITE_ERROR : SQLITE_DONE;
|
||||
}
|
||||
goto vdbe_return;
|
||||
@ -2737,6 +2737,7 @@ case OP_Savepoint: {
|
||||
pNew->pNext = db->pSavepoint;
|
||||
db->pSavepoint = pNew;
|
||||
pNew->nDeferredCons = db->nDeferredCons;
|
||||
pNew->nDeferredImmCons = db->nDeferredImmCons;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
@ -2824,6 +2825,7 @@ case OP_Savepoint: {
|
||||
}
|
||||
}else{
|
||||
db->nDeferredCons = pSavepoint->nDeferredCons;
|
||||
db->nDeferredImmCons = pSavepoint->nDeferredImmCons;
|
||||
}
|
||||
|
||||
if( !isTransaction ){
|
||||
@ -2978,6 +2980,7 @@ case OP_Transaction: {
|
||||
** counter. If the statement transaction needs to be rolled back,
|
||||
** the value of this counter needs to be restored too. */
|
||||
p->nStmtDefCons = db->nDeferredCons;
|
||||
p->nStmtDefImmCons = db->nDeferredImmCons;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -5319,7 +5322,9 @@ case OP_Param: { /* out2-prerelease */
|
||||
** statement counter is incremented (immediate foreign key constraints).
|
||||
*/
|
||||
case OP_FkCounter: {
|
||||
if( pOp->p1 ){
|
||||
if( db->flags & SQLITE_DeferForeignKeys ){
|
||||
db->nDeferredImmCons += pOp->p2;
|
||||
}else if( pOp->p1 ){
|
||||
db->nDeferredCons += pOp->p2;
|
||||
}else{
|
||||
p->nFkConstraint += pOp->p2;
|
||||
@ -5340,9 +5345,9 @@ case OP_FkCounter: {
|
||||
*/
|
||||
case OP_FkIfZero: { /* jump */
|
||||
if( pOp->p1 ){
|
||||
if( db->nDeferredCons==0 ) pc = pOp->p2-1;
|
||||
if( db->nDeferredCons==0 && db->nDeferredImmCons==0 ) pc = pOp->p2-1;
|
||||
}else{
|
||||
if( p->nFkConstraint==0 ) pc = pOp->p2-1;
|
||||
if( p->nFkConstraint==0 && db->nDeferredImmCons==0 ) pc = pOp->p2-1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -350,6 +350,7 @@ struct Vdbe {
|
||||
#endif
|
||||
i64 nFkConstraint; /* Number of imm. FK constraints this VM */
|
||||
i64 nStmtDefCons; /* Number of def. constraints when stmt started */
|
||||
i64 nStmtDefImmCons; /* Number of def. imm constraints when stmt started */
|
||||
char *zSql; /* Text of the SQL statement that generated this */
|
||||
void *pFree; /* Free this when deleting the vdbe */
|
||||
#ifdef SQLITE_DEBUG
|
||||
|
@ -386,7 +386,9 @@ static int sqlite3Step(Vdbe *p){
|
||||
db->u1.isInterrupted = 0;
|
||||
}
|
||||
|
||||
assert( db->writeVdbeCnt>0 || db->autoCommit==0 || db->nDeferredCons==0 );
|
||||
assert( db->writeVdbeCnt>0 || db->autoCommit==0
|
||||
|| (db->nDeferredCons==0 && db->nDeferredImmCons==0)
|
||||
);
|
||||
|
||||
#ifndef SQLITE_OMIT_TRACE
|
||||
if( db->xProfile && !db->init.busy ){
|
||||
@ -1496,3 +1498,5 @@ int sqlite3_preupdate_new(sqlite3 *db, int iIdx, sqlite3_value **ppValue){
|
||||
return sqlite3ApiExit(db, rc);
|
||||
}
|
||||
#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
|
||||
|
||||
int sqlite3_foreign_key_check(sqlite3 *db){ return db->nDeferredImmCons; }
|
||||
|
@ -2051,6 +2051,7 @@ int sqlite3VdbeCloseStatement(Vdbe *p, int eOp){
|
||||
** the statement transaction was opened. */
|
||||
if( eOp==SAVEPOINT_ROLLBACK ){
|
||||
db->nDeferredCons = p->nStmtDefCons;
|
||||
db->nDeferredImmCons = p->nStmtDefImmCons;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
@ -2069,7 +2070,9 @@ int sqlite3VdbeCloseStatement(Vdbe *p, int eOp){
|
||||
#ifndef SQLITE_OMIT_FOREIGN_KEY
|
||||
int sqlite3VdbeCheckFk(Vdbe *p, int deferred){
|
||||
sqlite3 *db = p->db;
|
||||
if( (deferred && db->nDeferredCons>0) || (!deferred && p->nFkConstraint>0) ){
|
||||
if( (deferred && (db->nDeferredCons+db->nDeferredImmCons)>0)
|
||||
|| (!deferred && p->nFkConstraint>0)
|
||||
){
|
||||
p->rc = SQLITE_CONSTRAINT_FOREIGNKEY;
|
||||
p->errorAction = OE_Abort;
|
||||
sqlite3SetString(&p->zErrMsg, db, "foreign key constraint failed");
|
||||
@ -2201,6 +2204,8 @@ int sqlite3VdbeHalt(Vdbe *p){
|
||||
sqlite3RollbackAll(db, SQLITE_OK);
|
||||
}else{
|
||||
db->nDeferredCons = 0;
|
||||
db->nDeferredImmCons = 0;
|
||||
db->flags &= ~SQLITE_DeferForeignKeys;
|
||||
sqlite3CommitInternalChanges(db);
|
||||
}
|
||||
}else{
|
||||
|
Reference in New Issue
Block a user