diff options
-rw-r--r-- | manifest | 36 | ||||
-rw-r--r-- | manifest.uuid | 2 | ||||
-rw-r--r-- | src/expr.c | 164 | ||||
-rw-r--r-- | src/insert.c | 26 | ||||
-rw-r--r-- | src/select.c | 18 | ||||
-rw-r--r-- | src/sqliteInt.h | 6 | ||||
-rw-r--r-- | src/trigger.c | 2 | ||||
-rw-r--r-- | src/vdbe.c | 257 | ||||
-rw-r--r-- | src/vdbe.h | 97 | ||||
-rw-r--r-- | src/where.c | 6 | ||||
-rw-r--r-- | test/expr.test | 21 | ||||
-rw-r--r-- | test/select4.test | 44 | ||||
-rw-r--r-- | test/subselect.test | 7 | ||||
-rw-r--r-- | test/trigger2.test | 14 | ||||
-rw-r--r-- | test/unique.test | 35 |
15 files changed, 491 insertions, 244 deletions
@@ -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 |