aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--manifest36
-rw-r--r--manifest.uuid2
-rw-r--r--src/expr.c164
-rw-r--r--src/insert.c26
-rw-r--r--src/select.c18
-rw-r--r--src/sqliteInt.h6
-rw-r--r--src/trigger.c2
-rw-r--r--src/vdbe.c257
-rw-r--r--src/vdbe.h97
-rw-r--r--src/where.c6
-rw-r--r--test/expr.test21
-rw-r--r--test/select4.test44
-rw-r--r--test/subselect.test7
-rw-r--r--test/trigger2.test14
-rw-r--r--test/unique.test35
15 files changed, 491 insertions, 244 deletions
diff --git a/manifest b/manifest
index 3a7b09999..c8b0abd05 100644
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Additional\stesting\sof\sLEFT\sOUTER\sJOIN.\s(CVS\s588)
-D 2002-05-25T00:18:21
+C NULL\svalues\sare\sdistinct.\s\sA\scomparison\sinvolving\sa\sNULL\sis\salways\sfalse.\nOperations\son\sa\sNULL\svalue\syield\sa\sNULL\sresult.\s\sThis\schange\smakes\sSQLite\noperate\smore\slike\sthe\sSQL\sspec,\sbut\sit\smay\sbreak\sexisting\sapplications\sthat\nassumed\sthe\sold\sbehavior.\s\sAll\sthe\sold\stests\spass\sbut\swe\sstill\sneed\sto\sadd\nnew\stests\sto\sbetter\sverify\sthe\snew\sbehavior.\s\sFix\sfor\sticket\s#44.\s(CVS\s589)
+D 2002-05-26T20:54:33
F Makefile.in 6291a33b87d2a395aafd7646ee1ed562c6f2c28c
F Makefile.template 4e11752e0b5c7a043ca50af4296ec562857ba495
F README a4c0ba11354ef6ba0776b400d057c59da47a4cc0
@@ -23,11 +23,11 @@ F src/btree.h 8abeabfe6e0b1a990b64fa457592a6482f6674f3
F src/build.c 36e42718a7a94f554ea39508993378482f5335c7
F src/delete.c a2b098cbbf518e6b641847e26de85827793bc523
F src/encode.c 346b12b46148506c32038524b95c4631ab46d760
-F src/expr.c 818a702ba93e444813b8935a7ab509f6e3352b49
+F src/expr.c 1a7a2f5f2b7dd37659783cdb6efaac74a092ba71
F src/func.c a31dcba85bc2ecb9b752980289cf7e6cd0cafbce
F src/hash.c 6a6236b89c8c060c65dabd300a1c8ce7c10edb72
F src/hash.h dca065dda89d4575f3176e75e9a3dc0f4b4fb8b9
-F src/insert.c bbbd803da8b125ec5a5f881f4d559887eb922c57
+F src/insert.c 24b4e146319bada6f82a1d5eae6b38b3065d132f
F src/main.c 6e53c49a390fabd5fecce9e3b128c61c85208000
F src/md5.c 0ae1f3e2cac92d06fc6246d1b4b8f61a2fe66d3b
F src/os.c 5ab8b6b4590d0c1ab8e96c67996c170e4462e0fc
@@ -37,11 +37,11 @@ F src/pager.h 6fddfddd3b73aa8abc081b973886320e3c614f0e
F src/parse.y c681da701bf142967325b8791f22418e2d81552d
F src/printf.c d8032ee18b860c812eeff596c9bebfdacb7930fd
F src/random.c 19e8e00fe0df32a742f115773f57651be327cabe
-F src/select.c bbf00ee2b4412c7249baf0ba737ba6a93fe82e78
+F src/select.c 9b404b6eeb3428896fdbddc6e1ace68ee931ec17
F src/shell.c 1d22fe870ee852cfb975fd000dbe3973713d0a15
F src/shell.tcl 27ecbd63dd88396ad16d81ab44f73e6c0ea9d20e
F src/sqlite.h.in 0038faa6d642de06b91143ee65a131bd831d020b
-F src/sqliteInt.h 9d565908e2ca53c54d1a0ae445cba039ee018aba
+F src/sqliteInt.h 9de24fb527b3e472be19600e7c22534b981c8e61
F src/table.c eed2098c9b577aa17f8abe89313a9c4413f57d63
F src/tclsqlite.c 9300c9606a38bc0c75d6c0bc8a6197ab979353d1
F src/test1.c 09d95048b66ce6dcd2bae90f443589043d7d631e
@@ -49,12 +49,12 @@ F src/test2.c 669cc22781c6461a273416ec1a7414d25c081730
F src/test3.c 4e52fff8b01f08bd202f7633feda5639b7ba2b5e
F src/threadtest.c 81f0598e0f031c1bd506af337fdc1b7e8dff263f
F src/tokenize.c facec7dc0b4a13e17ad67702f548dac2f7c6a732
-F src/trigger.c 75dd64808c56ff1b20ee6c6620f5d61487712d74
+F src/trigger.c f9adb404ea355a8be2c9cd9740794a898cf1096c
F src/update.c f68375173bf5338cae3e97012708e10f206aedd9
F src/util.c 707c30f8c13cddace7c08556ac450c0b786660b3
-F src/vdbe.c bde1dad84ea4b0de4ac590d0d29522e45bfd1470
-F src/vdbe.h def669b9f2728589aabcb5db756429db02465c9a
-F src/where.c 9030d188139f4de73c4b238706afeae8bc4e2f26
+F src/vdbe.c caa269517b2392986c8f55401f272e76b9de82d9
+F src/vdbe.h b8706429131c14b307a07aab7e47f95a9da53610
+F src/where.c b054f2f23127bd57eb5f973bcd38764b875d73fe
F test/all.test e4d3821eeba751829b419cd47814bd20af4286d1
F test/bigrow.test 8ab252dba108f12ad64e337b0f2ff31a807ac578
F test/btree.test bf326f546a666617367a7033fa2c07451bd4f8e1
@@ -63,7 +63,7 @@ F test/btree3.test 9caa9e22491dd8cd8aa36d7ac3b48b089817c895
F test/conflict.test 5149646703d3930c9111068b5cda7e2e938476e3
F test/copy.test b3cefcb520c64d7e7dfedbab06b4d4c31fa5b99a
F test/delete.test c904a62129fe102b314a96111a8417f10249e4d8
-F test/expr.test 846795016b5993a7411f772eebe82ab67bd7230a
+F test/expr.test bfb773721e69566d2a7565c7f03b466e1f33a53f
F test/func.test d34e461f0acb0cf2978a4b3a3e098460f2ea8fbc
F test/in.test c09312672e3f0709fa02c8e2e9cd8fb4bd6269aa
F test/index.test c8a471243bbf878974b99baf5badd59407237cf3
@@ -89,11 +89,11 @@ F test/rowid.test 4c55943300cddf73dd0f88d40a268cab14c83274
F test/select1.test 6ba20b52d563b7fb917d8a61a7560d02f90a1a52
F test/select2.test aceea74fd895b9d007512f72499db589735bd8e4
F test/select3.test 9469c332250a75a0ef1771fb5da62dc04ec77f18
-F test/select4.test c2313f8c16ca298b0b1ce9cc3c0cfed0939ffea9
+F test/select4.test 0bc0065102573c6b71003292fd4a5def63923c61
F test/select5.test c2a6c4a003316ee42cbbd689eebef8fdce0db2ac
F test/select6.test efb8d0c07a440441db87db2c4ade6904e1407e85
F test/sort.test 3b996ce7ca385f9cd559944ac0f4027a23aa546b
-F test/subselect.test 335d3dad8d585726c447dfee8d9c4f7383c76b78
+F test/subselect.test f3bc1dcbddddcea08d818fcff75228ad3464fc83
F test/table.test d9fd161dc9a2dbe0795d836336019ea6d0952ef8
F test/tableapi.test 3c80421a889e1d106df16e5800fa787f0d2914a6
F test/tclsqlite.test 79deeffd7cd637ca0f06c5dbbf2f44d272079533
@@ -101,8 +101,8 @@ F test/temptable.test daa83489eea2e9aaeeece09675c28be84c72cb67
F test/tester.tcl dc1b56bd628b487e4d75bfd1e7480b5ed8810ac6
F test/trans.test ae0b9a82d5d34122c3a3108781eb8d078091ccee
F test/trigger1.test a0550c5cce97170dbebb88eee09506d9ad1174e9
-F test/trigger2.test 2d23ad8a7e74c64018b6c7dff79bbf2574ffd776
-F test/unique.test 07776624b82221a80c8b4138ce0dd8b0853bb3ea
+F test/trigger2.test 7f2b0a9b20004449c78b834c2f22494db3b2e63a
+F test/unique.test 572aa791327c1e8d797932263e9d67f176cfdb44
F test/update.test a0aa0bf83e6fad8407d0e4ad25ebb09b513f5bf4
F test/vacuum.test 059871b312eb910bbe49dafde1d01490cc2c6bbe
F test/view.test b9851e9142de5e5831fdf18f125cbe1256cb550a
@@ -135,7 +135,7 @@ F www/speed.tcl da8afcc1d3ccc5696cfb388a68982bc3d9f7f00f
F www/sqlite.tcl 8b5884354cb615049aed83039f8dfe1552a44279
F www/tclsqlite.tcl 1db15abeb446aad0caf0b95b8b9579720e4ea331
F www/vdbe.tcl 2013852c27a02a091d39a766bc87cff329f21218
-P 99bd1f5b9a1a20bfeefe15c00d96a34a5f40923e
-R 289d0473700b92bde61e98c6617d8be0
+P d8d04c14f18d1feba89ccea0be70530a18248c51
+R 2ee233b2a9ebb3e4a8fe3e93b3747a0c
U drh
-Z f910247eaeb3d662acb5a7b6aeac9b33
+Z f507bf5d6b83b961339e312ca54783e8
diff --git a/manifest.uuid b/manifest.uuid
index 65d176ce9..f0e8ac5b7 100644
--- a/manifest.uuid
+++ b/manifest.uuid
@@ -1 +1 @@
-d8d04c14f18d1feba89ccea0be70530a18248c51 \ No newline at end of file
+9051173742f1b0e15a809d12a0c9c98fd2c4614d \ No newline at end of file
diff --git a/src/expr.c b/src/expr.c
index 94aa06df5..89508d26c 100644
--- a/src/expr.c
+++ b/src/expr.c
@@ -12,7 +12,7 @@
** This file contains routines used for analyzing expressions and
** for generating VDBE code that evaluates expressions in SQLite.
**
-** $Id: expr.c,v 1.63 2002/05/24 02:04:33 drh Exp $
+** $Id: expr.c,v 1.64 2002/05/26 20:54:33 drh Exp $
*/
#include "sqliteInt.h"
@@ -818,7 +818,13 @@ void sqliteExprCode(Parse *pParse, Expr *pExpr){
case TK_REM:
case TK_BITAND:
case TK_BITOR:
- case TK_SLASH: {
+ case TK_SLASH:
+ case TK_LT:
+ case TK_LE:
+ case TK_GT:
+ case TK_GE:
+ case TK_NE:
+ case TK_EQ: {
sqliteExprCode(pParse, pExpr->pLeft);
sqliteExprCode(pParse, pExpr->pRight);
sqliteVdbeAddOp(v, op, 0, 0);
@@ -837,21 +843,6 @@ void sqliteExprCode(Parse *pParse, Expr *pExpr){
sqliteVdbeAddOp(v, OP_Concat, 2, 0);
break;
}
- case TK_LT:
- case TK_LE:
- case TK_GT:
- case TK_GE:
- case TK_NE:
- case TK_EQ: {
- int dest;
- sqliteVdbeAddOp(v, OP_Integer, 1, 0);
- sqliteExprCode(pParse, pExpr->pLeft);
- sqliteExprCode(pParse, pExpr->pRight);
- dest = sqliteVdbeCurrentAddr(v) + 2;
- sqliteVdbeAddOp(v, op, 0, dest);
- sqliteVdbeAddOp(v, OP_AddImm, -1, 0);
- break;
- }
case TK_UMINUS: {
assert( pExpr->pLeft );
if( pExpr->pLeft->op==TK_FLOAT || pExpr->pLeft->op==TK_INTEGER ){
@@ -881,7 +872,7 @@ void sqliteExprCode(Parse *pParse, Expr *pExpr){
sqliteVdbeAddOp(v, OP_Integer, 1, 0);
sqliteExprCode(pParse, pExpr->pLeft);
dest = sqliteVdbeCurrentAddr(v) + 2;
- sqliteVdbeAddOp(v, op, 0, dest);
+ sqliteVdbeAddOp(v, op, 1, dest);
sqliteVdbeAddOp(v, OP_AddImm, -1, 0);
break;
}
@@ -913,20 +904,27 @@ void sqliteExprCode(Parse *pParse, Expr *pExpr){
sqliteVdbeAddOp(v, OP_Integer, 1, 0);
sqliteExprCode(pParse, pExpr->pLeft);
addr = sqliteVdbeCurrentAddr(v);
+ sqliteVdbeAddOp(v, OP_NotNull, -1, addr+4);
+ sqliteVdbeAddOp(v, OP_Pop, 1, 0);
+ sqliteVdbeAddOp(v, OP_String, 0, 0);
+ sqliteVdbeAddOp(v, OP_Goto, 0, addr+6);
if( pExpr->pSelect ){
- sqliteVdbeAddOp(v, OP_Found, pExpr->iTable, addr+2);
+ sqliteVdbeAddOp(v, OP_Found, pExpr->iTable, addr+6);
}else{
- sqliteVdbeAddOp(v, OP_SetFound, pExpr->iTable, addr+2);
+ sqliteVdbeAddOp(v, OP_SetFound, pExpr->iTable, addr+6);
}
sqliteVdbeAddOp(v, OP_AddImm, -1, 0);
break;
}
case TK_BETWEEN: {
- int lbl = sqliteVdbeMakeLabel(v);
- sqliteVdbeAddOp(v, OP_Integer, 0, 0);
- sqliteExprIfFalse(pParse, pExpr, lbl);
- sqliteVdbeAddOp(v, OP_AddImm, 1, 0);
- sqliteVdbeResolveLabel(v, lbl);
+ sqliteExprCode(pParse, pExpr->pLeft);
+ sqliteVdbeAddOp(v, OP_Dup, 0, 0);
+ sqliteExprCode(pParse, pExpr->pList->a[0].pExpr);
+ sqliteVdbeAddOp(v, OP_Ge, 0, 0);
+ sqliteVdbeAddOp(v, OP_Pull, 1, 0);
+ sqliteExprCode(pParse, pExpr->pList->a[1].pExpr);
+ sqliteVdbeAddOp(v, OP_Le, 0, 0);
+ sqliteVdbeAddOp(v, OP_And, 0, 0);
break;
}
case TK_AS: {
@@ -935,44 +933,54 @@ void sqliteExprCode(Parse *pParse, Expr *pExpr){
}
case TK_CASE: {
int expr_end_label;
- int next_when_label;
+ int null_result_label;
+ int jumpInst;
+ int addr;
+ int nExpr;
int i;
assert(pExpr->pList);
assert((pExpr->pList->nExpr % 2) == 0);
assert(pExpr->pList->nExpr > 0);
- expr_end_label = sqliteVdbeMakeLabel(pParse->pVdbe);
+ nExpr = pExpr->pList->nExpr;
+ expr_end_label = sqliteVdbeMakeLabel(v);
+ null_result_label = sqliteVdbeMakeLabel(v);
if( pExpr->pLeft ){
sqliteExprCode(pParse, pExpr->pLeft);
+ sqliteVdbeAddOp(v, OP_IsNull, -1, expr_end_label);
}
- for(i=0; i<pExpr->pList->nExpr; i=i+2){
- if( i!=0 ){
- sqliteVdbeResolveLabel(pParse->pVdbe, next_when_label);
- }
- next_when_label = sqliteVdbeMakeLabel(pParse->pVdbe);
+ for(i=0; i<nExpr; i=i+2){
+ sqliteExprCode(pParse, pExpr->pList->a[i].pExpr);
+ sqliteVdbeAddOp(v, OP_IsNull, -1, null_result_label);
if( pExpr->pLeft ){
- sqliteVdbeAddOp(pParse->pVdbe, OP_Dup, 0, 1);
- sqliteExprCode(pParse, pExpr->pList->a[i].pExpr);
- sqliteVdbeAddOp(pParse->pVdbe, OP_Ne, 0, next_when_label);
+ sqliteVdbeAddOp(v, OP_Dup, 1, 1);
+ jumpInst = sqliteVdbeAddOp(v, OP_Ne, 0, 0);
}else{
- sqliteExprIfFalse(pParse, pExpr->pList->a[i].pExpr, next_when_label);
- }
- if( pExpr->pLeft ){
- sqliteVdbeAddOp(pParse->pVdbe, OP_Pop, 1, 0);
+ jumpInst = sqliteVdbeAddOp(v, OP_IfNot, 0, 0);
}
sqliteExprCode(pParse, pExpr->pList->a[i+1].pExpr);
- sqliteVdbeAddOp(pParse->pVdbe, OP_Goto, 0, expr_end_label);
- }
- sqliteVdbeResolveLabel(pParse->pVdbe, next_when_label);
- if( pExpr->pLeft ){
- sqliteVdbeAddOp(pParse->pVdbe, OP_Pop, 1, 0);
+ sqliteVdbeAddOp(v, OP_Goto, 0, expr_end_label);
+ if( i>=nExpr-2 ){
+ sqliteVdbeResolveLabel(v, null_result_label);
+ sqliteVdbeAddOp(v, OP_Pop, 1, 0);
+ if( pExpr->pRight!=0 ){
+ sqliteVdbeAddOp(v, OP_String, 0, 0);
+ sqliteVdbeAddOp(v, OP_Goto, 0, expr_end_label);
+ }
+ }
+ addr = sqliteVdbeCurrentAddr(v);
+ sqliteVdbeChangeP2(v, jumpInst, addr);
}
if( pExpr->pRight ){
sqliteExprCode(pParse, pExpr->pRight);
}else{
- sqliteVdbeAddOp(pParse->pVdbe, OP_String, 0, 0);
+ sqliteVdbeAddOp(v, OP_String, 0, 0);
+ }
+ sqliteVdbeResolveLabel(v, expr_end_label);
+ if( pExpr->pLeft ){
+ sqliteVdbeAddOp(v, OP_Pull, 1, 0);
+ sqliteVdbeAddOp(v, OP_Pop, 1, 0);
}
- sqliteVdbeResolveLabel(pParse->pVdbe, expr_end_label);
}
break;
}
@@ -982,8 +990,11 @@ void sqliteExprCode(Parse *pParse, Expr *pExpr){
** Generate code for a boolean expression such that a jump is made
** to the label "dest" if the expression is true but execution
** continues straight thru if the expression is false.
+**
+** If the expression evaluates to NULL (neither true nor false), then
+** take the jump if the jumpIfNull flag is true.
*/
-void sqliteExprIfTrue(Parse *pParse, Expr *pExpr, int dest){
+void sqliteExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){
Vdbe *v = pParse->pVdbe;
int op = 0;
if( v==0 || pExpr==0 ) return;
@@ -1001,18 +1012,18 @@ void sqliteExprIfTrue(Parse *pParse, Expr *pExpr, int dest){
switch( pExpr->op ){
case TK_AND: {
int d2 = sqliteVdbeMakeLabel(v);
- sqliteExprIfFalse(pParse, pExpr->pLeft, d2);
- sqliteExprIfTrue(pParse, pExpr->pRight, dest);
+ sqliteExprIfFalse(pParse, pExpr->pLeft, d2, !jumpIfNull);
+ sqliteExprIfTrue(pParse, pExpr->pRight, dest, jumpIfNull);
sqliteVdbeResolveLabel(v, d2);
break;
}
case TK_OR: {
- sqliteExprIfTrue(pParse, pExpr->pLeft, dest);
- sqliteExprIfTrue(pParse, pExpr->pRight, dest);
+ sqliteExprIfTrue(pParse, pExpr->pLeft, dest, jumpIfNull);
+ sqliteExprIfTrue(pParse, pExpr->pRight, dest, jumpIfNull);
break;
}
case TK_NOT: {
- sqliteExprIfFalse(pParse, pExpr->pLeft, dest);
+ sqliteExprIfFalse(pParse, pExpr->pLeft, dest, jumpIfNull);
break;
}
case TK_LT:
@@ -1023,17 +1034,22 @@ void sqliteExprIfTrue(Parse *pParse, Expr *pExpr, int dest){
case TK_EQ: {
sqliteExprCode(pParse, pExpr->pLeft);
sqliteExprCode(pParse, pExpr->pRight);
- sqliteVdbeAddOp(v, op, 0, dest);
+ sqliteVdbeAddOp(v, op, jumpIfNull, dest);
break;
}
case TK_ISNULL:
case TK_NOTNULL: {
sqliteExprCode(pParse, pExpr->pLeft);
- sqliteVdbeAddOp(v, op, 0, dest);
+ sqliteVdbeAddOp(v, op, 1, dest);
break;
}
case TK_IN: {
+ int addr;
sqliteExprCode(pParse, pExpr->pLeft);
+ addr = sqliteVdbeCurrentAddr(v);
+ sqliteVdbeAddOp(v, OP_NotNull, -1, addr+3);
+ sqliteVdbeAddOp(v, OP_Pop, 1, 0);
+ sqliteVdbeAddOp(v, OP_Goto, 0, jumpIfNull ? dest : addr+4);
if( pExpr->pSelect ){
sqliteVdbeAddOp(v, OP_Found, pExpr->iTable, dest);
}else{
@@ -1042,21 +1058,21 @@ void sqliteExprIfTrue(Parse *pParse, Expr *pExpr, int dest){
break;
}
case TK_BETWEEN: {
- int lbl = sqliteVdbeMakeLabel(v);
+ int addr;
sqliteExprCode(pParse, pExpr->pLeft);
sqliteVdbeAddOp(v, OP_Dup, 0, 0);
sqliteExprCode(pParse, pExpr->pList->a[0].pExpr);
- sqliteVdbeAddOp(v, OP_Lt, 0, lbl);
+ addr = sqliteVdbeAddOp(v, OP_Lt, !jumpIfNull, 0);
sqliteExprCode(pParse, pExpr->pList->a[1].pExpr);
- sqliteVdbeAddOp(v, OP_Le, 0, dest);
+ sqliteVdbeAddOp(v, OP_Le, jumpIfNull, dest);
sqliteVdbeAddOp(v, OP_Integer, 0, 0);
- sqliteVdbeResolveLabel(v, lbl);
+ sqliteVdbeChangeP2(v, addr, sqliteVdbeCurrentAddr(v));
sqliteVdbeAddOp(v, OP_Pop, 1, 0);
break;
}
default: {
sqliteExprCode(pParse, pExpr);
- sqliteVdbeAddOp(v, OP_If, 0, dest);
+ sqliteVdbeAddOp(v, OP_If, jumpIfNull, dest);
break;
}
}
@@ -1066,8 +1082,11 @@ void sqliteExprIfTrue(Parse *pParse, Expr *pExpr, int dest){
** Generate code for a boolean expression such that a jump is made
** to the label "dest" if the expression is false but execution
** continues straight thru if the expression is true.
+**
+** If the expression evaluates to NULL (neither true nor false) then
+** jump if jumpIfNull is true or fall through if jumpIfNull is false.
*/
-void sqliteExprIfFalse(Parse *pParse, Expr *pExpr, int dest){
+void sqliteExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){
Vdbe *v = pParse->pVdbe;
int op = 0;
if( v==0 || pExpr==0 ) return;
@@ -1084,19 +1103,19 @@ void sqliteExprIfFalse(Parse *pParse, Expr *pExpr, int dest){
}
switch( pExpr->op ){
case TK_AND: {
- sqliteExprIfFalse(pParse, pExpr->pLeft, dest);
- sqliteExprIfFalse(pParse, pExpr->pRight, dest);
+ sqliteExprIfFalse(pParse, pExpr->pLeft, dest, jumpIfNull);
+ sqliteExprIfFalse(pParse, pExpr->pRight, dest, jumpIfNull);
break;
}
case TK_OR: {
int d2 = sqliteVdbeMakeLabel(v);
- sqliteExprIfTrue(pParse, pExpr->pLeft, d2);
- sqliteExprIfFalse(pParse, pExpr->pRight, dest);
+ sqliteExprIfTrue(pParse, pExpr->pLeft, d2, !jumpIfNull);
+ sqliteExprIfFalse(pParse, pExpr->pRight, dest, jumpIfNull);
sqliteVdbeResolveLabel(v, d2);
break;
}
case TK_NOT: {
- sqliteExprIfTrue(pParse, pExpr->pLeft, dest);
+ sqliteExprIfTrue(pParse, pExpr->pLeft, dest, jumpIfNull);
break;
}
case TK_LT:
@@ -1107,17 +1126,22 @@ void sqliteExprIfFalse(Parse *pParse, Expr *pExpr, int dest){
case TK_EQ: {
sqliteExprCode(pParse, pExpr->pLeft);
sqliteExprCode(pParse, pExpr->pRight);
- sqliteVdbeAddOp(v, op, 0, dest);
+ sqliteVdbeAddOp(v, op, jumpIfNull, dest);
break;
}
case TK_ISNULL:
case TK_NOTNULL: {
sqliteExprCode(pParse, pExpr->pLeft);
- sqliteVdbeAddOp(v, op, 0, dest);
+ sqliteVdbeAddOp(v, op, 1, dest);
break;
}
case TK_IN: {
+ int addr;
sqliteExprCode(pParse, pExpr->pLeft);
+ addr = sqliteVdbeCurrentAddr(v);
+ sqliteVdbeAddOp(v, OP_NotNull, -1, addr+3);
+ sqliteVdbeAddOp(v, OP_Pop, 1, 0);
+ sqliteVdbeAddOp(v, OP_Goto, 0, jumpIfNull ? dest : addr+4);
if( pExpr->pSelect ){
sqliteVdbeAddOp(v, OP_NotFound, pExpr->iTable, dest);
}else{
@@ -1131,17 +1155,17 @@ void sqliteExprIfFalse(Parse *pParse, Expr *pExpr, int dest){
sqliteVdbeAddOp(v, OP_Dup, 0, 0);
sqliteExprCode(pParse, pExpr->pList->a[0].pExpr);
addr = sqliteVdbeCurrentAddr(v);
- sqliteVdbeAddOp(v, OP_Ge, 0, addr+3);
+ sqliteVdbeAddOp(v, OP_Ge, !jumpIfNull, addr+3);
sqliteVdbeAddOp(v, OP_Pop, 1, 0);
sqliteVdbeAddOp(v, OP_Goto, 0, dest);
sqliteExprCode(pParse, pExpr->pList->a[1].pExpr);
- sqliteVdbeAddOp(v, OP_Gt, 0, dest);
+ sqliteVdbeAddOp(v, OP_Gt, jumpIfNull, dest);
break;
}
default: {
sqliteExprCode(pParse, pExpr);
sqliteVdbeAddOp(v, OP_Not, 0, 0);
- sqliteVdbeAddOp(v, OP_If, 0, dest);
+ sqliteVdbeAddOp(v, OP_If, jumpIfNull, dest);
break;
}
}
diff --git a/src/insert.c b/src/insert.c
index 53c3e7696..1c9b9b4cb 100644
--- a/src/insert.c
+++ b/src/insert.c
@@ -12,7 +12,7 @@
** This file contains C code routines that are called by the parser
** to handle INSERT statements in SQLite.
**
-** $Id: insert.c,v 1.58 2002/05/24 02:04:33 drh Exp $
+** $Id: insert.c,v 1.59 2002/05/26 20:54:33 drh Exp $
*/
#include "sqliteInt.h"
@@ -290,14 +290,12 @@ void sqliteInsert(
if( srcTab>=0 ){
sqliteVdbeAddOp(v, OP_Column, srcTab, keyColumn);
}else{
- int addr;
sqliteExprCode(pParse, pList->a[keyColumn].pExpr);
/* If the PRIMARY KEY expression is NULL, then use OP_NewRecno
** to generate a unique primary key value.
*/
- addr = sqliteVdbeAddOp(v, OP_Dup, 0, 1);
- sqliteVdbeAddOp(v, OP_NotNull, 0, addr+4);
+ sqliteVdbeAddOp(v, OP_NotNull, -1, sqliteVdbeCurrentAddr(v)+3);
sqliteVdbeAddOp(v, OP_Pop, 1, 0);
sqliteVdbeAddOp(v, OP_NewRecno, base, 0);
}
@@ -499,7 +497,7 @@ void sqliteGenerateConstraintChecks(
int iCur;
Index *pIdx;
int seenReplace = 0;
- int jumpInst;
+ int jumpInst1, jumpInst2;
int contAddr;
int hasTwoRecnos = (isUpdate && recnoChng);
@@ -527,7 +525,7 @@ void sqliteGenerateConstraintChecks(
onError = OE_Abort;
}
sqliteVdbeAddOp(v, OP_Dup, nCol-1-i, 1);
- addr = sqliteVdbeAddOp(v, OP_NotNull, 0, 0);
+ addr = sqliteVdbeAddOp(v, OP_NotNull, 1, 0);
switch( onError ){
case OE_Rollback:
case OE_Abort:
@@ -565,14 +563,13 @@ void sqliteGenerateConstraintChecks(
if( onError==OE_Default ) onError = OE_Abort;
}
if( onError!=OE_Replace ){
- int jumpInst2;
if( isUpdate ){
sqliteVdbeAddOp(v, OP_Dup, nCol+1, 1);
sqliteVdbeAddOp(v, OP_Dup, nCol+1, 1);
- jumpInst2 = sqliteVdbeAddOp(v, OP_Eq, 0, 0);
+ jumpInst1 = sqliteVdbeAddOp(v, OP_Eq, 0, 0);
}
sqliteVdbeAddOp(v, OP_Dup, nCol, 1);
- jumpInst = sqliteVdbeAddOp(v, OP_NotExists, base, 0);
+ jumpInst2 = sqliteVdbeAddOp(v, OP_NotExists, base, 0);
switch( onError ){
case OE_Rollback:
case OE_Abort:
@@ -588,9 +585,9 @@ void sqliteGenerateConstraintChecks(
default: assert(0);
}
contAddr = sqliteVdbeCurrentAddr(v);
- sqliteVdbeChangeP2(v, jumpInst, contAddr);
+ sqliteVdbeChangeP2(v, jumpInst2, contAddr);
if( isUpdate ){
- sqliteVdbeChangeP2(v, jumpInst2, contAddr);
+ sqliteVdbeChangeP2(v, jumpInst1, contAddr);
sqliteVdbeAddOp(v, OP_Dup, nCol+1, 1);
sqliteVdbeAddOp(v, OP_MoveTo, base, 0);
}
@@ -609,7 +606,7 @@ void sqliteGenerateConstraintChecks(
sqliteVdbeAddOp(v, OP_Dup, i+extra+nCol-idx, 1);
}
}
- sqliteVdbeAddOp(v, OP_MakeIdxKey, pIdx->nColumn, 0);
+ jumpInst1 = sqliteVdbeAddOp(v, OP_MakeIdxKey, pIdx->nColumn, 0);
onError = pIdx->onError;
if( onError==OE_None ) continue;
if( overrideError!=OE_Default ){
@@ -619,7 +616,7 @@ void sqliteGenerateConstraintChecks(
if( onError==OE_Default ) onError = OE_Abort;
}
sqliteVdbeAddOp(v, OP_Dup, extra+nCol+1+hasTwoRecnos, 1);
- jumpInst = sqliteVdbeAddOp(v, OP_IsUnique, base+iCur+1, 0);
+ jumpInst2 = sqliteVdbeAddOp(v, OP_IsUnique, base+iCur+1, 0);
switch( onError ){
case OE_Rollback:
case OE_Abort:
@@ -645,7 +642,8 @@ void sqliteGenerateConstraintChecks(
default: assert(0);
}
contAddr = sqliteVdbeCurrentAddr(v);
- sqliteVdbeChangeP2(v, jumpInst, contAddr);
+ sqliteVdbeChangeP2(v, jumpInst1, contAddr);
+ sqliteVdbeChangeP2(v, jumpInst2, contAddr);
}
}
diff --git a/src/select.c b/src/select.c
index e4117f9d6..3be8650ce 100644
--- a/src/select.c
+++ b/src/select.c
@@ -12,7 +12,7 @@
** This file contains C code routines that are called by the parser
** to handle SELECT statements in SQLite.
**
-** $Id: select.c,v 1.85 2002/05/25 00:18:21 drh Exp $
+** $Id: select.c,v 1.86 2002/05/26 20:54:34 drh Exp $
*/
#include "sqliteInt.h"
@@ -325,13 +325,12 @@ static int selectInnerLoop(
** and this row has been seen before, then do not make this row
** part of the result.
*/
- if( distinct>=0 ){
- int lbl = sqliteVdbeMakeLabel(v);
+ if( distinct>=0 && pEList && pEList->nExpr>0 ){
+ sqliteVdbeAddOp(v, OP_IsNull, -pEList->nExpr, sqliteVdbeCurrentAddr(v)+7);
sqliteVdbeAddOp(v, OP_MakeKey, pEList->nExpr, 1);
- sqliteVdbeAddOp(v, OP_Distinct, distinct, lbl);
+ sqliteVdbeAddOp(v, OP_Distinct, distinct, sqliteVdbeCurrentAddr(v)+3);
sqliteVdbeAddOp(v, OP_Pop, pEList->nExpr+1, 0);
sqliteVdbeAddOp(v, OP_Goto, 0, iContinue);
- sqliteVdbeResolveLabel(v, lbl);
sqliteVdbeAddOp(v, OP_String, 0, 0);
sqliteVdbeAddOp(v, OP_PutStrKey, distinct, 0);
}
@@ -359,8 +358,8 @@ static int selectInnerLoop(
** table iParm.
*/
if( eDest==SRT_Union ){
- sqliteVdbeAddOp(v, OP_MakeRecord, nColumn, 0);
- sqliteVdbeAddOp(v, OP_String, iParm, 0);
+ sqliteVdbeAddOp(v, OP_MakeRecord, nColumn, 1);
+ sqliteVdbeAddOp(v, OP_String, 0, 0);
sqliteVdbeAddOp(v, OP_PutStrKey, iParm, 0);
}else
@@ -378,7 +377,7 @@ static int selectInnerLoop(
** the temporary table iParm.
*/
if( eDest==SRT_Except ){
- int addr = sqliteVdbeAddOp(v, OP_MakeRecord, nColumn, 0);
+ int addr = sqliteVdbeAddOp(v, OP_MakeRecord, nColumn, 1);
sqliteVdbeAddOp(v, OP_NotFound, iParm, addr+3);
sqliteVdbeAddOp(v, OP_Delete, iParm, 0);
}else
@@ -389,6 +388,7 @@ static int selectInnerLoop(
*/
if( eDest==SRT_Set ){
assert( nColumn==1 );
+ sqliteVdbeAddOp(v, OP_IsNull, -1, sqliteVdbeCurrentAddr(v)+3);
sqliteVdbeAddOp(v, OP_String, 0, 0);
sqliteVdbeAddOp(v, OP_PutStrKey, iParm, 0);
}else
@@ -1738,7 +1738,7 @@ int sqliteSelect(
startagg = sqliteVdbeAddOp(v, OP_AggNext, 0, endagg);
pParse->useAgg = 1;
if( pHaving ){
- sqliteExprIfFalse(pParse, pHaving, startagg);
+ sqliteExprIfFalse(pParse, pHaving, startagg, 1);
}
if( selectInnerLoop(pParse, pEList, 0, 0, pOrderBy, distinct, eDest, iParm,
startagg, endagg) ){
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index 77cfe6282..4fec92231 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -11,7 +11,7 @@
*************************************************************************
** Internal interface definitions for SQLite.
**
-** @(#) $Id: sqliteInt.h,v 1.116 2002/05/25 00:18:21 drh Exp $
+** @(#) $Id: sqliteInt.h,v 1.117 2002/05/26 20:54:34 drh Exp $
*/
#include "sqlite.h"
#include "hash.h"
@@ -817,8 +817,8 @@ void sqliteUpdate(Parse*, Token*, ExprList*, Expr*, int);
WhereInfo *sqliteWhereBegin(Parse*, int, SrcList*, Expr*, int);
void sqliteWhereEnd(WhereInfo*);
void sqliteExprCode(Parse*, Expr*);
-void sqliteExprIfTrue(Parse*, Expr*, int);
-void sqliteExprIfFalse(Parse*, Expr*, int);
+void sqliteExprIfTrue(Parse*, Expr*, int, int);
+void sqliteExprIfFalse(Parse*, Expr*, int, int);
Table *sqliteFindTable(sqlite*,const char*);
Index *sqliteFindIndex(sqlite*,const char*);
void sqliteUnlinkAndDeleteIndex(sqlite*,Index*);
diff --git a/src/trigger.c b/src/trigger.c
index 48e7ecf79..82ba258a0 100644
--- a/src/trigger.c
+++ b/src/trigger.c
@@ -587,7 +587,7 @@ int sqliteCodeRowTrigger(
sqliteExprDelete(whenExpr);
return 1;
}
- sqliteExprIfFalse(pParse, whenExpr, endTrigger);
+ sqliteExprIfFalse(pParse, whenExpr, endTrigger, 1);
sqliteExprDelete(whenExpr);
codeTriggerProgram(pParse, pTrigger->step_list, orconf);
diff --git a/src/vdbe.c b/src/vdbe.c
index af42f9b52..fa558eb7c 100644
--- a/src/vdbe.c
+++ b/src/vdbe.c
@@ -30,7 +30,7 @@
** But other routines are also provided to help in building up
** a program instruction by instruction.
**
-** $Id: vdbe.c,v 1.148 2002/05/24 20:31:37 drh Exp $
+** $Id: vdbe.c,v 1.149 2002/05/26 20:54:34 drh Exp $
*/
#include "sqliteInt.h"
#include <ctype.h>
@@ -1068,17 +1068,17 @@ static char *zOpName[] = { 0,
"AggFunc", "AggInit", "AggPush", "AggPop",
"SetInsert", "SetFound", "SetNotFound", "MakeRecord",
"MakeKey", "MakeIdxKey", "IncrKey", "Goto",
- "If", "Halt", "ColumnCount", "ColumnName",
- "Callback", "NullCallback", "Integer", "String",
- "Pop", "Dup", "Pull", "Push",
- "MustBeInt", "Add", "AddImm", "Subtract",
- "Multiply", "Divide", "Remainder", "BitAnd",
- "BitOr", "BitNot", "ShiftLeft", "ShiftRight",
- "AbsValue", "Eq", "Ne", "Lt",
- "Le", "Gt", "Ge", "IsNull",
- "NotNull", "Negative", "And", "Or",
- "Not", "Concat", "Noop", "Function",
- "Limit",
+ "If", "IfNot", "Halt", "ColumnCount",
+ "ColumnName", "Callback", "NullCallback", "Integer",
+ "String", "Pop", "Dup", "Pull",
+ "Push", "MustBeInt", "Add", "AddImm",
+ "Subtract", "Multiply", "Divide", "Remainder",
+ "BitAnd", "BitOr", "BitNot", "ShiftLeft",
+ "ShiftRight", "AbsValue", "Eq", "Ne",
+ "Lt", "Le", "Gt", "Ge",
+ "IsNull", "NotNull", "Negative", "And",
+ "Or", "Not", "Concat", "Noop",
+ "Function", "Limit",
};
/*
@@ -1280,6 +1280,7 @@ int sqliteVdbeExec(
sqlite *db = p->db; /* The database */
char **zStack; /* Text stack */
Stack *aStack; /* Additional stack information */
+ unsigned uniqueCnt = 0; /* Used by OP_MakeRecord when P2!=0 */
int errorAction = OE_Abort; /* Recovery action to do in case of an error */
int undoTransOnError = 0; /* If error, either ROLLBACK or COMMIT */
char zBuf[100]; /* Space to sprintf() an integer */
@@ -1719,6 +1720,7 @@ case OP_Concat: {
** and push the result back onto the stack. If either element
** is a string then it is converted to a double using the atof()
** function before the addition.
+** If either operand is NULL, the result is NULL.
*/
/* Opcode: Multiply * * *
**
@@ -1726,6 +1728,7 @@ case OP_Concat: {
** and push the result back onto the stack. If either element
** is a string then it is converted to a double using the atof()
** function before the multiplication.
+** If either operand is NULL, the result is NULL.
*/
/* Opcode: Subtract * * *
**
@@ -1735,6 +1738,7 @@ case OP_Concat: {
** and push the result back onto the stack. If either element
** is a string then it is converted to a double using the atof()
** function before the subtraction.
+** If either operand is NULL, the result is NULL.
*/
/* Opcode: Divide * * *
**
@@ -1744,6 +1748,7 @@ case OP_Concat: {
** and push the result back onto the stack. If either element
** is a string then it is converted to a double using the atof()
** function before the division. Division by zero returns NULL.
+** If either operand is NULL, the result is NULL.
*/
/* Opcode: Remainder * * *
**
@@ -1753,6 +1758,7 @@ case OP_Concat: {
** and push the remainder after division onto the stack. If either element
** is a string then it is converted to a double using the atof()
** function before the division. Division by zero returns NULL.
+** If either operand is NULL, the result is NULL.
*/
case OP_Add:
case OP_Subtract:
@@ -1762,7 +1768,11 @@ case OP_Remainder: {
int tos = p->tos;
int nos = tos - 1;
VERIFY( if( nos<0 ) goto not_enough_stack; )
- if( (aStack[tos].flags & aStack[nos].flags & STK_Int)==STK_Int ){
+ if( ((aStack[tos].flags | aStack[nos].flags) & STK_Null)!=0 ){
+ POPSTACK;
+ Release(p, nos);
+ aStack[nos].flags = STK_Null;
+ }else if( (aStack[tos].flags & aStack[nos].flags & STK_Int)==STK_Int ){
int a, b;
a = aStack[tos].i;
b = aStack[nos].i;
@@ -1838,7 +1848,9 @@ case OP_Function: {
VERIFY( if( n<0 ) goto bad_instruction; )
VERIFY( if( p->tos+1<n ) goto not_enough_stack; )
for(i=p->tos-n+1; i<=p->tos; i++){
- if( (aStack[i].flags & STK_Null)==0 ){
+ if( aStack[i].flags & STK_Null ){
+ zStack[i] = 0;
+ }else{
if( Stringify(p, i) ) goto no_mem;
}
}
@@ -1872,24 +1884,28 @@ case OP_Function: {
** Pop the top two elements from the stack. Convert both elements
** to integers. Push back onto the stack the bit-wise AND of the
** two elements.
+** If either operand is NULL, the result is NULL.
*/
/* Opcode: BitOr * * *
**
** Pop the top two elements from the stack. Convert both elements
** to integers. Push back onto the stack the bit-wise OR of the
** two elements.
+** If either operand is NULL, the result is NULL.
*/
/* Opcode: ShiftLeft * * *
**
** Pop the top two elements from the stack. Convert both elements
** to integers. Push back onto the stack the top element shifted
** left by N bits where N is the second element on the stack.
+** If either operand is NULL, the result is NULL.
*/
/* Opcode: ShiftRight * * *
**
** Pop the top two elements from the stack. Convert both elements
** to integers. Push back onto the stack the top element shifted
** right by N bits where N is the second element on the stack.
+** If either operand is NULL, the result is NULL.
*/
case OP_BitAnd:
case OP_BitOr:
@@ -1899,6 +1915,12 @@ case OP_ShiftRight: {
int nos = tos - 1;
int a, b;
VERIFY( if( nos<0 ) goto not_enough_stack; )
+ if( (aStack[tos].flags | aStack[nos].flags) & STK_Null ){
+ POPSTACK;
+ Release(p,nos);
+ aStack[nos].flags = STK_Null;
+ break;
+ }
Integerify(p, tos);
Integerify(p, nos);
a = aStack[tos].i;
@@ -1973,40 +1995,82 @@ mismatch:
break;
}
-/* Opcode: Eq * P2 *
+/* Opcode: Eq P1 P2 *
**
** Pop the top two elements from the stack. If they are equal, then
** jump to instruction P2. Otherwise, continue to the next instruction.
+**
+** If either operand is NULL (and thus if the result is unknown) then
+** take the jump if P1 is true.
+**
+** If P2 is zero, do not jump. Instead, push an integer 1 onto the
+** stack if the jump would have been taken, or a 0 if not. Push a
+** NULL if either operand was NULL.
*/
-/* Opcode: Ne * P2 *
+/* Opcode: Ne P1 P2 *
**
** Pop the top two elements from the stack. If they are not equal, then
** jump to instruction P2. Otherwise, continue to the next instruction.
+**
+** If either operand is NULL (and thus if the result is unknown) then
+** take the jump if P1 is true.
+**
+** If P2 is zero, do not jump. Instead, push an integer 1 onto the
+** stack if the jump would have been taken, or a 0 if not. Push a
+** NULL if either operand was NULL.
*/
-/* Opcode: Lt * P2 *
+/* Opcode: Lt P1 P2 *
**
** Pop the top two elements from the stack. If second element (the
** next on stack) is less than the first (the top of stack), then
** jump to instruction P2. Otherwise, continue to the next instruction.
** In other words, jump if NOS<TOS.
+**
+** If either operand is NULL (and thus if the result is unknown) then
+** take the jump if P1 is true.
+**
+** If P2 is zero, do not jump. Instead, push an integer 1 onto the
+** stack if the jump would have been taken, or a 0 if not. Push a
+** NULL if either operand was NULL.
*/
-/* Opcode: Le * P2 *
+/* Opcode: Le P1 P2 *
**
** Pop the top two elements from the stack. If second element (the
** next on stack) is less than or equal to the first (the top of stack),
** then jump to instruction P2. In other words, jump if NOS<=TOS.
+**
+** If either operand is NULL (and thus if the result is unknown) then
+** take the jump if P1 is true.
+**
+** If P2 is zero, do not jump. Instead, push an integer 1 onto the
+** stack if the jump would have been taken, or a 0 if not. Push a
+** NULL if either operand was NULL.
*/
-/* Opcode: Gt * P2 *
+/* Opcode: Gt P1 P2 *
**
** Pop the top two elements from the stack. If second element (the
** next on stack) is greater than the first (the top of stack),
** then jump to instruction P2. In other words, jump if NOS>TOS.
+**
+** If either operand is NULL (and thus if the result is unknown) then
+** take the jump if P1 is true.
+**
+** If P2 is zero, do not jump. Instead, push an integer 1 onto the
+** stack if the jump would have been taken, or a 0 if not. Push a
+** NULL if either operand was NULL.
*/
-/* Opcode: Ge * P2 *
+/* Opcode: Ge P1 P2 *
**
** Pop the top two elements from the stack. If second element (the next
** on stack) is greater than or equal to the first (the top of stack),
** then jump to instruction P2. In other words, jump if NOS>=TOS.
+**
+** If either operand is NULL (and thus if the result is unknown) then
+** take the jump if P1 is true.
+**
+** If P2 is zero, do not jump. Instead, push an integer 1 onto the
+** stack if the jump would have been taken, or a 0 if not. Push a
+** NULL if either operand was NULL.
*/
case OP_Eq:
case OP_Ne:
@@ -2021,7 +2085,17 @@ case OP_Ge: {
VERIFY( if( nos<0 ) goto not_enough_stack; )
ft = aStack[tos].flags;
fn = aStack[nos].flags;
- if( (ft & fn & STK_Int)==STK_Int ){
+ if( (ft | fn) & STK_Null ){
+ POPSTACK;
+ POPSTACK;
+ if( pOp->p2 ){
+ if( pOp->p1 ) pc = pOp->p2-1;
+ }else{
+ p->tos++;
+ aStack[nos].flags = STK_Null;
+ }
+ break;
+ }else if( (ft & fn & STK_Int)==STK_Int ){
c = aStack[nos].i - aStack[tos].i;
}else if( (ft & STK_Int)!=0 && (fn & STK_Str)!=0 && isInteger(zStack[nos]) ){
Integerify(p, nos);
@@ -2043,7 +2117,13 @@ case OP_Ge: {
}
POPSTACK;
POPSTACK;
- if( c ) pc = pOp->p2-1;
+ if( pOp->p2 ){
+ if( c ) pc = pOp->p2-1;
+ }else{
+ p->tos++;
+ aStack[nos].flags = STK_Int;
+ aStack[nos].i = c;
+ }
break;
}
@@ -2052,12 +2132,14 @@ case OP_Ge: {
** Pop two values off the stack. Take the logical AND of the
** two values and push the resulting boolean value back onto the
** stack.
+** If either operand is NULL, the result is NULL.
*/
/* Opcode: Or * * *
**
** Pop two values off the stack. Take the logical OR of the
** two values and push the resulting boolean value back onto the
** stack.
+** If either operand is NULL, the result is NULL.
*/
case OP_And:
case OP_Or: {
@@ -2065,6 +2147,12 @@ case OP_Or: {
int nos = tos - 1;
int c;
VERIFY( if( nos<0 ) goto not_enough_stack; )
+ if( (aStack[tos].flags | aStack[nos].flags) & STK_Null ){
+ POPSTACK;
+ Release(p, nos);
+ aStack[nos].flags = STK_Null;
+ break;
+ }
Integerify(p, tos);
Integerify(p, nos);
if( pOp->opcode==OP_And ){
@@ -2082,12 +2170,14 @@ case OP_Or: {
/* Opcode: Negative * * *
**
** Treat the top of the stack as a numeric quantity. Replace it
-** with its additive inverse.
+** with its additive inverse. If the top of the stack is NULL
+** its value is unchanged.
*/
/* Opcode: AbsValue * * *
**
** Treat the top of the stack as a numeric quantity. Replace it
-** with its absolute value.
+** with its absolute value. If the top of the stack is NULL
+** its value is unchanged.
*/
case OP_Negative:
case OP_AbsValue: {
@@ -2105,6 +2195,8 @@ case OP_AbsValue: {
aStack[tos].i = -aStack[tos].i;
}
aStack[tos].flags = STK_Int;
+ }else if( aStack[tos].flags & STK_Null ){
+ /* Do nothing */
}else{
Realify(p, tos);
Release(p, tos);
@@ -2119,11 +2211,13 @@ case OP_AbsValue: {
/* Opcode: Not * * *
**
** Interpret the top of the stack as a boolean value. Replace it
-** with its complement.
+** with its complement. If the top of the stack is NULL its value
+** is unchanged.
*/
case OP_Not: {
int tos = p->tos;
VERIFY( if( p->tos<0 ) goto not_enough_stack; )
+ if( aStack[tos].flags & STK_Null ) break; /* Do nothing to NULLs */
Integerify(p, tos);
Release(p, tos);
aStack[tos].i = !aStack[tos].i;
@@ -2134,11 +2228,13 @@ case OP_Not: {
/* Opcode: BitNot * * *
**
** Interpret the top of the stack as an value. Replace it
-** with its ones-complement.
+** with its ones-complement. If the top of the stack is NULL its
+** value is unchanged.
*/
case OP_BitNot: {
int tos = p->tos;
VERIFY( if( p->tos<0 ) goto not_enough_stack; )
+ if( aStack[tos].flags & STK_Null ) break; /* Do nothing to NULLs */
Integerify(p, tos);
Release(p, tos);
aStack[tos].i = ~aStack[tos].i;
@@ -2155,60 +2251,92 @@ case OP_Noop: {
break;
}
-/* Opcode: If * P2 *
+/* Opcode: If P1 P2 *
**
** Pop a single boolean from the stack. If the boolean popped is
** true, then jump to p2. Otherwise continue to the next instruction.
** An integer is false if zero and true otherwise. A string is
** false if it has zero length and true otherwise.
+**
+** If the value popped of the stack is NULL, then take the jump if P1
+** is true and fall through if P1 is false.
+*/
+/* Opcode: IfNot P1 P2 *
+**
+** Pop a single boolean from the stack. If the boolean popped is
+** false, then jump to p2. Otherwise continue to the next instruction.
+** An integer is false if zero and true otherwise. A string is
+** false if it has zero length and true otherwise.
+**
+** If the value popped of the stack is NULL, then take the jump if P1
+** is true and fall through if P1 is false.
*/
-case OP_If: {
+case OP_If:
+case OP_IfNot: {
int c;
VERIFY( if( p->tos<0 ) goto not_enough_stack; )
- Integerify(p, p->tos);
- c = aStack[p->tos].i;
+ if( aStack[p->tos].flags & STK_Null ){
+ c = pOp->p1;
+ }else{
+ Integerify(p, p->tos);
+ c = aStack[p->tos].i;
+ if( pOp->opcode==OP_IfNot ) c = !c;
+ }
POPSTACK;
if( c ) pc = pOp->p2-1;
break;
}
-/* Opcode: IsNull * P2 *
+/* Opcode: IsNull P1 P2 *
**
-** Pop a single value from the stack. If the value popped is NULL
-** then jump to p2. Otherwise continue to the next
-** instruction.
+** If any of the top abs(P1) values on the stack are NULL, then jump
+** to P2. The stack is popped P1 times if P1>0. If P1<0 then all values
+** are left unchanged on the stack.
*/
case OP_IsNull: {
- int c;
- VERIFY( if( p->tos<0 ) goto not_enough_stack; )
- c = (aStack[p->tos].flags & STK_Null)!=0;
- POPSTACK;
- if( c ) pc = pOp->p2-1;
+ int i, cnt;
+ cnt = pOp->p1;
+ if( cnt<0 ) cnt = -cnt;
+ VERIFY( if( p->tos+1-cnt<0 ) goto not_enough_stack; )
+ for(i=0; i<cnt; i++){
+ if( aStack[p->tos-i].flags & STK_Null ){
+ pc = pOp->p2-1;
+ break;
+ }
+ }
+ if( pOp->p1>0 ) PopStack(p, cnt);
break;
}
-/* Opcode: NotNull * P2 *
+/* Opcode: NotNull P1 P2 *
**
-** Pop a single value from the stack. If the value popped is not
-** NULL, then jump to p2. Otherwise continue to the next
-** instruction.
+** Jump to P2 if the top value on the stack is not NULL. Pop the
+** stack if P1 is greater than zero. If P1 is less than or equal to
+** zero then leave the value on the stack.
*/
case OP_NotNull: {
- int c;
VERIFY( if( p->tos<0 ) goto not_enough_stack; )
- c = (aStack[p->tos].flags & STK_Null)==0;
- POPSTACK;
- if( c ) pc = pOp->p2-1;
+ if( (aStack[p->tos].flags & STK_Null)==0 ) pc = pOp->p2-1;
+ if( pOp->p1>0 ){ POPSTACK; }
break;
}
-/* Opcode: MakeRecord P1 * *
+/* Opcode: MakeRecord P1 P2 *
**
** Convert the top P1 entries of the stack into a single entry
** suitable for use as a data record in a database table. The
** details of the format are irrelavant as long as the OP_Column
** opcode can decode the record later. Refer to source code
** comments for the details of the record format.
+**
+** If P2 is true (non-zero) and one or more of the P1 entries
+** that go into building the record is NULL, then add some extra
+** bytes to the record to make it distinct for other entries created
+** during the same run of the VDBE. The extra bytes added are a
+** counter that is reset with each run of the VDBE, so records
+** created this way will not necessarily be distinct across runs.
+** But they should be distinct for transient tables (created using
+** OP_OpenTemp) which is what they are intended for.
*/
case OP_MakeRecord: {
char *zNewRecord;
@@ -2217,6 +2345,8 @@ case OP_MakeRecord: {
int i, j;
int idxWidth;
u32 addr;
+ int addUnique = 0; /* True to cause bytes to be added to make the
+ ** generated record distinct */
/* Assuming the record contains N fields, the record format looks
** like this:
@@ -2240,11 +2370,14 @@ case OP_MakeRecord: {
VERIFY( if( p->tos+1<nField ) goto not_enough_stack; )
nByte = 0;
for(i=p->tos-nField+1; i<=p->tos; i++){
- if( (aStack[i].flags & STK_Null)==0 ){
+ if( (aStack[i].flags & STK_Null) ){
+ addUnique = pOp->p2;
+ }else{
if( Stringify(p, i) ) goto no_mem;
nByte += aStack[i].n;
}
}
+ if( addUnique ) nByte += sizeof(uniqueCnt);
if( nByte + nField + 1 < 256 ){
idxWidth = 1;
}else if( nByte + 2*nField + 2 < 65536 ){
@@ -2260,7 +2393,7 @@ case OP_MakeRecord: {
zNewRecord = sqliteMalloc( nByte );
if( zNewRecord==0 ) goto no_mem;
j = 0;
- addr = idxWidth*(nField+1);
+ addr = idxWidth*(nField+1) + addUnique*sizeof(uniqueCnt);
for(i=p->tos-nField+1; i<=p->tos; i++){
zNewRecord[j++] = addr & 0xff;
if( idxWidth>1 ){
@@ -2280,6 +2413,11 @@ case OP_MakeRecord: {
zNewRecord[j++] = (addr>>16)&0xff;
}
}
+ if( addUnique ){
+ memcpy(&zNewRecord[j], &uniqueCnt, sizeof(uniqueCnt));
+ uniqueCnt++;
+ j += sizeof(uniqueCnt);
+ }
for(i=p->tos-nField+1; i<=p->tos; i++){
if( (aStack[i].flags & STK_Null)==0 ){
memcpy(&zNewRecord[j], zStack[i], aStack[i].n);
@@ -2311,7 +2449,7 @@ case OP_MakeRecord: {
**
** See also: MakeIdxKey, SortMakeKey
*/
-/* Opcode: MakeIdxKey P1 * *
+/* Opcode: MakeIdxKey P1 P2 *
**
** Convert the top P1 entries of the stack into a single entry suitable
** for use as the key in an index. In addition, take one additional integer
@@ -2327,6 +2465,12 @@ case OP_MakeRecord: {
** in the stack is the first field and the top of the stack becomes the
** last.
**
+** If P2 is not zero and one or more of the P1 entries that go into the
+** generated key is NULL, then jump to P2 after the new key has been
+** pushed on the stack. In other words, jump to P2 if the key is
+** guaranteed to be unique. This jump can be used to skip a subsequent
+** uniqueness test.
+**
** See also: MakeKey, SortMakeKey
*/
case OP_MakeIdxKey:
@@ -2336,6 +2480,7 @@ case OP_MakeKey: {
int nField;
int addRowid;
int i, j;
+ int containsNull = 0;
addRowid = pOp->opcode==OP_MakeIdxKey;
nField = pOp->p1;
@@ -2347,6 +2492,7 @@ case OP_MakeKey: {
char *z;
if( flags & STK_Null ){
nByte += 2;
+ containsNull = 1;
}else if( flags & STK_Real ){
z = aStack[i].z;
sqliteRealToSortable(aStack[i].r, &z[1]);
@@ -2406,8 +2552,11 @@ case OP_MakeKey: {
Integerify(p, p->tos-nField);
iKey = intToKey(aStack[p->tos-nField].i);
memcpy(&zNewKey[j], &iKey, sizeof(u32));
+ PopStack(p, nField+1);
+ if( pOp->p2 && containsNull ) pc = pOp->p2 - 1;
+ }else{
+ if( pOp->p2==0 ) PopStack(p, nField+addRowid);
}
- if( pOp->p2==0 ) PopStack(p, nField+addRowid);
VERIFY( NeedStack(p, p->tos+1); )
p->tos++;
aStack[p->tos].n = nByte;
@@ -4373,7 +4522,9 @@ case OP_AggFunc: {
VERIFY( if( p->tos+1<n ) goto not_enough_stack; )
VERIFY( if( aStack[p->tos].flags!=STK_Int ) goto bad_instruction; )
for(i=p->tos-n; i<p->tos; i++){
- if( (aStack[i].flags & STK_Null)==0 ){
+ if( aStack[i].flags & STK_Null ){
+ zStack[i] = 0;
+ }else{
if( Stringify(p, i) ) goto no_mem;
}
}
diff --git a/src/vdbe.h b/src/vdbe.h
index 23410437e..42e9a07b0 100644
--- a/src/vdbe.h
+++ b/src/vdbe.h
@@ -15,7 +15,7 @@
** or VDBE. The VDBE implements an abstract machine that runs a
** simple program to access and modify the underlying database.
**
-** $Id: vdbe.h,v 1.52 2002/05/23 22:07:03 drh Exp $
+** $Id: vdbe.h,v 1.53 2002/05/26 20:54:34 drh Exp $
*/
#ifndef _SQLITE_VDBE_H_
#define _SQLITE_VDBE_H_
@@ -157,53 +157,54 @@ typedef struct VdbeOp VdbeOp;
#define OP_Goto 76
#define OP_If 77
-#define OP_Halt 78
-
-#define OP_ColumnCount 79
-#define OP_ColumnName 80
-#define OP_Callback 81
-#define OP_NullCallback 82
-
-#define OP_Integer 83
-#define OP_String 84
-#define OP_Pop 85
-#define OP_Dup 86
-#define OP_Pull 87
-#define OP_Push 88
-#define OP_MustBeInt 89
-
-#define OP_Add 90
-#define OP_AddImm 91
-#define OP_Subtract 92
-#define OP_Multiply 93
-#define OP_Divide 94
-#define OP_Remainder 95
-#define OP_BitAnd 96
-#define OP_BitOr 97
-#define OP_BitNot 98
-#define OP_ShiftLeft 99
-#define OP_ShiftRight 100
-#define OP_AbsValue 101
-#define OP_Eq 102
-#define OP_Ne 103
-#define OP_Lt 104
-#define OP_Le 105
-#define OP_Gt 106
-#define OP_Ge 107
-#define OP_IsNull 108
-#define OP_NotNull 109
-#define OP_Negative 110
-#define OP_And 111
-#define OP_Or 112
-#define OP_Not 113
-#define OP_Concat 114
-#define OP_Noop 115
-#define OP_Function 116
-
-#define OP_Limit 117
-
-
-#define OP_MAX 117
+#define OP_IfNot 78
+#define OP_Halt 79
+
+#define OP_ColumnCount 80
+#define OP_ColumnName 81
+#define OP_Callback 82
+#define OP_NullCallback 83
+
+#define OP_Integer 84
+#define OP_String 85
+#define OP_Pop 86
+#define OP_Dup 87
+#define OP_Pull 88
+#define OP_Push 89
+#define OP_MustBeInt 90
+
+#define OP_Add 91
+#define OP_AddImm 92
+#define OP_Subtract 93
+#define OP_Multiply 94
+#define OP_Divide 95
+#define OP_Remainder 96
+#define OP_BitAnd 97
+#define OP_BitOr 98
+#define OP_BitNot 99
+#define OP_ShiftLeft 100
+#define OP_ShiftRight 101
+#define OP_AbsValue 102
+#define OP_Eq 103
+#define OP_Ne 104
+#define OP_Lt 105
+#define OP_Le 106
+#define OP_Gt 107
+#define OP_Ge 108
+#define OP_IsNull 109
+#define OP_NotNull 110
+#define OP_Negative 111
+#define OP_And 112
+#define OP_Or 113
+#define OP_Not 114
+#define OP_Concat 115
+#define OP_Noop 116
+#define OP_Function 117
+
+#define OP_Limit 118
+
+
+#define OP_MAX 118
/*
** Prototypes for the VDBE interface. See comments on the implementation
diff --git a/src/where.c b/src/where.c
index a3576cae4..0f14df9de 100644
--- a/src/where.c
+++ b/src/where.c
@@ -13,7 +13,7 @@
** the WHERE clause of SQL statements. Also found here are subroutines
** to generate VDBE code to evaluate expressions.
**
-** $Id: where.c,v 1.47 2002/05/24 20:31:38 drh Exp $
+** $Id: where.c,v 1.48 2002/05/26 20:54:34 drh Exp $
*/
#include "sqliteInt.h"
@@ -199,7 +199,7 @@ WhereInfo *sqliteWhereBegin(
** expression and either jump over all of the code or fall thru.
*/
if( pWhere && sqliteExprIsConstant(pWhere) ){
- sqliteExprIfFalse(pParse, pWhere, pWInfo->iBreak);
+ sqliteExprIfFalse(pParse, pWhere, pWInfo->iBreak, 1);
}
/* Split the WHERE clause into as many as 32 separate subexpressions
@@ -795,7 +795,7 @@ WhereInfo *sqliteWhereBegin(
haveKey = 0;
sqliteVdbeAddOp(v, OP_MoveTo, base+idx, 0);
}
- sqliteExprIfFalse(pParse, aExpr[j].p, cont);
+ sqliteExprIfFalse(pParse, aExpr[j].p, cont, 1);
aExpr[j].p = 0;
}
brk = cont;
diff --git a/test/expr.test b/test/expr.test
index 784b80298..0d93df662 100644
--- a/test/expr.test
+++ b/test/expr.test
@@ -11,7 +11,7 @@
# This file implements regression tests for SQLite library. The
# focus of this file is testing expressions.
#
-# $Id: expr.test,v 1.19 2002/03/24 13:13:29 drh Exp $
+# $Id: expr.test,v 1.20 2002/05/26 20:54:34 drh Exp $
set testdir [file dirname $argv0]
source $testdir/tester.tcl
@@ -22,8 +22,7 @@ execsql {CREATE TABLE test1(i1 int, i2 int, r1 real, r2 real, t1 text, t2 text)}
execsql {INSERT INTO test1 VALUES(1,2,1.1,2.2,'hello','world')}
proc test_expr {name settings expr result} {
do_test $name [format {
- execsql {UPDATE test1 SET %s}
- execsql {SELECT %s FROM test1}
+ execsql {BEGIN; UPDATE test1 SET %s; SELECT %s FROM test1; ROLLBACK;}
} $settings $expr] $result
}
@@ -63,7 +62,7 @@ test_expr expr-1.33 {i1=1, i2=2} {i1=1 OR i2=1} {1}
test_expr expr-1.34 {i1=1, i2=2} {i1=2 OR i2=2} {1}
test_expr expr-1.35 {i1=1, i2=2} {i1-i2=-1} {1}
test_expr expr-1.36 {i1=1, i2=0} {not i1} {0}
-test_expr expr-1.37 {i1=1, i2=NULL} {not i2} {1}
+test_expr expr-1.37 {i1=1, i2=0} {not i2} {1}
test_expr expr-1.38 {i1=1} {-i1} {-1}
test_expr expr-1.39 {i1=1} {+i1} {1}
test_expr expr-1.40 {i1=1, i2=2} {+(i2+i1)} {3}
@@ -320,8 +319,8 @@ proc test_expr2 {name expr result} {
test_expr2 expr-7.2 {a<10 AND a>8} {9}
test_expr2 expr-7.3 {a<=10 AND a>=8} {8 9 10}
test_expr2 expr-7.4 {a>=8 AND a<=10} {8 9 10}
-test_expr2 expr-7.5 {a>=20 OR a<=1} {{} 1 20}
-test_expr2 expr-7.6 {b!=4 AND a<=3} {{} 1 3}
+test_expr2 expr-7.5 {a>=20 OR a<=1} {1 20}
+test_expr2 expr-7.6 {b!=4 AND a<=3} {1 3}
test_expr2 expr-7.7 {b==8 OR b==16 OR b==32} {3 4 5}
test_expr2 expr-7.8 {NOT b<>8 OR b==1024} {3 10}
test_expr2 expr-7.9 {b LIKE '10%'} {10 20}
@@ -332,13 +331,13 @@ test_expr2 expr-7.13 {b GLOB '*1[456]'} {4}
test_expr2 expr-7.14 {a ISNULL} {{}}
test_expr2 expr-7.15 {a NOTNULL AND a<3} {1 2}
test_expr2 expr-7.16 {a AND a<3} {1 2}
-test_expr2 expr-7.17 {NOT a} {{}}
+test_expr2 expr-7.17 {NOT a} {}
test_expr2 expr-7.18 {a==11 OR (b>1000 AND b<2000)} {10 11}
-test_expr2 expr-7.19 {a<=1 OR a>=20} {{} 1 20}
-test_expr2 expr-7.20 {a<1 OR a>20} {{}}
-test_expr2 expr-7.21 {a>19 OR a<1} {{} 20}
+test_expr2 expr-7.19 {a<=1 OR a>=20} {1 20}
+test_expr2 expr-7.20 {a<1 OR a>20} {}
+test_expr2 expr-7.21 {a>19 OR a<1} {20}
test_expr2 expr-7.22 {a!=1 OR a=100} \
- {{} 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20}
+ {2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20}
test_expr2 expr-7.23 {(a notnull AND a<4) OR a==8} {1 2 3 8}
test_expr2 expr-7.24 {a LIKE '2_' OR a==8} {8 20}
test_expr2 expr-7.25 {a GLOB '2?' OR a==8} {8 20}
diff --git a/test/select4.test b/test/select4.test
index 3a32dc0de..2b47b3574 100644
--- a/test/select4.test
+++ b/test/select4.test
@@ -12,7 +12,7 @@
# focus of this file is testing UNION, INTERSECT and EXCEPT operators
# in SELECT statements.
#
-# $Id: select4.test,v 1.6 2002/05/24 16:14:16 drh Exp $
+# $Id: select4.test,v 1.7 2002/05/26 20:54:35 drh Exp $
set testdir [file dirname $argv0]
source $testdir/tester.tcl
@@ -249,6 +249,48 @@ do_test select4-6.2 {
}
} {0 1 1 1 2 2 3 4 3 7 4 8 5 15}
+# NULLs are distinct. Make sure the UNION operator recognizes this
+#
+do_test select4-6.3 {
+ execsql {
+ SELECT NULL UNION SELECT NULL UNION
+ SELECT 1 UNION SELECT 2 AS 'x'
+ ORDER BY x;
+ }
+} {{} {} 1 2}
+do_test select4-6.3 {
+ execsql {
+ SELECT NULL UNION ALL SELECT NULL UNION ALL
+ SELECT 1 UNION ALL SELECT 2 AS 'x'
+ ORDER BY x;
+ }
+} {{} {} 1 2}
+
+# Make sure the DISTINCT keyword treats NULLs as DISTINCT
+#
+do_test select4-6.4 {
+ execsql {
+ SELECT * FROM (
+ SELECT NULL, 1 UNION ALL SELECT NULL, 1
+ );
+ }
+} {{} 1 {} 1}
+do_test select4-6.5 {
+ execsql {
+ SELECT DISTINCT * FROM (
+ SELECT NULL, 1 UNION ALL SELECT NULL, 1
+ );
+ }
+} {{} 1 {} 1}
+do_test select4-6.6 {
+ execsql {
+ SELECT DISTINCT * FROM (
+ SELECT 1,2 UNION ALL SELECT 1,2
+ );
+ }
+} {1 2}
+
+
# Make sure column names are correct when a compound select appears as
# an expression in the WHERE clause.
#
diff --git a/test/subselect.test b/test/subselect.test
index e2f2559f5..392ab5319 100644
--- a/test/subselect.test
+++ b/test/subselect.test
@@ -12,7 +12,7 @@
# focus of this file is testing SELECT statements that are part of
# expressions.
#
-# $Id: subselect.test,v 1.4 2001/09/16 00:13:28 drh Exp $
+# $Id: subselect.test,v 1.5 2002/05/26 20:54:35 drh Exp $
set testdir [file dirname $argv0]
source $testdir/tester.tcl
@@ -55,9 +55,8 @@ do_test subselect-1.3c {
# NULL as the result. Check it out.
#
do_test subselect-1.4 {
- execsql {INSERT INTO t1 VALUES(NULL,8)}
- execsql {SELECT b from t1 where a = (SELECT a FROM t1 WHERE b=5)}
-} {8}
+ execsql {SELECT b from t1 where a = coalesce((SELECT a FROM t1 WHERE b=5),1)}
+} {2}
# Try multiple subselects within a single expression.
#
diff --git a/test/trigger2.test b/test/trigger2.test
index adce11450..ae103ea85 100644
--- a/test/trigger2.test
+++ b/test/trigger2.test
@@ -77,7 +77,7 @@ foreach tbl_defn [ list \
CREATE TRIGGER before_update_row BEFORE UPDATE ON tbl FOR EACH ROW
BEGIN
- INSERT INTO rlog VALUES ( (SELECT max(idx) + 1 FROM rlog),
+ INSERT INTO rlog VALUES ( (SELECT coalesce(max(idx),0) + 1 FROM rlog),
old.a, old.b,
(SELECT sum(a) FROM tbl), (SELECT sum(b) FROM tbl),
new.a, new.b);
@@ -85,7 +85,7 @@ foreach tbl_defn [ list \
CREATE TRIGGER after_update_row AFTER UPDATE ON tbl FOR EACH ROW
BEGIN
- INSERT INTO rlog VALUES ( (SELECT max(idx) + 1 FROM rlog),
+ INSERT INTO rlog VALUES ( (SELECT coalesce(max(idx),0) + 1 FROM rlog),
old.a, old.b,
(SELECT sum(a) FROM tbl), (SELECT sum(b) FROM tbl),
new.a, new.b);
@@ -94,7 +94,7 @@ foreach tbl_defn [ list \
CREATE TRIGGER conditional_update_row AFTER UPDATE ON tbl FOR EACH ROW
WHEN old.a = 1
BEGIN
- INSERT INTO clog VALUES ( (SELECT max(idx) + 1 FROM clog),
+ INSERT INTO clog VALUES ( (SELECT coalesce(max(idx),0) + 1 FROM clog),
old.a, old.b,
(SELECT sum(a) FROM tbl), (SELECT sum(b) FROM tbl),
new.a, new.b);
@@ -120,7 +120,7 @@ foreach tbl_defn [ list \
INSERT INTO tbl VALUES (300, 200);
CREATE TRIGGER delete_before_row BEFORE DELETE ON tbl FOR EACH ROW
BEGIN
- INSERT INTO rlog VALUES ( (SELECT max(idx) + 1 FROM rlog),
+ INSERT INTO rlog VALUES ( (SELECT coalesce(max(idx),0) + 1 FROM rlog),
old.a, old.b,
(SELECT sum(a) FROM tbl), (SELECT sum(b) FROM tbl),
0, 0);
@@ -128,7 +128,7 @@ foreach tbl_defn [ list \
CREATE TRIGGER delete_after_row AFTER DELETE ON tbl FOR EACH ROW
BEGIN
- INSERT INTO rlog VALUES ( (SELECT max(idx) + 1 FROM rlog),
+ INSERT INTO rlog VALUES ( (SELECT coalesce(max(idx),0) + 1 FROM rlog),
old.a, old.b,
(SELECT sum(a) FROM tbl), (SELECT sum(b) FROM tbl),
0, 0);
@@ -148,7 +148,7 @@ foreach tbl_defn [ list \
DELETE FROM rlog;
CREATE TRIGGER insert_before_row BEFORE INSERT ON tbl FOR EACH ROW
BEGIN
- INSERT INTO rlog VALUES ( (SELECT max(idx) + 1 FROM rlog),
+ INSERT INTO rlog VALUES ( (SELECT coalesce(max(idx),0) + 1 FROM rlog),
0, 0,
(SELECT sum(a) FROM tbl), (SELECT sum(b) FROM tbl),
new.a, new.b);
@@ -156,7 +156,7 @@ foreach tbl_defn [ list \
CREATE TRIGGER insert_after_row AFTER INSERT ON tbl FOR EACH ROW
BEGIN
- INSERT INTO rlog VALUES ( (SELECT max(idx) + 1 FROM rlog),
+ INSERT INTO rlog VALUES ( (SELECT coalesce(max(idx),0) + 1 FROM rlog),
0, 0,
(SELECT sum(a) FROM tbl), (SELECT sum(b) FROM tbl),
new.a, new.b);
diff --git a/test/unique.test b/test/unique.test
index 146ddf028..27cb3e716 100644
--- a/test/unique.test
+++ b/test/unique.test
@@ -12,7 +12,7 @@
# focus of this file is testing the CREATE UNIQUE INDEX statement,
# and primary keys, and the UNIQUE constraint on table columns
#
-# $Id: unique.test,v 1.3 2001/12/21 14:30:44 drh Exp $
+# $Id: unique.test,v 1.4 2002/05/26 20:54:35 drh Exp $
set testdir [file dirname $argv0]
source $testdir/tester.tcl
@@ -162,4 +162,37 @@ do_test unique-3.4 {
}
} {1 {constraint failed}}
+# Make sure NULLs are distinct as far as the UNIQUE tests are
+# concerned.
+#
+do_test unique-4.1 {
+ execsql {
+ CREATE TABLE t4(a UNIQUE, b, c, UNIQUE(b,c));
+ INSERT INTO t4 VALUES(1,2,3);
+ INSERT INTO t4 VALUES(NULL, 2, NULL);
+ SELECT * FROM t4;
+ }
+} {1 2 3 {} 2 {}}
+do_test unique-4.2 {
+ catchsql {
+ INSERT INTO t4 VALUES(NULL, 3, 4);
+ }
+} {0 {}}
+do_test unique-4.3 {
+ execsql {
+ SELECT * FROM t4
+ }
+} {1 2 3 {} 2 {} {} 3 4}
+do_test unique-4.4 {
+ catchsql {
+ INSERT INTO t4 VALUES(2, 2, NULL);
+ }
+} {0 {}}
+do_test unique-4.5 {
+ execsql {
+ SELECT * FROM t4
+ }
+} {1 2 3 {} 2 {} {} 3 4 2 2 {}}
+
+
finish_test