mirror of
https://github.com/sqlite/sqlite.git
synced 2025-11-12 13:01:09 +03:00
Add APIs to the sessions module for "rebasing" changesets.
FossilOrigin-Name: 509506c76b7c104961826721013889d6c6b2ed9b563dcd029e0cb5cb5c34693a
This commit is contained in:
@@ -169,3 +169,4 @@ proc changeset_to_list {c} {
|
|||||||
sqlite3session_foreach elem $c { lappend list $elem }
|
sqlite3session_foreach elem $c { lappend list $elem }
|
||||||
lsort $list
|
lsort $list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ source $testdir/tester.tcl
|
|||||||
ifcapable !session {finish_test; return}
|
ifcapable !session {finish_test; return}
|
||||||
set testprefix sessionfault2
|
set testprefix sessionfault2
|
||||||
|
|
||||||
|
if 1 {
|
||||||
|
|
||||||
do_execsql_test 1.0.0 {
|
do_execsql_test 1.0.0 {
|
||||||
CREATE TABLE t1(a PRIMARY KEY, b UNIQUE);
|
CREATE TABLE t1(a PRIMARY KEY, b UNIQUE);
|
||||||
INSERT INTO t1 VALUES(1, 1);
|
INSERT INTO t1 VALUES(1, 1);
|
||||||
@@ -103,5 +105,180 @@ do_faultsim_test 2 -faults oom-p* -prep {
|
|||||||
faultsim_integrity_check
|
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 {
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
finish_test
|
||||||
|
|
||||||
|
|||||||
477
ext/session/sessionrebase.test
Normal file
477
ext/session/sessionrebase.test
Normal file
@@ -0,0 +1,477 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
@@ -232,8 +232,8 @@ struct SessionTable {
|
|||||||
** statement.
|
** statement.
|
||||||
**
|
**
|
||||||
** For a DELETE change, all fields within the record except those associated
|
** For a DELETE change, all fields within the record except those associated
|
||||||
** with PRIMARY KEY columns are set to "undefined". The PRIMARY KEY fields
|
** with PRIMARY KEY columns are omitted. The PRIMARY KEY fields contain the
|
||||||
** contain the values identifying the row to delete.
|
** values identifying the row to delete.
|
||||||
**
|
**
|
||||||
** For an UPDATE change, all fields except those associated with PRIMARY KEY
|
** For an UPDATE change, all fields except those associated with PRIMARY KEY
|
||||||
** columns and columns that are modified by the UPDATE are set to "undefined".
|
** columns and columns that are modified by the UPDATE are set to "undefined".
|
||||||
@@ -516,7 +516,7 @@ static int sessionPreupdateHash(
|
|||||||
static int sessionSerialLen(u8 *a){
|
static int sessionSerialLen(u8 *a){
|
||||||
int e = *a;
|
int e = *a;
|
||||||
int n;
|
int n;
|
||||||
if( e==0 ) return 1;
|
if( e==0 || e==0xFF ) return 1;
|
||||||
if( e==SQLITE_NULL ) return 1;
|
if( e==SQLITE_NULL ) return 1;
|
||||||
if( e==SQLITE_INTEGER || e==SQLITE_FLOAT ) return 9;
|
if( e==SQLITE_INTEGER || e==SQLITE_FLOAT ) return 9;
|
||||||
return sessionVarintGet(&a[1], &n) + 1 + n;
|
return sessionVarintGet(&a[1], &n) + 1 + n;
|
||||||
@@ -596,7 +596,7 @@ static int sessionChangeEqual(
|
|||||||
int n1 = sessionSerialLen(a1);
|
int n1 = sessionSerialLen(a1);
|
||||||
int n2 = sessionSerialLen(a2);
|
int n2 = sessionSerialLen(a2);
|
||||||
|
|
||||||
if( pTab->abPK[iCol] && (n1!=n2 || memcmp(a1, a2, n1)) ){
|
if( n1!=n2 || memcmp(a1, a2, n1) ){
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
a1 += n1;
|
a1 += n1;
|
||||||
@@ -2183,6 +2183,7 @@ static int sessionSelectStmt(
|
|||||||
"SELECT tbl, ?2, stat FROM %Q.sqlite_stat1 WHERE tbl IS ?1 AND "
|
"SELECT tbl, ?2, stat FROM %Q.sqlite_stat1 WHERE tbl IS ?1 AND "
|
||||||
"idx IS (CASE WHEN ?2=X'' THEN NULL ELSE ?2 END)", zDb
|
"idx IS (CASE WHEN ?2=X'' THEN NULL ELSE ?2 END)", zDb
|
||||||
);
|
);
|
||||||
|
if( zSql==0 ) rc = SQLITE_NOMEM;
|
||||||
}else{
|
}else{
|
||||||
int i;
|
int i;
|
||||||
const char *zSep = "";
|
const char *zSep = "";
|
||||||
@@ -2918,7 +2919,8 @@ static int sessionChangesetReadTblhdr(sqlite3_changeset_iter *p){
|
|||||||
static int sessionChangesetNext(
|
static int sessionChangesetNext(
|
||||||
sqlite3_changeset_iter *p, /* Changeset iterator */
|
sqlite3_changeset_iter *p, /* Changeset iterator */
|
||||||
u8 **paRec, /* If non-NULL, store record pointer here */
|
u8 **paRec, /* If non-NULL, store record pointer here */
|
||||||
int *pnRec /* If non-NULL, store size of record here */
|
int *pnRec, /* If non-NULL, store size of record here */
|
||||||
|
int *pbNew /* If non-NULL, true if new table */
|
||||||
){
|
){
|
||||||
int i;
|
int i;
|
||||||
u8 op;
|
u8 op;
|
||||||
@@ -2953,6 +2955,7 @@ static int sessionChangesetNext(
|
|||||||
|
|
||||||
op = p->in.aData[p->in.iNext++];
|
op = p->in.aData[p->in.iNext++];
|
||||||
while( op=='T' || op=='P' ){
|
while( op=='T' || op=='P' ){
|
||||||
|
if( pbNew ) *pbNew = 1;
|
||||||
p->bPatchset = (op=='P');
|
p->bPatchset = (op=='P');
|
||||||
if( sessionChangesetReadTblhdr(p) ) return p->rc;
|
if( sessionChangesetReadTblhdr(p) ) return p->rc;
|
||||||
if( (p->rc = sessionInputBuffer(&p->in, 2)) ) return p->rc;
|
if( (p->rc = sessionInputBuffer(&p->in, 2)) ) return p->rc;
|
||||||
@@ -3031,7 +3034,7 @@ static int sessionChangesetNext(
|
|||||||
** callback by changeset_apply().
|
** callback by changeset_apply().
|
||||||
*/
|
*/
|
||||||
int sqlite3changeset_next(sqlite3_changeset_iter *p){
|
int sqlite3changeset_next(sqlite3_changeset_iter *p){
|
||||||
return sessionChangesetNext(p, 0, 0);
|
return sessionChangesetNext(p, 0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -3410,6 +3413,8 @@ struct SessionApplyCtx {
|
|||||||
int bStat1; /* True if table is sqlite_stat1 */
|
int bStat1; /* True if table is sqlite_stat1 */
|
||||||
int bDeferConstraints; /* True to defer constraints */
|
int bDeferConstraints; /* True to defer constraints */
|
||||||
SessionBuffer constraints; /* Deferred constraints are stored here */
|
SessionBuffer constraints; /* Deferred constraints are stored here */
|
||||||
|
SessionBuffer rebase; /* Rebase information (if any) here */
|
||||||
|
int bRebaseStarted; /* If table header is already in rebase */
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -3676,7 +3681,6 @@ static int sessionStat1Sql(sqlite3 *db, SessionApplyCtx *p){
|
|||||||
"AND (?4 OR stat IS ?3)"
|
"AND (?4 OR stat IS ?3)"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
assert( rc==SQLITE_OK );
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3791,6 +3795,54 @@ static int sessionSeekToRow(
|
|||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** This function is called from within sqlite3changset_apply_v2() when
|
||||||
|
** a conflict is encountered and resolved using conflict resolution
|
||||||
|
** mode eType (either SQLITE_CHANGESET_OMIT or SQLITE_CHANGESET_REPLACE)..
|
||||||
|
** It adds a conflict resolution record to the buffer in
|
||||||
|
** SessionApplyCtx.rebase, which will eventually be returned to the caller
|
||||||
|
** of apply_v2() as the "rebase" buffer.
|
||||||
|
**
|
||||||
|
** Return SQLITE_OK if successful, or an SQLite error code otherwise.
|
||||||
|
*/
|
||||||
|
static int sessionRebaseAdd(
|
||||||
|
SessionApplyCtx *p, /* Apply context */
|
||||||
|
int eType, /* Conflict resolution (OMIT or REPLACE) */
|
||||||
|
sqlite3_changeset_iter *pIter /* Iterator pointing at current change */
|
||||||
|
){
|
||||||
|
int rc = SQLITE_OK;
|
||||||
|
int i;
|
||||||
|
int eOp = pIter->op;
|
||||||
|
if( p->bRebaseStarted==0 ){
|
||||||
|
/* Append a table-header to the rebase buffer */
|
||||||
|
const char *zTab = pIter->zTab;
|
||||||
|
sessionAppendByte(&p->rebase, 'T', &rc);
|
||||||
|
sessionAppendVarint(&p->rebase, p->nCol, &rc);
|
||||||
|
sessionAppendBlob(&p->rebase, p->abPK, p->nCol, &rc);
|
||||||
|
sessionAppendBlob(&p->rebase, (u8*)zTab, (int)strlen(zTab)+1, &rc);
|
||||||
|
p->bRebaseStarted = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert( eType==SQLITE_CHANGESET_REPLACE||eType==SQLITE_CHANGESET_OMIT );
|
||||||
|
assert( eOp==SQLITE_DELETE || eOp==SQLITE_INSERT || eOp==SQLITE_UPDATE );
|
||||||
|
|
||||||
|
sessionAppendByte(&p->rebase,
|
||||||
|
(eOp==SQLITE_DELETE ? SQLITE_DELETE : SQLITE_INSERT), &rc
|
||||||
|
);
|
||||||
|
sessionAppendByte(&p->rebase, (eType==SQLITE_CHANGESET_REPLACE), &rc);
|
||||||
|
for(i=0; i<p->nCol; i++){
|
||||||
|
sqlite3_value *pVal = 0;
|
||||||
|
if( eOp==SQLITE_DELETE || (eOp==SQLITE_UPDATE && p->abPK[i]) ){
|
||||||
|
sqlite3changeset_old(pIter, i, &pVal);
|
||||||
|
}else{
|
||||||
|
sqlite3changeset_new(pIter, i, &pVal);
|
||||||
|
}
|
||||||
|
sessionAppendValue(&p->rebase, pVal, &rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** Invoke the conflict handler for the change that the changeset iterator
|
** Invoke the conflict handler for the change that the changeset iterator
|
||||||
** currently points to.
|
** currently points to.
|
||||||
@@ -3866,7 +3918,7 @@ static int sessionConflictHandler(
|
|||||||
u8 *aBlob = &pIter->in.aData[pIter->in.iCurrent];
|
u8 *aBlob = &pIter->in.aData[pIter->in.iCurrent];
|
||||||
int nBlob = pIter->in.iNext - pIter->in.iCurrent;
|
int nBlob = pIter->in.iNext - pIter->in.iCurrent;
|
||||||
sessionAppendBlob(&p->constraints, aBlob, nBlob, &rc);
|
sessionAppendBlob(&p->constraints, aBlob, nBlob, &rc);
|
||||||
res = SQLITE_CHANGESET_OMIT;
|
return SQLITE_OK;
|
||||||
}else{
|
}else{
|
||||||
/* No other row with the new.* primary key. */
|
/* No other row with the new.* primary key. */
|
||||||
res = xConflict(pCtx, eType+1, pIter);
|
res = xConflict(pCtx, eType+1, pIter);
|
||||||
@@ -3892,6 +3944,9 @@ static int sessionConflictHandler(
|
|||||||
rc = SQLITE_MISUSE;
|
rc = SQLITE_MISUSE;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if( rc==SQLITE_OK ){
|
||||||
|
rc = sessionRebaseAdd(p, res, pIter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
@@ -4067,8 +4122,7 @@ static int sessionApplyOneWithRetry(
|
|||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
rc = sessionApplyOneOp(pIter, pApply, xConflict, pCtx, &bReplace, &bRetry);
|
rc = sessionApplyOneOp(pIter, pApply, xConflict, pCtx, &bReplace, &bRetry);
|
||||||
assert( rc==SQLITE_OK || (bRetry==0 && bReplace==0) );
|
if( rc==SQLITE_OK ){
|
||||||
|
|
||||||
/* If the bRetry flag is set, the change has not been applied due to an
|
/* If the bRetry flag is set, the change has not been applied due to an
|
||||||
** SQLITE_CHANGESET_DATA problem (i.e. this is an UPDATE or DELETE and
|
** SQLITE_CHANGESET_DATA problem (i.e. this is an UPDATE or DELETE and
|
||||||
** a row with the correct PK is present in the db, but one or more other
|
** a row with the correct PK is present in the db, but one or more other
|
||||||
@@ -4105,6 +4159,7 @@ static int sessionApplyOneWithRetry(
|
|||||||
rc = sqlite3_exec(db, "RELEASE replace_op", 0, 0, 0);
|
rc = sqlite3_exec(db, "RELEASE replace_op", 0, 0, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
@@ -4178,7 +4233,8 @@ static int sessionChangesetApply(
|
|||||||
int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
|
int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
|
||||||
sqlite3_changeset_iter *p /* Handle describing change and conflict */
|
sqlite3_changeset_iter *p /* Handle describing change and conflict */
|
||||||
),
|
),
|
||||||
void *pCtx /* First argument passed to xConflict */
|
void *pCtx, /* First argument passed to xConflict */
|
||||||
|
void **ppRebase, int *pnRebase /* OUT: Rebase information */
|
||||||
){
|
){
|
||||||
int schemaMismatch = 0;
|
int schemaMismatch = 0;
|
||||||
int rc; /* Return code */
|
int rc; /* Return code */
|
||||||
@@ -4216,9 +4272,18 @@ static int sessionChangesetApply(
|
|||||||
sqlite3_finalize(sApply.pUpdate);
|
sqlite3_finalize(sApply.pUpdate);
|
||||||
sqlite3_finalize(sApply.pInsert);
|
sqlite3_finalize(sApply.pInsert);
|
||||||
sqlite3_finalize(sApply.pSelect);
|
sqlite3_finalize(sApply.pSelect);
|
||||||
memset(&sApply, 0, sizeof(sApply));
|
|
||||||
sApply.db = db;
|
sApply.db = db;
|
||||||
|
sApply.pDelete = 0;
|
||||||
|
sApply.pUpdate = 0;
|
||||||
|
sApply.pInsert = 0;
|
||||||
|
sApply.pSelect = 0;
|
||||||
|
sApply.nCol = 0;
|
||||||
|
sApply.azCol = 0;
|
||||||
|
sApply.abPK = 0;
|
||||||
|
sApply.bStat1 = 0;
|
||||||
sApply.bDeferConstraints = 1;
|
sApply.bDeferConstraints = 1;
|
||||||
|
sApply.bRebaseStarted = 0;
|
||||||
|
memset(&sApply.constraints, 0, sizeof(SessionBuffer));
|
||||||
|
|
||||||
/* If an xFilter() callback was specified, invoke it now. If the
|
/* If an xFilter() callback was specified, invoke it now. If the
|
||||||
** xFilter callback returns zero, skip this table. If it returns
|
** xFilter callback returns zero, skip this table. If it returns
|
||||||
@@ -4328,16 +4393,52 @@ static int sessionChangesetApply(
|
|||||||
sqlite3_exec(db, "RELEASE changeset_apply", 0, 0, 0);
|
sqlite3_exec(db, "RELEASE changeset_apply", 0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( rc==SQLITE_OK && bPatchset==0 && ppRebase && pnRebase ){
|
||||||
|
*ppRebase = (void*)sApply.rebase.aBuf;
|
||||||
|
*pnRebase = sApply.rebase.nBuf;
|
||||||
|
sApply.rebase.aBuf = 0;
|
||||||
|
}
|
||||||
sqlite3_finalize(sApply.pInsert);
|
sqlite3_finalize(sApply.pInsert);
|
||||||
sqlite3_finalize(sApply.pDelete);
|
sqlite3_finalize(sApply.pDelete);
|
||||||
sqlite3_finalize(sApply.pUpdate);
|
sqlite3_finalize(sApply.pUpdate);
|
||||||
sqlite3_finalize(sApply.pSelect);
|
sqlite3_finalize(sApply.pSelect);
|
||||||
sqlite3_free((char*)sApply.azCol); /* cast works around VC++ bug */
|
sqlite3_free((char*)sApply.azCol); /* cast works around VC++ bug */
|
||||||
sqlite3_free((char*)sApply.constraints.aBuf);
|
sqlite3_free((char*)sApply.constraints.aBuf);
|
||||||
|
sqlite3_free((char*)sApply.rebase.aBuf);
|
||||||
sqlite3_mutex_leave(sqlite3_db_mutex(db));
|
sqlite3_mutex_leave(sqlite3_db_mutex(db));
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Apply the changeset passed via pChangeset/nChangeset to the main
|
||||||
|
** database attached to handle "db".
|
||||||
|
*/
|
||||||
|
int sqlite3changeset_apply_v2(
|
||||||
|
sqlite3 *db, /* Apply change to "main" db of this handle */
|
||||||
|
int nChangeset, /* Size of changeset in bytes */
|
||||||
|
void *pChangeset, /* Changeset blob */
|
||||||
|
int(*xFilter)(
|
||||||
|
void *pCtx, /* Copy of sixth arg to _apply() */
|
||||||
|
const char *zTab /* Table name */
|
||||||
|
),
|
||||||
|
int(*xConflict)(
|
||||||
|
void *pCtx, /* Copy of sixth arg to _apply() */
|
||||||
|
int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
|
||||||
|
sqlite3_changeset_iter *p /* Handle describing change and conflict */
|
||||||
|
),
|
||||||
|
void *pCtx, /* First argument passed to xConflict */
|
||||||
|
void **ppRebase, int *pnRebase
|
||||||
|
){
|
||||||
|
sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */
|
||||||
|
int rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset);
|
||||||
|
if( rc==SQLITE_OK ){
|
||||||
|
rc = sessionChangesetApply(
|
||||||
|
db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** Apply the changeset passed via pChangeset/nChangeset to the main database
|
** Apply the changeset passed via pChangeset/nChangeset to the main database
|
||||||
** attached to handle "db". Invoke the supplied conflict handler callback
|
** attached to handle "db". Invoke the supplied conflict handler callback
|
||||||
@@ -4358,12 +4459,9 @@ int sqlite3changeset_apply(
|
|||||||
),
|
),
|
||||||
void *pCtx /* First argument passed to xConflict */
|
void *pCtx /* First argument passed to xConflict */
|
||||||
){
|
){
|
||||||
sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */
|
return sqlite3changeset_apply_v2(
|
||||||
int rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset);
|
db, nChangeset, pChangeset, xFilter, xConflict, pCtx, 0, 0
|
||||||
if( rc==SQLITE_OK ){
|
);
|
||||||
rc = sessionChangesetApply(db, pIter, xFilter, xConflict, pCtx);
|
|
||||||
}
|
|
||||||
return rc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -4371,6 +4469,31 @@ int sqlite3changeset_apply(
|
|||||||
** attached to handle "db". Invoke the supplied conflict handler callback
|
** attached to handle "db". Invoke the supplied conflict handler callback
|
||||||
** to resolve any conflicts encountered while applying the change.
|
** to resolve any conflicts encountered while applying the change.
|
||||||
*/
|
*/
|
||||||
|
int sqlite3changeset_apply_v2_strm(
|
||||||
|
sqlite3 *db, /* Apply change to "main" db of this handle */
|
||||||
|
int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */
|
||||||
|
void *pIn, /* First arg for xInput */
|
||||||
|
int(*xFilter)(
|
||||||
|
void *pCtx, /* Copy of sixth arg to _apply() */
|
||||||
|
const char *zTab /* Table name */
|
||||||
|
),
|
||||||
|
int(*xConflict)(
|
||||||
|
void *pCtx, /* Copy of sixth arg to _apply() */
|
||||||
|
int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
|
||||||
|
sqlite3_changeset_iter *p /* Handle describing change and conflict */
|
||||||
|
),
|
||||||
|
void *pCtx, /* First argument passed to xConflict */
|
||||||
|
void **ppRebase, int *pnRebase
|
||||||
|
){
|
||||||
|
sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */
|
||||||
|
int rc = sqlite3changeset_start_strm(&pIter, xInput, pIn);
|
||||||
|
if( rc==SQLITE_OK ){
|
||||||
|
rc = sessionChangesetApply(
|
||||||
|
db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
int sqlite3changeset_apply_strm(
|
int sqlite3changeset_apply_strm(
|
||||||
sqlite3 *db, /* Apply change to "main" db of this handle */
|
sqlite3 *db, /* Apply change to "main" db of this handle */
|
||||||
int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */
|
int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */
|
||||||
@@ -4386,12 +4509,9 @@ int sqlite3changeset_apply_strm(
|
|||||||
),
|
),
|
||||||
void *pCtx /* First argument passed to xConflict */
|
void *pCtx /* First argument passed to xConflict */
|
||||||
){
|
){
|
||||||
sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */
|
return sqlite3changeset_apply_v2_strm(
|
||||||
int rc = sqlite3changeset_start_strm(&pIter, xInput, pIn);
|
db, xInput, pIn, xFilter, xConflict, pCtx, 0, 0
|
||||||
if( rc==SQLITE_OK ){
|
);
|
||||||
rc = sessionChangesetApply(db, pIter, xFilter, xConflict, pCtx);
|
|
||||||
}
|
|
||||||
return rc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -4410,6 +4530,7 @@ struct sqlite3_changegroup {
|
|||||||
*/
|
*/
|
||||||
static int sessionChangeMerge(
|
static int sessionChangeMerge(
|
||||||
SessionTable *pTab, /* Table structure */
|
SessionTable *pTab, /* Table structure */
|
||||||
|
int bRebase, /* True for a rebase hash-table */
|
||||||
int bPatchset, /* True for patchsets */
|
int bPatchset, /* True for patchsets */
|
||||||
SessionChange *pExist, /* Existing change */
|
SessionChange *pExist, /* Existing change */
|
||||||
int op2, /* Second change operation */
|
int op2, /* Second change operation */
|
||||||
@@ -4419,6 +4540,7 @@ static int sessionChangeMerge(
|
|||||||
SessionChange **ppNew /* OUT: Merged change */
|
SessionChange **ppNew /* OUT: Merged change */
|
||||||
){
|
){
|
||||||
SessionChange *pNew = 0;
|
SessionChange *pNew = 0;
|
||||||
|
int rc = SQLITE_OK;
|
||||||
|
|
||||||
if( !pExist ){
|
if( !pExist ){
|
||||||
pNew = (SessionChange *)sqlite3_malloc(sizeof(SessionChange) + nRec);
|
pNew = (SessionChange *)sqlite3_malloc(sizeof(SessionChange) + nRec);
|
||||||
@@ -4428,9 +4550,66 @@ static int sessionChangeMerge(
|
|||||||
memset(pNew, 0, sizeof(SessionChange));
|
memset(pNew, 0, sizeof(SessionChange));
|
||||||
pNew->op = op2;
|
pNew->op = op2;
|
||||||
pNew->bIndirect = bIndirect;
|
pNew->bIndirect = bIndirect;
|
||||||
pNew->nRecord = nRec;
|
|
||||||
pNew->aRecord = (u8*)&pNew[1];
|
pNew->aRecord = (u8*)&pNew[1];
|
||||||
|
if( bIndirect==0 || bRebase==0 ){
|
||||||
|
pNew->nRecord = nRec;
|
||||||
memcpy(pNew->aRecord, aRec, nRec);
|
memcpy(pNew->aRecord, aRec, nRec);
|
||||||
|
}else{
|
||||||
|
int i;
|
||||||
|
u8 *pIn = aRec;
|
||||||
|
u8 *pOut = pNew->aRecord;
|
||||||
|
for(i=0; i<pTab->nCol; i++){
|
||||||
|
int nIn = sessionSerialLen(pIn);
|
||||||
|
if( *pIn==0 ){
|
||||||
|
*pOut++ = 0;
|
||||||
|
}else if( pTab->abPK[i]==0 ){
|
||||||
|
*pOut++ = 0xFF;
|
||||||
|
}else{
|
||||||
|
memcpy(pOut, pIn, nIn);
|
||||||
|
pOut += nIn;
|
||||||
|
}
|
||||||
|
pIn += nIn;
|
||||||
|
}
|
||||||
|
pNew->nRecord = pOut - pNew->aRecord;
|
||||||
|
}
|
||||||
|
}else if( bRebase ){
|
||||||
|
if( pExist->op==SQLITE_DELETE && pExist->bIndirect ){
|
||||||
|
*ppNew = pExist;
|
||||||
|
}else{
|
||||||
|
int nByte = nRec + pExist->nRecord + sizeof(SessionChange);
|
||||||
|
pNew = (SessionChange*)sqlite3_malloc(nByte);
|
||||||
|
if( pNew==0 ){
|
||||||
|
rc = SQLITE_NOMEM;
|
||||||
|
}else{
|
||||||
|
int i;
|
||||||
|
u8 *a1 = pExist->aRecord;
|
||||||
|
u8 *a2 = aRec;
|
||||||
|
u8 *pOut;
|
||||||
|
|
||||||
|
memset(pNew, 0, nByte);
|
||||||
|
pNew->bIndirect = bIndirect || pExist->bIndirect;
|
||||||
|
pNew->op = op2;
|
||||||
|
pOut = pNew->aRecord = (u8*)&pNew[1];
|
||||||
|
|
||||||
|
for(i=0; i<pTab->nCol; i++){
|
||||||
|
int n1 = sessionSerialLen(a1);
|
||||||
|
int n2 = sessionSerialLen(a2);
|
||||||
|
if( *a1==0xFF || (pTab->abPK[i]==0 && bIndirect) ){
|
||||||
|
*pOut++ = 0xFF;
|
||||||
|
}else if( *a2==0 ){
|
||||||
|
memcpy(pOut, a1, n1);
|
||||||
|
pOut += n1;
|
||||||
|
}else{
|
||||||
|
memcpy(pOut, a2, n2);
|
||||||
|
pOut += n2;
|
||||||
|
}
|
||||||
|
a1 += n1;
|
||||||
|
a2 += n2;
|
||||||
|
}
|
||||||
|
pNew->nRecord = pOut - pNew->aRecord;
|
||||||
|
}
|
||||||
|
sqlite3_free(pExist);
|
||||||
|
}
|
||||||
}else{
|
}else{
|
||||||
int op1 = pExist->op;
|
int op1 = pExist->op;
|
||||||
|
|
||||||
@@ -4524,7 +4703,7 @@ static int sessionChangeMerge(
|
|||||||
}
|
}
|
||||||
|
|
||||||
*ppNew = pNew;
|
*ppNew = pNew;
|
||||||
return SQLITE_OK;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -4533,15 +4712,15 @@ static int sessionChangeMerge(
|
|||||||
*/
|
*/
|
||||||
static int sessionChangesetToHash(
|
static int sessionChangesetToHash(
|
||||||
sqlite3_changeset_iter *pIter, /* Iterator to read from */
|
sqlite3_changeset_iter *pIter, /* Iterator to read from */
|
||||||
sqlite3_changegroup *pGrp /* Changegroup object to add changeset to */
|
sqlite3_changegroup *pGrp, /* Changegroup object to add changeset to */
|
||||||
|
int bRebase /* True if hash table is for rebasing */
|
||||||
){
|
){
|
||||||
u8 *aRec;
|
u8 *aRec;
|
||||||
int nRec;
|
int nRec;
|
||||||
int rc = SQLITE_OK;
|
int rc = SQLITE_OK;
|
||||||
SessionTable *pTab = 0;
|
SessionTable *pTab = 0;
|
||||||
|
|
||||||
|
while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec, 0) ){
|
||||||
while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec) ){
|
|
||||||
const char *zNew;
|
const char *zNew;
|
||||||
int nCol;
|
int nCol;
|
||||||
int op;
|
int op;
|
||||||
@@ -4621,7 +4800,7 @@ static int sessionChangesetToHash(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rc = sessionChangeMerge(pTab,
|
rc = sessionChangeMerge(pTab, bRebase,
|
||||||
pIter->bPatchset, pExist, op, bIndirect, aRec, nRec, &pChange
|
pIter->bPatchset, pExist, op, bIndirect, aRec, nRec, &pChange
|
||||||
);
|
);
|
||||||
if( rc ) break;
|
if( rc ) break;
|
||||||
@@ -4729,7 +4908,7 @@ int sqlite3changegroup_add(sqlite3_changegroup *pGrp, int nData, void *pData){
|
|||||||
|
|
||||||
rc = sqlite3changeset_start(&pIter, nData, pData);
|
rc = sqlite3changeset_start(&pIter, nData, pData);
|
||||||
if( rc==SQLITE_OK ){
|
if( rc==SQLITE_OK ){
|
||||||
rc = sessionChangesetToHash(pIter, pGrp);
|
rc = sessionChangesetToHash(pIter, pGrp, 0);
|
||||||
}
|
}
|
||||||
sqlite3changeset_finalize(pIter);
|
sqlite3changeset_finalize(pIter);
|
||||||
return rc;
|
return rc;
|
||||||
@@ -4760,7 +4939,7 @@ int sqlite3changegroup_add_strm(
|
|||||||
|
|
||||||
rc = sqlite3changeset_start_strm(&pIter, xInput, pIn);
|
rc = sqlite3changeset_start_strm(&pIter, xInput, pIn);
|
||||||
if( rc==SQLITE_OK ){
|
if( rc==SQLITE_OK ){
|
||||||
rc = sessionChangesetToHash(pIter, pGrp);
|
rc = sessionChangesetToHash(pIter, pGrp, 0);
|
||||||
}
|
}
|
||||||
sqlite3changeset_finalize(pIter);
|
sqlite3changeset_finalize(pIter);
|
||||||
return rc;
|
return rc;
|
||||||
@@ -4845,4 +5024,347 @@ int sqlite3changeset_concat_strm(
|
|||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Changeset rebaser handle.
|
||||||
|
*/
|
||||||
|
struct sqlite3_rebaser {
|
||||||
|
sqlite3_changegroup grp; /* Hash table */
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Buffers a1 and a2 must both contain a sessions module record nCol
|
||||||
|
** fields in size. This function appends an nCol sessions module
|
||||||
|
** record to buffer pBuf that is a copy of a1, except that for
|
||||||
|
** each field that is undefined in a1[], swap in the field from a2[].
|
||||||
|
*/
|
||||||
|
static void sessionAppendRecordMerge(
|
||||||
|
SessionBuffer *pBuf, /* Buffer to append to */
|
||||||
|
int nCol, /* Number of columns in each record */
|
||||||
|
u8 *a1, int n1, /* Record 1 */
|
||||||
|
u8 *a2, int n2, /* Record 2 */
|
||||||
|
int *pRc /* IN/OUT: error code */
|
||||||
|
){
|
||||||
|
sessionBufferGrow(pBuf, n1+n2, pRc);
|
||||||
|
if( *pRc==SQLITE_OK ){
|
||||||
|
int i;
|
||||||
|
u8 *pOut = &pBuf->aBuf[pBuf->nBuf];
|
||||||
|
for(i=0; i<nCol; i++){
|
||||||
|
int nn1 = sessionSerialLen(a1);
|
||||||
|
int nn2 = sessionSerialLen(a2);
|
||||||
|
if( *a1==0 || *a1==0xFF ){
|
||||||
|
memcpy(pOut, a2, nn2);
|
||||||
|
pOut += nn2;
|
||||||
|
}else{
|
||||||
|
memcpy(pOut, a1, nn1);
|
||||||
|
pOut += nn1;
|
||||||
|
}
|
||||||
|
a1 += nn1;
|
||||||
|
a2 += nn2;
|
||||||
|
}
|
||||||
|
|
||||||
|
pBuf->nBuf = pOut-pBuf->aBuf;
|
||||||
|
assert( pBuf->nBuf<=pBuf->nAlloc );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** This function is called when rebasing a local UPDATE change against one
|
||||||
|
** or more remote UPDATE changes. The aRec/nRec buffer contains the current
|
||||||
|
** old.* and new.* records for the change. The rebase buffer (a single
|
||||||
|
** record) is in aChange/nChange. The rebased change is appended to buffer
|
||||||
|
** pBuf.
|
||||||
|
**
|
||||||
|
** Rebasing the UPDATE involves:
|
||||||
|
**
|
||||||
|
** * Removing any changes to fields for which the corresponding field
|
||||||
|
** in the rebase buffer is set to "replaced" (type 0xFF). If this
|
||||||
|
** means the UPDATE change updates no fields, nothing is appended
|
||||||
|
** to the output buffer.
|
||||||
|
**
|
||||||
|
** * For each field modified by the local change for which the
|
||||||
|
** corresponding field in the rebase buffer is not "undefined" (0x00)
|
||||||
|
** or "replaced" (0xFF), the old.* value is replaced by the value
|
||||||
|
** in the rebase buffer.
|
||||||
|
*/
|
||||||
|
static void sessionAppendPartialUpdate(
|
||||||
|
SessionBuffer *pBuf, /* Append record here */
|
||||||
|
sqlite3_changeset_iter *pIter, /* Iterator pointed at local change */
|
||||||
|
u8 *aRec, int nRec, /* Local change */
|
||||||
|
u8 *aChange, int nChange, /* Record to rebase against */
|
||||||
|
int *pRc /* IN/OUT: Return Code */
|
||||||
|
){
|
||||||
|
sessionBufferGrow(pBuf, 2+nRec+nChange, pRc);
|
||||||
|
if( *pRc==SQLITE_OK ){
|
||||||
|
int bData = 0;
|
||||||
|
u8 *pOut = &pBuf->aBuf[pBuf->nBuf];
|
||||||
|
int i;
|
||||||
|
u8 *a1 = aRec;
|
||||||
|
u8 *a2 = aChange;
|
||||||
|
|
||||||
|
*pOut++ = SQLITE_UPDATE;
|
||||||
|
*pOut++ = pIter->bIndirect;
|
||||||
|
for(i=0; i<pIter->nCol; i++){
|
||||||
|
int n1 = sessionSerialLen(a1);
|
||||||
|
int n2 = sessionSerialLen(a2);
|
||||||
|
if( pIter->abPK[i] || a2[0]==0 ){
|
||||||
|
if( !pIter->abPK[i] ) bData = 1;
|
||||||
|
memcpy(pOut, a1, n1);
|
||||||
|
pOut += n1;
|
||||||
|
}else if( a2[0]!=0xFF ){
|
||||||
|
bData = 1;
|
||||||
|
memcpy(pOut, a2, n2);
|
||||||
|
pOut += n2;
|
||||||
|
}else{
|
||||||
|
*pOut++ = '\0';
|
||||||
|
}
|
||||||
|
a1 += n1;
|
||||||
|
a2 += n2;
|
||||||
|
}
|
||||||
|
if( bData ){
|
||||||
|
a2 = aChange;
|
||||||
|
for(i=0; i<pIter->nCol; i++){
|
||||||
|
int n1 = sessionSerialLen(a1);
|
||||||
|
int n2 = sessionSerialLen(a2);
|
||||||
|
if( pIter->abPK[i] || a2[0]!=0xFF ){
|
||||||
|
memcpy(pOut, a1, n1);
|
||||||
|
pOut += n1;
|
||||||
|
}else{
|
||||||
|
*pOut++ = '\0';
|
||||||
|
}
|
||||||
|
a1 += n1;
|
||||||
|
a2 += n2;
|
||||||
|
}
|
||||||
|
pBuf->nBuf = (pOut - pBuf->aBuf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** pIter is configured to iterate through a changeset. This function rebases
|
||||||
|
** that changeset according to the current configuration of the rebaser
|
||||||
|
** object passed as the first argument. If no error occurs and argument xOutput
|
||||||
|
** is not NULL, then the changeset is returned to the caller by invoking
|
||||||
|
** xOutput zero or more times and SQLITE_OK returned. Or, if xOutput is NULL,
|
||||||
|
** then (*ppOut) is set to point to a buffer containing the rebased changeset
|
||||||
|
** before this function returns. In this case (*pnOut) is set to the size of
|
||||||
|
** the buffer in bytes. It is the responsibility of the caller to eventually
|
||||||
|
** free the (*ppOut) buffer using sqlite3_free().
|
||||||
|
**
|
||||||
|
** If an error occurs, an SQLite error code is returned. If ppOut and
|
||||||
|
** pnOut are not NULL, then the two output parameters are set to 0 before
|
||||||
|
** returning.
|
||||||
|
*/
|
||||||
|
static int sessionRebase(
|
||||||
|
sqlite3_rebaser *p, /* Rebaser hash table */
|
||||||
|
sqlite3_changeset_iter *pIter, /* Input data */
|
||||||
|
int (*xOutput)(void *pOut, const void *pData, int nData),
|
||||||
|
void *pOut, /* Context for xOutput callback */
|
||||||
|
int *pnOut, /* OUT: Number of bytes in output changeset */
|
||||||
|
void **ppOut /* OUT: Inverse of pChangeset */
|
||||||
|
){
|
||||||
|
int rc = SQLITE_OK;
|
||||||
|
u8 *aRec = 0;
|
||||||
|
int nRec = 0;
|
||||||
|
int bNew = 0;
|
||||||
|
SessionTable *pTab = 0;
|
||||||
|
SessionBuffer sOut = {0,0,0};
|
||||||
|
|
||||||
|
while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec, &bNew) ){
|
||||||
|
SessionChange *pChange = 0;
|
||||||
|
int bDone = 0;
|
||||||
|
|
||||||
|
if( bNew ){
|
||||||
|
const char *zTab = pIter->zTab;
|
||||||
|
for(pTab=p->grp.pList; pTab; pTab=pTab->pNext){
|
||||||
|
if( 0==sqlite3_stricmp(pTab->zName, zTab) ) break;
|
||||||
|
}
|
||||||
|
bNew = 0;
|
||||||
|
|
||||||
|
/* A patchset may not be rebased */
|
||||||
|
if( pIter->bPatchset ){
|
||||||
|
rc = SQLITE_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Append a table header to the output for this new table */
|
||||||
|
sessionAppendByte(&sOut, pIter->bPatchset ? 'P' : 'T', &rc);
|
||||||
|
sessionAppendVarint(&sOut, pIter->nCol, &rc);
|
||||||
|
sessionAppendBlob(&sOut, pIter->abPK, pIter->nCol, &rc);
|
||||||
|
sessionAppendBlob(&sOut, (u8*)pIter->zTab, strlen(pIter->zTab)+1, &rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
if( pTab && rc==SQLITE_OK ){
|
||||||
|
int iHash = sessionChangeHash(pTab, 0, aRec, pTab->nChange);
|
||||||
|
|
||||||
|
for(pChange=pTab->apChange[iHash]; pChange; pChange=pChange->pNext){
|
||||||
|
if( sessionChangeEqual(pTab, 0, aRec, 0, pChange->aRecord) ){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( pChange ){
|
||||||
|
assert( pChange->op==SQLITE_DELETE || pChange->op==SQLITE_INSERT );
|
||||||
|
switch( pIter->op ){
|
||||||
|
case SQLITE_INSERT:
|
||||||
|
if( pChange->op==SQLITE_INSERT ){
|
||||||
|
bDone = 1;
|
||||||
|
if( pChange->bIndirect==0 ){
|
||||||
|
sessionAppendByte(&sOut, SQLITE_UPDATE, &rc);
|
||||||
|
sessionAppendByte(&sOut, pIter->bIndirect, &rc);
|
||||||
|
sessionAppendBlob(&sOut, pChange->aRecord, pChange->nRecord, &rc);
|
||||||
|
sessionAppendBlob(&sOut, aRec, nRec, &rc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SQLITE_UPDATE:
|
||||||
|
bDone = 1;
|
||||||
|
if( pChange->op==SQLITE_DELETE ){
|
||||||
|
if( pChange->bIndirect==0 ){
|
||||||
|
u8 *pCsr = aRec;
|
||||||
|
sessionSkipRecord(&pCsr, pIter->nCol);
|
||||||
|
sessionAppendByte(&sOut, SQLITE_INSERT, &rc);
|
||||||
|
sessionAppendByte(&sOut, pIter->bIndirect, &rc);
|
||||||
|
sessionAppendRecordMerge(&sOut, pIter->nCol,
|
||||||
|
pCsr, nRec-(pCsr-aRec),
|
||||||
|
pChange->aRecord, pChange->nRecord, &rc
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
sessionAppendPartialUpdate(&sOut, pIter,
|
||||||
|
aRec, nRec, pChange->aRecord, pChange->nRecord, &rc
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
assert( pIter->op==SQLITE_DELETE );
|
||||||
|
bDone = 1;
|
||||||
|
if( pChange->op==SQLITE_INSERT ){
|
||||||
|
sessionAppendByte(&sOut, SQLITE_DELETE, &rc);
|
||||||
|
sessionAppendByte(&sOut, pIter->bIndirect, &rc);
|
||||||
|
sessionAppendRecordMerge(&sOut, pIter->nCol,
|
||||||
|
pChange->aRecord, pChange->nRecord, aRec, nRec, &rc
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( bDone==0 ){
|
||||||
|
sessionAppendByte(&sOut, pIter->op, &rc);
|
||||||
|
sessionAppendByte(&sOut, pIter->bIndirect, &rc);
|
||||||
|
sessionAppendBlob(&sOut, aRec, nRec, &rc);
|
||||||
|
}
|
||||||
|
if( rc==SQLITE_OK && xOutput && sOut.nBuf>SESSIONS_STRM_CHUNK_SIZE ){
|
||||||
|
rc = xOutput(pOut, sOut.aBuf, sOut.nBuf);
|
||||||
|
sOut.nBuf = 0;
|
||||||
|
}
|
||||||
|
if( rc ) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( rc!=SQLITE_OK ){
|
||||||
|
sqlite3_free(sOut.aBuf);
|
||||||
|
memset(&sOut, 0, sizeof(sOut));
|
||||||
|
}
|
||||||
|
|
||||||
|
if( rc==SQLITE_OK ){
|
||||||
|
if( xOutput ){
|
||||||
|
if( sOut.nBuf>0 ){
|
||||||
|
rc = xOutput(pOut, sOut.aBuf, sOut.nBuf);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
*ppOut = (void*)sOut.aBuf;
|
||||||
|
*pnOut = sOut.nBuf;
|
||||||
|
sOut.aBuf = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sqlite3_free(sOut.aBuf);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Create a new rebaser object.
|
||||||
|
*/
|
||||||
|
int sqlite3rebaser_create(sqlite3_rebaser **ppNew){
|
||||||
|
int rc = SQLITE_OK;
|
||||||
|
sqlite3_rebaser *pNew;
|
||||||
|
|
||||||
|
pNew = sqlite3_malloc(sizeof(sqlite3_rebaser));
|
||||||
|
if( pNew==0 ){
|
||||||
|
rc = SQLITE_NOMEM;
|
||||||
|
}else{
|
||||||
|
memset(pNew, 0, sizeof(sqlite3_rebaser));
|
||||||
|
}
|
||||||
|
*ppNew = pNew;
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Call this one or more times to configure a rebaser.
|
||||||
|
*/
|
||||||
|
int sqlite3rebaser_configure(
|
||||||
|
sqlite3_rebaser *p,
|
||||||
|
int nRebase, const void *pRebase
|
||||||
|
){
|
||||||
|
sqlite3_changeset_iter *pIter = 0; /* Iterator opened on pData/nData */
|
||||||
|
int rc; /* Return code */
|
||||||
|
rc = sqlite3changeset_start(&pIter, nRebase, (void*)pRebase);
|
||||||
|
if( rc==SQLITE_OK ){
|
||||||
|
rc = sessionChangesetToHash(pIter, &p->grp, 1);
|
||||||
|
}
|
||||||
|
sqlite3changeset_finalize(pIter);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Rebase a changeset according to current rebaser configuration
|
||||||
|
*/
|
||||||
|
int sqlite3rebaser_rebase(
|
||||||
|
sqlite3_rebaser *p,
|
||||||
|
int nIn, const void *pIn,
|
||||||
|
int *pnOut, void **ppOut
|
||||||
|
){
|
||||||
|
sqlite3_changeset_iter *pIter = 0; /* Iterator to skip through input */
|
||||||
|
int rc = sqlite3changeset_start(&pIter, nIn, (void*)pIn);
|
||||||
|
|
||||||
|
if( rc==SQLITE_OK ){
|
||||||
|
rc = sessionRebase(p, pIter, 0, 0, pnOut, ppOut);
|
||||||
|
sqlite3changeset_finalize(pIter);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Rebase a changeset according to current rebaser configuration
|
||||||
|
*/
|
||||||
|
int sqlite3rebaser_rebase_strm(
|
||||||
|
sqlite3_rebaser *p,
|
||||||
|
int (*xInput)(void *pIn, void *pData, int *pnData),
|
||||||
|
void *pIn,
|
||||||
|
int (*xOutput)(void *pOut, const void *pData, int nData),
|
||||||
|
void *pOut
|
||||||
|
){
|
||||||
|
sqlite3_changeset_iter *pIter = 0; /* Iterator to skip through input */
|
||||||
|
int rc = sqlite3changeset_start_strm(&pIter, xInput, pIn);
|
||||||
|
|
||||||
|
if( rc==SQLITE_OK ){
|
||||||
|
rc = sessionRebase(p, pIter, xOutput, pOut, 0, 0);
|
||||||
|
sqlite3changeset_finalize(pIter);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Destroy a rebaser object
|
||||||
|
*/
|
||||||
|
void sqlite3rebaser_delete(sqlite3_rebaser *p){
|
||||||
|
if( p ){
|
||||||
|
sessionDeleteTable(p->grp.pList);
|
||||||
|
sqlite3_free(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endif /* SQLITE_ENABLE_SESSION && SQLITE_ENABLE_PREUPDATE_HOOK */
|
#endif /* SQLITE_ENABLE_SESSION && SQLITE_ENABLE_PREUPDATE_HOOK */
|
||||||
|
|||||||
@@ -948,19 +948,18 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
|
|||||||
/*
|
/*
|
||||||
** CAPI3REF: Apply A Changeset To A Database
|
** CAPI3REF: Apply A Changeset To A Database
|
||||||
**
|
**
|
||||||
** Apply a changeset to a database. This function attempts to update the
|
** Apply a changeset or patchset to a database. These functions attempt to
|
||||||
** "main" database attached to handle db with the changes found in the
|
** update the "main" database attached to handle db with the changes found in
|
||||||
** changeset passed via the second and third arguments.
|
** the changeset passed via the second and third arguments.
|
||||||
**
|
**
|
||||||
** The fourth argument (xFilter) passed to this function is the "filter
|
** The fourth argument (xFilter) passed to these functions is the "filter
|
||||||
** callback". If it is not NULL, then for each table affected by at least one
|
** callback". If it is not NULL, then for each table affected by at least one
|
||||||
** change in the changeset, the filter callback is invoked with
|
** change in the changeset, the filter callback is invoked with
|
||||||
** the table name as the second argument, and a copy of the context pointer
|
** the table name as the second argument, and a copy of the context pointer
|
||||||
** passed as the sixth argument to this function as the first. If the "filter
|
** passed as the sixth argument as the first. If the "filter callback"
|
||||||
** callback" returns zero, then no attempt is made to apply any changes to
|
** returns zero, then no attempt is made to apply any changes to the table.
|
||||||
** the table. Otherwise, if the return value is non-zero or the xFilter
|
** Otherwise, if the return value is non-zero or the xFilter argument to
|
||||||
** argument to this function is NULL, all changes related to the table are
|
** is NULL, all changes related to the table are attempted.
|
||||||
** attempted.
|
|
||||||
**
|
**
|
||||||
** For each table that is not excluded by the filter callback, this function
|
** For each table that is not excluded by the filter callback, this function
|
||||||
** tests that the target database contains a compatible table. A table is
|
** tests that the target database contains a compatible table. A table is
|
||||||
@@ -1005,7 +1004,7 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
|
|||||||
**
|
**
|
||||||
** <dl>
|
** <dl>
|
||||||
** <dt>DELETE Changes<dd>
|
** <dt>DELETE Changes<dd>
|
||||||
** For each DELETE change, this function checks if the target database
|
** For each DELETE change, the function checks if the target database
|
||||||
** contains a row with the same primary key value (or values) as the
|
** contains a row with the same primary key value (or values) as the
|
||||||
** original row values stored in the changeset. If it does, and the values
|
** original row values stored in the changeset. If it does, and the values
|
||||||
** stored in all non-primary key columns also match the values stored in
|
** stored in all non-primary key columns also match the values stored in
|
||||||
@@ -1050,7 +1049,7 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
|
|||||||
** [SQLITE_CHANGESET_REPLACE].
|
** [SQLITE_CHANGESET_REPLACE].
|
||||||
**
|
**
|
||||||
** <dt>UPDATE Changes<dd>
|
** <dt>UPDATE Changes<dd>
|
||||||
** For each UPDATE change, this function checks if the target database
|
** For each UPDATE change, the function checks if the target database
|
||||||
** contains a row with the same primary key value (or values) as the
|
** contains a row with the same primary key value (or values) as the
|
||||||
** original row values stored in the changeset. If it does, and the values
|
** original row values stored in the changeset. If it does, and the values
|
||||||
** stored in all modified non-primary key columns also match the values
|
** stored in all modified non-primary key columns also match the values
|
||||||
@@ -1081,11 +1080,21 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
|
|||||||
** This can be used to further customize the applications conflict
|
** This can be used to further customize the applications conflict
|
||||||
** resolution strategy.
|
** resolution strategy.
|
||||||
**
|
**
|
||||||
** All changes made by this function are enclosed in a savepoint transaction.
|
** All changes made by these functions are enclosed in a savepoint transaction.
|
||||||
** If any other error (aside from a constraint failure when attempting to
|
** If any other error (aside from a constraint failure when attempting to
|
||||||
** write to the target database) occurs, then the savepoint transaction is
|
** write to the target database) occurs, then the savepoint transaction is
|
||||||
** rolled back, restoring the target database to its original state, and an
|
** rolled back, restoring the target database to its original state, and an
|
||||||
** SQLite error code returned.
|
** SQLite error code returned.
|
||||||
|
**
|
||||||
|
** If the output parameters (ppRebase) and (pnRebase) are non-NULL and
|
||||||
|
** the input is a changeset (not a patchset), then sqlite3changeset_apply_v2()
|
||||||
|
** may set (*ppRebase) to point to a "rebase" that may be used with the
|
||||||
|
** sqlite3_rebaser APIs buffer before returning. In this case (*pnRebase)
|
||||||
|
** is set to the size of the buffer in bytes. It is the responsibility of the
|
||||||
|
** caller to eventually free any such buffer using sqlite3_free(). The buffer
|
||||||
|
** is only allocated and populated if one or more conflicts were encountered
|
||||||
|
** while applying the patchset. See comments surrounding the sqlite3_rebaser
|
||||||
|
** APIs for further details.
|
||||||
*/
|
*/
|
||||||
int sqlite3changeset_apply(
|
int sqlite3changeset_apply(
|
||||||
sqlite3 *db, /* Apply change to "main" db of this handle */
|
sqlite3 *db, /* Apply change to "main" db of this handle */
|
||||||
@@ -1102,6 +1111,22 @@ int sqlite3changeset_apply(
|
|||||||
),
|
),
|
||||||
void *pCtx /* First argument passed to xConflict */
|
void *pCtx /* First argument passed to xConflict */
|
||||||
);
|
);
|
||||||
|
int sqlite3changeset_apply_v2(
|
||||||
|
sqlite3 *db, /* Apply change to "main" db of this handle */
|
||||||
|
int nChangeset, /* Size of changeset in bytes */
|
||||||
|
void *pChangeset, /* Changeset blob */
|
||||||
|
int(*xFilter)(
|
||||||
|
void *pCtx, /* Copy of sixth arg to _apply() */
|
||||||
|
const char *zTab /* Table name */
|
||||||
|
),
|
||||||
|
int(*xConflict)(
|
||||||
|
void *pCtx, /* Copy of sixth arg to _apply() */
|
||||||
|
int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
|
||||||
|
sqlite3_changeset_iter *p /* Handle describing change and conflict */
|
||||||
|
),
|
||||||
|
void *pCtx, /* First argument passed to xConflict */
|
||||||
|
void **ppRebase, int *pnRebase
|
||||||
|
);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** CAPI3REF: Constants Passed To The Conflict Handler
|
** CAPI3REF: Constants Passed To The Conflict Handler
|
||||||
@@ -1199,6 +1224,156 @@ int sqlite3changeset_apply(
|
|||||||
#define SQLITE_CHANGESET_REPLACE 1
|
#define SQLITE_CHANGESET_REPLACE 1
|
||||||
#define SQLITE_CHANGESET_ABORT 2
|
#define SQLITE_CHANGESET_ABORT 2
|
||||||
|
|
||||||
|
/*
|
||||||
|
** CAPI3REF: Rebasing changesets
|
||||||
|
**
|
||||||
|
** Suppose there is a site hosting a database in state S0. And that
|
||||||
|
** modifications are made that move that database to state S1 and a
|
||||||
|
** changeset recorded (the "local" changeset). Then, a changeset based
|
||||||
|
** on S0 is received from another site (the "remote" changeset) and
|
||||||
|
** applied to the database. The database is then in state
|
||||||
|
** (S1+"remote"), where the exact state depends on any conflict
|
||||||
|
** resolution decisions (OMIT or REPLACE) made while applying "remote".
|
||||||
|
** Rebasing a changeset is to update it to take those conflict
|
||||||
|
** resolution decisions into account, so that the same conflicts
|
||||||
|
** do not have to be resolved elsewhere in the network.
|
||||||
|
**
|
||||||
|
** For example, if both the local and remote changesets contain an
|
||||||
|
** INSERT of the same key on "CREATE TABLE t1(a PRIMARY KEY, b)":
|
||||||
|
**
|
||||||
|
** local: INSERT INTO t1 VALUES(1, 'v1');
|
||||||
|
** remote: INSERT INTO t1 VALUES(1, 'v2');
|
||||||
|
**
|
||||||
|
** and the conflict resolution is REPLACE, then the INSERT change is
|
||||||
|
** removed from the local changeset (it was overridden). Or, if the
|
||||||
|
** conflict resolution was "OMIT", then the local changeset is modified
|
||||||
|
** to instead contain:
|
||||||
|
**
|
||||||
|
** UPDATE t1 SET b = 'v2' WHERE a=1;
|
||||||
|
**
|
||||||
|
** Changes within the local changeset are rebased as follows:
|
||||||
|
**
|
||||||
|
** <dl>
|
||||||
|
** <dt>Local INSERT<dd>
|
||||||
|
** This may only conflict with a remote INSERT. If the conflict
|
||||||
|
** resolution was OMIT, then add an UPDATE change to the rebased
|
||||||
|
** changeset. Or, if the conflict resolution was REPLACE, add
|
||||||
|
** nothing to the rebased changeset.
|
||||||
|
**
|
||||||
|
** <dt>Local DELETE<dd>
|
||||||
|
** This may conflict with a remote UPDATE or DELETE. In both cases the
|
||||||
|
** only possible resolution is OMIT. If the remote operation was a
|
||||||
|
** DELETE, then add no change to the rebased changeset. If the remote
|
||||||
|
** operation was an UPDATE, then the old.* fields of change are updated
|
||||||
|
** to reflect the new.* values in the UPDATE.
|
||||||
|
**
|
||||||
|
** <dt>Local UPDATE<dd>
|
||||||
|
** This may conflict with a remote UPDATE or DELETE. If it conflicts
|
||||||
|
** with a DELETE, and the conflict resolution was OMIT, then the update
|
||||||
|
** is changed into an INSERT. Any undefined values in the new.* record
|
||||||
|
** from the update change are filled in using the old.* values from
|
||||||
|
** the conflicting DELETE. Or, if the conflict resolution was REPLACE,
|
||||||
|
** the UPDATE change is simply omitted from the rebased changeset.
|
||||||
|
**
|
||||||
|
** If conflict is with a remote UPDATE and the resolution is OMIT, then
|
||||||
|
** the old.* values are rebased using the new.* values in the remote
|
||||||
|
** change. Or, if the resolution is REPLACE, then the change is copied
|
||||||
|
** into the rebased changeset with updates to columns also updated by
|
||||||
|
** the conflicting remote UPDATE removed. If this means no columns would
|
||||||
|
** be updated, the change is omitted.
|
||||||
|
** </dl>
|
||||||
|
**
|
||||||
|
** A local change may be rebased against multiple remote changes
|
||||||
|
** simultaneously. If a single key is modified by multiple remote
|
||||||
|
** changesets, they are combined as follows before the local changeset
|
||||||
|
** is rebased:
|
||||||
|
**
|
||||||
|
** <ul>
|
||||||
|
** <li> If there has been one or more REPLACE resolutions on a
|
||||||
|
** key, it is rebased according to a REPLACE.
|
||||||
|
**
|
||||||
|
** <li> If there have been no REPLACE resolutions on a key, then
|
||||||
|
** the local changeset is rebased according to the most recent
|
||||||
|
** of the OMIT resolutions.
|
||||||
|
** </ul>
|
||||||
|
**
|
||||||
|
** Note that conflict resolutions from multiple remote changesets are
|
||||||
|
** combined on a per-field basis, not per-row. This means that in the
|
||||||
|
** case of multiple remote UPDATE operations, some fields of a single
|
||||||
|
** local change may be rebased for REPLACE while others are rebased for
|
||||||
|
** OMIT.
|
||||||
|
**
|
||||||
|
** In order to rebase a local changeset, the remote changeset must first
|
||||||
|
** be applied to the local database using sqlite3changeset_apply_v2() and
|
||||||
|
** the buffer of rebase information captured. Then:
|
||||||
|
**
|
||||||
|
** <ol>
|
||||||
|
** <li> An sqlite3_rebaser object is created by calling
|
||||||
|
** sqlite3rebaser_create().
|
||||||
|
** <li> The new object is configured with the rebase buffer obtained from
|
||||||
|
** sqlite3changeset_apply_v2() by calling sqlite3rebaser_configure().
|
||||||
|
** If the local changeset is to be rebased against multiple remote
|
||||||
|
** changesets, then sqlite3rebaser_configure() should be called
|
||||||
|
** multiple times, in the same order that the multiple
|
||||||
|
** sqlite3changeset_apply_v2() calls were made.
|
||||||
|
** <li> Each local changeset is rebased by calling sqlite3rebaser_rebase().
|
||||||
|
** <li> The sqlite3_rebaser object is deleted by calling
|
||||||
|
** sqlite3rebaser_delete().
|
||||||
|
** </ol>
|
||||||
|
*/
|
||||||
|
typedef struct sqlite3_rebaser sqlite3_rebaser;
|
||||||
|
|
||||||
|
/*
|
||||||
|
** CAPIREF: Create a changeset rebaser object.
|
||||||
|
**
|
||||||
|
** Allocate a new changeset rebaser object. If successful, set (*ppNew) to
|
||||||
|
** point to the new object and return SQLITE_OK. Otherwise, if an error
|
||||||
|
** occurs, return an SQLite error code (e.g. SQLITE_NOMEM) and set (*ppNew)
|
||||||
|
** to NULL.
|
||||||
|
*/
|
||||||
|
int sqlite3rebaser_create(sqlite3_rebaser **ppNew);
|
||||||
|
|
||||||
|
/*
|
||||||
|
** CAPIREF: Configure a changeset rebaser object.
|
||||||
|
**
|
||||||
|
** Configure the changeset rebaser object to rebase changesets according
|
||||||
|
** to the conflict resolutions described by buffer pRebase (size nRebase
|
||||||
|
** bytes), which must have been obtained from a previous call to
|
||||||
|
** sqlite3changeset_apply_v2().
|
||||||
|
*/
|
||||||
|
int sqlite3rebaser_configure(
|
||||||
|
sqlite3_rebaser*,
|
||||||
|
int nRebase, const void *pRebase
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
** CAPIREF: Rebase a changeset
|
||||||
|
**
|
||||||
|
** Argument pIn must point to a buffer containing a changeset nIn bytes
|
||||||
|
** in size. This function allocates and populates a buffer with a copy
|
||||||
|
** of the changeset rebased rebased according to the configuration of the
|
||||||
|
** rebaser object passed as the first argument. If successful, (*ppOut)
|
||||||
|
** is set to point to the new buffer containing the rebased changset and
|
||||||
|
** (*pnOut) to its size in bytes and SQLITE_OK returned. It is the
|
||||||
|
** responsibility of the caller to eventually free the new buffer using
|
||||||
|
** sqlite3_free(). Otherwise, if an error occurs, (*ppOut) and (*pnOut)
|
||||||
|
** are set to zero and an SQLite error code returned.
|
||||||
|
*/
|
||||||
|
int sqlite3rebaser_rebase(
|
||||||
|
sqlite3_rebaser*,
|
||||||
|
int nIn, const void *pIn,
|
||||||
|
int *pnOut, void **ppOut
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
** CAPIREF: Delete a changeset rebaser object.
|
||||||
|
**
|
||||||
|
** Delete the changeset rebaser object and all associated resources. There
|
||||||
|
** should be one call to this function for each successful invocation
|
||||||
|
** of sqlite3rebaser_create().
|
||||||
|
*/
|
||||||
|
void sqlite3rebaser_delete(sqlite3_rebaser *p);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** CAPI3REF: Streaming Versions of API functions.
|
** CAPI3REF: Streaming Versions of API functions.
|
||||||
**
|
**
|
||||||
@@ -1303,6 +1478,22 @@ int sqlite3changeset_apply_strm(
|
|||||||
),
|
),
|
||||||
void *pCtx /* First argument passed to xConflict */
|
void *pCtx /* First argument passed to xConflict */
|
||||||
);
|
);
|
||||||
|
int sqlite3changeset_apply_v2_strm(
|
||||||
|
sqlite3 *db, /* Apply change to "main" db of this handle */
|
||||||
|
int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */
|
||||||
|
void *pIn, /* First arg for xInput */
|
||||||
|
int(*xFilter)(
|
||||||
|
void *pCtx, /* Copy of sixth arg to _apply() */
|
||||||
|
const char *zTab /* Table name */
|
||||||
|
),
|
||||||
|
int(*xConflict)(
|
||||||
|
void *pCtx, /* Copy of sixth arg to _apply() */
|
||||||
|
int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
|
||||||
|
sqlite3_changeset_iter *p /* Handle describing change and conflict */
|
||||||
|
),
|
||||||
|
void *pCtx, /* First argument passed to xConflict */
|
||||||
|
void **ppRebase, int *pnRebase
|
||||||
|
);
|
||||||
int sqlite3changeset_concat_strm(
|
int sqlite3changeset_concat_strm(
|
||||||
int (*xInputA)(void *pIn, void *pData, int *pnData),
|
int (*xInputA)(void *pIn, void *pData, int *pnData),
|
||||||
void *pInA,
|
void *pInA,
|
||||||
@@ -1340,6 +1531,13 @@ int sqlite3changegroup_output_strm(sqlite3_changegroup*,
|
|||||||
int (*xOutput)(void *pOut, const void *pData, int nData),
|
int (*xOutput)(void *pOut, const void *pData, int nData),
|
||||||
void *pOut
|
void *pOut
|
||||||
);
|
);
|
||||||
|
int sqlite3rebaser_rebase_strm(
|
||||||
|
sqlite3_rebaser *pRebaser,
|
||||||
|
int (*xInput)(void *pIn, void *pData, int *pnData),
|
||||||
|
void *pIn,
|
||||||
|
int (*xOutput)(void *pOut, const void *pData, int nData),
|
||||||
|
void *pOut
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -14,6 +14,10 @@
|
|||||||
# endif
|
# endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef SQLITE_AMALGAMATION
|
||||||
|
typedef unsigned char u8;
|
||||||
|
#endif
|
||||||
|
|
||||||
typedef struct TestSession TestSession;
|
typedef struct TestSession TestSession;
|
||||||
struct TestSession {
|
struct TestSession {
|
||||||
sqlite3_session *pSession;
|
sqlite3_session *pSession;
|
||||||
@@ -711,10 +715,8 @@ static int testStreamInput(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
static int SQLITE_TCLAPI testSqlite3changesetApply(
|
||||||
** sqlite3changeset_apply DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?
|
int bV2,
|
||||||
*/
|
|
||||||
static int SQLITE_TCLAPI test_sqlite3changeset_apply(
|
|
||||||
void * clientData,
|
void * clientData,
|
||||||
Tcl_Interp *interp,
|
Tcl_Interp *interp,
|
||||||
int objc,
|
int objc,
|
||||||
@@ -727,6 +729,8 @@ static int SQLITE_TCLAPI test_sqlite3changeset_apply(
|
|||||||
int nChangeset; /* Size of buffer aChangeset in bytes */
|
int nChangeset; /* Size of buffer aChangeset in bytes */
|
||||||
TestConflictHandler ctx;
|
TestConflictHandler ctx;
|
||||||
TestStreamInput sStr;
|
TestStreamInput sStr;
|
||||||
|
void *pRebase = 0;
|
||||||
|
int nRebase = 0;
|
||||||
|
|
||||||
memset(&sStr, 0, sizeof(sStr));
|
memset(&sStr, 0, sizeof(sStr));
|
||||||
sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
|
sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
|
||||||
@@ -748,24 +752,68 @@ static int SQLITE_TCLAPI test_sqlite3changeset_apply(
|
|||||||
ctx.interp = interp;
|
ctx.interp = interp;
|
||||||
|
|
||||||
if( sStr.nStream==0 ){
|
if( sStr.nStream==0 ){
|
||||||
|
if( bV2==0 ){
|
||||||
rc = sqlite3changeset_apply(db, nChangeset, pChangeset,
|
rc = sqlite3changeset_apply(db, nChangeset, pChangeset,
|
||||||
(objc==5)?test_filter_handler:0, test_conflict_handler, (void *)&ctx
|
(objc==5)?test_filter_handler:0, test_conflict_handler, (void *)&ctx
|
||||||
);
|
);
|
||||||
|
}else{
|
||||||
|
rc = sqlite3changeset_apply_v2(db, nChangeset, pChangeset,
|
||||||
|
(objc==5)?test_filter_handler:0, test_conflict_handler, (void *)&ctx,
|
||||||
|
&pRebase, &nRebase
|
||||||
|
);
|
||||||
|
}
|
||||||
}else{
|
}else{
|
||||||
sStr.aData = (unsigned char*)pChangeset;
|
sStr.aData = (unsigned char*)pChangeset;
|
||||||
sStr.nData = nChangeset;
|
sStr.nData = nChangeset;
|
||||||
|
if( bV2==0 ){
|
||||||
rc = sqlite3changeset_apply_strm(db, testStreamInput, (void*)&sStr,
|
rc = sqlite3changeset_apply_strm(db, testStreamInput, (void*)&sStr,
|
||||||
(objc==5) ? test_filter_handler : 0, test_conflict_handler, (void *)&ctx
|
(objc==5) ? test_filter_handler : 0,
|
||||||
|
test_conflict_handler, (void *)&ctx
|
||||||
);
|
);
|
||||||
|
}else{
|
||||||
|
rc = sqlite3changeset_apply_v2_strm(db, testStreamInput, (void*)&sStr,
|
||||||
|
(objc==5) ? test_filter_handler : 0,
|
||||||
|
test_conflict_handler, (void *)&ctx,
|
||||||
|
&pRebase, &nRebase
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if( rc!=SQLITE_OK ){
|
if( rc!=SQLITE_OK ){
|
||||||
return test_session_error(interp, rc, 0);
|
return test_session_error(interp, rc, 0);
|
||||||
}
|
}else{
|
||||||
Tcl_ResetResult(interp);
|
Tcl_ResetResult(interp);
|
||||||
|
if( bV2 && pRebase ){
|
||||||
|
Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pRebase, nRebase));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sqlite3_free(pRebase);
|
||||||
return TCL_OK;
|
return TCL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** sqlite3changeset_apply DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?
|
||||||
|
*/
|
||||||
|
static int SQLITE_TCLAPI test_sqlite3changeset_apply(
|
||||||
|
void * clientData,
|
||||||
|
Tcl_Interp *interp,
|
||||||
|
int objc,
|
||||||
|
Tcl_Obj *CONST objv[]
|
||||||
|
){
|
||||||
|
return testSqlite3changesetApply(0, clientData, interp, objc, objv);
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
** sqlite3changeset_apply_v2 DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?
|
||||||
|
*/
|
||||||
|
static int SQLITE_TCLAPI test_sqlite3changeset_apply_v2(
|
||||||
|
void * clientData,
|
||||||
|
Tcl_Interp *interp,
|
||||||
|
int objc,
|
||||||
|
Tcl_Obj *CONST objv[]
|
||||||
|
){
|
||||||
|
return testSqlite3changesetApply(1, clientData, interp, objc, objv);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** sqlite3changeset_apply_replace_all DB CHANGESET
|
** sqlite3changeset_apply_replace_all DB CHANGESET
|
||||||
*/
|
*/
|
||||||
@@ -1019,6 +1067,125 @@ static int SQLITE_TCLAPI test_sqlite3session_foreach(
|
|||||||
return TCL_OK;
|
return TCL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** tclcmd: CMD configure REBASE-BLOB
|
||||||
|
** tclcmd: CMD rebase CHANGESET
|
||||||
|
** tclcmd: CMD delete
|
||||||
|
*/
|
||||||
|
static int SQLITE_TCLAPI test_rebaser_cmd(
|
||||||
|
void * clientData,
|
||||||
|
Tcl_Interp *interp,
|
||||||
|
int objc,
|
||||||
|
Tcl_Obj *CONST objv[]
|
||||||
|
){
|
||||||
|
struct RebaseSubcmd {
|
||||||
|
const char *zSub;
|
||||||
|
int nArg;
|
||||||
|
const char *zMsg;
|
||||||
|
int iSub;
|
||||||
|
} aSub[] = {
|
||||||
|
{ "configure", 1, "REBASE-BLOB" }, /* 0 */
|
||||||
|
{ "delete", 0, "" }, /* 1 */
|
||||||
|
{ "rebase", 1, "CHANGESET" }, /* 2 */
|
||||||
|
{ 0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
sqlite3_rebaser *p = (sqlite3_rebaser*)clientData;
|
||||||
|
int iSub;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
if( objc<2 ){
|
||||||
|
Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
|
||||||
|
return TCL_ERROR;
|
||||||
|
}
|
||||||
|
rc = Tcl_GetIndexFromObjStruct(interp,
|
||||||
|
objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub
|
||||||
|
);
|
||||||
|
if( rc!=TCL_OK ) return rc;
|
||||||
|
if( objc!=2+aSub[iSub].nArg ){
|
||||||
|
Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg);
|
||||||
|
return TCL_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert( iSub==0 || iSub==1 || iSub==2 );
|
||||||
|
assert( rc==SQLITE_OK );
|
||||||
|
switch( iSub ){
|
||||||
|
case 0: { /* configure */
|
||||||
|
int nRebase = 0;
|
||||||
|
unsigned char *pRebase = Tcl_GetByteArrayFromObj(objv[2], &nRebase);
|
||||||
|
rc = sqlite3rebaser_configure(p, nRebase, pRebase);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 1: /* delete */
|
||||||
|
Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: { /* rebase */
|
||||||
|
TestStreamInput sStr; /* Input stream */
|
||||||
|
TestSessionsBlob sOut; /* Output blob */
|
||||||
|
|
||||||
|
memset(&sStr, 0, sizeof(sStr));
|
||||||
|
memset(&sOut, 0, sizeof(sOut));
|
||||||
|
sStr.aData = Tcl_GetByteArrayFromObj(objv[2], &sStr.nData);
|
||||||
|
sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
|
||||||
|
|
||||||
|
if( sStr.nStream ){
|
||||||
|
rc = sqlite3rebaser_rebase_strm(p,
|
||||||
|
testStreamInput, (void*)&sStr,
|
||||||
|
testStreamOutput, (void*)&sOut
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
rc = sqlite3rebaser_rebase(p, sStr.nData, sStr.aData, &sOut.n, &sOut.p);
|
||||||
|
}
|
||||||
|
|
||||||
|
if( rc==SQLITE_OK ){
|
||||||
|
Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(sOut.p, sOut.n));
|
||||||
|
}
|
||||||
|
sqlite3_free(sOut.p);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( rc!=SQLITE_OK ){
|
||||||
|
return test_session_error(interp, rc, 0);
|
||||||
|
}
|
||||||
|
return TCL_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SQLITE_TCLAPI test_rebaser_del(void *clientData){
|
||||||
|
sqlite3_rebaser *p = (sqlite3_rebaser*)clientData;
|
||||||
|
sqlite3rebaser_delete(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** tclcmd: sqlite3rebaser_create NAME
|
||||||
|
*/
|
||||||
|
static int SQLITE_TCLAPI test_sqlite3rebaser_create(
|
||||||
|
void * clientData,
|
||||||
|
Tcl_Interp *interp,
|
||||||
|
int objc,
|
||||||
|
Tcl_Obj *CONST objv[]
|
||||||
|
){
|
||||||
|
int rc;
|
||||||
|
sqlite3_rebaser *pNew = 0;
|
||||||
|
if( objc!=2 ){
|
||||||
|
Tcl_WrongNumArgs(interp, 1, objv, "NAME");
|
||||||
|
return SQLITE_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = sqlite3rebaser_create(&pNew);
|
||||||
|
if( rc!=SQLITE_OK ){
|
||||||
|
return test_session_error(interp, rc, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Tcl_CreateObjCommand(interp, Tcl_GetString(objv[1]), test_rebaser_cmd,
|
||||||
|
(ClientData)pNew, test_rebaser_del
|
||||||
|
);
|
||||||
|
Tcl_SetObjResult(interp, objv[1]);
|
||||||
|
return TCL_OK;
|
||||||
|
}
|
||||||
|
|
||||||
int TestSession_Init(Tcl_Interp *interp){
|
int TestSession_Init(Tcl_Interp *interp){
|
||||||
struct Cmd {
|
struct Cmd {
|
||||||
const char *zCmd;
|
const char *zCmd;
|
||||||
@@ -1029,9 +1196,11 @@ int TestSession_Init(Tcl_Interp *interp){
|
|||||||
{ "sqlite3changeset_invert", test_sqlite3changeset_invert },
|
{ "sqlite3changeset_invert", test_sqlite3changeset_invert },
|
||||||
{ "sqlite3changeset_concat", test_sqlite3changeset_concat },
|
{ "sqlite3changeset_concat", test_sqlite3changeset_concat },
|
||||||
{ "sqlite3changeset_apply", test_sqlite3changeset_apply },
|
{ "sqlite3changeset_apply", test_sqlite3changeset_apply },
|
||||||
|
{ "sqlite3changeset_apply_v2", test_sqlite3changeset_apply_v2 },
|
||||||
{ "sqlite3changeset_apply_replace_all",
|
{ "sqlite3changeset_apply_replace_all",
|
||||||
test_sqlite3changeset_apply_replace_all },
|
test_sqlite3changeset_apply_replace_all },
|
||||||
{ "sql_exec_changeset", test_sql_exec_changeset },
|
{ "sql_exec_changeset", test_sql_exec_changeset },
|
||||||
|
{ "sqlite3rebaser_create", test_sqlite3rebaser_create },
|
||||||
};
|
};
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
|||||||
22
manifest
22
manifest
@@ -1,5 +1,5 @@
|
|||||||
C Fix\san\sRBU\sproblem\scausing\serrors\swhen\supdating\stables\swith\sdefault\scollation\nsequences\sthat\srequire\squoting\s(e.g.\sCOLLATE\s"ICU_root-u-kn-on").
|
C Add\sAPIs\sto\sthe\ssessions\smodule\sfor\s"rebasing"\schangesets.
|
||||||
D 2018-03-22T17:13:44.592
|
D 2018-03-22T20:35:20.917
|
||||||
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
|
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
|
||||||
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
|
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
|
||||||
F Makefile.in 7016fc56c6b9bfe5daac4f34be8be38d8c0b5fab79ccbfb764d3b23bf1c6fff3
|
F Makefile.in 7016fc56c6b9bfe5daac4f34be8be38d8c0b5fab79ccbfb764d3b23bf1c6fff3
|
||||||
@@ -395,17 +395,18 @@ F ext/session/sessionE.test 0a616c4ad8fd2c05f23217ebb6212ef80b7fef30f5f086a6633a
|
|||||||
F ext/session/sessionF.test c2f178d4dfd723a5fd94a730ea2ccb44c669e3ce
|
F ext/session/sessionF.test c2f178d4dfd723a5fd94a730ea2ccb44c669e3ce
|
||||||
F ext/session/sessionG.test 63f9a744341d670775af29e4f19c1ef09a4810798400f28cd76704803a2e56ff
|
F ext/session/sessionG.test 63f9a744341d670775af29e4f19c1ef09a4810798400f28cd76704803a2e56ff
|
||||||
F ext/session/sessionH.test 332b60e4c2e0a680105e11936201cabe378216f307e2747803cea56fa7d9ebae
|
F ext/session/sessionH.test 332b60e4c2e0a680105e11936201cabe378216f307e2747803cea56fa7d9ebae
|
||||||
F ext/session/session_common.tcl 7776eda579773113b30c7abfd4545c445228cb73
|
F ext/session/session_common.tcl 748141b02042b942e04a7afad9ffb2212a3997de536ed95f6dec7bb5018ede2c
|
||||||
F ext/session/session_speed_test.c edc1f96fd5e0e4b16eb03e2a73041013d59e8723
|
F ext/session/session_speed_test.c edc1f96fd5e0e4b16eb03e2a73041013d59e8723
|
||||||
F ext/session/sessionat.test efe88965e74ff1bc2af9c310b28358c02d420c1fb2705cc7a28f0c1cc142c3ec
|
F ext/session/sessionat.test efe88965e74ff1bc2af9c310b28358c02d420c1fb2705cc7a28f0c1cc142c3ec
|
||||||
F ext/session/sessiondiff.test ad13dd65664bae26744e1f18eb3cbd5588349b7e9118851d8f9364248d67bcec
|
F ext/session/sessiondiff.test ad13dd65664bae26744e1f18eb3cbd5588349b7e9118851d8f9364248d67bcec
|
||||||
F ext/session/sessionfault.test da273f2712b6411e85e71465a1733b8501dbf6f7
|
F ext/session/sessionfault.test da273f2712b6411e85e71465a1733b8501dbf6f7
|
||||||
F ext/session/sessionfault2.test 04aa0bc9aa70ea43d8de82c4f648db4de1e990b0
|
F ext/session/sessionfault2.test c76c76fe3c47737cb55cad7a254c0f05d0e3122f13e16de94d3dd3a4c6653913
|
||||||
|
F ext/session/sessionrebase.test 4e1bcfd26fd8ed8ac571746f56cceeb45184f4d65490ea0d405227cfc8a9cba8
|
||||||
F ext/session/sessionstat1.test 41cd97c2e48619a41cdf8ae749e1b25f34719de638689221aa43971be693bf4e
|
F ext/session/sessionstat1.test 41cd97c2e48619a41cdf8ae749e1b25f34719de638689221aa43971be693bf4e
|
||||||
F ext/session/sessionwor.test 2f3744236dc8b170a695b7d8ddc8c743c7e79fdc
|
F ext/session/sessionwor.test 2f3744236dc8b170a695b7d8ddc8c743c7e79fdc
|
||||||
F ext/session/sqlite3session.c 9edfaaa74977ddecd7bbd94e8f844d9b0f6eec22d1d547e806361670db814c1e
|
F ext/session/sqlite3session.c b411b1fa4640d09e516a880aecaa78a0a96b86c0ad43d838f01ed9bea9e4d502
|
||||||
F ext/session/sqlite3session.h 2e1584b030fbd841cefdce15ba984871978d305f586da2d1972f6e1958fa10b1
|
F ext/session/sqlite3session.h 5f40a0660ff972c0c50f5fd6b33488fdbd2eb0c1244ea95777f8dbd5e529be04
|
||||||
F ext/session/test_session.c eb0bd6c1ea791c1d66ee4ef94c16500dad936386
|
F ext/session/test_session.c f253742ea01b089326f189b5ae15a5b55c1c9e97452e4a195ee759ba51b404d5
|
||||||
F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3
|
F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3
|
||||||
F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04
|
F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04
|
||||||
F ext/userauth/userauth.c 3410be31283abba70255d71fd24734e017a4497f
|
F ext/userauth/userauth.c 3410be31283abba70255d71fd24734e017a4497f
|
||||||
@@ -1716,7 +1717,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
|
|||||||
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
|
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
|
||||||
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
|
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
|
||||||
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
|
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
|
||||||
P 901cb3b6a2c8d0cc33bd34ec1dbeea49c779ae1ac3ed6733dd9826c8e0eb80c8
|
P eb4f452e354065d610ff57a6a9312ad119b6b0cc467f9dff105f0718bc27ef01 07cc955eab0e993a75be82d58e17ca53c8abbcaf851983d235049599c19e582f
|
||||||
R ea4beed5a6f5a13ca5aa6b2ab2f884d0
|
R 27f6cf97018270780b80846440bc882c
|
||||||
|
T +closed 07cc955eab0e993a75be82d58e17ca53c8abbcaf851983d235049599c19e582f
|
||||||
U dan
|
U dan
|
||||||
Z 1a6cc6f516e2cd588ac4c5f3e803854c
|
Z c2acdc4842096b171e69d1edd2d47e40
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
eb4f452e354065d610ff57a6a9312ad119b6b0cc467f9dff105f0718bc27ef01
|
509506c76b7c104961826721013889d6c6b2ed9b563dcd029e0cb5cb5c34693a
|
||||||
Reference in New Issue
Block a user