distinct binary operator

This commit is contained in:
jackfoxy 2023-02-16 11:49:29 -08:00
parent 99ae6f8366
commit e27bef98e6
3 changed files with 54 additions and 31 deletions

View File

@ -22,7 +22,8 @@
| { <qualified-column> | { <qualified-column>
| <constant> } | <constant> }
| <scalar-name> | <scalar-name>
| <aggregate-name>( { <column> | <scalar-name> } ) | <scalar-query>
| <aggregate-function>( { <column> | <scalar-name> } )
} [ [ AS ] <column-alias> ] } [ [ AS ] <column-alias> ]
} [ ,...n ] } [ ,...n ]
[ ORDER BY [ { <qualified-column> | <column-alias> | <column-ordinal> } [ ORDER BY [ { <qualified-column> | <column-alias> | <column-ordinal> }
@ -84,7 +85,12 @@ Set operators `UNION`, etc. apply the previous result set to the next query resu
| expression <inequality operator> { ALL | ANY} ( <single-column-query> ) | expression <inequality operator> { ALL | ANY} ( <single-column-query> )
| [ NOT ] EXISTS { <column value> | <single-column-query> } } | [ NOT ] EXISTS { <column value> | <single-column-query> } }
``` ```
`DISTINCT FROM` is like equals `=` except comparing two `NOT EXISTS` yields false. Since nullable table columns are not allowed, `NOT EXISTS` can only yield `true` on the column of an outer join that is not in a returned row or a `<scalar-query>` that returns nothing. `NULL` is a marker for this case.
`IS [ NOT ] DISTINCT FROM` is a binary operator like [ NOT ] equals `<>`, `=` except comparing two `NOT EXISTS` yields false.
`A IS DISTINCT FROM B` decodes to: `((A <> B OR A IS NULL OR B IS NULL) AND NOT (A IS NULL AND B IS NULL))`
`A IS NOT DISTINCT FROM B` decodes to: `(NOT (A <> B OR A IS NULL OR B IS NULL) OR (A IS NULL AND B IS NULL))`
`<single-column-query>` is defined in a CTE and must return only one column. `<single-column-query>` is defined in a CTE and must return only one column.
``` ```
@ -98,6 +104,7 @@ Set operators `UNION`, etc. apply the previous result set to the next query resu
END END
| COALESCE ( <expression> [ ,...n ] ) | COALESCE ( <expression> [ ,...n ] )
| BEGIN <arithmetic on expressions and scalar functions> END | BEGIN <arithmetic on expressions and scalar functions> END
| <predicate>
| *hoon (TBD) | *hoon (TBD)
``` ```
If a `CASE` expression uses `<predicate>`, the expected boolean (or loobean) logic applies. If a `CASE` expression uses `<predicate>`, the expected boolean (or loobean) logic applies.
@ -110,11 +117,16 @@ If it uses `<expression>` `@`0 is treated as false and any other value as true (
{ <column> { <column>
| <scalar-function> | <scalar-function>
| <scalar-query> | <scalar-query>
| <aggregate-name>( { <column> | <scalar-name> } ) | <aggregate-function>( { <column> | <scalar-name> } )
} }
``` ```
`<scalar-query>` is defined in a CTE and must return only one column. The first returned value is accepted and subsequent values ignored. `<scalar-query>` is defined in a CTE and must return only one column. The first returned value is accepted and subsequent values ignored.
```
<aggregate-function> ::=
{ AVG | MAX | MIN | SUM | COUNT | AND | OR | <user-defined> }
```
``` ```
<column> ::= <column> ::=
{ [ <qualified-column> { [ <qualified-column>

View File

@ -561,9 +561,8 @@
(cold %lt (just '<')) (cold %lt (just '<'))
(cold %and ;~(plug (jester 'and') whitespace)) (cold %and ;~(plug (jester 'and') whitespace))
(cold %or ;~(plug (jester 'or') whitespace)) (cold %or ;~(plug (jester 'or') whitespace))
:: to do %distinct %not-distinct: (cold %distinct ;~(plug (jester 'is') whitespace (jester 'distinct') whitespace (jester 'from') whitespace))
:: (cold %distinct ;~(plug (jester 'is') whitespace (jester 'distinct') whitespace (jester 'from'))) (cold %not-distinct ;~(plug (jester 'is') whitespace (jester 'not') whitespace (jester 'distinct') whitespace (jester 'from') whitespace))
:: (cold %not-distinct ;~(plug (jester 'is') whitespace (jester 'not') whitespace (jester 'distinct') whitespace (jester 'from')))
:: ternary operator :: ternary operator
(cold %between ;~(plug (jester 'between') whitespace)) (cold %between ;~(plug (jester 'between') whitespace))
:: nesting :: nesting

View File

@ -1156,9 +1156,21 @@
%+ expect-eq %+ expect-eq
!> ~[expected] !> ~[expected]
!> (parse:parse(current-database 'db1') query) !> (parse:parse(current-database 'db1') query)
++ test-predicate-10
=/ query "FROM foo WHERE foobar IS DISTINCT FROM bar SELECT DISTINCT *"
=/ pred=(tree predicate-component:ast) [%distinct foobar bar]
%+ expect-eq
!> ~[[%simple-query from-foo [%scalars ~] `pred [%group-by ~] [%having ~] [%select top=~ bottom=~ distinct=%.y columns=~[all-columns]] ~]]
!> (parse:parse(current-database 'db1') query)
++ test-predicate-11
=/ query "FROM foo WHERE foobar IS NOT DISTINCT FROM bar SELECT *"
=/ pred=(tree predicate-component:ast) [%not-distinct foobar bar]
%+ expect-eq
!> ~[[%simple-query from-foo [%scalars ~] `pred [%group-by ~] [%having ~] [%select top=~ bottom=~ distinct=%.n columns=~[all-columns]] ~]]
!> (parse:parse(current-database 'db1') query)
:: ::
:: remaining simple predicates, varying spacing and keywork casing :: remaining simple predicates, varying spacing and keywork casing
++ test-predicate-10 ++ test-predicate-12
=/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ". =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
" WHERE foobar Not Between foo And bar ". " WHERE foobar Not Between foo And bar ".
" SELECT *" " SELECT *"
@ -1169,7 +1181,7 @@
%+ expect-eq %+ expect-eq
!> ~[expected] !> ~[expected]
!> (parse:parse(current-database 'db1') query) !> (parse:parse(current-database 'db1') query)
++ test-predicate-11 ++ test-predicate-13
=/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ". =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
" WHERE foobar Not Between foo bar ". " WHERE foobar Not Between foo bar ".
" SELECT *" " SELECT *"
@ -1180,7 +1192,7 @@
%+ expect-eq %+ expect-eq
!> ~[expected] !> ~[expected]
!> (parse:parse(current-database 'db1') query) !> (parse:parse(current-database 'db1') query)
++ test-predicate-12 ++ test-predicate-14
=/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ". =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
" WHERE foobar Between foo And bar ". " WHERE foobar Between foo And bar ".
" SELECT *" " SELECT *"
@ -1191,7 +1203,7 @@
%+ expect-eq %+ expect-eq
!> ~[expected] !> ~[expected]
!> (parse:parse(current-database 'db1') query) !> (parse:parse(current-database 'db1') query)
++ test-predicate-13 ++ test-predicate-15
=/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ". =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
" WHERE foobar between foo And bar ". " WHERE foobar between foo And bar ".
" SELECT *" " SELECT *"
@ -1202,7 +1214,7 @@
%+ expect-eq %+ expect-eq
!> ~[expected] !> ~[expected]
!> (parse:parse(current-database 'db1') query) !> (parse:parse(current-database 'db1') query)
++ test-predicate-14 ++ test-predicate-16
=/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ". =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
" WHERE T1.foo>=aLl bar ". " WHERE T1.foo>=aLl bar ".
" SELECT *" " SELECT *"
@ -1213,7 +1225,7 @@
%+ expect-eq %+ expect-eq
!> ~[expected] !> ~[expected]
!> (parse:parse(current-database 'db1') query) !> (parse:parse(current-database 'db1') query)
++ test-predicate-15 ++ test-predicate-17
=/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ". =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
" WHERE T1.foo nOt In bar ". " WHERE T1.foo nOt In bar ".
" SELECT *" " SELECT *"
@ -1224,7 +1236,7 @@
%+ expect-eq %+ expect-eq
!> ~[expected] !> ~[expected]
!> (parse:parse(current-database 'db1') query) !> (parse:parse(current-database 'db1') query)
++ test-predicate-16 ++ test-predicate-18
=/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ". =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
" WHERE T1.foo not in (1,2,3) ". " WHERE T1.foo not in (1,2,3) ".
" SELECT *" " SELECT *"
@ -1235,7 +1247,7 @@
%+ expect-eq %+ expect-eq
!> ~[expected] !> ~[expected]
!> (parse:parse(current-database 'db1') query) !> (parse:parse(current-database 'db1') query)
++ test-predicate-17 ++ test-predicate-19
=/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ". =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
" WHERE T1.foo in bar ". " WHERE T1.foo in bar ".
" SELECT *" " SELECT *"
@ -1246,7 +1258,7 @@
%+ expect-eq %+ expect-eq
!> ~[expected] !> ~[expected]
!> (parse:parse(current-database 'db1') query) !> (parse:parse(current-database 'db1') query)
++ test-predicate-18 ++ test-predicate-20
=/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ". =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
" WHERE T1.foo in (1,2,3) ". " WHERE T1.foo in (1,2,3) ".
" SELECT *" " SELECT *"
@ -1257,7 +1269,7 @@
%+ expect-eq %+ expect-eq
!> ~[expected] !> ~[expected]
!> (parse:parse(current-database 'db1') query) !> (parse:parse(current-database 'db1') query)
++ test-predicate-19 ++ test-predicate-21
=/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ". =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
" WHERE NOT EXISTS T1.foo ". " WHERE NOT EXISTS T1.foo ".
" SELECT *" " SELECT *"
@ -1268,7 +1280,7 @@
%+ expect-eq %+ expect-eq
!> ~[expected] !> ~[expected]
!> (parse:parse(current-database 'db1') query) !> (parse:parse(current-database 'db1') query)
++ test-predicate-20 ++ test-predicate-22
=/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ". =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
" WHERE NOT exists foo ". " WHERE NOT exists foo ".
" SELECT *" " SELECT *"
@ -1279,7 +1291,7 @@
%+ expect-eq %+ expect-eq
!> ~[expected] !> ~[expected]
!> (parse:parse(current-database 'db1') query) !> (parse:parse(current-database 'db1') query)
++ test-predicate-21 ++ test-predicate-23
=/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ". =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
" WHERE EXISTS T1.foo ". " WHERE EXISTS T1.foo ".
" SELECT *" " SELECT *"
@ -1290,7 +1302,7 @@
%+ expect-eq %+ expect-eq
!> ~[expected] !> ~[expected]
!> (parse:parse(current-database 'db1') query) !> (parse:parse(current-database 'db1') query)
++ test-predicate-22 ++ test-predicate-24
=/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ". =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
" WHERE EXISTS foo ". " WHERE EXISTS foo ".
" SELECT *" " SELECT *"
@ -1303,7 +1315,7 @@
!> (parse:parse(current-database 'db1') query) !> (parse:parse(current-database 'db1') query)
:: ::
:: test conjunctions, varying spacing and keyword casing :: test conjunctions, varying spacing and keyword casing
++ test-predicate-23 ++ test-predicate-25
=/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ". =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
" WHERE foobar >=foo And foobar<=bar ". " WHERE foobar >=foo And foobar<=bar ".
" SELECT *" " SELECT *"
@ -1316,7 +1328,7 @@
!> (parse:parse(current-database 'db1') query) !> (parse:parse(current-database 'db1') query)
:: expected/actual match :: expected/actual match
::++ test-predicate-24 ::++ test-predicate-26
:: =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ". :: =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
:: " WHERE foobar >=foo And foobar<=bar ". :: " WHERE foobar >=foo And foobar<=bar ".
:: " and T1.foo2 = ~zod ". :: " and T1.foo2 = ~zod ".
@ -1330,7 +1342,7 @@
:: !> (parse:parse(current-database 'db1') query) :: !> (parse:parse(current-database 'db1') query)
:: expected/actual match :: expected/actual match
::++ test-predicate-25 ::++ test-predicate-27
:: =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ". :: =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
:: " WHERE foobar >=foo And foobar<=bar ". :: " WHERE foobar >=foo And foobar<=bar ".
:: " and T1.foo2 = ~zod ". :: " and T1.foo2 = ~zod ".
@ -1345,7 +1357,7 @@
:: !> (parse:parse(current-database 'db1') query) :: !> (parse:parse(current-database 'db1') query)
:: expected/actual match :: expected/actual match
::++ test-predicate-26 ::++ test-predicate-28
:: =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ". :: =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
:: " WHERE foobar >=foo And foobar<=bar ". :: " WHERE foobar >=foo And foobar<=bar ".
:: " and T1.foo2 = ~zod ". :: " and T1.foo2 = ~zod ".
@ -1362,7 +1374,7 @@
:: !> (parse:parse(current-database 'db1') query) :: !> (parse:parse(current-database 'db1') query)
:: expected/actual match :: expected/actual match
::++ test-predicate-27 ::++ test-predicate-29
:: =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ". :: =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
:: " WHERE foobar >=foo And foobar<=bar ". :: " WHERE foobar >=foo And foobar<=bar ".
:: " and T1.foo2 = ~zod ". :: " and T1.foo2 = ~zod ".
@ -1384,7 +1396,7 @@
:: ::
:: simple nesting :: simple nesting
:: expected/actual match :: expected/actual match
::++ test-predicate-28 ::++ test-predicate-30
:: =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ". :: =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
:: " WHERE (foobar > foo OR foobar < bar) ". :: " WHERE (foobar > foo OR foobar < bar) ".
:: " AND T1.foo>foo2 ". :: " AND T1.foo>foo2 ".
@ -1402,7 +1414,7 @@
:: ::
:: nesting :: nesting
:: expected/actual match :: expected/actual match
::++ test-predicate-29 ::++ test-predicate-31
:: =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ". :: =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
:: " WHERE foobar > foo AND foobar < bar ". :: " WHERE foobar > foo AND foobar < bar ".
:: " AND ( T1.foo>foo2 AND T2.bar IN (1,2,3) ". :: " AND ( T1.foo>foo2 AND T2.bar IN (1,2,3) ".
@ -1424,7 +1436,7 @@
:: ::
:: simple nesting, superfluous () around entire predicate :: simple nesting, superfluous () around entire predicate
:: expected/actual match :: expected/actual match
::++ test-predicate-30 ::++ test-predicate-32
:: =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ". :: =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
:: " WHERE ((foobar > foo OR foobar < bar) ". :: " WHERE ((foobar > foo OR foobar < bar) ".
:: " AND T1.foo>foo2 ". :: " AND T1.foo>foo2 ".
@ -1441,7 +1453,7 @@
:: ::
:: aggregate inequality :: aggregate inequality
++ test-predicate-31 ++ test-predicate-33
=/ select "from foo where count( foobar ) > 10 select * " =/ select "from foo where count( foobar ) > 10 select * "
=/ pred=(tree predicate-component:ast) [%gt [aggregate-count-foobar ~ ~] literal-10] =/ pred=(tree predicate-component:ast) [%gt [aggregate-count-foobar ~ ~] literal-10]
%+ expect-eq %+ expect-eq
@ -1449,7 +1461,7 @@
!> (parse:parse(current-database 'db1') select) !> (parse:parse(current-database 'db1') select)
:: ::
:: aggregate inequality, no whitespace :: aggregate inequality, no whitespace
++ test-predicate-32 ++ test-predicate-34
=/ select "from foo where count(foobar) > 10 select *" =/ select "from foo where count(foobar) > 10 select *"
=/ pred=(tree predicate-component:ast) [%gt [aggregate-count-foobar ~ ~] literal-10] =/ pred=(tree predicate-component:ast) [%gt [aggregate-count-foobar ~ ~] literal-10]
%+ expect-eq %+ expect-eq
@ -1457,7 +1469,7 @@
!> (parse:parse(current-database 'db1') select) !> (parse:parse(current-database 'db1') select)
:: ::
:: aggregate equality :: aggregate equality
++ test-predicate-33 ++ test-predicate-35
=/ select "from foo where bar = count(foobar) select *" =/ select "from foo where bar = count(foobar) select *"
=/ pred=(tree predicate-component:ast) [%eq bar [aggregate-count-foobar ~ ~]] =/ pred=(tree predicate-component:ast) [%eq bar [aggregate-count-foobar ~ ~]]
%+ expect-eq %+ expect-eq
@ -1466,7 +1478,7 @@
:: ::
:: complext predicate, bug test :: complext predicate, bug test
:: expected/actual match :: expected/actual match
::++ test-predicate-34 ::++ test-predicate-36
:: =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ". :: =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
:: " WHERE A1.adoption-email = A2.adoption-email ". :: " WHERE A1.adoption-email = A2.adoption-email ".
:: " AND A1.adoption-date = A2.adoption-date ". :: " AND A1.adoption-date = A2.adoption-date ".