modernc.org/cc@v1.0.1/v2/testdata/_sqlite/ext/session/session9.test (about)

     1  # 2013 July 04
     2  #
     3  # The author disclaims copyright to this source code.  In place of
     4  # a legal notice, here is a blessing:
     5  #
     6  #    May you do good and not evil.
     7  #    May you find forgiveness for yourself and forgive others.
     8  #    May you share freely, never taking more than you give.
     9  #
    10  #***********************************************************************
    11  #
    12  # This file tests that the sessions module handles foreign key constraint
    13  # violations when applying changesets as required.
    14  #
    15  
    16  if {![info exists testdir]} {
    17    set testdir [file join [file dirname [info script]] .. .. test]
    18  } 
    19  source [file join [file dirname [info script]] session_common.tcl]
    20  source $testdir/tester.tcl
    21  ifcapable !session {finish_test; return}
    22  set testprefix session9
    23  
    24  
    25  #--------------------------------------------------------------------
    26  # Basic tests.
    27  #
    28  proc populate_db {} {
    29    drop_all_tables
    30    execsql {
    31      PRAGMA foreign_keys = 1;
    32      CREATE TABLE p1(a PRIMARY KEY, b);
    33      CREATE TABLE c1(a PRIMARY KEY, b REFERENCES p1);
    34      CREATE TABLE c2(a PRIMARY KEY, 
    35          b REFERENCES p1 DEFERRABLE INITIALLY DEFERRED
    36      );
    37  
    38      INSERT INTO p1 VALUES(1, 'one');
    39      INSERT INTO p1 VALUES(2, 'two');
    40      INSERT INTO p1 VALUES(3, 'three');
    41      INSERT INTO p1 VALUES(4, 'four');
    42    }
    43  }
    44  
    45  proc capture_changeset {sql} {
    46    sqlite3session S db main
    47  
    48    foreach t [db eval {SELECT name FROM sqlite_master WHERE type='table'}] {
    49      S attach $t
    50    }
    51    execsql $sql
    52    set ret [S changeset]
    53    S delete
    54  
    55    return $ret
    56  }
    57  
    58  do_test 1.1 {
    59    populate_db
    60    set cc [capture_changeset {
    61      INSERT INTO c1 VALUES('ii', 2);
    62      INSERT INTO c2 VALUES('iii', 3);
    63    }]
    64    set {} {}
    65  } {}
    66  
    67  proc xConflict {args} {
    68    lappend ::xConflict {*}$args
    69    return $::conflictret
    70  }
    71  
    72  foreach {tn delrow trans conflictargs conflictret} {
    73    1   2 0 {FOREIGN_KEY 1} OMIT
    74    2   3 0 {FOREIGN_KEY 1} OMIT
    75    3   2 1 {FOREIGN_KEY 1} OMIT
    76    4   3 1 {FOREIGN_KEY 1} OMIT
    77    5   2 0 {FOREIGN_KEY 1} ABORT
    78    6   3 0 {FOREIGN_KEY 1} ABORT
    79    7   2 1 {FOREIGN_KEY 1} ABORT
    80    8   3 1 {FOREIGN_KEY 1} ABORT
    81  } {
    82  
    83    set A(OMIT)  {0 {}}
    84    set A(ABORT) {1 SQLITE_CONSTRAINT}
    85    do_test 1.2.$tn.1 {
    86      populate_db
    87      execsql { DELETE FROM p1 WHERE a=($delrow+0) }
    88      if {$trans} { execsql BEGIN }
    89  
    90      set ::xConflict [list]
    91      list [catch {sqlite3changeset_apply db $::cc xConflict} msg] $msg
    92    } $A($conflictret)
    93  
    94    do_test 1.2.$tn.2 { set ::xConflict } $conflictargs
    95  
    96    set A(OMIT)  {1 1}
    97    set A(ABORT) {0 0}
    98    do_test 1.2.$tn.3 {
    99      execsql { SELECT count(*) FROM c1 UNION ALL SELECT count(*) FROM c2 }
   100    } $A($conflictret)
   101  
   102    do_test 1.2.$tn.4 { expr ![sqlite3_get_autocommit db] } $trans
   103    do_test 1.2.$tn.5 {
   104      if { $trans } { execsql COMMIT }
   105    } {}
   106  }
   107  
   108  #--------------------------------------------------------------------
   109  # Test that closing a transaction clears the defer_foreign_keys flag.
   110  #
   111  foreach {tn open noclose close} {
   112    1 BEGIN {} COMMIT
   113    2 BEGIN {} ROLLBACK
   114  
   115    3 {SAVEPOINT one} {}                {RELEASE one}
   116    4 {SAVEPOINT one} {ROLLBACK TO one} {RELEASE one}
   117  } {
   118    execsql $open
   119    do_execsql_test 2.$tn.1 { PRAGMA defer_foreign_keys } {0}
   120  
   121    do_execsql_test 2.$tn.2 {
   122      PRAGMA defer_foreign_keys = 1;
   123      PRAGMA defer_foreign_keys;
   124    } {1}
   125  
   126    execsql $noclose
   127    do_execsql_test 2.$tn.3 { PRAGMA defer_foreign_keys } {1}
   128  
   129    execsql $close
   130    do_execsql_test 2.$tn.4 { PRAGMA defer_foreign_keys } {0}
   131  }
   132  
   133  #--------------------------------------------------------------------
   134  # Test that a cyclic relationship can be inserted and deleted.
   135  #
   136  # This situation does not come up in practice, but testing it serves to 
   137  # show that it does not matter which order parent and child keys 
   138  # are processed in internally when applying a changeset.
   139  #
   140  drop_all_tables
   141  
   142  do_execsql_test 3.1 {
   143    CREATE TABLE t1(a PRIMARY KEY, b);
   144    CREATE TABLE t2(x PRIMARY KEY, y);
   145  }
   146  
   147  # Create changesets as follows:
   148  # 
   149  #   $cc1    - Insert a row into t1.
   150  #   $cc2    - Insert a row into t2.
   151  #   $cc     - Combination of $cc1 and $cc2.
   152  #
   153  #   $ccdel1 - Delete the row from t1.
   154  #   $ccdel2 - Delete the row from t2.
   155  #   $ccdel  - Combination of $cc1 and $cc2.
   156  #
   157  do_test 3.2 {
   158    set cc1 [capture_changeset {
   159      INSERT INTO t1 VALUES('one', 'value one');
   160    }]
   161    set ccdel1 [capture_changeset { DELETE FROM t1; }]
   162    set cc2 [capture_changeset {
   163      INSERT INTO t2 VALUES('value one', 'one');
   164    }]
   165    set ccdel2 [capture_changeset { DELETE FROM t2; }]
   166    set cc [capture_changeset {
   167      INSERT INTO t1 VALUES('one', 'value one');
   168      INSERT INTO t2 VALUES('value one', 'one');
   169    }]
   170    set ccdel [capture_changeset {
   171      DELETE FROM t1;
   172      DELETE FROM t2;
   173    }]
   174    set {} {}
   175  } {}
   176  
   177  # Now modify the database schema to create a cyclic foreign key dependency
   178  # between tables t1 and t2. This means that although changesets $cc and
   179  # $ccdel can be applied, none of the others may without violating the
   180  # foreign key constraints. 
   181  # 
   182  do_test 3.3 {
   183  
   184    drop_all_tables
   185    execsql {
   186      CREATE TABLE t1(a PRIMARY KEY, b REFERENCES t2);
   187      CREATE TABLE t2(x PRIMARY KEY, y REFERENCES t1);
   188    }
   189  
   190  
   191    proc conflict_handler {args} { return "ABORT" }
   192    sqlite3changeset_apply db $cc conflict_handler
   193  
   194    execsql {
   195      SELECT * FROM t1;
   196      SELECT * FROM t2;
   197    }
   198  } {one {value one} {value one} one}
   199  
   200  do_test 3.3.1 {
   201    list [catch {sqlite3changeset_apply db $::ccdel1 conflict_handler} msg] $msg
   202  } {1 SQLITE_CONSTRAINT}
   203  
   204  do_test 3.3.2 {
   205    list [catch {sqlite3changeset_apply db $::ccdel2 conflict_handler} msg] $msg
   206  } {1 SQLITE_CONSTRAINT}
   207  
   208  do_test 3.3.4.1 {
   209    list [catch {sqlite3changeset_apply db $::ccdel conflict_handler} msg] $msg
   210  } {0 {}}
   211  do_execsql_test 3.3.4.2 {
   212    SELECT * FROM t1;
   213    SELECT * FROM t2;
   214  } {}
   215  
   216  do_test 3.5.1 {
   217    list [catch {sqlite3changeset_apply db $::cc1 conflict_handler} msg] $msg
   218  } {1 SQLITE_CONSTRAINT}
   219  do_test 3.5.2 {
   220    list [catch {sqlite3changeset_apply db $::cc2 conflict_handler} msg] $msg
   221  } {1 SQLITE_CONSTRAINT}
   222  
   223  #--------------------------------------------------------------------
   224  # Test that if a change that affects FK processing is not applied 
   225  # due to a separate constraint, SQLite does not get confused and
   226  # increment FK counters anyway.
   227  #
   228  drop_all_tables
   229  do_execsql_test 4.1 {
   230    CREATE TABLE p1(x PRIMARY KEY, y);
   231    CREATE TABLE c1(a PRIMARY KEY, b REFERENCES p1);
   232    INSERT INTO p1 VALUES(1,1);
   233  }
   234  
   235  do_execsql_test 4.2.1 {
   236    BEGIN;
   237      PRAGMA defer_foreign_keys = 1;
   238      INSERT INTO c1 VALUES('x', 'x');
   239  }
   240  do_catchsql_test 4.2.2 { COMMIT } {1 {FOREIGN KEY constraint failed}}
   241  do_catchsql_test 4.2.3 { ROLLBACK } {0 {}}
   242  
   243  do_execsql_test 4.3.1 {
   244    BEGIN;
   245      PRAGMA defer_foreign_keys = 1;
   246      INSERT INTO c1 VALUES(1, 1);
   247  }
   248  do_catchsql_test 4.3.2 { 
   249    INSERT INTO c1 VALUES(1, 'x') 
   250  } {1 {UNIQUE constraint failed: c1.a}}
   251  
   252  do_catchsql_test 4.3.3 { COMMIT } {0 {}}
   253  do_catchsql_test 4.3.4 { BEGIN ; COMMIT } {0 {}}
   254  
   255  #--------------------------------------------------------------------
   256  # Test that if a DELETE change cannot be applied due to an 
   257  # SQLITE_CONSTRAINT error thrown by a trigger program, things do not
   258  # go awry.
   259  
   260  drop_all_tables
   261  reset_db
   262  do_execsql_test 5.1 {
   263    CREATE TABLE x1(x PRIMARY KEY, y);
   264    CREATE TABLE x2(x PRIMARY KEY, y);
   265    INSERT INTO x2 VALUES(1, 1);
   266    INSERT INTO x1 VALUES(1, 1);
   267  }
   268  
   269  set ::cc [changeset_from_sql { DELETE FROM x1; }]
   270  
   271  do_execsql_test 5.2 {
   272    INSERT INTO x1 VALUES(1, 1);
   273    CREATE TRIGGER tr1 AFTER DELETE ON x1 BEGIN
   274      INSERT INTO x2 VALUES(old.x, old.y);
   275    END;
   276  } {}
   277  
   278  proc conflict_handler {args} { return "ABORT" }
   279  do_test 5.3 {
   280    list [catch {sqlite3changeset_apply db $::cc conflict_handler} msg] $msg
   281  } {1 SQLITE_ABORT}
   282  
   283  do_execsql_test 5.4 {
   284    SELECT * FROM X1;
   285  } {1 1}
   286  
   287  finish_test