clean-up and a few more predicate test cases

This commit is contained in:
jackfoxy 2023-01-11 13:09:20 -08:00
parent f04d177ab4
commit 959efa30f5
6 changed files with 498 additions and 602 deletions

View File

@ -9,6 +9,7 @@ The relational data model is a fundamental component of the computing stack that
3. Proprietary closed-source RDBMS implementations.
4. Trendy _no sql_ alternatives.
5. Re-inventing the wheel for reasons.
6. The prevalence of artificial keys in real world SQL implementations.
Some of these rearsons are irrational, others are just wrong.
@ -17,14 +18,16 @@ Some of these rearsons are irrational, others are just wrong.
3. Urbit fixes this.
4. Most programmers will never face a situation where an RDBMS is inadequate or inferior for the task. _Key-value Store_ is a very simple relational database. The SQL standard was hastily developed and has some unnecesary baggage which makes it hard to master. Cyclic graphs such as social graphs are difficult to model and work with in SQL. This can be addressed in a blank-slate Urbit implementation.
5. New and junior programmers with little or no SQL exposure mistakenly think they can write better/faster IO by hand, whereas experienced engineers know to use SQL first for all the functionality wherein it can be used (except sorting, which is not strictly part of the relational model).
6. Explaining the case for using natural keys on tables over artificial keys is beyond the scope of this document. See for instance <here>. Suffice it to say almost all sample databases for learning SQL incorporate artificial keys, which is counter-productive for learning. And so most SQL database implementations also make this mistake. Artificial keys make the database schema brittle and hard for humans to comprehend.
An Urbit native RDBMS implementation opens new opportunities for composability. All of a ship's data is transparently available for _mash up_ apps and _ad hoc_ queries.
An Urbit RDBMS deserves a _first principles_ approach to design and implementation. The _urQL_ language is heavily influenced by _The Third Manefesto_ (Date and Darwen), emphasizing composability and type safety. Areas where SQL was too hastily designed and/or developed without regard to theory (like nullable columns) have been eliminated, making urQl much more like the _ur Query Language_ Codd and Date would have been proud of. Excellent integration with the entire Urbit stack including through traditional SQL extensions like _Stored Procedures_ and _Triggers_ is to be expected.
An Urbit native RDBMS implementation opens new opportunities for composability. All of a ship's data is transparently available for _mash up_ apps and _ad hoc_ queries.
An Urbit RDBMS deserves a _first principles_ approach to design and implementation. The _urQL_ language is heavily influenced by _The Third Manefesto_ (Date and Darwen), emphasizing composability and type safety. Areas where SQL was too hastily designed and/or developed without regard to theory (like nullable columns) have been eliminated, making urQl much more like the _ur Query Language_ Codd and Date would have been proud of, integrating with the entire Urbit stack through traditional SQL extensions like _Stored Procedures_ and _Triggers_.
## Functionality
The Urbit RDBMS (still to be named) consists of
The Urbit RDBMS (still to be named) consists of
1. A scripting language and parser (this document)
2. A plan builder
@ -40,7 +43,7 @@ Table definitions do not allow for nullable columns.
All user-defined names follow the hoon term naming standard.
All except the simplest functions are collected in their own section and aliased inline into select clause and predicates.
All except the simplest functions are collected in their own section and aliased inline into SELECT clause and predicates.
Emphasizes composability and improves readability.
There are no subqueries.
@ -51,6 +54,9 @@ Emphasizes composability and improves readability.
Reading and/or updating data on foreign ships is allowed provided the ship's pilot has granted permission. Cross database joins are allowed, but not cross ship joins.
Views cannot be defined on foreign databases.
This document has placeholders for Stored Procedures and Triggers, which have yet to be defined.
Pivoting and Windowing will be in a future release.
## urQL language diagrams
[ ] indicate optional entries.

View File

@ -61,11 +61,12 @@ Set operators apply the previous result set to the next query unless otherwise q
| expression [ NOT ] BETWEEN expression [ AND ] expression
| expression IS [ NOT ] DISTINCT FROM expression
| expression [ NOT ] IN
{ <cte one column query> | ( <value> ,...n ) }
| expression <inequality operator> { ALL | ANY} ( <cte one column query> )
| [ NOT ] EXISTS { <column value> | <cte one column query> } }
{ <single-column-query> | ( <value> ,...n ) }
| expression <inequality operator> { ALL | ANY} ( <single-column-query> )
| [ NOT ] EXISTS { <column value> | <single-column-query> } }
```
`DISTINCT FROM` is like equals `=` except comparing two `NOT EXISTS` yields false.
`<single-column-query>` is defined in a CTE and must return only one column.
```
<scalar-function> ::=
@ -89,10 +90,11 @@ If it uses `<expression>` `@`0 is treated as false and any other value as true (
<expression> ::=
{ <column>
| <scalar-function>
| <scalar-query>
| <scalar-query>
| <aggregate-name>( { <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.
```
<column> ::=
@ -105,7 +107,6 @@ If it uses `<expression>` `@`0 is treated as false and any other value as true (
<binary-operator> ::=
{ = | <> | != | > | >= | !> | < | <= | !< }
```
Whitespace is not required between operands and binary-operators, except when the left operand is a numeric literal, in which case whitespace is required.
```

View File

@ -55,45 +55,9 @@
is-clustered=?
columns=(list ordered-column:ast)
==
::+$ expression ?(qualified-column:ast value-literal:ast value-literal-list:ast aggregate:ast) :: fish-loop
+$ expression ?(qualified-column:ast value-literal:ast value-literal-list:ast) :: aggregate:ast)
+$ parens ?(%pal %par)
+$ raw-predicate-component ?(parens predicate-component:ast predicate:ast)
+$ raw-predicate-component2 ?(%pal %par ternary-operator:ast binary-operator:ast unary-operator:ast conjunction:ast qualified-column:ast value-literal:ast value-literal-list:ast)
+$ list6
$:
%list6
l1=raw-predicate-component
l2=raw-predicate-component
l3=raw-predicate-component
l4=raw-predicate-component
l5=raw-predicate-component
l6=raw-predicate-component
==
+$ list5
$:
%list5
l1=raw-predicate-component
l2=raw-predicate-component
l3=raw-predicate-component
l4=raw-predicate-component
l5=raw-predicate-component
==
+$ list4
$:
%list4
l1=raw-predicate-component
l2=raw-predicate-component
l3=raw-predicate-component
l4=raw-predicate-component
==
+$ try-fail %fail
+$ try-success
$:
%try-success
result=raw-predicate-component
==
+$ try-result $%(try-success try-fail)
+$ raw-predicate-component2 ?(parens predicate-component:ast)
::
:: get next position in script
::
@ -525,7 +489,6 @@
(query-object:ast %query-object -.parsed `+.parsed)
~|("cannot parse query-object {<parsed>}" !!)
++ parse-cross-joined-object ~+ ;~(plug parse-cross-join-type parse-query-object)
::(cook cook-joined-object ;~(plug parse-cross-join-type parse-query-object))
++ parse-joined-object ~+ ;~ plug
parse-join-type
parse-query-object
@ -533,24 +496,9 @@
::;~(pfix whitespace ;~(pfix (jester 'on') ;~(less predicate-stop prn)))
::(easy ~)
==
++ build-joined-object parse-joined-object
::(cook cook-joined-object parse-joined-object)
::++ cook-joined-object
:: |= parsed=*
:: ~+
:: ~| "-.parsed: {<-.parsed>}"
:: ~| "+<.parsed: {<+<.parsed>}"
:: ~| "+>.parsed: {<+>.parsed>}"
:: ^- joined-object:ast
:: ?> ?=(join-type:ast -.parsed)
:: ?: ?&(?=([@ @ [@ @ @ @ @] @ @] parsed) ?=(query-object:ast +.parsed))
:: (joined-object:ast %joined-object -.parsed +.parsed ~)
:: ?: ?&(?=([@ @ [@ @ @ @ @] @] parsed) ?=(query-object:ast +.parsed))
:: (joined-object:ast %joined-object -.parsed +.parsed ~)
:: (joined-object:ast %joined-object -.parsed +<.parsed (produce-predicate (predicate-list +>.parsed)))
++ parse-object-and-joins ~+ ;~ plug
parse-query-object
;~(pose parse-cross-joined-object (star build-joined-object))
;~(pose parse-cross-joined-object (star parse-joined-object))
==
::
:: column in "join on" or "where" predicate, qualified or aliased
@ -611,135 +559,39 @@
(cold %pal pal)
(cold %par par)
==
++ resolve-between-operator
|= [operator=ternary-operator:ast c1=expression c2=expression c3=expression]
~+
^- (tree predicate-component:ast)
=/ left `(tree predicate-component:ast)`[%gte `(tree predicate-component:ast)`[c1 ~ ~] `(tree predicate-component:ast)`[c2 ~ ~]]
=/ right `(tree predicate-component:ast)`[%lte `(tree predicate-component:ast)`[c1 ~ ~] `(tree predicate-component:ast)`[c3 ~ ~]]
`(tree predicate-component:ast)`[operator left right]
++ resolve-not-between-operator
|= [operator=ternary-operator:ast c1=expression c2=expression c3=expression]
~+
^- (tree predicate-component:ast)
=/ left `(tree predicate-component:ast)`[%gte `(tree predicate-component:ast)`[c1 ~ ~] `(tree predicate-component:ast)`[c2 ~ ~]]
=/ right `(tree predicate-component:ast)`[%lte `(tree predicate-component:ast)`[c1 ~ ~] `(tree predicate-component:ast)`[c3 ~ ~]]
`(tree predicate-component:ast)`[%not `(tree predicate-component:ast)`[operator left right] ~]
++ resolve-binary-operator
|= [operator=binary-operator:ast c1=expression c2=expression]
~+
^- (tree predicate-component:ast)
`(tree predicate-component:ast)`[operator `(tree predicate-component:ast)`[`predicate-component:ast`c1 ~ ~] `(tree predicate-component:ast)`[`predicate-component:ast`c2 ~ ~]]
++ resolve-all-any
|= b=[l1=expression l2=inequality-operator:ast l3=all-any-operator:ast l4=expression]
~+
^- (tree predicate-component:ast)
=/ all-any `(tree predicate-component:ast)`[l3.b `(tree predicate-component:ast)`[l4.b ~ ~] ~]
`(tree predicate-component:ast)`[l2.b `(tree predicate-component:ast)`[l1.b ~ ~] all-any]
++ try-not-between-and
|= b=list6
~+
^- try-result
?. ?&(?=(expression l1.b) ?=(%not l2.b) ?=(ternary-operator:ast l3.b) ?=(expression l4.b) ?=(%and l5.b) ?=(expression l6.b))
`try-result`%fail
(try-success %try-success (resolve-not-between-operator [l3.b l1.b l4.b l6.b]))
++ try-5
|= b=list5
~+
^- try-result
?: ?&(?=(expression l1.b) ?=(%not l2.b) ?=(ternary-operator:ast l3.b) ?=(expression l4.b) ?=(expression l5.b))
(try-success %try-success (resolve-not-between-operator [l3.b l1.b l4.b l5.b]))
?: ?&(?=(expression l1.b) ?=(ternary-operator:ast l2.b) ?=(expression l3.b) ?=(%and l4.b) ?=(expression l5.b))
(try-success %try-success (resolve-between-operator [l2.b l1.b l3.b l5.b]))
`try-result`%fail
++ try-4
|= b=list4
~+
^- try-result
:: expression between expression expression
?: ?&(?=(expression l1.b) ?=(ternary-operator:ast l2.b) ?=(expression l3.b) ?=(expression l4.b))
(try-success %try-success (resolve-between-operator [l2.b l1.b l3.b l4.b]))
:: expression inequality all/any cte-one-column-query
?: ?&(?=(expression l1.b) ?=(inequality-operator:ast l2.b) ?=(all-any-operator:ast l3.b) ?=(expression l4.b))
(try-success %try-success (resolve-all-any [l1.b l2.b l3.b l4.b]))
:: expression not in query or value list
?: ?&(?=(expression l1.b) ?=(%not l2.b) ?=(%in l3.b) ?=(expression l4.b))
(try-success %try-success `(tree predicate-component:ast)`[%not `(tree predicate-component:ast)`[%in [l1.b ~ ~] [l4.b ~ ~]] ~])
`try-result`%fail
++ resolve-operators
::
:: resolve non-unary (and some unary) operators into trees
|= a=(list raw-predicate-component)
~+
^- (list raw-predicate-component)
=/ resolved=(list raw-predicate-component) ~
=+ result=`try-result`%fail
=+ result2=`try-result`%fail
=+ result3=`try-result`%fail
++ predicate-list
|= a=*
^- (list raw-predicate-component2)
=/ new-list=(list raw-predicate-component2) ~
|-
?: =(a ~) (flop resolved)
::
:: expression not between expression and expression
=. result ?: (gte (lent a) 6)
(try-not-between-and (list6 %list6 -.a +<.a +>-.a +>+<.a +>+>-.a +>+>+<.a))
`try-result`%fail
?. ?=(try-fail result) $(a +>+>+>.a, resolved [result.result resolved])
::
:: expression not between expression expression
:: expression between expression and expression
=. result2 ?: (gte (lent a) 5)
(try-5 (list5 %list5 -.a +<.a +>-.a +>+<.a +>+>-.a))
`try-result`%fail
?. ?=(try-fail result2) $(a +>+>+.a, resolved [result.result2 resolved])
::
:: expression between expression expression
:: expression inequality all/any cte-one-column-query
:: expression not in query or value list
=. result3 ?: (gte (lent a) 4)
(try-4 (list4 %list4 -.a +<.a +>-.a +>+<.a))
`try-result`%fail
?. ?=(try-fail result3) $(a +>+>.a, resolved [result.result3 resolved])
::
:: expression binary operator expression
?: ?&((gte (lent a) 3) ?=(expression -.a) ?=(binary-operator:ast +<.a) ?=(expression +>-.a))
$(a +>+.a, resolved [(resolve-binary-operator [+<.a -.a +>-.a]) resolved])
::
:: not exists column or cte-one-column-query
?: ?&((gte (lent a) 3) ?=(%not -.a) ?=(%exists +<.a) ?=(expression +>-.a))
$(a +>+.a, resolved [`(tree predicate-component:ast)`[%not `(tree predicate-component:ast)`[%exists [`(tree predicate-component:ast)`[+>-.a ~ ~]] ~] ~] resolved])
::
:: exists column or cte-one-column-query
?: ?&((gte (lent a) 2) ?=(%exists -.a) ?=(expression +<.a))
$(a +>.a, resolved [`(tree predicate-component:ast)`[%exists [`(tree predicate-component:ast)`[+<.a ~ ~]] ~] resolved])
$(a +.a, resolved [-.a resolved])
++ resolve-depth
::
:: determine deepest parenthesis nesting, eliminating superfluous nesting
|= a=(list raw-predicate-component)
~+
^- [@ud (list raw-predicate-component)]
=/ resolved=(list raw-predicate-component) ~
=/ depth 0
=/ working-depth 0
|-
?: =(a ~) [depth (flop resolved)]
?: =(-.a %pal)
?: ?&((gte (lent +.a) 2) =(+>-.a %par)) :: single parenthesised entity
$(a +>+.a, resolved [+<.a resolved])
?. (gth (add working-depth 1) depth) $(working-depth (add working-depth 1), a +.a, resolved [-.a resolved])
%= $
depth (add depth 1)
working-depth (add working-depth 1)
a +.a
resolved [-.a resolved]
==
?. =(-.a %par) $(a +.a, resolved [-.a resolved])
%= $
working-depth (sub working-depth 1)
a +.a
resolved [-.a resolved]
?: =(a ~) (flop new-list)
?: ?=(parens -.a) $(new-list [i=`parens`-.a t=new-list], a +.a)
?: ?=(ops-and-conjs:ast -.a) $(new-list [i=`ops-and-conjs:ast`-.a t=new-list], a +.a)
?: ?=(qualified-column:ast -.a) $(new-list [i=`qualified-column:ast`-.a t=new-list], a +.a)
?: ?=(value-literal:ast -.a) $(new-list [i=`value-literal:ast`-.a t=new-list], a +.a)
?: ?=(value-literal-list:ast -.a) $(new-list [i=`value-literal-list:ast`-.a t=new-list], a +.a)
:: ?: ?=(aggregate:ast -.a) $(new-list [i=`aggregate:ast`-.a t=new-list], a +.a) :: to do
~|("problem with predicate noun: {<a>}" !!)
++ predicate-stop ~+ ;~ pose
;~(plug whitespace mic)
mic
;~(plug whitespace (jester 'where'))
;~(plug whitespace (jester 'select'))
;~(plug whitespace (jester 'as'))
;~(plug whitespace (jester 'join'))
;~(plug whitespace (jester 'left'))
;~(plug whitespace (jester 'right'))
;~(plug whitespace (jester 'outer'))
;~(plug whitespace (jester 'then'))
==
++ resolve-conjunctions
++ predicate-part ~+ ;~ pose
:: parse-aggregate
value-literal-list
;~(pose ;~(pfix whitespace parse-operator) parse-operator)
parse-datum
==
++ parse-predicate
(star ;~(less predicate-stop predicate-part))
::
:: when not qualified by () right conjunction takes precedence and "or" takes precedence over "and"
::
@ -789,206 +641,24 @@
:: /\
:: 1=2 3=3
::
|= a=[target-depth=@ud components=(list raw-predicate-component) predicates=(list predicate:ast)]
^- [(list raw-predicate-component) (list predicate:ast)]
::~& "(lent components.a): {<(lent components.a)>}"
::?: =((lent components.a) 1) [~ `(list predicate:ast)`[-.components.a ~]]
=/ resolved=(list raw-predicate-component) ~
=/ working-depth 0
=/ working-tree=predicate:ast ~
=/ resolved-trees=(list predicate:ast) ~
|-
?: =(components.a ~) [(flop resolved) (flop resolved-trees)]
?: ?&(=(-.components.a %pal) !=(+>-.components.a %par))
%= $
components.a +.components.a
resolved [-.components.a resolved]
working-depth (add working-depth 1)
==
?. =(working-depth target-depth.a)
$(components.a +.components.a, resolved [-.components.a resolved])
|-
::
:: if there are superfluous levels of nesting we will end up here
:: to do: test if this is still working/required
?: =(components.a ~) ^$(resolved [working-tree resolved])
::
:: if () enclosed tree is first thing, then it is always the left subtree
:: ~& "(lent components.a): {<(lent components.a)>}"
?: =(-.components.a %pal)
?: =(+>-.components.a %par)
:: stand-alone tree
?: =((lent components.a) 3)
%= ^$
components.a ~ :: end of comonents +>+.components.a
resolved [+<.components.a resolved]
:: working-tree -.resolved-trees (not necessary, we are at end)
resolved-trees +.resolved-trees
==
?: ?&((gth (lent resolved) 1) =(-.resolved %pal))
::$(components.a +>+.components.a, working-tree +<.components.a)
%= ^$
components.a +>+.components.a
working-tree -.predicates.a
predicates.a +.predicates.a
==
?: =((lent components.a) 4)
::$(components.a ~, working-tree +<.components.a)
%= ^$
components.a ~
working-tree -.predicates.a
predicates.a +.predicates.a
==
::$(components.a +>+>+.components.a, working-tree [+>+<.components.a +<.components.a +>+>-.components.a])
::%= $
:: components.a +>+>+.components.a
:: working-tree [+>+<.components.a +<.components.a +>+>-.components.a]
::==
!!
::$(components.a +>.components.a, working-tree [+>-.components.a +<.components.a +>+<.components.a])
!!
?: =(-.components.a %par) :: time to close out the nested tree
?: =(working-depth 0)
%= ^$
components.a +.components.a
resolved [%par [working-tree resolved]]
resolved-trees [working-tree resolved-trees]
working-tree ~
==
%= ^$
components.a +.components.a
resolved [%par [working-tree resolved]]
working-depth (sub working-depth 1)
resolved-trees [working-tree resolved-trees]
working-tree ~
==
::
:: below this point we deal with components only
?@ -.components.a
?: =(-.components.a %or) :: "or" the whole tree
?: =(%pal +<.components.a) :: new right is () enclosed tree
%= ^$
components.a +>+>.components.a
working-tree (next-working-tree [%or working-tree +>-.components.a])
==
%= $
components.a +>.components.a
working-tree (next-working-tree [%or working-tree +<.components.a])
==
:: working tree is an "or" and we are given an "and"; "and" the right tree
?: ?&(!=(working-tree ~) =(-.working-tree %or))
?: =(%pal +<.components.a) :: new right is () enclosed tree
%= ^$
components.a +>+.components.a
working-tree
(next-working-tree [%or +<.working-tree (next-working-tree [%and +>.working-tree +>-.components.a])])
==
%= ^$
components.a +>.components.a
working-tree
(next-working-tree [%or +<.working-tree (next-working-tree [%and +>.working-tree +<.components.a])])
==
:: working tree is an "and" and we are given an "and"
:: "and" the whole tree
:: new right is () enclosed tree
?: =(%pal +<.components.a)
%= ^$
components.a +>+>.components.a
working-tree (next-working-tree [%and working-tree +>-.components.a])
==
%= ^$
components.a +>.components.a
working-tree (next-working-tree [%and working-tree +<.components.a])
::working-tree
:: (next-working-tree [working-tree `(list raw-predicate-component)`+<.components.a predicates.a])
==
::
::
::~|('betting for now this never happens' !!)
:: can only be tree on first time
^$(components.a +.components.a, working-tree -.predicates.a)
++ next-working-tree
|= a=[conjunction=conjunction:ast working-tree=predicate:ast component=raw-predicate-component]
~| "working-tree: {<working-tree.a>}"
~| "component: {<component.a>}"
^- predicate:ast
?+ component.a ~|("next component unexpected type: {<component.a>}" !!)
qualified-column:ast [conjunction.a working-tree.a [`predicate-component:ast`component.a ~ ~]]
value-literal:ast [conjunction.a working-tree.a [`predicate-component:ast`component.a ~ ~]]
value-literal-list:ast [conjunction.a working-tree.a [`predicate-component:ast`component.a ~ ~]]
==
++ predicate-list
|= a=*
^- (list raw-predicate-component2)
=/ new-list=(list raw-predicate-component2) ~
|-
?: =(a ~) (flop new-list)
?: ?=(parens -.a) $(new-list [i=`parens`-.a t=new-list], a +.a)
?: ?=(ops-and-conjs:ast -.a) $(new-list [i=`ops-and-conjs:ast`-.a t=new-list], a +.a)
?: ?=(qualified-column:ast -.a) $(new-list [i=`qualified-column:ast`-.a t=new-list], a +.a)
?: ?=(value-literal:ast -.a) $(new-list [i=`value-literal:ast`-.a t=new-list], a +.a)
?: ?=(value-literal-list:ast -.a) $(new-list [i=`value-literal-list:ast`-.a t=new-list], a +.a)
:: ?: ?=(aggregate:ast -.a) $(new-list [i=`aggregate:ast`-.a t=new-list], a +.a) :: to do
~|("problem with predicate noun: {<a>}" !!)
++ predicate-stop ~+ ;~ pose
;~(plug whitespace mic)
mic
;~(plug whitespace (jester 'where'))
;~(plug whitespace (jester 'select'))
;~(plug whitespace (jester 'as'))
;~(plug whitespace (jester 'join'))
;~(plug whitespace (jester 'left'))
;~(plug whitespace (jester 'right'))
;~(plug whitespace (jester 'outer'))
;~(plug whitespace (jester 'then'))
==
++ predicate-part ~+ ;~ pose
:: parse-aggregate
value-literal-list
;~(pose ;~(pfix whitespace parse-operator) parse-operator)
parse-datum
==
++ parse-predicate
(star ;~(less predicate-stop predicate-part))
++ produce-predicate
::
:: 1. resolve operators into trees
:: 2. determine deepest parenthesis nesting
:: 3. work from deepest nesting up to resolve conjunctions into trees
|= a=(list raw-predicate-component)
~& "raw-predicate2: {<a>}"
^- predicate:ast
=/ b=[@ud (list raw-predicate-component)] (resolve-depth (resolve-operators a))
=/ target-depth=@ud -.b
=/ working-list=(list raw-predicate-component) +.b
=/ parm=[(list raw-predicate-component) (list predicate:ast)] [working-list ~]
|-
?. (gth target-depth 0)
:: ~| "target-depth: {<target-depth>}"
:: ~| "-.parm: {<-.parm>}"
:: ~| "+.parm: {<+.parm>}"
::`predicate:ast`(snag 0 `(list predicate:ast)`+:(resolve-conjunctions [target-depth `(list raw-predicate-component)`-.parm `(list predicate:ast)`+.parm]))
::(snag 0 +:(resolve-conjunctions [target-depth -.parm +.parm]))
+<:(resolve-conjunctions [target-depth -.parm +.parm])
%= $
target-depth (sub target-depth 1)
parm (resolve-conjunctions [target-depth -.parm +.parm])
==
++ predicate-state-machine
|= parsed=(list raw-predicate-component2)
^- predicate:ast
=/ working-tree=predicate:ast ~
=/ tree-stack=(list predicate:ast) ~
~| "predicate-state-machine parsed: {<parsed>}"
~| "produce-predicate parsed: {<parsed>}"
|-
?: =((lent parsed) 0) working-tree
~| "-.parsed: {<-.parsed>}"
?: =((lent parsed) 0)
|-
?~ tree-stack
working-tree
%= $
working-tree
?~ ->-.tree-stack [-<.tree-stack working-tree ~]
[-<.tree-stack ->-.tree-stack working-tree]
tree-stack +.tree-stack
==
?- -.parsed
%pal :: push working predicate onto the stack
%= $
@ -1005,25 +675,58 @@
parsed +.parsed
==
unary-operator:ast
?~ working-tree !!
?~ l.working-tree !!
?~ r.working-tree !!
!!
?~ working-tree
%= $
working-tree [-.parsed ~ ~]
parsed +.parsed
==
?~ l.working-tree
?: ?&(=(%not -.working-tree) =(%exists -.parsed))
?> ?=(qualified-column:ast +<.parsed)
%= $
working-tree [-.working-tree [-.parsed [+<.parsed ~ ~] ~] ~]
parsed +>.parsed
==
~|("invalid compbination of unary operators {<-.working-tree>} and {<-.parsed>}" !!)
?~ r.working-tree ~|("unary-operator, right tree empty" !!)
~|("unary-operator can't get here" !!)
binary-operator:ast
?~ working-tree !!
?~ l.working-tree !!
?~ r.working-tree !!
!!
?~ l.working-tree ~|("binary-operator, left tree empty" !!)
?~ r.working-tree ~|("binary-operator, right tree empty" !!)
~|("binary-operator can't get here {<working-tree>}" !!)
:: %= $
:: working-tree [-.parsed working-tree ~]
:: parsed +.parsed
:: ==
ternary-operator:ast
?~ working-tree !!
?~ l.working-tree !!
?~ r.working-tree !!
!!
?~ l.working-tree ~|("ternary-operator, left tree empty" !!)
?~ r.working-tree ~|("ternary-operator, right tree empty" !!)
~|("ternary-operator can't get here" !!)
conjunction:ast
?~ working-tree !!
?~ l.working-tree !!
?~ r.working-tree !!
!!
?~ working-tree
%= $
working-tree [-.parsed ~ ~]
parsed +.parsed
==
?~ l.working-tree ~|("conjunction, left tree empty" !!)
?~ r.working-tree ~|("conjunction, right tree empty" !!)
%= $
working-tree [-.parsed working-tree ~]
parsed +.parsed
==
all-any-operator:ast
?~ working-tree ~|("operator {<-.parsed>} can only follow equality or inequality operator" !!)
?~ r.working-tree
?: ?&(?=(binary-operator:ast n.working-tree) ?!(=(%in n.working-tree)))
?> ?=(qualified-column:ast +<.parsed) :: to do: this must resolve to a CTE
%= $
working-tree [-.working-tree +<.working-tree [-.parsed [+<.parsed ~ ~] ~]]
parsed +>.parsed
==
~|("operator {<-.parsed>} can only follow equality or inequality operator" !!)
~|("all-any-operator can't get here" !!)
qualified-column:ast
?~ working-tree
?: ?=(binary-operator:ast +<.parsed)
@ -1031,17 +734,36 @@
working-tree [+<.parsed [-.parsed ~ ~] ~]
parsed +>.parsed
==
?: ?&(=(%not +<.parsed) =(%between +>-.parsed))
?: =(%and +>+>-.parsed)
?: ?=(unary-operator:ast +<.parsed)
?: ?&(=(%not +<.parsed) =(%between +>-.parsed))
?: =(%and +>+>-.parsed)
%= $
working-tree
[%not [%between (produce-predicate ~[-.parsed %gte +>+<.parsed]) (produce-predicate ~[-.parsed %lte +>+>+<.parsed])] ~]
parsed +>+>+>.parsed
==
%= $
working-tree
[%not [%between (predicate-state-machine ~[-.parsed %gte +>+<.parsed]) (predicate-state-machine ~[-.parsed %lte +>+>+<.parsed])] ~]
parsed +>+>+>.parsed
[%not [%between (produce-predicate ~[-.parsed %gte +>+<.parsed]) (produce-predicate ~[-.parsed %lte +>+>-.parsed])] ~]
parsed +>+>+.parsed
==
?: =(%in +>-.parsed)
%= $
working-tree [%not (produce-predicate ~[-.parsed %in +>+<.parsed]) ~]
parsed +>+>.parsed
==
!!
?: =(%between +<.parsed)
?: =(%and +>+<.parsed)
%= $
working-tree
[%between (produce-predicate ~[-.parsed %gte +>-.parsed]) (produce-predicate ~[-.parsed %lte +>+>-.parsed])]
parsed +>+>+.parsed
==
%= $
working-tree
[%not [%between (predicate-state-machine ~[-.parsed %gte +>+<.parsed]) (predicate-state-machine ~[-.parsed %lte +>+>-.parsed])] ~]
parsed +>+>+.parsed
[%between (produce-predicate ~[-.parsed %gte +>-.parsed]) (produce-predicate ~[-.parsed %lte +>+<.parsed])]
parsed +>+>.parsed
==
!!
?~ l.working-tree
@ -1050,21 +772,80 @@
parsed +.parsed
==
?~ r.working-tree
?: ?=(conjunction:ast -.working-tree)
%= $
working-tree ~
tree-stack [working-tree tree-stack]
==
%= $
working-tree [-.working-tree +<.working-tree [-.parsed ~ ~]]
parsed +.parsed
==
~|("can't get here" !!)
~|("qualified-column can't get here" !!)
value-literal:ast
?~ working-tree !!
?~ l.working-tree !!
?~ r.working-tree !!
!!
?~ working-tree
?: ?=(binary-operator:ast +<.parsed)
%= $
working-tree [+<.parsed [-.parsed ~ ~] ~]
parsed +>.parsed
==
?: ?=(unary-operator:ast +<.parsed)
?: ?&(=(%not +<.parsed) =(%between +>-.parsed))
?: =(%and +>+>-.parsed)
%= $
working-tree
[%not [%between (produce-predicate ~[-.parsed %gte +>+<.parsed]) (produce-predicate ~[-.parsed %lte +>+>+<.parsed])] ~]
parsed +>+>+>.parsed
==
%= $
working-tree
[%not [%between (produce-predicate ~[-.parsed %gte +>+<.parsed]) (produce-predicate ~[-.parsed %lte +>+>-.parsed])] ~]
parsed +>+>+.parsed
==
?: =(%in +>-.parsed)
%= $
working-tree [%not (produce-predicate ~[-.parsed %in +>+<.parsed]) ~]
parsed +>+>.parsed
==
!!
?: =(%between +<.parsed)
?: =(%and +>+<.parsed)
%= $
working-tree
[%between (produce-predicate ~[-.parsed %gte +>-.parsed]) (produce-predicate ~[-.parsed %lte +>+>-.parsed])]
parsed +>+>+.parsed
==
%= $
working-tree
[%between (produce-predicate ~[-.parsed %gte +>-.parsed]) (produce-predicate ~[-.parsed %lte +>+<.parsed])]
parsed +>+>.parsed
==
!!
?~ l.working-tree
%= $
working-tree [-.working-tree [-.parsed ~ ~] ~]
parsed +.parsed
==
?~ r.working-tree
?: ?=(conjunction:ast -.working-tree)
%= $
working-tree ~
tree-stack [working-tree tree-stack]
==
%= $
working-tree [-.working-tree +<.working-tree [-.parsed ~ ~]]
parsed +.parsed
==
~|("value-literal can't get here" !!)
value-literal-list:ast
?~ working-tree !!
?~ working-tree ~|("Literal list in a predicate can only follow the IN operator" !!)
?~ l.working-tree !!
?~ r.working-tree !!
!!
?~ r.working-tree
%= $
working-tree [-.working-tree +<.working-tree [-.parsed ~ ~]]
parsed +.parsed
==
~|("value-literal-list can't get here" !!)
==
::
:: parse scalar
@ -1280,25 +1061,6 @@
(cold %order-by ;~(plug whitespace (jester 'order') whitespace (jester 'by')))
(more com parse-ordering-column)
==
::@@@@@@@@@@@@@@@@@@@@@@
::++ produce-joins
:: |= a=* ::(list *)
:: =/ joins=(list joined-object:ast) ~
:: ^- (list joined-object:ast)
:: |-
:: ?: =(a ~) (flop joins)
:: ?: ?=(joined-object:ast -.a) $(joins [-.a joins], a +.a)
::(crash "cannot produce join from {<-.a>}")
:: !!
::++ produce-from
:: |= a=* ::(list *)
:: ^- from:ast
:: ?: =(%query-object -<.a) ::?&(=(%query-object -<.a) (gth (lent a) 0))
:: ?: =(+.a ~) (from:ast %from -.a ~)
:: (from:ast -.a (produce-joins +.a))
::(crash "cannot produce query-object from {<-.a>}")
:: !!
::@@@@@@@@@@@@@@@@@@@@@@
++ produce-from
|= a=*
^- from:ast
@ -1326,7 +1088,7 @@
raw-joined-objects +.raw-joined-objects
==
=/ joined=joined-object:ast
(joined-object:ast %joined-object -<.raw-joined-objects ->-.raw-joined-objects `(predicate-state-machine (predicate-list ->+.raw-joined-objects)))
(joined-object:ast %joined-object -<.raw-joined-objects ->-.raw-joined-objects `(produce-predicate (predicate-list ->+.raw-joined-objects)))
%= $
joined-objects [joined joined-objects]
raw-joined-objects +.raw-joined-objects
@ -1394,7 +1156,7 @@
?: =(i.a %end-command) (build-simple-query [from scalars predicate (need select) group-by having order-by])
::?: =(i.a %scalars) $(a t.a, scalars +.i.a)
?: =(-<.a %scalars) $(a t.a, scalars ~)
?: =(-<.a %where) $(a t.a, predicate `(predicate-state-machine (predicate-list +.i.a)))
?: =(-<.a %where) $(a t.a, predicate `(produce-predicate (predicate-list +.i.a)))
?: =(-<.a %select) $(a t.a, select `(produce-select +.i.a))
?: =(-<.a %group-by) $(a t.a, group-by ~)
?: =(-<.a %having) $(a t.a, having ~)

View File

@ -74,11 +74,10 @@
+$ ternary-operator %between
+$ inequality-operator ?(%neq %gt %gte %lt %lte)
+$ all-any-operator ?(%all %any)
+$ binary-operator ?(%eq inequality-operator %distinct %not-distinct %in all-any-operator)
+$ binary-operator ?(%eq inequality-operator %distinct %not-distinct %in)
+$ unary-operator ?(%not %exists)
+$ conjunction ?(%and %or)
+$ ops-and-conjs ?(ternary-operator binary-operator unary-operator conjunction)
::+$ predicate-component ?(ternary-operator binary-operator unary-operator conjunction qualified-column value-literal value-literal-list) :: aggregate)
+$ ops-and-conjs ?(ternary-operator binary-operator unary-operator all-any-operator conjunction)
+$ predicate-component ?(ops-and-conjs qualified-column value-literal value-literal-list) :: aggregate)
+$ predicate (tree predicate-component)
+$ datum $%(qualified-column value-literal)

View File

@ -975,159 +975,6 @@
++ king-and [%and [second-and] last-or]
++ test-predicate-12
%+ expect-eq
!> [%between foobar-gte-foo foobar-lte-bar]
!> (wonk (parse-predicate:parse [[1 1] " foobar Between foo And bar"]))
++ test-predicate-13
%+ expect-eq
!> [%between foobar-gte-foo foobar-lte-bar]
!> (wonk (parse-predicate:parse [[1 1] "foobar Between foo bar"]))
++ test-predicate-14
%+ expect-eq
!> [%gte t1-foo [%all bar ~]]
!> (wonk (parse-predicate:parse [[1 1] "T1.foo>=aLl bar"]))
++ test-predicate-15
%+ expect-eq
!> [%not [%in t1-foo bar] ~]
!> (wonk (parse-predicate:parse [[1 1] "T1.foo nOt In bar"]))
++ test-predicate-16
%+ expect-eq
!> [%not [%in t1-foo value-literal-list] ~]
!> (wonk (parse-predicate:parse [[1 1] "T1.foo not in (1,2,3)"]))
++ test-predicate-17
%+ expect-eq
!> [%in t1-foo bar]
!> (wonk (parse-predicate:parse [[1 1] "T1.foo in bar"]))
++ test-predicate-18
%+ expect-eq
!> [%in t1-foo value-literal-list]
!> (wonk (parse-predicate:parse [[1 1] "T1.foo in (1,2,3)"]))
++ test-predicate-19
%+ expect-eq
!> [%not [%exists t1-foo ~] ~]
!> (wonk (parse-predicate:parse [[1 1] "NOT EXISTS T1.foo"]))
++ test-predicate-20
%+ expect-eq
!> [%not [%exists foo ~] ~]
!> (wonk (parse-predicate:parse [[1 1] "NOT exists foo"]))
++ test-predicate-21
%+ expect-eq
!> [%exists t1-foo ~]
!> (wonk (parse-predicate:parse [[1 1] "EXISTS T1.foo"]))
++ test-predicate-22
%+ expect-eq
!> [%exists foo ~]
!> (wonk (parse-predicate:parse [[1 1] "EXISTS foo"]))
::
:: test conjunctions, varying spacing and keyword casing
++ test-predicate-23
%+ expect-eq
!> and-fb-gte-f--fb-lte-b
!> (wonk (parse-predicate:parse [[1 1] "foobar >=foo And foobar<=bar"]))
++ test-predicate-24
=/ predicate "foobar >=foo And foobar<=bar ".
" and T1.foo2 = ~zod"
%+ expect-eq
!> and-and
!> (wonk (parse-predicate:parse [[1 1] predicate]))
++ test-predicate-25
=/ predicate "foobar >=foo And foobar<=bar ".
" and T1.foo2 = ~zod ".
" or T2.bar in (1,2,3)"
%+ expect-eq
!> and-and-or
!> (wonk (parse-predicate:parse [[1 1] predicate]))
++ test-predicate-26
=/ predicate "foobar >=foo And foobar<=bar ".
" and T1.foo2 = ~zod ".
" or ".
" foobar>=foo ".
" AND T1.foo2=~zod"
%+ expect-eq
!> and-and-or-and
!> (wonk (parse-predicate:parse [[1 1] predicate]))
++ test-predicate-27
=/ predicate "foobar >=foo And foobar<=bar ".
" and T1.foo2 = ~zod ".
" or ".
" foobar>=foo ".
" AND T1.foo2=~zod ".
" OR ".
" foo = 1 ".
" AND T1.foo3 < any (1,2,3)"
%+ expect-eq
!> and-and-or-and-or-and
!> (wonk (parse-predicate:parse [[1 1] predicate]))
::
:: simple nesting
++ test-predicate-28
=/ predicate "(foobar > foo OR foobar < bar) ".
" AND T1.foo>foo2 ".
" AND T2.bar IN (1,2,3) ".
" AND (T1.foo3< any (1,2,3) OR T1.foo2=~zod AND foo=1 ) "
%+ expect-eq
!> king-and
!> (wonk (parse-predicate:parse [[1 1] predicate]))
::
:: nesting
++ test-predicate-29
=/ predicate "foobar > foo AND foobar < bar ".
" AND ( T1.foo>foo2 AND T2.bar IN (1,2,3) ".
" OR (T1.foo3< any (1,2,3) AND T1.foo2=~zod AND foo=1 ) ".
" OR (foo3=foo4 AND foo5=foo6) ".
" OR foo4=foo5 ".
" ) ".
" AND foo6=foo7"
%+ expect-eq
!> a-a-l-a-o-l-a-a-r-o-r-a-l-o-r-a
!> (wonk (parse-predicate:parse [[1 1] predicate]))
::
:: simple nesting, superfluous () around entire predicate
++ test-predicate-30
=/ predicate "((foobar > foo OR foobar < bar) ".
" AND T1.foo>foo2 ".
" AND T2.bar IN (1,2,3) ".
" AND (T1.foo3< any (1,2,3) OR T1.foo2=~zod AND foo=1 )) "
%+ expect-eq
!> king-and
!> (wonk (parse-predicate:parse [[1 1] predicate]))
::
:: aggregate inequality
++ test-predicate-31
=/ predicate " count( foo ) > 10 "
%+ expect-eq
!> [%gt [aggregate-count-foo 0 0] literal-10]
!> (wonk (parse-predicate:parse [[1 1] predicate]))
::
:: aggregate inequality, no whitespace
++ test-predicate-32
=/ predicate "count(foo) > 10"
%+ expect-eq
!> [%gt [aggregate-count-foo 0 0] literal-10]
!> (wonk (parse-predicate:parse [[1 1] predicate]))
::
:: aggregate equality
++ test-predicate-33
=/ predicate "bar = count(foo)"
%+ expect-eq
!> [%eq bar [aggregate-count-foo 0 0]]
!> (wonk (parse-predicate:parse [[1 1] predicate]))
::
:: complext predicate, bug test
++ test-predicate-34
=/ predicate " A1.adoption-email = A2.adoption-email ".
" AND A1.adoption-date = A2.adoption-date ".
" AND foo = bar ".
" AND ((A1.name = A2.name AND A1.species > A2.species) ".
" OR ".
" (A1.name > A2.name AND A1.species = A2.species) ".
" OR ".
" (A1.name > A2.name AND A1.species > A2.species) ".
" ) "
%+ expect-eq
!> [%and [%and [%and [%eq a1-adoption-email a2-adoption-email] [%eq a1-adoption-date a2-adoption-date]] [%eq foo bar]] [%or [%or [%and [%eq a1-name a2-name] [%gt a1-species a2-species]] [%and [%gt a1-name a2-name] [%eq a1-species a2-species]]] [%and [%gt a1-name a2-name] [%gt a1-species a2-species]]]]
!> (wonk (parse-predicate:parse [[1 1] predicate]))
::
:: scalar
::

View File

@ -5,9 +5,9 @@
::
:: re-used components
++ foo
`(tree predicate-component:ast)`[[%qualified-column [%qualified-object ~ 'UNKNOWN' 'COLUMN-OR-CTE' 'foo'] 'foo' ~] ~ ~]
++ t1-foo
`(tree predicate-component:ast)`[[%qualified-column [%qualified-object ~ 'UNKNOWN' 'COLUMN' 'T1'] 'foo' ~] ~ ~]
[[%qualified-column [%qualified-object ~ 'UNKNOWN' 'COLUMN-OR-CTE' 'foo'] 'foo' ~] ~ ~]
++ t1-foo
[[%qualified-column [%qualified-object ~ 'UNKNOWN' 'COLUMN' 'T1'] 'foo' ~] ~ ~]
++ foo2 [[%qualified-column [%qualified-object ~ 'UNKNOWN' 'COLUMN-OR-CTE' 'foo2'] 'foo2' ~] ~ ~]
++ t1-foo2 [[%qualified-column [%qualified-object ~ 'UNKNOWN' 'COLUMN' 'T1'] 'foo2' ~] ~ ~]
@ -18,9 +18,9 @@
++ foo6 [[%qualified-column [%qualified-object ~ 'UNKNOWN' 'COLUMN-OR-CTE' 'foo6'] 'foo6' ~] ~ ~]
++ foo7 [[%qualified-column [%qualified-object ~ 'UNKNOWN' 'COLUMN-OR-CTE' 'foo7'] 'foo7' ~] ~ ~]
++ bar
`(tree predicate-component:ast)`[[%qualified-column [%qualified-object ~ 'UNKNOWN' 'COLUMN-OR-CTE' 'bar'] 'bar' ~] ~ ~]
++ t2-bar
`(tree predicate-component:ast)`[[%qualified-column [%qualified-object ~ 'UNKNOWN' 'COLUMN' 'T2'] 'bar' ~] ~ ~]
[[%qualified-column [%qualified-object ~ 'UNKNOWN' 'COLUMN-OR-CTE' 'bar'] 'bar' ~] ~ ~]
++ t2-bar
[[%qualified-column [%qualified-object ~ 'UNKNOWN' 'COLUMN' 'T2'] 'bar' ~] ~ ~]
++ foobar [[%qualified-column [%qualified-object ~ 'UNKNOWN' 'COLUMN-OR-CTE' 'foobar'] 'foobar' ~] ~ ~]
@ -40,8 +40,8 @@
++ literal-10 [[%ud 10] 0 0]
::
:: re-used simple predicates
++ foobar-gte-foo `(tree predicate-component:ast)`[%gte foobar foo]
++ foobar-lte-bar `(tree predicate-component:ast)`[%lte foobar bar]
++ foobar-gte-foo [%gte foobar foo]
++ foobar-lte-bar [%lte foobar bar]
++ foo-eq-1 [%eq foo [[%ud 1] ~ ~]]
++ t1-foo-gt-foo2 [%gt t1-foo foo2]
++ t2-bar-in-list [%in t2-bar value-literal-list]
@ -64,7 +64,7 @@
++ or3 [%and [%eq foo3 foo4] [%eq foo5 foo6]]
++ big-or [%or [%or [%or and-t1f-gt-f2--t2b-in-l or2] or3] [%eq foo4 foo5]]
++ big-and [%and and-fb-gt-f--fb-lt-b big-or]
++ a-a-l-a-o-l-a-a-r-o-r-a-l-o-r-a
++ a-a-l-a-o-l-a-a-r-o-r-a-l-o-r-a
[%and big-and [%eq foo6 foo7]]
++ first-or [%or [%gt foobar foo] [%lt foobar bar]]
++ last-or [%or t1-foo3-lt-any-list [%and t1-foo2-eq-zod foo-eq-1]]
@ -140,25 +140,306 @@
:: remaining simple predicates, varying spacing and keywork casing
++ test-predicate-10
=/ 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 *"
=/ joinpred=(tree predicate-component:ast) [%eq t1-foo t2-bar]
=/ pred=(tree predicate-component:ast) [%not [%between foobar-gte-foo foobar-lte-bar] ~]
=/ expected=simple-query:ast
=/ expected=simple-query:ast
[%simple-query [~ [%priori [~ [%from object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T1']] joins=~[[%joined-object join=%join object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T2']] predicate=`joinpred]]]] ~ `pred]] [%select top=~ bottom=~ distinct=%.n columns=~[%all]] ~]
%+ expect-eq
!> ~[expected]
!> (parse:parse(current-database 'db1') query)
++ test-predicate-11
=/ 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 *"
=/ joinpred=(tree predicate-component:ast) [%eq t1-foo t2-bar]
=/ pred=(tree predicate-component:ast) [%not [%between foobar-gte-foo foobar-lte-bar] ~]
=/ expected=simple-query:ast
=/ expected=simple-query:ast
[%simple-query [~ [%priori [~ [%from object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T1']] joins=~[[%joined-object join=%join object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T2']] predicate=`joinpred]]]] ~ `pred]] [%select top=~ bottom=~ distinct=%.n columns=~[%all]] ~]
%+ expect-eq
!> ~[expected]
!> (parse:parse(current-database 'db1') query)
++ test-predicate-12
=/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
" WHERE foobar Between foo And bar ".
" SELECT *"
=/ joinpred=(tree predicate-component:ast) [%eq t1-foo t2-bar]
=/ pred=(tree predicate-component:ast) [%between foobar-gte-foo foobar-lte-bar]
=/ expected=simple-query:ast
[%simple-query [~ [%priori [~ [%from object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T1']] joins=~[[%joined-object join=%join object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T2']] predicate=`joinpred]]]] ~ `pred]] [%select top=~ bottom=~ distinct=%.n columns=~[%all]] ~]
%+ expect-eq
!> ~[expected]
!> (parse:parse(current-database 'db1') query)
++ test-predicate-13
=/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
" WHERE foobar between foo And bar ".
" SELECT *"
=/ joinpred=(tree predicate-component:ast) [%eq t1-foo t2-bar]
=/ pred=(tree predicate-component:ast) [%between foobar-gte-foo foobar-lte-bar]
=/ expected=simple-query:ast
[%simple-query [~ [%priori [~ [%from object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T1']] joins=~[[%joined-object join=%join object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T2']] predicate=`joinpred]]]] ~ `pred]] [%select top=~ bottom=~ distinct=%.n columns=~[%all]] ~]
%+ expect-eq
!> ~[expected]
!> (parse:parse(current-database 'db1') query)
++ test-predicate-14
=/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
" WHERE T1.foo>=aLl bar ".
" SELECT *"
=/ joinpred=(tree predicate-component:ast) [%eq t1-foo t2-bar]
=/ pred=(tree predicate-component:ast) [%gte t1-foo [%all bar ~]]
=/ expected=simple-query:ast
[%simple-query [~ [%priori [~ [%from object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T1']] joins=~[[%joined-object join=%join object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T2']] predicate=`joinpred]]]] ~ `pred]] [%select top=~ bottom=~ distinct=%.n columns=~[%all]] ~]
%+ expect-eq
!> ~[expected]
!> (parse:parse(current-database 'db1') query)
++ test-predicate-15
=/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
" WHERE T1.foo nOt In bar ".
" SELECT *"
=/ joinpred=(tree predicate-component:ast) [%eq t1-foo t2-bar]
=/ pred=(tree predicate-component:ast) [%not [%in t1-foo bar] ~]
=/ expected=simple-query:ast
[%simple-query [~ [%priori [~ [%from object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T1']] joins=~[[%joined-object join=%join object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T2']] predicate=`joinpred]]]] ~ `pred]] [%select top=~ bottom=~ distinct=%.n columns=~[%all]] ~]
%+ expect-eq
!> ~[expected]
!> (parse:parse(current-database 'db1') query)
++ test-predicate-16
=/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
" WHERE T1.foo not in (1,2,3) ".
" SELECT *"
=/ joinpred=(tree predicate-component:ast) [%eq t1-foo t2-bar]
=/ pred=(tree predicate-component:ast) [%not [%in t1-foo value-literal-list] ~]
=/ expected=simple-query:ast
[%simple-query [~ [%priori [~ [%from object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T1']] joins=~[[%joined-object join=%join object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T2']] predicate=`joinpred]]]] ~ `pred]] [%select top=~ bottom=~ distinct=%.n columns=~[%all]] ~]
%+ expect-eq
!> ~[expected]
!> (parse:parse(current-database 'db1') query)
++ test-predicate-17
=/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
" WHERE T1.foo in bar ".
" SELECT *"
=/ joinpred=(tree predicate-component:ast) [%eq t1-foo t2-bar]
=/ pred=(tree predicate-component:ast) [%in t1-foo bar]
=/ expected=simple-query:ast
[%simple-query [~ [%priori [~ [%from object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T1']] joins=~[[%joined-object join=%join object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T2']] predicate=`joinpred]]]] ~ `pred]] [%select top=~ bottom=~ distinct=%.n columns=~[%all]] ~]
%+ expect-eq
!> ~[expected]
!> (parse:parse(current-database 'db1') query)
++ test-predicate-18
=/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
" WHERE T1.foo in (1,2,3) ".
" SELECT *"
=/ joinpred=(tree predicate-component:ast) [%eq t1-foo t2-bar]
=/ pred=(tree predicate-component:ast) [%in t1-foo value-literal-list]
=/ expected=simple-query:ast
[%simple-query [~ [%priori [~ [%from object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T1']] joins=~[[%joined-object join=%join object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T2']] predicate=`joinpred]]]] ~ `pred]] [%select top=~ bottom=~ distinct=%.n columns=~[%all]] ~]
%+ expect-eq
!> ~[expected]
!> (parse:parse(current-database 'db1') query)
++ test-predicate-19
=/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
" WHERE NOT EXISTS T1.foo ".
" SELECT *"
=/ joinpred=(tree predicate-component:ast) [%eq t1-foo t2-bar]
=/ pred=(tree predicate-component:ast) [%not [%exists t1-foo ~] ~]
=/ expected=simple-query:ast
[%simple-query [~ [%priori [~ [%from object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T1']] joins=~[[%joined-object join=%join object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T2']] predicate=`joinpred]]]] ~ `pred]] [%select top=~ bottom=~ distinct=%.n columns=~[%all]] ~]
%+ expect-eq
!> ~[expected]
!> (parse:parse(current-database 'db1') query)
++ test-predicate-20
=/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
" WHERE NOT exists foo ".
" SELECT *"
=/ joinpred=(tree predicate-component:ast) [%eq t1-foo t2-bar]
=/ pred=(tree predicate-component:ast) [%not [%exists foo ~] ~]
=/ expected=simple-query:ast
[%simple-query [~ [%priori [~ [%from object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T1']] joins=~[[%joined-object join=%join object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T2']] predicate=`joinpred]]]] ~ `pred]] [%select top=~ bottom=~ distinct=%.n columns=~[%all]] ~]
%+ expect-eq
!> ~[expected]
!> (parse:parse(current-database 'db1') query)
++ test-predicate-21
=/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
" WHERE EXISTS T1.foo ".
" SELECT *"
=/ joinpred=(tree predicate-component:ast) [%eq t1-foo t2-bar]
=/ pred=(tree predicate-component:ast) [%exists t1-foo ~]
=/ expected=simple-query:ast
[%simple-query [~ [%priori [~ [%from object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T1']] joins=~[[%joined-object join=%join object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T2']] predicate=`joinpred]]]] ~ `pred]] [%select top=~ bottom=~ distinct=%.n columns=~[%all]] ~]
%+ expect-eq
!> ~[expected]
!> (parse:parse(current-database 'db1') query)
++ test-predicate-22
=/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
" WHERE EXISTS foo ".
" SELECT *"
=/ joinpred=(tree predicate-component:ast) [%eq t1-foo t2-bar]
=/ pred=(tree predicate-component:ast) [%exists foo ~]
=/ expected=simple-query:ast
[%simple-query [~ [%priori [~ [%from object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T1']] joins=~[[%joined-object join=%join object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T2']] predicate=`joinpred]]]] ~ `pred]] [%select top=~ bottom=~ distinct=%.n columns=~[%all]] ~]
%+ expect-eq
!> ~[expected]
!> (parse:parse(current-database 'db1') query)
::
:: test conjunctions, varying spacing and keyword casing
++ test-predicate-23
=/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
" WHERE foobar >=foo And foobar<=bar ".
" SELECT *"
=/ joinpred=(tree predicate-component:ast) [%eq t1-foo t2-bar]
=/ pred=(tree predicate-component:ast) and-fb-gte-f--fb-lte-b
=/ expected=simple-query:ast
[%simple-query [~ [%priori [~ [%from object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T1']] joins=~[[%joined-object join=%join object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T2']] predicate=`joinpred]]]] ~ `pred]] [%select top=~ bottom=~ distinct=%.n columns=~[%all]] ~]
%+ expect-eq
!> ~[expected]
!> (parse:parse(current-database 'db1') query)
:: expected/actual match
::++ test-predicate-24
:: =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
:: " WHERE foobar >=foo And foobar<=bar ".
:: " and T1.foo2 = ~zod ".
:: " SELECT *"
:: =/ joinpred=(tree predicate-component:ast) [%eq t1-foo t2-bar]
:: =/ pred=(tree predicate-component:ast) and-and
:: =/ expected=simple-query:ast
:: [%simple-query [~ [%priori [~ [%from object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T1']] joins=~[[%joined-object join=%join object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T2']] predicate=`joinpred]]]] ~ `pred]] [%select top=~ bottom=~ distinct=%.n columns=~[%all]] ~]
:: %+ expect-eq
:: !> ~[expected]
:: !> (parse:parse(current-database 'db1') query)
:: expected/actual match
::++ test-predicate-25
:: =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
:: " WHERE foobar >=foo And foobar<=bar ".
:: " and T1.foo2 = ~zod ".
:: " or T2.bar in (1,2,3)".
:: " SELECT *"
:: =/ joinpred=(tree predicate-component:ast) [%eq t1-foo t2-bar]
:: =/ pred=(tree predicate-component:ast) and-and-or
:: =/ expected=simple-query:ast
:: [%simple-query [~ [%priori [~ [%from object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T1']] joins=~[[%joined-object join=%join object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T2']] predicate=`joinpred]]]] ~ `pred]] [%select top=~ bottom=~ distinct=%.n columns=~[%all]] ~]
:: %+ expect-eq
:: !> ~[expected]
:: !> (parse:parse(current-database 'db1') query)
:: expected/actual match
::++ test-predicate-26
:: =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
:: " WHERE foobar >=foo And foobar<=bar ".
:: " and T1.foo2 = ~zod ".
:: " or ".
:: " foobar>=foo ".
:: " AND T1.foo2=~zod ".
:: " SELECT *"
:: =/ joinpred=(tree predicate-component:ast) [%eq t1-foo t2-bar]
:: =/ pred=(tree predicate-component:ast) and-and-or-and
:: =/ expected=simple-query:ast
:: [%simple-query [~ [%priori [~ [%from object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T1']] joins=~[[%joined-object join=%join object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T2']] predicate=`joinpred]]]] ~ `pred]] [%select top=~ bottom=~ distinct=%.n columns=~[%all]] ~]
:: %+ expect-eq
:: !> ~[expected]
:: !> (parse:parse(current-database 'db1') query)
++ test-predicate-27
:: =/ predicate "foobar >=foo And foobar<=bar ".
:: " and T1.foo2 = ~zod ".
:: " or ".
:: " foobar>=foo ".
:: " AND T1.foo2=~zod ".
:: " OR ".
:: " foo = 1 ".
:: " AND T1.foo3 < any (1,2,3)"
:: %+ expect-eq
:: !> and-and-or-and-or-and
:: !> (wonk (parse-predicate:parse [[1 1] predicate]))
=/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
" WHERE foobar >=foo And foobar<=bar ".
" and T1.foo2 = ~zod ".
" or ".
" foobar>=foo ".
" AND T1.foo2=~zod ".
" OR ".
" foo = 1 ".
" AND T1.foo3 < any (1,2,3) ".
" SELECT *"
=/ joinpred=(tree predicate-component:ast) [%eq t1-foo t2-bar]
=/ pred=(tree predicate-component:ast) and-and-or-and-or-and
=/ expected=simple-query:ast
[%simple-query [~ [%priori [~ [%from object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T1']] joins=~[[%joined-object join=%join object=[%query-object object=[%qualified-object ship=~ database='db1' namespace='dbo' name='adoptions'] alias=[~ 'T2']] predicate=`joinpred]]]] ~ `pred]] [%select top=~ bottom=~ distinct=%.n columns=~[%all]] ~]
%+ expect-eq
!> ~[expected]
!> (parse:parse(current-database 'db1') query)
::
:: simple nesting
::++ test-predicate-28
:: =/ predicate "(foobar > foo OR foobar < bar) ".
:: " AND T1.foo>foo2 ".
:: " AND T2.bar IN (1,2,3) ".
:: " AND (T1.foo3< any (1,2,3) OR T1.foo2=~zod AND foo=1 ) "
:: %+ expect-eq
:: !> king-and
:: !> (wonk (parse-predicate:parse [[1 1] predicate]))
::
:: nesting
::++ test-predicate-29
:: =/ predicate "foobar > foo AND foobar < bar ".
:: " AND ( T1.foo>foo2 AND T2.bar IN (1,2,3) ".
:: " OR (T1.foo3< any (1,2,3) AND T1.foo2=~zod AND foo=1 ) ".
:: " OR (foo3=foo4 AND foo5=foo6) ".
:: " OR foo4=foo5 ".
:: " ) ".
:: " AND foo6=foo7"
:: %+ expect-eq
:: !> a-a-l-a-o-l-a-a-r-o-r-a-l-o-r-a
:: !> (wonk (parse-predicate:parse [[1 1] predicate]))
::
:: simple nesting, superfluous () around entire predicate
::++ test-predicate-30
:: =/ predicate "((foobar > foo OR foobar < bar) ".
:: " AND T1.foo>foo2 ".
:: " AND T2.bar IN (1,2,3) ".
:: " AND (T1.foo3< any (1,2,3) OR T1.foo2=~zod AND foo=1 )) "
:: %+ expect-eq
:: !> king-and
:: !> (wonk (parse-predicate:parse [[1 1] predicate]))
::
:: aggregate inequality
::++ test-predicate-31
:: =/ predicate " count( foo ) > 10 "
:: %+ expect-eq
:: !> [%gt [aggregate-count-foo 0 0] literal-10]
:: !> (wonk (parse-predicate:parse [[1 1] predicate]))
::
:: aggregate inequality, no whitespace
::++ test-predicate-32
:: =/ predicate "count(foo) > 10"
:: %+ expect-eq
:: !> [%gt [aggregate-count-foo 0 0] literal-10]
:: !> (wonk (parse-predicate:parse [[1 1] predicate]))
::
:: aggregate equality
::++ test-predicate-33
:: =/ predicate "bar = count(foo)"
:: %+ expect-eq
:: !> [%eq bar [aggregate-count-foo 0 0]]
:: !> (wonk (parse-predicate:parse [[1 1] predicate]))
::
:: complext predicate, bug test
::++ test-predicate-34
:: =/ predicate " A1.adoption-email = A2.adoption-email ".
:: " AND A1.adoption-date = A2.adoption-date ".
:: " AND foo = bar ".
:: " AND ((A1.name = A2.name AND A1.species > A2.species) ".
:: " OR ".
:: " (A1.name > A2.name AND A1.species = A2.species) ".
:: " OR ".
:: " (A1.name > A2.name AND A1.species > A2.species) ".
:: " ) "
:: %+ expect-eq
:: !> [%and [%and [%and [%eq a1-adoption-email a2-adoption-email] [%eq a1-adoption-date a2-adoption-date]] [%eq foo bar]] [%or [%or [%and [%eq a1-name a2-name] [%gt a1-species a2-species]] [%and [%gt a1-name a2-name] [%eq a1-species a2-species]]] [%and [%gt a1-name a2-name] [%gt a1-species a2-species]]]]
:: !> (wonk (parse-predicate:parse [[1 1] predicate]))
--