mirror of
https://github.com/sqlite/sqlite.git
synced 2026-10-25 09:59:22 +03:00
410 lines
12 KiB
Plaintext
410 lines
12 KiB
Plaintext
# 2015 March 20
|
|
#
|
|
# The author disclaims copyright to this source code. In place of
|
|
# a legal notice, here is a blessing:
|
|
#
|
|
# May you do good and not evil.
|
|
# May you find forgiveness for yourself and forgive others.
|
|
# May you share freely, never taking more than you give.
|
|
#
|
|
#***********************************************************************
|
|
#
|
|
# The tests in this file ensure that sorter objects are used by
|
|
# "INSERT INTO ... SELECT ..." statements when possible.
|
|
#
|
|
|
|
set testdir [file dirname $argv0]
|
|
source $testdir/tester.tcl
|
|
set testprefix insert6
|
|
|
|
# Return the number of OP_SorterOpen instructions in the SQL passed as
|
|
# the only argument if it is compiled using connection [db].
|
|
#
|
|
proc sorter_count {sql} {
|
|
set res 0
|
|
db cache flush
|
|
db eval "EXPLAIN $sql" x {
|
|
if {$x(opcode) == "SorterOpen"} { incr res }
|
|
}
|
|
return $res
|
|
}
|
|
|
|
|
|
#-------------------------------------------------------------------------
|
|
# Warm body test. This verifies that the simplest case works for both
|
|
# regular and WITHOUT ROWID tables.
|
|
#
|
|
do_execsql_test 1.1 {
|
|
CREATE TABLE t2(x UNIQUE ON CONFLICT IGNORE, y, z);
|
|
WITH cnt(x) AS ( SELECT 0 UNION ALL SELECT x+1 FROM cnt WHERE x<99 )
|
|
INSERT INTO t2 SELECT abs(random()), abs(random()), abs(random()) FROM cnt;
|
|
}
|
|
|
|
foreach {tn nSort schema} {
|
|
1 3 { CREATE TABLE t1(a, b, c) }
|
|
2 4 { CREATE TABLE t1(a PRIMARY KEY, b, c) WITHOUT ROWID }
|
|
} {
|
|
|
|
do_test 1.$tn.1 {
|
|
execsql { DROP TABLE IF EXISTS t1 }
|
|
execsql $schema
|
|
} {}
|
|
|
|
do_execsql_test 1.$tn.2 {
|
|
CREATE INDEX t1a ON t1(a);
|
|
CREATE INDEX t1b ON t1(b);
|
|
CREATE INDEX t1c ON t1(c);
|
|
}
|
|
|
|
do_execsql_test 1.$tn.3 {
|
|
INSERT INTO t1 SELECT x, y, z FROM t2;
|
|
PRAGMA integrity_check;
|
|
SELECT count(*) FROM t1;
|
|
} {ok 100}
|
|
|
|
do_execsql_test 1.$tn.4 {
|
|
INSERT INTO t1 SELECT -x, y, z FROM t2;
|
|
PRAGMA integrity_check;
|
|
} {ok}
|
|
|
|
do_execsql_test 1.$tn.5 {
|
|
SELECT count(*) FROM t1;
|
|
} {200}
|
|
|
|
do_test 1.$tn.6 {
|
|
sorter_count { INSERT INTO t1 SELECT * FROM t2 }
|
|
} $nSort
|
|
}
|
|
|
|
#-------------------------------------------------------------------------
|
|
# The following test cases check that the sorters are disabled if any
|
|
# of the following are true:
|
|
#
|
|
# 2.1: The statement specifies "ON CONFLICT FAIL", "IGNORE" or "REPLACE".
|
|
#
|
|
# 2.2: The statement does not explicitly specify a conflict mode and
|
|
# there are one or more PRIMARY KEY or UNIQUE constraints with
|
|
# "OR FAIL", "OR IGNORE" or "OR REPLACE" as the conflict handling
|
|
# mode.
|
|
#
|
|
# 2.3: There are one or more INSERT triggers on the target table.
|
|
#
|
|
# 2.4: The target table is the parent or child of an FK constraint.
|
|
#
|
|
|
|
do_execsql_test 2.1.1 {
|
|
CREATE TABLE x1(a, b, c);
|
|
CREATE INDEX x1a ON x1(a);
|
|
|
|
CREATE TABLE x2(a, b, c);
|
|
CREATE UNIQUE INDEX x2a ON x2(a);
|
|
|
|
CREATE TABLE x3(a PRIMARY KEY, b, c);
|
|
CREATE TABLE x4(a PRIMARY KEY, b, c) WITHOUT ROWID;
|
|
}
|
|
|
|
do_test 2.1.2 { sorter_count { INSERT OR REPLACE INTO x1 SELECT * FROM t2 } } 0
|
|
do_test 2.1.3 { sorter_count { INSERT OR REPLACE INTO x2 SELECT * FROM t2 } } 0
|
|
do_test 2.1.4 { sorter_count { INSERT OR REPLACE INTO x3 SELECT * FROM t2 } } 0
|
|
do_test 2.1.5 { sorter_count { INSERT OR REPLACE INTO x4 SELECT * FROM t2 } } 0
|
|
|
|
do_test 2.1.6 { sorter_count { INSERT OR IGNORE INTO x1 SELECT * FROM t2 } } 0
|
|
do_test 2.1.7 { sorter_count { INSERT OR IGNORE INTO x2 SELECT * FROM t2 } } 0
|
|
do_test 2.1.8 { sorter_count { INSERT OR IGNORE INTO x3 SELECT * FROM t2 } } 0
|
|
do_test 2.1.9 { sorter_count { INSERT OR IGNORE INTO x4 SELECT * FROM t2 } } 0
|
|
|
|
do_test 2.1.10 { sorter_count { INSERT OR FAIL INTO x1 SELECT * FROM t2 } } 0
|
|
do_test 2.1.11 { sorter_count { INSERT OR FAIL INTO x2 SELECT * FROM t2 } } 0
|
|
do_test 2.1.12 { sorter_count { INSERT OR FAIL INTO x3 SELECT * FROM t2 } } 0
|
|
do_test 2.1.13 { sorter_count { INSERT OR FAIL INTO x4 SELECT * FROM t2 } } 0
|
|
|
|
do_test 2.1.14 { sorter_count { INSERT OR ROLLBACK INTO x1 SELECT * FROM t2} } 1
|
|
do_test 2.1.15 { sorter_count { INSERT OR ROLLBACK INTO x2 SELECT * FROM t2} } 1
|
|
do_test 2.1.16 { sorter_count { INSERT OR ROLLBACK INTO x3 SELECT * FROM t2} } 1
|
|
do_test 2.1.17 { sorter_count { INSERT OR ROLLBACK INTO x4 SELECT * FROM t2} } 1
|
|
|
|
do_test 2.1.18 { sorter_count { INSERT OR ABORT INTO x1 SELECT * FROM t2 } } 1
|
|
do_test 2.1.19 { sorter_count { INSERT OR ABORT INTO x2 SELECT * FROM t2 } } 1
|
|
do_test 2.1.20 { sorter_count { INSERT OR ABORT INTO x3 SELECT * FROM t2 } } 1
|
|
do_test 2.1.21 { sorter_count { INSERT OR ABORT INTO x4 SELECT * FROM t2 } } 1
|
|
|
|
|
|
foreach {tn scount schema} {
|
|
2.1 0 { CREATE TABLE t1(a UNIQUE ON CONFLICT FAIL, b, c) }
|
|
2.2 0 { CREATE TABLE t1(a, b UNIQUE ON CONFLICT IGNORE, c) }
|
|
2.3 0 { CREATE TABLE t1(a, b, c UNIQUE ON CONFLICT REPLACE) }
|
|
2.4 0 { CREATE TABLE t1(a PRIMARY KEY ON CONFLICT FAIL, b, c) }
|
|
2.5 0 { CREATE TABLE t1(a, b PRIMARY KEY ON CONFLICT IGNORE, c) }
|
|
2.6 0 { CREATE TABLE t1(a, b, c PRIMARY KEY ON CONFLICT REPLACE) }
|
|
2.7 0 {
|
|
CREATE TABLE t1(a PRIMARY KEY ON CONFLICT FAIL, b, c) WITHOUT ROWID
|
|
}
|
|
2.8 0 {
|
|
CREATE TABLE t1(a, b PRIMARY KEY ON CONFLICT IGNORE, c) WITHOUT ROWID
|
|
}
|
|
2.9 0 {
|
|
CREATE TABLE t1(a, b, c PRIMARY KEY ON CONFLICT REPLACE) WITHOUT ROWID
|
|
}
|
|
|
|
3.1 1 {
|
|
CREATE TABLE t1(a, b, c);
|
|
CREATE INDEX i1 ON t1(a);
|
|
}
|
|
3.2 0 {
|
|
CREATE TABLE t1(a, b, c);
|
|
CREATE INDEX i1 ON t1(a);
|
|
CREATE TRIGGER tr1 AFTER INSERT ON t1 BEGIN SELECT 1; END;
|
|
}
|
|
3.3 0 {
|
|
CREATE TABLE t1(a, b, c);
|
|
CREATE INDEX i1 ON t1(a);
|
|
CREATE TRIGGER tr2 BEFORE INSERT ON t1 BEGIN SELECT 1; END;
|
|
}
|
|
|
|
4.1 2 {
|
|
CREATE TABLE t1(a PRIMARY KEY, b, c);
|
|
CREATE INDEX i1 ON t1(a);
|
|
CREATE TABLE c1(x, y REFERENCES t1 DEFERRABLE INITIALLY DEFERRED);
|
|
PRAGMA foreign_keys = 0;
|
|
}
|
|
4.2 0 {
|
|
CREATE TABLE t1(a PRIMARY KEY, b, c);
|
|
CREATE INDEX i1 ON t1(a);
|
|
CREATE TABLE c1(x, y REFERENCES t1 DEFERRABLE INITIALLY DEFERRED);
|
|
PRAGMA foreign_keys = 1;
|
|
}
|
|
|
|
4.3 1 {
|
|
CREATE TABLE p1(x, y UNIQUE);
|
|
CREATE TABLE t1(a, b, c REFERENCES p1(y));
|
|
CREATE INDEX i1 ON t1(a);
|
|
PRAGMA foreign_keys = 0;
|
|
}
|
|
4.4 0 {
|
|
CREATE TABLE p1(x, y UNIQUE);
|
|
CREATE TABLE t1(a, b, c REFERENCES p1(y));
|
|
CREATE INDEX i1 ON t1(a);
|
|
PRAGMA foreign_keys = 1;
|
|
}
|
|
|
|
} {
|
|
execsql {
|
|
DROP TABLE IF EXISTS t1;
|
|
DROP TABLE IF EXISTS c1;
|
|
DROP TABLE IF EXISTS p1;
|
|
}
|
|
|
|
do_test 2.2.$tn {
|
|
execsql $schema
|
|
sorter_count { INSERT INTO t1 SELECT * FROM t2 }
|
|
} $scount
|
|
}
|
|
|
|
#-------------------------------------------------------------------------
|
|
# Test that if a UNIQUE constraint is violated and the on conflict mode
|
|
# is either ABORT or ROLLBACK, the conflict is handled correctly.
|
|
#
|
|
# 3.2: Check that conflicts are actually detected.
|
|
# 3.3: Check that OR ROLLBACK really does rollback the transaction.
|
|
# 3.4: Check that OR ABORT does not.
|
|
#
|
|
do_execsql_test 3.1 {
|
|
DROP TABLE IF EXISTS t1;
|
|
CREATE TABLE t1(a PRIMARY KEY, b, c, UNIQUE(b, c));
|
|
INSERT INTO t1 VALUES(1, 2, 3);
|
|
INSERT INTO t1 VALUES(4, 5, 6);
|
|
INSERT INTO t1 VALUES(7, 8, 9);
|
|
CREATE TABLE src(a, b, c);
|
|
}
|
|
|
|
do_catchsql_test 3.2.1 {
|
|
INSERT INTO src VALUES (10, 11, 12), (7, 14, 12);
|
|
INSERT INTO t1 SELECT * FROM src;
|
|
} {1 {UNIQUE constraint failed: t1.a}}
|
|
|
|
do_catchsql_test 3.2.2 {
|
|
DELETE FROM src;
|
|
INSERT INTO src VALUES (10, 11, 12), (13, 5, 6);
|
|
INSERT INTO t1 SELECT * FROM src;
|
|
} {1 {UNIQUE constraint failed: t1.b, t1.c}}
|
|
|
|
do_catchsql_test 3.2.3.1 {
|
|
CREATE TABLE t3(a);
|
|
CREATE UNIQUE INDEX t3a ON t3(a);
|
|
|
|
CREATE TABLE t3src(a);
|
|
WITH cnt(x) AS ( SELECT 0 UNION ALL SELECT x+1 FROM cnt WHERE x<10 )
|
|
INSERT INTO t3src SELECT 'abc' FROM cnt;
|
|
} {0 {}}
|
|
|
|
# execsql { PRAGMA vdbe_trace = 1 }
|
|
do_catchsql_test 3.2.3.2 {
|
|
INSERT INTO t3 SELECT * FROM t3src;
|
|
} {1 {UNIQUE constraint failed: t3.a}}
|
|
|
|
do_catchsql_test 3.3.1 {
|
|
DELETE FROM src;
|
|
BEGIN;
|
|
INSERT INTO src VALUES (10, 11, 12), (7, 13, 14);
|
|
INSERT OR ROLLBACK INTO t1 SELECT * FROM src;
|
|
} {1 {UNIQUE constraint failed: t1.a}}
|
|
do_catchsql_test 3.3.2 {
|
|
DELETE FROM src;
|
|
BEGIN;
|
|
INSERT INTO src VALUES (10, 11, 12), (13, 5, 6);
|
|
INSERT OR ROLLBACK INTO t1 SELECT * FROM src;
|
|
} {1 {UNIQUE constraint failed: t1.b, t1.c}}
|
|
do_test 3.3.3 {
|
|
sqlite3_get_autocommit db
|
|
} 1
|
|
|
|
do_catchsql_test 3.4.1 {
|
|
DELETE FROM src;
|
|
BEGIN;
|
|
INSERT INTO src VALUES (10, 11, 12), (7, 14, 12);
|
|
INSERT OR ABORT INTO t1 SELECT * FROM src;
|
|
} {1 {UNIQUE constraint failed: t1.a}}
|
|
do_catchsql_test 3.4.2 {
|
|
ROLLBACK;
|
|
DELETE FROM src;
|
|
BEGIN;
|
|
INSERT INTO src VALUES (10, 11, 12), (13, 5, 6);
|
|
INSERT OR ABORT INTO t1 SELECT * FROM src;
|
|
} {1 {UNIQUE constraint failed: t1.b, t1.c}}
|
|
do_test 3.4.3 {
|
|
sqlite3_get_autocommit db
|
|
} 0
|
|
do_execsql_test 3.4.4 { ROLLBACK }
|
|
|
|
#-------------------------------------------------------------------------
|
|
# The following tests - 4.* - check that this optimization is actually
|
|
# doing something helpful. They do this by executing a big
|
|
# "INSERT INTO SELECT" statement in wal mode with a small pager cache.
|
|
# Once with "OR FAIL" (so that the sorters are not used) and once with
|
|
# the default "OR ABORT" (so that they are).
|
|
#
|
|
# If the sorters are doing their job, the wal file generated by the
|
|
# "OR ABORT" case should be much smaller than the "OR FAIL" trial.
|
|
#
|
|
|
|
proc odd_collate {lhs rhs} {
|
|
string compare [string range $lhs 6 end] [string range $rhs 6 end]
|
|
}
|
|
|
|
proc do_insert6_4_test {tn sql} {
|
|
|
|
reset_db
|
|
db collate odd_collate odd_collate
|
|
execsql $sql
|
|
db_save_and_close
|
|
|
|
foreach {tn2 ::onerror ::var} {
|
|
1 "OR ABORT" ::sz1
|
|
2 "OR FAIL" ::sz2
|
|
} {
|
|
do_test $tn.$tn2 {
|
|
db_restore_and_reopen
|
|
db collate odd_collate odd_collate
|
|
execsql "
|
|
PRAGMA journal_mode = wal;
|
|
PRAGMA cache_size = 5;
|
|
PRAGMA wal_autocheckpoint = 0;
|
|
INSERT $onerror INTO t1 SELECT * FROM src;
|
|
"
|
|
set $var [file size test.db-wal]
|
|
db close
|
|
} {}
|
|
}
|
|
|
|
do_test $tn.3.($::sz1<$::sz2) {
|
|
expr {$sz1 < ($sz2/2)}
|
|
} 1
|
|
|
|
sqlite3 db test.db
|
|
db collate odd_collate odd_collate
|
|
integrity_check $tn.4
|
|
}
|
|
|
|
do_insert6_4_test 4.1 {
|
|
CREATE TABLE t1(a, b, c);
|
|
CREATE UNIQUE INDEX t1a ON t1(a);
|
|
CREATE UNIQUE INDEX t1bc ON t1(b, c);
|
|
|
|
CREATE TABLE src(x, y, z);
|
|
WITH cnt(x) AS ( SELECT 0 UNION ALL SELECT x+1 FROM cnt WHERE x<2999 )
|
|
INSERT INTO src
|
|
SELECT randomblob(50), randomblob(50), randomblob(50) FROM cnt;
|
|
}
|
|
|
|
do_insert6_4_test 4.2 {
|
|
CREATE TABLE t1(a INTEGER PRIMARY KEY, b, x);
|
|
CREATE UNIQUE INDEX t1b ON t1(b);
|
|
CREATE INDEX t1x1 ON t1(x);
|
|
CREATE INDEX t1x2 ON t1(x);
|
|
CREATE INDEX t1x3 ON t1(x);
|
|
CREATE INDEX t1x4 ON t1(x);
|
|
|
|
CREATE TABLE src(a, b, x);
|
|
WITH cnt(x) AS ( SELECT 0 UNION ALL SELECT x+1 FROM cnt WHERE x<2999 )
|
|
INSERT INTO src
|
|
SELECT random(), x, zeroblob(50) FROM cnt;
|
|
}
|
|
|
|
do_insert6_4_test 4.3 {
|
|
CREATE TABLE t1(a, b, c);
|
|
CREATE UNIQUE INDEX t1ab ON t1(a, b);
|
|
CREATE UNIQUE INDEX t1ac ON t1(a, c);
|
|
|
|
CREATE TABLE src(a, b, c);
|
|
WITH cnt(x) AS ( SELECT 0 UNION ALL SELECT x+1 FROM cnt WHERE x<2999 )
|
|
INSERT INTO src
|
|
SELECT zeroblob(50), randomblob(50), randomblob(50) FROM cnt;
|
|
}
|
|
|
|
db collate odd_collate odd_collate
|
|
do_insert6_4_test 4.5 {
|
|
CREATE TABLE t1(t COLLATE odd_collate, v COLLATE odd_collate);
|
|
CREATE UNIQUE INDEX t1t ON t1(t);
|
|
CREATE UNIQUE INDEX t1v ON t1(v);
|
|
|
|
CREATE TABLE src(t, v);
|
|
WITH cnt(x) AS ( SELECT 0 UNION ALL SELECT x+1 FROM cnt WHERE x<2999 )
|
|
INSERT INTO src
|
|
SELECT hex(randomblob(50)), hex(randomblob(50)) FROM cnt;
|
|
}
|
|
|
|
db collate odd_collate odd_collate
|
|
do_insert6_4_test 4.6 {
|
|
CREATE TABLE t1(t COLLATE odd_collate PRIMARY KEY) WITHOUT ROWID;
|
|
CREATE TABLE src(t);
|
|
WITH cnt(x) AS ( SELECT 0 UNION ALL SELECT x+1 FROM cnt WHERE x<2999 )
|
|
INSERT INTO src
|
|
SELECT hex(randomblob(50)) FROM cnt;
|
|
}
|
|
|
|
#-------------------------------------------------------------------------
|
|
# At one point the sorters were used for INSERT statements that specify
|
|
# "OR FAIL", "REPLACE" or "IGNORE" if there were no PRIMARY KEY or
|
|
# UNIQUE indexes. This is incorrect, as all such tables have an implicit
|
|
# IPK column. So using the sorters can cause corruption. This test checks
|
|
# that that problem no longer exists.
|
|
#
|
|
reset_db
|
|
do_execsql_test 5.1 {
|
|
CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
|
|
CREATE INDEX t1b ON t1(b);
|
|
INSERT INTO t1 VALUES(1, 2, 3);
|
|
INSERT INTO t1 VALUES(4, 5, 6);
|
|
}
|
|
|
|
do_catchsql_test 5.2 {
|
|
INSERT OR FAIL INTO t1
|
|
SELECT 2, 'x', 'x' UNION ALL SELECT 3, 'x', 'x' UNION ALL SELECT 4, 'x', 'x';
|
|
} {1 {UNIQUE constraint failed: t1.a}}
|
|
|
|
integrity_check 5.3
|
|
|
|
|
|
finish_test
|
|
|