{INSERT t1 {} {f 1.5 f 99.9}}
}
-test_reset
-do_common_sql {
- CREATE TABLE t1(a PRIMARY KEY, b);
- CREATE TABLE t2(a, b INTEGER PRIMARY KEY);
- 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) }
+
+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;
+ 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 }
+ 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;
+ INSERT INTO %T3% SELECT a||a, b||b, NULL FROM %T3%;
+ DELETE FROM %T3% WHERE rowid%2;
}
- 5 { UPDATE t3 SET c = a||b }
+ 5 { UPDATE %T3% SET c = a||b }
- 6 { UPDATE t1 SET a = 32 }
+ 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;
+ 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;
+ INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
ROLLBACK;
}
9 {
BEGIN;
- UPDATE t1 SET b = 'xxx';
+ UPDATE %T1% SET b = 'xxx';
ROLLBACK;
}
10 {
BEGIN;
- DELETE FROM t1 WHERE 1;
+ 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);
+ CREATE TABLE t2(a, b INTEGER PRIMARY KEY);
+ CREATE TABLE t3(a, b, c, PRIMARY KEY(a, b));
+}
+
+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 } {}
}
proc xTrace {args} { puts $args }
-foreach {tn sql} {
+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}
- 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 }
+#-------------------------------------------------------------------------
+# 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);
+}
- 4 {
- INSERT INTO aux.t3 SELECT a||a, b||b, NULL FROM aux.t3;
- DELETE FROM aux.t3 WHERE rowid%2;
+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}}
}
- 5 { UPDATE aux.t3 SET c = a||b }
+ 2 {
+ UPDATE t1 SET a = NULL;
+ } {
+ {DELETE t1 {i 456} {}}
+ {DELETE t1 {i 123} {}}
+ }
- 6 { UPDATE aux.t1 SET a = 32 }
+ 3 { DELETE FROM t1 } { }
- 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;
+ 4 {
+ INSERT INTO t3 VALUES(NULL, NULL)
+ } {
+ {INSERT t3 {} {n {} i 1}}
}
- 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);
- }
+ 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_then_apply_sql $sql aux
- do_test 2.$tn { compare_db db3 db2 } {}
+ do_iterator_test 3.$tn {t1 t2 t3} $sql $changeset
}
-catch {db3 close}
-
finish_test
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] ){
h = sessionHashAppendBlob(h, n, z);
break;
}
+
+ default:
+ assert( eType==SQLITE_NULL );
+ *pbNullPK = 1;
+ return SQLITE_OK;
}
}
}
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);
- }
- a += 8;
- break;
- }
- case SQLITE_TEXT:
- case SQLITE_BLOB: {
- int n;
- a += sessionVarintGet(a, &n);
- if( isPK ){
- h = sessionHashAppendBlob(h, n, a);
- }
- a += n;
- break;
- }
+ if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
+ if( isPK ) h = sessionHashAppendI64(h, sessionGetI64(a));
+ a += 8;
+ }else{
+ int n;
+ a += sessionVarintGet(a, &n);
+ if( isPK ) h = sessionHashAppendBlob(h, n, a);
+ a += n;
}
}
return (h % nBucket);
SessionChange *pChange;
SessionChange *pC;
int iHash;
+ int bNullPk = 0;
int rc = SQLITE_OK;
if( pSession->rc ) return;
if( sessionGrowHash(pSession, pTab) ) return;
/* Search the hash table for an existing entry for rowid=iKey2. If
- ** 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);
- for(pC=pTab->apChange[iHash]; rc==SQLITE_OK && pC; pC=pC->pNext){
- int bEqual;
- rc = sessionPreupdateEqual(db, pTab, pC, op==SQLITE_INSERT, &bEqual);
- if( bEqual ) break;
- }
- if( pC==0 ){
- /* Create a new change object containing all the old values (if
- ** this is an SQLITE_UPDATE or SQLITE_DELETE), or just the PK
- ** values (if this is an INSERT). */
- int nByte; /* Number of bytes to allocate */
- int i; /* Used to iterate through columns */
-
- pTab->nEntry++;
-
- /* Figure out how large an allocation is required */
- nByte = sizeof(SessionChange);
- for(i=0; i<pTab->nCol && rc==SQLITE_OK; i++){
- sqlite3_value *p = 0;
- if( op!=SQLITE_INSERT ){
- rc = sqlite3_preupdate_old(pSession->db, i, &p);
- }else if( 1 || pTab->abPK[i] ){
- rc = sqlite3_preupdate_new(pSession->db, i, &p);
+ ** 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, &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);
+ if( bEqual ) break;
+ }
+ if( pC==0 ){
+ /* Create a new change object containing all the old values (if
+ ** this is an SQLITE_UPDATE or SQLITE_DELETE), or just the PK
+ ** values (if this is an INSERT). */
+ int nByte; /* Number of bytes to allocate */
+ int i; /* Used to iterate through columns */
+
+ pTab->nEntry++;
+
+ /* Figure out how large an allocation is required */
+ nByte = sizeof(SessionChange);
+ for(i=0; i<pTab->nCol && rc==SQLITE_OK; i++){
+ sqlite3_value *p = 0;
+ if( op!=SQLITE_INSERT ){
+ rc = sqlite3_preupdate_old(pSession->db, i, &p);
+ }else if( 1 || pTab->abPK[i] ){
+ rc = sqlite3_preupdate_new(pSession->db, i, &p);
+ }
+ if( p && rc==SQLITE_OK ){
+ rc = sessionSerializeValue(0, p, &nByte);
+ }
}
- if( p && rc==SQLITE_OK ){
- rc = sessionSerializeValue(0, p, &nByte);
+
+ /* Allocate the change object */
+ pChange = (SessionChange *)sqlite3_malloc(nByte);
+ if( !pChange ){
+ rc = SQLITE_NOMEM;
+ }else{
+ memset(pChange, 0, sizeof(SessionChange));
+ pChange->aRecord = (u8 *)&pChange[1];
}
- }
-
- /* Allocate the change object */
- pChange = (SessionChange *)sqlite3_malloc(nByte);
- if( !pChange ){
- rc = SQLITE_NOMEM;
- }else{
- memset(pChange, 0, sizeof(SessionChange));
- pChange->aRecord = (u8 *)&pChange[1];
- }
-
- /* Populate the change object */
- nByte = 0;
- for(i=0; i<pTab->nCol && rc==SQLITE_OK; i++){
- sqlite3_value *p = 0;
- if( op!=SQLITE_INSERT ){
- rc = sqlite3_preupdate_old(pSession->db, i, &p);
- }else if( 1 || pTab->abPK[i] ){
- rc = sqlite3_preupdate_new(pSession->db, i, &p);
+
+ /* Populate the change object */
+ nByte = 0;
+ for(i=0; i<pTab->nCol && rc==SQLITE_OK; i++){
+ sqlite3_value *p = 0;
+ if( op!=SQLITE_INSERT ){
+ rc = sqlite3_preupdate_old(pSession->db, i, &p);
+ }else if( 1 || pTab->abPK[i] ){
+ rc = sqlite3_preupdate_new(pSession->db, i, &p);
+ }
+ if( p && rc==SQLITE_OK ){
+ rc = sessionSerializeValue(&pChange->aRecord[nByte], p, &nByte);
+ }
}
- if( p && rc==SQLITE_OK ){
- rc = sessionSerializeValue(&pChange->aRecord[nByte], p, &nByte);
+ pChange->nRecord = nByte;
+
+ /* If an error has occurred, mark the session object as failed. */
+ if( rc!=SQLITE_OK ){
+ sqlite3_free(pChange);
+ pSession->rc = rc;
+ }else{
+ /* Add the change back to the hash-table */
+ pChange->bInsert = (op==SQLITE_INSERT);
+ pChange->pNext = pTab->apChange[iHash];
+ pTab->apChange[iHash] = pChange;
}
}
- pChange->nRecord = nByte;
-
- /* If an error has occurred, mark the session object as failed. */
- if( rc!=SQLITE_OK ){
- sqlite3_free(pChange);
- pSession->rc = rc;
- }else{
- /* Add the change back to the hash-table */
- pChange->bInsert = (op==SQLITE_INSERT);
- pChange->pNext = pTab->apChange[iHash];
- pTab->apChange[iHash] = pChange;
- }
}
}
** 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.
** 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()]
** 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
-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
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
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
-a192d04f4e3a9e4960a4d96d1d3ee8635bc1034d
\ No newline at end of file
+aed4273054cbd150c86b36ea951d17c981633ba0
\ No newline at end of file