1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-30 19:03:16 +03:00

Refinements to NULL processing: NULLs are indistinct for DISTINCT and UNION.

Multiplying a NULL by zero yields zero. In a CASE expression, a NULL comparison
is considered false, not NULL.  With these changes, NULLs in SQLite now work
the same as in PostgreSQL and in Oracle. (CVS 600)

FossilOrigin-Name: da61aa1d238539dff9c43fd9f464d311e28d669f
This commit is contained in:
drh
2002-05-31 15:51:25 +00:00
parent 0f89253e21
commit f570f011eb
11 changed files with 279 additions and 59 deletions

View File

@ -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.65 2002/05/30 02:35:12 drh Exp $
** $Id: expr.c,v 1.66 2002/05/31 15:51:25 drh Exp $
*/
#include "sqliteInt.h"
@ -933,9 +933,7 @@ void sqliteExprCode(Parse *pParse, Expr *pExpr){
}
case TK_CASE: {
int expr_end_label;
int null_result_label;
int jumpInst;
int nullBypassInst;
int addr;
int nExpr;
int i;
@ -945,44 +943,32 @@ void sqliteExprCode(Parse *pParse, Expr *pExpr){
assert(pExpr->pList->nExpr > 0);
nExpr = pExpr->pList->nExpr;
expr_end_label = sqliteVdbeMakeLabel(v);
null_result_label = sqliteVdbeMakeLabel(v);
if( pExpr->pLeft ){
sqliteExprCode(pParse, pExpr->pLeft);
nullBypassInst = sqliteVdbeAddOp(v, OP_IsNull, -1, 0);
}
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(v, OP_Dup, 1, 1);
jumpInst = sqliteVdbeAddOp(v, OP_Ne, 0, 0);
jumpInst = sqliteVdbeAddOp(v, OP_Ne, 1, 0);
sqliteVdbeAddOp(v, OP_Pop, 1, 0);
}else{
jumpInst = sqliteVdbeAddOp(v, OP_IfNot, 0, 0);
jumpInst = sqliteVdbeAddOp(v, OP_IfNot, 1, 0);
}
sqliteExprCode(pParse, pExpr->pList->a[i+1].pExpr);
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->pLeft ){
sqliteVdbeAddOp(v, OP_Pop, 1, 0);
}
if( pExpr->pRight ){
sqliteExprCode(pParse, pExpr->pRight);
}else{
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);
sqliteVdbeChangeP2(v, nullBypassInst, sqliteVdbeCurrentAddr(v));
}
}
break;
}

View File

@ -16,7 +16,7 @@
** sqliteRegisterBuildinFunctions() found at the bottom of the file.
** All other code has file scope.
**
** $Id: func.c,v 1.18 2002/05/29 23:22:23 drh Exp $
** $Id: func.c,v 1.19 2002/05/31 15:51:25 drh Exp $
*/
#include <ctype.h>
#include <math.h>
@ -362,7 +362,7 @@ static void minStep(sqlite_func *context, int argc, const char **argv){
MinMaxCtx *p;
p = sqlite_aggregate_context(context, sizeof(*p));
if( p==0 || argc<1 || argv[0]==0 ) return;
if( sqlite_aggregate_count(context)==1 || sqliteCompare(argv[0],p->z)<0 ){
if( p->z==0 || sqliteCompare(argv[0],p->z)<0 ){
int len;
if( p->z && p->z!=p->zBuf ){
sqliteFree(p->z);
@ -381,7 +381,7 @@ static void maxStep(sqlite_func *context, int argc, const char **argv){
MinMaxCtx *p;
p = sqlite_aggregate_context(context, sizeof(*p));
if( p==0 || argc<1 || argv[0]==0 ) return;
if( sqlite_aggregate_count(context)==1 || sqliteCompare(argv[0],p->z)>0 ){
if( p->z==0 || sqliteCompare(argv[0],p->z)>0 ){
int len;
if( p->z && p->z!=p->zBuf ){
sqliteFree(p->z);

View File

@ -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.88 2002/05/27 12:24:48 drh Exp $
** $Id: select.c,v 1.89 2002/05/31 15:51:25 drh Exp $
*/
#include "sqliteInt.h"
@ -326,7 +326,12 @@ static int selectInnerLoop(
** part of the result.
*/
if( distinct>=0 && pEList && pEList->nExpr>0 ){
sqliteVdbeAddOp(v, OP_IsNull, -pEList->nExpr, sqliteVdbeCurrentAddr(v)+7);
/* For the purposes of the DISTINCT keyword to a SELECT, NULLs
** are indistinct. This was confirmed by experiment in Oracle
** and PostgreSQL. It seems contradictory, but it appears to be
** true.
** sqliteVdbeAddOp(v, OP_IsNull, -pEList->nExpr,sqliteVdbeCurrentAddr(v)+7);
*/
sqliteVdbeAddOp(v, OP_MakeKey, pEList->nExpr, 1);
sqliteVdbeAddOp(v, OP_Distinct, distinct, sqliteVdbeCurrentAddr(v)+3);
sqliteVdbeAddOp(v, OP_Pop, pEList->nExpr+1, 0);
@ -358,7 +363,7 @@ static int selectInnerLoop(
** table iParm.
*/
if( eDest==SRT_Union ){
sqliteVdbeAddOp(v, OP_MakeRecord, nColumn, 1);
sqliteVdbeAddOp(v, OP_MakeRecord, nColumn, 0);
sqliteVdbeAddOp(v, OP_String, 0, 0);
sqliteVdbeAddOp(v, OP_PutStrKey, iParm, 0);
}else
@ -377,7 +382,7 @@ static int selectInnerLoop(
** the temporary table iParm.
*/
if( eDest==SRT_Except ){
int addr = sqliteVdbeAddOp(v, OP_MakeRecord, nColumn, 1);
int addr = sqliteVdbeAddOp(v, OP_MakeRecord, nColumn, 0);
sqliteVdbeAddOp(v, OP_NotFound, iParm, addr+3);
sqliteVdbeAddOp(v, OP_Delete, iParm, 0);
}else

View File

@ -14,7 +14,7 @@
** This file contains functions for allocating memory, comparing
** strings, and stuff like that.
**
** $Id: util.c,v 1.44 2002/05/26 21:34:58 drh Exp $
** $Id: util.c,v 1.45 2002/05/31 15:51:25 drh Exp $
*/
#include "sqliteInt.h"
#include <stdarg.h>
@ -728,6 +728,13 @@ int sqliteSortCompare(const char *a, const char *b){
int isNumA, isNumB;
while( res==0 && *a && *b ){
if( a[1]==0 ){
res = -1;
break;
}else if( b[1]==0 ){
res = +1;
break;
}
isNumA = sqliteIsNumber(&a[1]);
isNumB = sqliteIsNumber(&b[1]);
if( isNumA ){

View File

@ -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.150 2002/05/27 01:04:51 drh Exp $
** $Id: vdbe.c,v 1.151 2002/05/31 15:51:26 drh Exp $
*/
#include "sqliteInt.h"
#include <ctype.h>
@ -1769,9 +1769,28 @@ case OP_Remainder: {
int nos = tos - 1;
VERIFY( if( nos<0 ) goto not_enough_stack; )
if( ((aStack[tos].flags | aStack[nos].flags) & STK_Null)!=0 ){
int resultType = STK_Null;
if( pOp->opcode==OP_Multiply ){
/* Special case: multiplying NULL by zero gives a zero result, not a
** NULL result as it would normally. */
if( (aStack[tos].flags & (STK_Int|STK_Real))!=0
|| ((aStack[tos].flags & STK_Str)!=0 && isNumber(zStack[tos])) ){
Integerify(p,tos);
if( aStack[tos].i==0 ){
resultType = STK_Int;
aStack[nos].i = 0;
}
}else if( (aStack[nos].flags & (STK_Int|STK_Real))!=0
|| ((aStack[nos].flags & STK_Str)!=0 && isNumber(zStack[nos])) ){
Integerify(p,nos);
if( aStack[nos].i==0 ){
resultType = STK_Int;
}
}
}
POPSTACK;
Release(p, nos);
aStack[nos].flags = STK_Null;
aStack[nos].flags = resultType;
}else if( (aStack[tos].flags & aStack[nos].flags & STK_Int)==STK_Int ){
int a, b;
a = aStack[tos].i;
@ -2346,6 +2365,10 @@ case OP_NotNull: {
** 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.
**
** (Later:) The P2==1 option was intended to make NULLs distinct
** for the UNION operator. But I have since discovered that NULLs
** are indistinct for UNION. So this option is never used.
*/
case OP_MakeRecord: {
char *zNewRecord;