more predicate tests, more clean-up

This commit is contained in:
jackfoxy 2023-01-19 13:36:03 -08:00
parent ae1d4382b2
commit b49267d458
4 changed files with 124 additions and 98 deletions

View File

@ -1,2 +1,5 @@
# urQL
RDBMS for Urbit
Scripting language grammar and parser for an Urbit RDBMS.
Pull Requests are appreciated, but you should start a discussion before you proceed. If green-lit then open an issue.

View File

@ -50,7 +50,12 @@ There are no subqueries.
JOINs and/or CTEs handle all such use cases and emphasize composability.
CTEs can be referenced for certain use cases in predicates.
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.
The result of a UNION set operation is as according to _union_ in set theory (no duplicate rows returned).
Use COMBINE to include duplicates.
Relational division is supported with a DIVIDED BY operator.
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. We anticipate these will be points for integration with hoon.

View File

@ -493,8 +493,6 @@
parse-join-type
parse-query-object
;~(pfix whitespace ;~(pfix (jester 'on') parse-predicate))
::;~(pfix whitespace ;~(pfix (jester 'on') ;~(less predicate-stop prn)))
::(easy ~)
==
++ parse-object-and-joins ~+ ;~ plug
parse-query-object
@ -551,11 +549,12 @@
(cold %lt (just '<'))
(cold %and ;~(plug (jester 'and') whitespace))
(cold %or ;~(plug (jester 'or') whitespace))
:: to do %distinct %not-distinct:
:: (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')))
:: ternary operator
(cold %between ;~(plug (jester 'between') whitespace))
:: nesting directors
:: nesting
(cold %pal pal)
(cold %par par)
==
@ -646,9 +645,7 @@
^- predicate:ast
=/ working-tree=predicate:ast ~
=/ tree-stack=(list predicate:ast) ~
~| "produce-predicate parsed: {<parsed>}"
|-
~| "-.parsed: {<-.parsed>}"
?: =((lent parsed) 0)
|-
?~ tree-stack
@ -661,6 +658,7 @@
==
?- -.parsed
%pal :: push working predicate onto the stack
?~ working-tree $(parsed +.parsed)
%= $
tree-stack [working-tree tree-stack]
working-tree ~
@ -695,10 +693,6 @@
?~ 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 ~|("ternary-operator, left tree empty" !!)
@ -720,13 +714,19 @@
?~ 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" !!)
?: ?=(value-literal-list:ast +<.parsed)
%= $
working-tree [-.working-tree +<.working-tree [-.parsed [+<.parsed ~ ~] ~]]
parsed +>.parsed
==
?: ?=(qualified-column:ast +<.parsed) :: to do: this must resolve to a CTE or list
%= $
working-tree [-.working-tree +<.working-tree [-.parsed [+<.parsed ~ ~] ~]]
parsed +>.parsed
==
~|("all-any-operator {<-.parsed>} must target CTE or literal list {<+<.parsed>}" !!)
~|("all-any-operator {<-.parsed>} can only follow equality or inequality operator" !!)
~|("all-any-operator {<-.parsed>} can't get here, working-tree {<working-tree>}" !!)
qualified-column:ast
?~ working-tree
?: ?=(binary-operator:ast +<.parsed)
@ -752,7 +752,7 @@
working-tree [%not (produce-predicate ~[-.parsed %in +>+<.parsed]) ~]
parsed +>+>.parsed
==
!!
~|("unary-operator {<+<.parsed>} can't get here after qualified-column, working-tree {<working-tree>}" !!)
?: =(%between +<.parsed)
?: =(%and +>+<.parsed)
%= $
@ -807,7 +807,7 @@
working-tree [%not (produce-predicate ~[-.parsed %in +>+<.parsed]) ~]
parsed +>+>.parsed
==
!!
~|("unary-operator {<+<.parsed>} can't get here after value-literal, working-tree {<working-tree>}" !!)
?: =(%between +<.parsed)
?: =(%and +>+<.parsed)
%= $
@ -820,7 +820,7 @@
[%between (produce-predicate ~[-.parsed %gte +>-.parsed]) (produce-predicate ~[-.parsed %lte +>+<.parsed])]
parsed +>+>.parsed
==
!!
~|("value-literal can't get here after {<+<.parsed>} , working-tree {<working-tree>}" !!)
?~ l.working-tree
%= $
working-tree [-.working-tree [-.parsed ~ ~] ~]
@ -1076,10 +1076,8 @@
(from:ast %from query-object (flop joined-objects))
~|("cross join must be only join in query" !!) :: to do, not sure this is required, investigate later
(from:ast %from query-object (flop joined-objects))
?> ?=(join-type:ast -<.raw-joined-objects)
?> ?=(query-object:ast ->-.raw-joined-objects)
?: ?=(%cross-join -<.raw-joined-objects)
%= $
joined-objects
@ -1502,13 +1500,13 @@
script-position next-cursor
commands [`command-ast`(drop-database:ast %drop-database parsed %.n) commands]
==
~| "Cannot parse drop-database {<parsed>}"
?: ?=([@ @] parsed) :: force name
%= $
script q.q.u.+3.q:drop-database-nail
script-position next-cursor
commands [`command-ast`(drop-database:ast %drop-database +.parsed %.y) commands]
==
::~|("Cannot parse drop-database {<parsed>}" !!)
!!
%drop-index
=/ drop-index-nail (parse-drop-index [[1 1] q.q.command-nail])
@ -1543,13 +1541,13 @@
script-position next-cursor
commands [`command-ast`(drop-namespace:ast %drop-namespace -.parsed +.parsed %.n) commands]
==
~| "Cannot parse drop-namespace {<parsed>}"
?: ?=([* [@ @]] parsed) :: force db.name
%= $
script q.q.u.+3.q:drop-namespace-nail
script-position next-cursor
commands [`command-ast`(drop-namespace:ast %drop-namespace +<.parsed +>.parsed %.y) commands]
==
::~|("Cannot parse drop-namespace {<parsed>}" !!)
!!
%drop-table
=/ drop-table-nail (drop-table-or-view [[1 1] q.q.command-nail])
@ -1652,8 +1650,8 @@
=/ parsed (wonk query-nail)
=/ next-cursor
(get-next-cursor [script-position +<.command-nail p.q.u.+3:q.+3:query-nail])
~| "parsed: {<parsed>}"
~| "remainder: {<q.q.u.+3:q.+3.query-nail>}"
:: ~| "parsed: {<parsed>}"
:: ~| "remainder: {<q.q.u.+3:q.+3.query-nail>}"
%= $
script q.q.u.+3.q:query-nail
script-position next-cursor
@ -1661,7 +1659,6 @@
[`command-ast`(produce-simple-query parsed) commands]
==
%revoke
=/ revoke-nail (parse-revoke [[1 1] q.q.command-nail])
=/ parsed (wonk revoke-nail)
=/ next-cursor

View File

@ -24,16 +24,16 @@
++ foobar [[%qualified-column [%qualified-object ~ 'UNKNOWN' 'COLUMN-OR-CTE' 'foobar'] 'foobar' ~] ~ ~]
++ a1-adoption-email [[%qualified-column [%qualified-object 0 'UNKNOWN' 'COLUMN' 'A1'] 'adoption-email' 0] 0 0]
++ a2-adoption-email [[%qualified-column [%qualified-object 0 'UNKNOWN' 'COLUMN' 'A2'] 'adoption-email' 0] 0 0]
++ a1-adoption-email [[%qualified-column [%qualified-object ~ 'UNKNOWN' 'COLUMN' 'A1'] 'adoption-email' ~] ~ ~]
++ a2-adoption-email [[%qualified-column [%qualified-object ~ 'UNKNOWN' 'COLUMN' 'A2'] 'adoption-email' ~] ~ ~]
++ a1-adoption-date [[%qualified-column [%qualified-object 0 'UNKNOWN' 'COLUMN' 'A1'] 'adoption-date' 0] 0 0]
++ a2-adoption-date [[%qualified-column [%qualified-object 0 'UNKNOWN' 'COLUMN' 'A2'] 'adoption-date' 0] 0 0]
++ a1-adoption-date [[%qualified-column [%qualified-object ~ 'UNKNOWN' 'COLUMN' 'A1'] 'adoption-date' ~] ~ ~]
++ a2-adoption-date [[%qualified-column [%qualified-object ~ 'UNKNOWN' 'COLUMN' 'A2'] 'adoption-date' ~] ~ ~]
++ a1-name [[%qualified-column [%qualified-object 0 'UNKNOWN' 'COLUMN' 'A1'] 'name' 0] 0 0]
++ a2-name [[%qualified-column [%qualified-object 0 'UNKNOWN' 'COLUMN' 'A2'] 'name' 0] 0 0]
++ a1-species [[%qualified-column [%qualified-object 0 'UNKNOWN' 'COLUMN' 'A1'] 'species' 0] 0 0]
++ a2-species [[%qualified-column [%qualified-object 0 'UNKNOWN' 'COLUMN' 'A2'] 'species' 0] 0 0]
++ a1-name [[%qualified-column [%qualified-object ~ 'UNKNOWN' 'COLUMN' 'A1'] 'name' ~] ~ ~]
++ a2-name [[%qualified-column [%qualified-object ~ 'UNKNOWN' 'COLUMN' 'A2'] 'name' ~] ~ ~]
++ a1-species [[%qualified-column [%qualified-object ~ 'UNKNOWN' 'COLUMN' 'A1'] 'species' ~] ~ ~]
++ a2-species [[%qualified-column [%qualified-object ~ 'UNKNOWN' 'COLUMN' 'A2'] 'species' ~] ~ ~]
++ value-literal-list [[%value-literal-list %ud '3;2;1'] ~ ~]
++ aggregate-count-foo [%aggregate %count %qualified-column [%qualified-object 0 'UNKNOWN' 'COLUMN-OR-CTE' %foo] %foo 0]
@ -341,70 +341,84 @@
:: !> ~[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)"
:: expected/actual match
::++ test-predicate-27
:: =/ 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
:: !> 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)
:: !> ~[expected]
:: !> (parse:parse(current-database 'db1') query)
::
:: simple nesting
:: expected/actual match
::++ 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 ) "
:: =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
:: " WHERE (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 ) ".
:: " SELECT *"
:: =/ joinpred=(tree predicate-component:ast) [%eq t1-foo t2-bar]
:: =/ pred=(tree predicate-component:ast) king-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
:: !> king-and
:: !> (wonk (parse-predicate:parse [[1 1] predicate]))
:: !> ~[expected]
:: !> (parse:parse(current-database 'db1') query)
::
:: nesting
:: expected/actual match
::++ 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"
:: =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
:: " WHERE 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".
:: " SELECT *"
:: =/ joinpred=(tree predicate-component:ast) [%eq t1-foo t2-bar]
:: =/ pred=(tree predicate-component:ast) a-a-l-a-o-l-a-a-r-o-r-a-l-o-r-a
:: =/ 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
:: !> a-a-l-a-o-l-a-a-r-o-r-a-l-o-r-a
:: !> (wonk (parse-predicate:parse [[1 1] predicate]))
:: !> ~[expected]
:: !> (parse:parse(current-database 'db1') query)
::
:: simple nesting, superfluous () around entire predicate
:: expected/actual match
::++ 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 )) "
:: =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
:: " WHERE ((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 )) ".
:: " SELECT *"
:: =/ joinpred=(tree predicate-component:ast) [%eq t1-foo t2-bar]
:: =/ pred=(tree predicate-component:ast) king-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
:: !> king-and
:: !> (wonk (parse-predicate:parse [[1 1] predicate]))
:: !> ~[expected]
:: !> (parse:parse(current-database 'db1') query)
::
:: aggregate inequality
::++ test-predicate-31
@ -428,18 +442,25 @@
:: !> (wonk (parse-predicate:parse [[1 1] predicate]))
::
:: complext predicate, bug test
:: expected/actual match
::++ 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) ".
:: " ) "
:: =/ query "FROM adoptions AS T1 JOIN adoptions AS T2 ON T1.foo = T2.bar ".
:: " WHERE 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) ".
:: " ) ".
:: " SELECT *"
:: =/ joinpred=(tree predicate-component:ast) [%eq t1-foo t2-bar]
:: =/ pred=(tree predicate-component:ast)
:: [%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]]]]
:: =/ 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
:: !> [%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]))
:: !> ~[expected]
:: !> (parse:parse(current-database 'db1') query)
--