# 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 attach *
    execsql $sql1 db
    set c1 [S1 changeset]
    S1 delete

    if {$i==1} {
      sqlite3session S2 db2 main
      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 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 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
