mirror of https://github.com/sqlite/sqlite
Clarify handling of NULL values in PK columns in sqlite3session.h. Add tests and fixes for the same.
FossilOrigin-Name: aed4273054cbd150c86b36ea951d17c981633ba0
This commit is contained in:
parent
4e895da185
commit
27453faef8
|
@ -111,6 +111,75 @@ do_iterator_test 1.1 t1 {
|
|||
{INSERT t1 {} {f 1.5 f 99.9}}
|
||||
}
|
||||
|
||||
# Execute each of the following blocks of SQL on database [db1]. Collect
|
||||
# changes using a session object. Apply the resulting changeset to
|
||||
# database [db2]. Then check that the contents of the two databases are
|
||||
# identical.
|
||||
#
|
||||
|
||||
set set_of_tests {
|
||||
1 { INSERT INTO %T1% VALUES(1, 2) }
|
||||
|
||||
2 {
|
||||
INSERT INTO %T2% VALUES(1, NULL);
|
||||
INSERT INTO %T2% VALUES(2, NULL);
|
||||
INSERT INTO %T2% VALUES(3, NULL);
|
||||
DELETE FROM %T2% WHERE a = 2;
|
||||
INSERT INTO %T2% VALUES(4, NULL);
|
||||
UPDATE %T2% SET b=0 WHERE b=1;
|
||||
}
|
||||
|
||||
3 { INSERT INTO %T3% SELECT *, NULL FROM %T2% }
|
||||
|
||||
4 {
|
||||
INSERT INTO %T3% SELECT a||a, b||b, NULL FROM %T3%;
|
||||
DELETE FROM %T3% WHERE rowid%2;
|
||||
}
|
||||
|
||||
5 { UPDATE %T3% SET c = a||b }
|
||||
|
||||
6 { UPDATE %T1% SET a = 32 }
|
||||
|
||||
7 {
|
||||
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
|
||||
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
|
||||
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
|
||||
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
|
||||
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
|
||||
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
|
||||
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
|
||||
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
|
||||
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
|
||||
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
|
||||
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
|
||||
DELETE FROM %T1% WHERE (rowid%3)==0;
|
||||
}
|
||||
|
||||
8 {
|
||||
BEGIN;
|
||||
INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
|
||||
ROLLBACK;
|
||||
}
|
||||
9 {
|
||||
BEGIN;
|
||||
UPDATE %T1% SET b = 'xxx';
|
||||
ROLLBACK;
|
||||
}
|
||||
10 {
|
||||
BEGIN;
|
||||
DELETE FROM %T1% WHERE 1;
|
||||
ROLLBACK;
|
||||
}
|
||||
11 {
|
||||
INSERT INTO %T1% VALUES(randomblob(21000), randomblob(0));
|
||||
INSERT INTO %T1% VALUES(1.5, 1.5);
|
||||
INSERT INTO %T1% VALUES(4.56, -99.999999999999999999999);
|
||||
}
|
||||
12 {
|
||||
INSERT INTO %T2% VALUES(NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
test_reset
|
||||
do_common_sql {
|
||||
CREATE TABLE t1(a PRIMARY KEY, b);
|
||||
|
@ -118,65 +187,7 @@ do_common_sql {
|
|||
CREATE TABLE t3(a, b, c, PRIMARY KEY(a, b));
|
||||
}
|
||||
|
||||
# Execute each of the following blocks of SQL on database [db1]. Collect
|
||||
# changes using a session object. Apply the resulting changeset to
|
||||
# database [db2]. Then check that the contents of the two databases are
|
||||
# identical.
|
||||
#
|
||||
foreach {tn sql} {
|
||||
1 { INSERT INTO t1 VALUES(1, 2) }
|
||||
|
||||
2 {
|
||||
INSERT INTO t2 VALUES(1, NULL);
|
||||
INSERT INTO t2 VALUES(2, NULL);
|
||||
INSERT INTO t2 VALUES(3, NULL);
|
||||
DELETE FROM t2 WHERE a = 2;
|
||||
INSERT INTO t2 VALUES(4, NULL);
|
||||
UPDATE t2 SET b=0 WHERE b=1;
|
||||
}
|
||||
|
||||
3 { INSERT INTO t3 SELECT *, NULL FROM t2 }
|
||||
|
||||
4 {
|
||||
INSERT INTO t3 SELECT a||a, b||b, NULL FROM t3;
|
||||
DELETE FROM t3 WHERE rowid%2;
|
||||
}
|
||||
|
||||
5 { UPDATE t3 SET c = a||b }
|
||||
|
||||
6 { UPDATE t1 SET a = 32 }
|
||||
|
||||
7 {
|
||||
INSERT INTO t1 SELECT randomblob(32), randomblob(32) FROM t1; -- 2
|
||||
INSERT INTO t1 SELECT randomblob(32), randomblob(32) FROM t1; -- 4
|
||||
INSERT INTO t1 SELECT randomblob(32), randomblob(32) FROM t1; -- 8
|
||||
INSERT INTO t1 SELECT randomblob(32), randomblob(32) FROM t1; -- 16
|
||||
INSERT INTO t1 SELECT randomblob(32), randomblob(32) FROM t1; -- 32
|
||||
INSERT INTO t1 SELECT randomblob(32), randomblob(32) FROM t1; -- 64
|
||||
INSERT INTO t1 SELECT randomblob(32), randomblob(32) FROM t1; -- 128
|
||||
INSERT INTO t1 SELECT randomblob(32), randomblob(32) FROM t1; -- 256
|
||||
INSERT INTO t1 SELECT randomblob(32), randomblob(32) FROM t1; -- 512
|
||||
INSERT INTO t1 SELECT randomblob(32), randomblob(32) FROM t1; -- 1024
|
||||
INSERT INTO t1 SELECT randomblob(32), randomblob(32) FROM t1; -- 2048
|
||||
DELETE FROM t1 WHERE (rowid%3)==0;
|
||||
}
|
||||
|
||||
8 {
|
||||
BEGIN;
|
||||
INSERT INTO t1 SELECT randomblob(32), randomblob(32) FROM t1;
|
||||
ROLLBACK;
|
||||
}
|
||||
9 {
|
||||
BEGIN;
|
||||
UPDATE t1 SET b = 'xxx';
|
||||
ROLLBACK;
|
||||
}
|
||||
10 {
|
||||
BEGIN;
|
||||
DELETE FROM t1 WHERE 1;
|
||||
ROLLBACK;
|
||||
}
|
||||
} {
|
||||
foreach {tn sql} [string map {%T1% t1 %T2% t2 %T3% t3} $set_of_tests] {
|
||||
do_then_apply_sql $sql
|
||||
do_test 1.$tn { compare_db db db2 } {}
|
||||
}
|
||||
|
@ -209,73 +220,61 @@ do_test 2.0 {
|
|||
|
||||
proc xTrace {args} { puts $args }
|
||||
|
||||
foreach {tn sql} {
|
||||
|
||||
1 { INSERT INTO aux.t1 VALUES(1, 2) }
|
||||
|
||||
2 {
|
||||
INSERT INTO aux.t2 VALUES(1, NULL);
|
||||
INSERT INTO aux.t2 VALUES(2, NULL);
|
||||
INSERT INTO aux.t2 VALUES(3, NULL);
|
||||
DELETE FROM aux.t2 WHERE a = 2;
|
||||
INSERT INTO aux.t2 VALUES(4, NULL);
|
||||
UPDATE aux.t2 SET b=0 WHERE b=1;
|
||||
}
|
||||
|
||||
3 { INSERT INTO aux.t3 SELECT *, NULL FROM aux.t2 }
|
||||
|
||||
4 {
|
||||
INSERT INTO aux.t3 SELECT a||a, b||b, NULL FROM aux.t3;
|
||||
DELETE FROM aux.t3 WHERE rowid%2;
|
||||
}
|
||||
|
||||
5 { UPDATE aux.t3 SET c = a||b }
|
||||
|
||||
6 { UPDATE aux.t1 SET a = 32 }
|
||||
|
||||
7 {
|
||||
INSERT INTO aux.t1 SELECT randomblob(32), randomblob(32) FROM aux.t1;
|
||||
INSERT INTO aux.t1 SELECT randomblob(32), randomblob(32) FROM aux.t1;
|
||||
INSERT INTO aux.t1 SELECT randomblob(32), randomblob(32) FROM aux.t1;
|
||||
INSERT INTO aux.t1 SELECT randomblob(32), randomblob(32) FROM aux.t1;
|
||||
INSERT INTO aux.t1 SELECT randomblob(32), randomblob(32) FROM aux.t1;
|
||||
INSERT INTO aux.t1 SELECT randomblob(32), randomblob(32) FROM aux.t1;
|
||||
INSERT INTO aux.t1 SELECT randomblob(32), randomblob(32) FROM aux.t1;
|
||||
INSERT INTO aux.t1 SELECT randomblob(32), randomblob(32) FROM aux.t1;
|
||||
INSERT INTO aux.t1 SELECT randomblob(32), randomblob(32) FROM aux.t1;
|
||||
INSERT INTO aux.t1 SELECT randomblob(32), randomblob(32) FROM aux.t1;
|
||||
INSERT INTO aux.t1 SELECT randomblob(32), randomblob(32) FROM aux.t1;
|
||||
DELETE FROM aux.t1 WHERE (rowid%3)==0;
|
||||
}
|
||||
|
||||
8 {
|
||||
BEGIN;
|
||||
INSERT INTO aux.t1 SELECT randomblob(32), randomblob(32) FROM aux.t1;
|
||||
ROLLBACK;
|
||||
}
|
||||
9 {
|
||||
BEGIN;
|
||||
UPDATE aux.t1 SET b = 'xxx';
|
||||
ROLLBACK;
|
||||
}
|
||||
10 {
|
||||
BEGIN;
|
||||
DELETE FROM aux.t1 WHERE 1;
|
||||
ROLLBACK;
|
||||
}
|
||||
11 {
|
||||
INSERT INTO aux.t1 VALUES(randomblob(21000), randomblob(0));
|
||||
INSERT INTO aux.t1 VALUES(1.5, 1.5);
|
||||
INSERT INTO aux.t1 VALUES(4.56, -99.999999999999999999999);
|
||||
}
|
||||
|
||||
} {
|
||||
foreach {tn sql} [
|
||||
string map {%T1% aux.t1 %T2% aux.t2 %T3% aux.t3} $set_of_tests
|
||||
] {
|
||||
do_then_apply_sql $sql aux
|
||||
do_test 2.$tn { compare_db db3 db2 } {}
|
||||
}
|
||||
|
||||
|
||||
catch {db3 close}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# The following tests verify that NULL values in primary key columns are
|
||||
# handled correctly by the session module.
|
||||
#
|
||||
test_reset
|
||||
do_execsql_test 3.0 {
|
||||
CREATE TABLE t1(a PRIMARY KEY);
|
||||
CREATE TABLE t2(a, b, c, PRIMARY KEY(c, b));
|
||||
CREATE TABLE t3(a, b INTEGER PRIMARY KEY);
|
||||
}
|
||||
|
||||
foreach {tn sql changeset} {
|
||||
1 {
|
||||
INSERT INTO t1 VALUES(123);
|
||||
INSERT INTO t1 VALUES(NULL);
|
||||
INSERT INTO t1 VALUES(456);
|
||||
} {
|
||||
{INSERT t1 {} {i 456}}
|
||||
{INSERT t1 {} {i 123}}
|
||||
}
|
||||
|
||||
2 {
|
||||
UPDATE t1 SET a = NULL;
|
||||
} {
|
||||
{DELETE t1 {i 456} {}}
|
||||
{DELETE t1 {i 123} {}}
|
||||
}
|
||||
|
||||
3 { DELETE FROM t1 } { }
|
||||
|
||||
4 {
|
||||
INSERT INTO t3 VALUES(NULL, NULL)
|
||||
} {
|
||||
{INSERT t3 {} {n {} i 1}}
|
||||
}
|
||||
|
||||
5 { INSERT INTO t2 VALUES(1, 2, NULL) } { }
|
||||
6 { INSERT INTO t2 VALUES(1, NULL, 3) } { }
|
||||
7 { INSERT INTO t2 VALUES(1, NULL, NULL) } { }
|
||||
8 { INSERT INTO t2 VALUES(1, 2, 3) } { {INSERT t2 {} {i 1 i 2 i 3}} }
|
||||
9 { DELETE FROM t2 WHERE 1 } { {DELETE t2 {i 1 i 2 i 3} {}} }
|
||||
|
||||
} {
|
||||
do_iterator_test 3.$tn {t1 t2 t3} $sql $changeset
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
|
|
|
@ -286,11 +286,13 @@ static unsigned int sessionPreupdateHash(
|
|||
sqlite3 *db, /* Database handle */
|
||||
SessionTable *pTab, /* Session table handle */
|
||||
int bNew, /* True to hash the new.* PK */
|
||||
int *piHash /* OUT: Hash value */
|
||||
int *piHash, /* OUT: Hash value */
|
||||
int *pbNullPK
|
||||
){
|
||||
unsigned int h = 0; /* Hash value to return */
|
||||
int i; /* Used to iterate through columns */
|
||||
|
||||
assert( *pbNullPK==0 );
|
||||
assert( pTab->nCol==sqlite3_preupdate_count(db) );
|
||||
for(i=0; i<pTab->nCol; i++){
|
||||
if( pTab->abPK[i] ){
|
||||
|
@ -329,6 +331,11 @@ static unsigned int sessionPreupdateHash(
|
|||
h = sessionHashAppendBlob(h, n, z);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
assert( eType==SQLITE_NULL );
|
||||
*pbNullPK = 1;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -357,27 +364,22 @@ static unsigned int sessionChangeHash(
|
|||
int eType = *a++;
|
||||
int isPK = pTab->abPK[i];
|
||||
|
||||
/* It is not possible for eType to be SQLITE_NULL here. The session
|
||||
** module does not record changes for rows with NULL values stored in
|
||||
** primary key columns. */
|
||||
assert( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT
|
||||
|| eType==SQLITE_TEXT || eType==SQLITE_BLOB
|
||||
);
|
||||
|
||||
if( isPK ) h = HASH_APPEND(h, eType);
|
||||
switch( eType ){
|
||||
case SQLITE_INTEGER:
|
||||
case SQLITE_FLOAT: {
|
||||
if( isPK ){
|
||||
i64 iVal = sessionGetI64(a);
|
||||
h = sessionHashAppendI64(h, iVal);
|
||||
}
|
||||
if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
|
||||
if( isPK ) h = sessionHashAppendI64(h, sessionGetI64(a));
|
||||
a += 8;
|
||||
break;
|
||||
}
|
||||
case SQLITE_TEXT:
|
||||
case SQLITE_BLOB: {
|
||||
}else{
|
||||
int n;
|
||||
a += sessionVarintGet(a, &n);
|
||||
if( isPK ){
|
||||
h = sessionHashAppendBlob(h, n, a);
|
||||
}
|
||||
if( isPK ) h = sessionHashAppendBlob(h, n, a);
|
||||
a += n;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return (h % nBucket);
|
||||
|
@ -665,6 +667,7 @@ static void sessionPreupdateOneChange(
|
|||
SessionChange *pChange;
|
||||
SessionChange *pC;
|
||||
int iHash;
|
||||
int bNullPk = 0;
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
if( pSession->rc ) return;
|
||||
|
@ -679,7 +682,8 @@ static void sessionPreupdateOneChange(
|
|||
** one is found, store a pointer to it in pChange and unlink it from
|
||||
** the hash table. Otherwise, set pChange to NULL.
|
||||
*/
|
||||
rc = sessionPreupdateHash(db, pTab, op==SQLITE_INSERT, &iHash);
|
||||
rc = sessionPreupdateHash(db, pTab, op==SQLITE_INSERT, &iHash, &bNullPk);
|
||||
if( bNullPk==0 ){
|
||||
for(pC=pTab->apChange[iHash]; rc==SQLITE_OK && pC; pC=pC->pNext){
|
||||
int bEqual;
|
||||
rc = sessionPreupdateEqual(db, pTab, pC, op==SQLITE_INSERT, &bEqual);
|
||||
|
@ -743,6 +747,7 @@ static void sessionPreupdateOneChange(
|
|||
pTab->apChange[iHash] = pChange;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -106,6 +106,9 @@ int sqlite3session_enable(sqlite3_session *pSession, int bEnable);
|
|||
** is it an error if the named table does not have a PRIMARY KEY. However,
|
||||
** no changes will be recorded in either of these scenarios.
|
||||
**
|
||||
** Changes are not recorded for individual rows that have NULL values stored
|
||||
** in one or more of their PRIMARY KEY columns.
|
||||
**
|
||||
** SQLITE_OK is returned if the table is successfully attached to the session
|
||||
** object. Or, if an error occurs, an SQLite error code (e.g. SQLITE_NOMEM)
|
||||
** is returned.
|
||||
|
@ -135,6 +138,16 @@ int sqlite3session_attach(
|
|||
** modifies the values of primary key columns. If such a change is made, it
|
||||
** is represented in a changeset as a DELETE followed by an INSERT.
|
||||
**
|
||||
** Changes are not recorded for rows that have NULL values stored in one or
|
||||
** more of their PRIMARY KEY columns. If such a row is inserted or deleted,
|
||||
** no corresponding change is present in the changesets returned by this
|
||||
** function. If an existing row with one or more NULL values stored in
|
||||
** PRIMARY KEY columns is updated so that all PRIMARY KEY columns are non-NULL,
|
||||
** only an INSERT is appears in the changeset. Similarly, if an existing row
|
||||
** with non-NULL PRIMARY KEY values is updated so that one or more of its
|
||||
** PRIMARY KEY columns are set to NULL, the resulting changeset contains a
|
||||
** DELETE change only.
|
||||
**
|
||||
** The contents of a changeset may be traversed using an iterator created
|
||||
** using the [sqlite3changeset_start()] API. A changeset may be applied to
|
||||
** a database with a compatible schema using the [sqlite3changeset_apply()]
|
||||
|
@ -153,6 +166,10 @@ int sqlite3session_attach(
|
|||
** recorded once - the first time a row with said primary key is inserted,
|
||||
** updated or deleted in the lifetime of the session.
|
||||
**
|
||||
** There is one exception to the previous paragraph: when a row is inserted,
|
||||
** updated or deleted, if one or more of its primary key columns contains a
|
||||
** NULL value, no record of the change is made.
|
||||
**
|
||||
** The session object therefore accumulates two types of records - those
|
||||
** that consist of primary key values only (created when the user inserts
|
||||
** a new record) and those that consist of the primary key values and the
|
||||
|
|
16
manifest
16
manifest
|
@ -1,5 +1,5 @@
|
|||
C Fix\ssession\smodule\sproblems\swith\sreal\s(floating\spoint)\svalues.
|
||||
D 2011-03-21T11:03:25
|
||||
C Clarify\shandling\sof\sNULL\svalues\sin\sPK\scolumns\sin\ssqlite3session.h.\sAdd\stests\sand\sfixes\sfor\sthe\ssame.
|
||||
D 2011-03-21T11:55:07
|
||||
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
|
||||
F Makefile.in 27701a1653595a1f2187dc61c8117e00a6c1d50f
|
||||
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
|
||||
|
@ -100,9 +100,9 @@ F ext/rtree/sqlite3rtree.h 1af0899c63a688e272d69d8e746f24e76f10a3f0
|
|||
F ext/rtree/tkt3363.test 142ab96eded44a3615ec79fba98c7bde7d0f96de
|
||||
F ext/rtree/viewrtree.tcl eea6224b3553599ae665b239bd827e182b466024
|
||||
F ext/session/session1.test 3f982c74ee4ba97069917cc35aae25b4ed858e6a
|
||||
F ext/session/session2.test 45c9ff2052bf132d25d272b1d4b53f95c1c31463
|
||||
F ext/session/sqlite3session.c 3ed836ee8c6faff866bc59da800b6f20b0285071
|
||||
F ext/session/sqlite3session.h b77b014793162a77ac16507d720fe085cc15d06c
|
||||
F ext/session/session2.test 3ef304f660b2a929e6bfec2df125c1809f5501ff
|
||||
F ext/session/sqlite3session.c 70b19f80eadf7060836eaa90928f08a58aa3b35f
|
||||
F ext/session/sqlite3session.h 2c071ee5925e82c21c7c9c296a0422c039607106
|
||||
F ext/session/test_session.c 2559ef68e421c7fb83e2c19ef08a17343b70d535
|
||||
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895
|
||||
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
|
||||
|
@ -921,7 +921,7 @@ F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff
|
|||
F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
|
||||
F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
|
||||
F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
|
||||
P 0853e530cc8d96f025f5160540e8ab3243dea11b
|
||||
R 482fc377ccc3f262af8beae8e4f384bf
|
||||
P a192d04f4e3a9e4960a4d96d1d3ee8635bc1034d
|
||||
R 0282a54a03134becd2cdef1929ff705c
|
||||
U dan
|
||||
Z 767fabeb255e365fd4d794b5ace5ff7b
|
||||
Z 9cc92283568709d19359195efd9bde16
|
||||
|
|
|
@ -1 +1 @@
|
|||
a192d04f4e3a9e4960a4d96d1d3ee8635bc1034d
|
||||
aed4273054cbd150c86b36ea951d17c981633ba0
|
Loading…
Reference in New Issue