Index: src/parse.y ================================================================== --- src/parse.y +++ src/parse.y @@ -989,10 +989,12 @@ } } // expr1 IS expr2 // expr1 IS NOT expr2 +// expr1 IS DISTINCT FROM expr2 +// expr1 IS NOT DISTINCT FROM expr2 // // If expr2 is NULL then code as TK_ISNULL or TK_NOTNULL. If expr2 // is any other expression, code as TK_IS or TK_ISNOT. // expr(A) ::= expr(X) IS expr(Y). { @@ -1001,10 +1003,18 @@ } expr(A) ::= expr(X) IS NOT expr(Y). { spanBinaryExpr(&A,pParse,TK_ISNOT,&X,&Y); binaryToUnaryIfNull(pParse, Y.pExpr, A.pExpr, TK_NOTNULL); } +expr(A) ::= expr(X) IS DISTINCT FROM expr(Y). { + spanBinaryExpr(&A,pParse,TK_ISNOT,&X,&Y); + binaryToUnaryIfNull(pParse, Y.pExpr, A.pExpr, TK_NOTNULL); +} +expr(A) ::= expr(X) IS NOT DISTINCT FROM expr(Y). { + spanBinaryExpr(&A,pParse,TK_IS,&X,&Y); + binaryToUnaryIfNull(pParse, Y.pExpr, A.pExpr, TK_ISNULL); +} %include { /* Construct an expression node for a unary prefix operator */ static void spanUnaryPrefix( ADDED test/momjian1.test Index: test/momjian1.test ================================================================== --- /dev/null +++ test/momjian1.test @@ -0,0 +1,262 @@ +# 2015-06-12 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# Test cases derived from Bruce Momjian's talk on NULL handling in SQL. +# http://momjian.us/main/writings/pgsql/nulls.pdf +# +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +db null nil +do_execsql_test momjian1-100 { + SELECT null; +} {nil} + +do_execsql_test momjian1-110 { + CREATE TABLE nulltest (x INTEGER, y INTEGER); + INSERT INTO nulltest VALUES (1,NULL); + SELECT * FROM nulltest; +} {1 nil} + +do_execsql_test momjian1-120 { + INSERT INTO nulltest (x) VALUES (2); + SELECT * FROM nulltest; +} {1 nil 2 nil} + +do_catchsql_test momjian1-130 { + CREATE TABLE nulltest2 (x INTEGER NOT NULL, y INTEGER NOT NULL); + INSERT INTO nulltest2 VALUES (3,NULL ) +} {1 {NOT NULL constraint failed: nulltest2.y}} +do_catchsql_test momjian1-131 { + INSERT INTO nulltest2 (x) VALUES (4); +} {1 {NOT NULL constraint failed: nulltest2.y}} + +do_execsql_test momjian1-140 { + SELECT NULL+1; +} {nil} +do_execsql_test momjian1-141 { + SELECT NULL || 'a'; +} {nil} +do_execsql_test momjian1-142 { + SELECT 'b' || NULL; +} {nil} + +do_execsql_test momjian1-150 { + CREATE TABLE inctest (x INTEGER); + INSERT INTO inctest VALUES (30), (40),(NULL); + SELECT x + 1 FROM inctest; +} {31 41 nil} + +do_execsql_test momjian1-160 { + SELECT NULL = 1; +} {nil} +do_execsql_test momjian1-161 { + SELECT NULL = ''; +} {nil} +do_execsql_test momjian1-162 { + SELECT NULL = NULL; +} {nil} +do_execsql_test momjian1-163 { + SELECT NULL < NULL + 1; +} {nil} + +do_execsql_test momjian1-170 { + SELECT 1 WHERE 1; +} {1} +do_execsql_test momjian1-171 { + SELECT 1 WHERE 0; +} {} +do_execsql_test momjian1-172 { + SELECT 1 WHERE NULL; +} {} + +do_execsql_test momjian1-180 { + SELECT 1 AND NULL; +} {nil} +do_execsql_test momjian1-181 { + SELECT NOT NULL; +} {nil} + +do_execsql_test momjian1-190 { + SELECT * FROM inctest WHERE x >= 0; +} {30 40} +do_execsql_test momjian1-191 { + SELECT * FROM inctest WHERE x < 0; +} {} +do_execsql_test momjian1-192 { + SELECT * FROM inctest WHERE x < 0 OR x >= 0; +} {30 40} + +do_execsql_test momjian1-200 { + SELECT * FROM inctest WHERE x <> 10; +} {30 40} +do_execsql_test momjian1-201 { + SELECT * FROM inctest WHERE x <> 10 OR x = 10; +} {30 40} + +do_execsql_test momjian1-210 { + SELECT 1 <> 2 AND 1 <> 3; +} {1} +do_execsql_test momjian1-211 { + SELECT 1 <> 2 AND 1 <> 3 AND 1 <> NULL; +} {nil} + +do_execsql_test momjian1-220 { + SELECT 'a' IN (SELECT NULL); +} {nil} +do_execsql_test momjian1-221 { + SELECT 'a' NOT IN (SELECT NULL); +} {nil} + +do_execsql_test momjian1-230 { + SELECT 'a' IN (SELECT 'a' UNION ALL SELECT NULL); +} {1} +do_execsql_test momjian1-231 { + SELECT 'a' NOT IN (SELECT 'a' UNION ALL SELECT NULL); +} {0} +do_execsql_test momjian1-232 { + SELECT 'a' IN (SELECT 'b' UNION ALL SELECT NULL); +} {nil} +do_execsql_test momjian1-233 { + SELECT 'a' NOT IN (SELECT 'b' UNION ALL SELECT NULL); +} {nil} + +do_execsql_test momjian1-240 { + SELECT 'a' = 'b' OR 'a' = NULL; +} {nil} +do_execsql_test momjian1-241 { + SELECT 'a' <> 'b' AND 'a' <> NULL; +} {nil} + +do_execsql_test momjian1-250 { + SELECT NULL = NULL; +} {nil} +do_execsql_test momjian1-251 { + SELECT NULL IS NULL; +} {1} +do_execsql_test momjian1-252 { + SELECT NULL IS NOT NULL; +} {0} + +do_execsql_test momjian1-260 { + SELECT * FROM inctest WHERE x IS NULL; +} {nil} +do_execsql_test momjian1-261 { + SELECT * FROM inctest WHERE x IS NOT NULL; +} {30 40} + +do_execsql_test momjian1-270 { + SELECT 2 IS DISTINCT FROM 1; + SELECT 2 IS NOT 1 +} {1 1} +do_execsql_test momjian1-271 { + SELECT NULL IS DISTINCT FROM 1; + SELECT NULL IS NOT 1; +} {1 1} +do_execsql_test momjian1-272 { + SELECT NULL IS DISTINCT FROM NULL; + SELECT NULL IS NOT NULL; +} {0 0} +do_execsql_test momjian1-273 { + SELECT NULL <> 1; +} {nil} + +do_execsql_test momjian1-280 { + -- SELECT * FROM inctest WHERE x IS DISTINCT FROM 30; + SELECT * FROM inctest WHERE x IS NOT 30; +} {40 nil} +do_execsql_test momjian1-281 { + -- SELECT * FROM inctest WHERE x IS NOT DISTINCT FROM 30; + SELECT * FROM inctest WHERE x IS 30; +} {30} + +do_execsql_test momjian1-300 { + CREATE TABLE disttest (x INTEGER, y INTEGER); + INSERT INTO disttest VALUES (1, 1), (2, 3), (NULL, NULL); + SELECT * FROM disttest where x IS NOT DISTINCT FROM y; + SELECT * FROM disttest WHERE x IS y; +} {1 1 nil nil 1 1 nil nil} + +do_execsql_test momjian1-310 { + WITH ordertest AS ( + SELECT NULL + UNION ALL + SELECT 2 + UNION ALL + SELECT 1 + UNION ALL + SELECT NULL + ) + SELECT * FROM ordertest + ORDER BY 1; +} {nil nil 1 2} + +do_execsql_test momjian1-320 { + CREATE TABLE uniqtest (x INTEGER); + CREATE UNIQUE INDEX i_uniqtest ON uniqtest (x); + INSERT INTO uniqtest VALUES (1),(NULL), (NULL); + SELECT * FROM uniqtest; +} {1 nil nil} + +do_execsql_test momjian1-330 { + CREATE TABLE aggtest (x INTEGER); + INSERT INTO aggtest VALUES (7), (8), (NULL); + SELECT COUNT(*), COUNT(x), SUM(x), MIN(x), MAX(x), AVG(x) FROM aggtest; +} {3 2 15 7 8 7.5} +do_execsql_test momjian1-331 { + DELETE FROM aggtest; + SELECT COUNT(*), COUNT(x), SUM(x), MIN(x), MAX(x), AVG(x) FROM aggtest; +} {0 0 nil nil nil nil} + +do_execsql_test momjian1-340 { + DELETE FROM aggtest; + INSERT INTO aggtest VALUES (7), (8), (NULL), (NULL); + SELECT x, COUNT(*), COUNT(x), SUM(x), MIN(x), MAX(x), AVG(x) + FROM aggtest + GROUP BY x ORDER BY x; +} {nil 2 0 nil nil nil nil 7 1 1 7 7 7 7.0 8 1 1 8 8 8 8.0} + +do_execsql_test momjian1-350 { + SELECT COALESCE(NULL, 0); +} {0} +do_execsql_test momjian1-351 { + SELECT COALESCE(NULL, 'I am null.'); +} {{I am null.}} + +do_execsql_test momjian1-352 { + CREATE TABLE nullmaptest(x TEXT); + INSERT INTO nullmaptest VALUES ('f'), ('g'), (NULL); + SELECT x, COALESCE(x, 'n/a') FROM nullmaptest; +} {f f g g nil n/a} +do_execsql_test momjian1-353 { + SELECT 'a' || COALESCE(NULL, '') || 'b'; +} {ab} +do_execsql_test momjian1-354 { + SELECT SUM(x), COALESCE(SUM(x), 0) FROM aggtest; +} {15 15} +do_execsql_test momjian1-355 { + DELETE FROM aggtest; + SELECT SUM(x), COALESCE(SUM(x), 0) FROM aggtest; +} {nil 0} + +do_execsql_test momjian1-360 { + CREATE TABLE emptytest (x INTEGER); + SELECT * from emptytest; +} {} +do_execsql_test momjian1-361 { + SELECT (SELECT * from emptytest); +} {nil} +do_execsql_test momjian1-362 { + SELECT (SELECT * from emptytest) IS NULL; +} {1} + + +finish_test