4 Reference
#lang plisqin | package: plisqin |
syntax
(from id queryable statement ...)
syntax
(join id queryable statement ...)
statement = (define val-id val-expr) | (define (proc-id args ...) body ...) | (join-attach join-id rest-of-join ...) | (join join-id rest-of-join ...) | statement-expr
queryable : queryable?
statement-expr : statement-expr?
The id argument is introduced as an identifier which refers to the query being constructed. Its value is a source? and it is in scope for all the statements. It is analogous to an alias in SQL.
The queryable argument defines the initial value of the query, before any of the statements are applied. It must be a value that satisfies the queryable? contract. See the documentation on queryable? for further information.
Next come zero or more statements. Each statement may be a special form (define or join-attach), or it may be an expression.
The define statement behaves similar to Racket’s built-in define. The val-id or proc-id that is introduced remains in scope for the rest of the statements.
The join and join-attach statements do exactly the same thing. In other words, when join appears as a statement, it gets treated as if it were join-attach. (The only reason join-attach exists is to make writing this documentation easier.)
The join-attach statement does three things. First, it creates a join as (join join-id rest-of-join ...). Second, it attaches that join to the enclosing query. Third, it makes that attached join available via join-id for the rest of the statements. Read more about the difference between attached and detached joins at Attached vs Detached Joins.
If a statement does not match one of the special forms, it is a statement-expr which must satisfy the statement-expr? contract.
procedure
token : sql-token?
syntax
(def-table constructor [table-name] [default-alias] [tester?])
constructor = id tester? = id
table-name : (or/c #f string?)
default-alias : (or/c #f string?)
> (def-table Foo) > (Foo) '(source "_foo" "Foo" -64)
> (Foo "custom-alias") '(source "custom-alias" "Foo" -65)
> (def-table Foo)
> (and (Foo? (Foo)) (Foo? (Foo "f")) (Foo? (from f Foo)) (Foo? (join f Foo)) (Foo? (join f "Foo"))) #t
> (Foo? (join f Rental)) #f
(def-table MyTable) (def-table MyTable #f) (def-table MyTable #f #f) (def-table MyTable #f #f MyTable?) (def-table MyTable "MyTable" "_myTable" MyTable?)
syntax
(table constructor [table-name] [default-alias] [tester?])
syntax
(field-cases (id arg) [test-expr then-body] ...)
> (field-cases (twice x) [(integer? x) (* x 2)]) ; we can append more conditions to the existing definition of twice:
> (field-cases (twice x) [(string? x) (format "~a ~a" x x)]) > (twice 3) 6
> (twice "pizza") "pizza pizza"
> (field-cases (twice x) [(integer? x) (format "Two times ~a is ~a" x (* 2 x))]) > (twice 3) "Two times 3 is 6"
> (twice "pizza") "pizza pizza"
The above examples are contrived. For the most complete explanation of how field-cases is meant to be used, read Layer 1 - Schema as Procedures. For some quick but still realistic examples, look at the definitions of ItemTypeId or Item-of/s or anything from that example schema.
syntax
For Racketeers: this works by customizing the caller’s #%app, so I think it is accurate to say that it remains in effect for the remainder of the module.
> (aggregate (Rentals-of/g (Item)) (Rentals-of/g (Copy))) Aggregate expression has 2 targets. At most one target is
allowed.
Target 1 was:
'(join InnerJoin (query (source "_rental" "Rental" 1)
((GroupBy ((Scalar ((join InnerJoin (query (source "_copy"
"Copy" 2) () () #hash()) ((JoinOn ((source "_copy" "Copy" 2)
".CopyId = " (source "_rental" "Rental" 1) ".CopyId"))))
".ItemId"))))) () #has...
Target 2 was:
'(join InnerJoin (query (source "_rental" "Rental" 1)
((GroupBy ((Scalar ((source "_rental" "Rental" 1)
".CopyId"))))) () #hash()) ((JoinOn ((Scalar ((source
"_rental" "Rental" 1) ".CopyId")) " = " (Scalar ((source
"_copy" "Copy" -2) ".CopyId"))))))
> (attach-callstacks) > (define (foo) (Rentals-of/g (Copy)))
> (aggregate (Rentals-of/g (Item)) (foo)) Aggregate expression has 2 targets. At most one target is
allowed.
Target 1 was:
#<syntax:eval:8:0 (Rentals-of/g (Item))>
Target 2 was:
#<syntax:eval:8:0 (foo)>
#<syntax:eval:7:0 (Rentals-of/g (Copy))>
4.1 Fragments
procedure
tokens : token-list?
procedure
tokens : token-list?
procedure
tokens : token-list?
procedure
tokens : token-list?
procedure
tokens : token-list?
procedure
tokens : token-list?
procedure
(aggregate tokens ...) → aggregate?
tokens : token-list?
procedure
tokens : token-list?
procedure
tokens : token-list?
procedure
tokens : token-list?
procedure
maybe-dir : (or/c 'asc 'desc sql-token? (listof sql-token?)) tokens : token-list?
procedure
x : any/c
procedure
x : any/c
procedure
x : any/c
procedure
x : any/c
procedure
x : any/c
procedure
x : any/c
procedure
x : any/c
procedure
(aggregate? x) → boolean?
x : any/c
procedure
x : any/c
procedure
x : any/c
procedure
x : any/c
procedure
x : any/c
> (where? (where "1=1")) #t
> (group-by? (where "1=1")) #f
> (fragment? (where "1=1")) #t
> (fragment? "1=1") #f
4.2 SQL Functions
procedure
(count tokens ... [#:distinct? distinct?]) → aggregate?
tokens : token-list? distinct? : any/c = #f
procedure
(avg tokens ... [#:distinct? distinct?]) → aggregate?
tokens : token-list? distinct? : any/c = #f
procedure
(max tokens ... [#:distinct? distinct?]) → aggregate?
tokens : token-list? distinct? : any/c = #f
procedure
(min tokens ... [#:distinct? distinct?]) → aggregate?
tokens : token-list? distinct? : any/c = #f
procedure
(sum tokens ... [#:distinct? distinct?]) → aggregate?
tokens : token-list? distinct? : any/c = #f
> (display (to-sql (from c "information_schema.columns" (select (sum c".ordinal_position")))))
select
sum(c.ordinal_position)
from information_schema.columns c
> (display (to-sql (from c "information_schema.columns" (select (sum c".ordinal_position" #:distinct? #t)))))
select
sum(distinct c.ordinal_position)
from information_schema.columns c
> (define (columns-of/g t) (join c "information_schema.columns" (group-by (scalar c".table_name")) (join-on (scalar c".table_name") " = " (scalar t".table_name"))))
> (display (to-sql (from t "information_schema.tables" (join c (columns-of/g t)) (select (max c".ordinal_position")) (select t".table_name"))))
select
c.__INJECT2
, t.table_name
from information_schema.tables t
inner join (
select
c.table_name as __INJECT1
, max(c.ordinal_position) as __INJECT2
from information_schema.columns c
group by c.table_name) c
on c.__INJECT1 = t.table_name
> (display (to-sql (from t "information_schema.tables" (join c (columns-of/g t)) (select (max c".ordinal_position" #:distinct? #t)) (select t".table_name"))))
select
c.__INJECT2
, t.table_name
from information_schema.tables t
inner join (
select
c.table_name as __INJECT1
, max(distinct c.ordinal_position) as __INJECT2
from information_schema.columns c
group by c.table_name) c
on c.__INJECT1 = t.table_name
> (display (to-sql (from t "information_schema.tables" (select (count t)))))
select
count(*)
from information_schema.tables t
> (display (to-sql (from t "information_schema.tables" (select (avg t))))) tokens: contract violation
expected: a scalar-looking SQL expression
given: '(source "t" "information_schema.tables" 1)
procedure
tokens : token-list?
> (display (to-sql (from x "X" (where (exists "select 1 as ONE")))))
select x.*
from X x
where exists (select 1 as ONE)
> (display (to-sql (from x "X" (where (exists (from y "Y"))))))
select x.*
from X x
where exists (
select y.*
from Y y)
> (display (to-sql (from x "X" (where (exists (join y "Y" (join-on y".foo = "x".bar")))))))
select x.*
from X x
where exists (
select y.*
from Y y
where y.foo = x.bar)
4.3 Data Model
value
(or/c 'Select 'Where 'JoinOn 'GroupBy 'OrderBy 'Having 'Scalar 'Aggregate 'Bool 'Subquery 'Sql 'Silence)
value
(or/c 'InnerJoin 'LeftJoin 'CrossApply 'OuterApply)
value
value
> (require racket)
> (define/contract (autoflatten . tokens) (->* () () #:rest token-list? (listof sql-token?)) ; 'tokens' has already been flattened by its contract here: tokens) > (autoflatten "a" '(1 (2 3)) "z") '("a" 1 2 3 "z")
> (where "a" '(1 (2 (3 4))) "z") '(Where ("a" 1 2 3 4 "z"))
> (order-by "a" '(7 8 (9)) "z") '(OrderBy ("a" 7 8 9 "z"))
value
(or/c source? procedure? string? query? join? subquery?)
(from jkl "MyTable")
(from a (from b "B" (select "1 as ONE")) (select "2 as TWO")) ; is equivalent to: (from b "B" (select "1 as ONE") (select "2 as TWO"))
(join a (from b "B" (select "1 as ONE")) (join-on "2=2")) ; is equivalent to: (join b "B" (select "1 as ONE") (join-on "2=2"))
(from a (join b "B" (join-on "1=1")) (select a".foo")) ; is equivalent to: (from b "B" (where "1=1") (select b".foo"))
> (define (my-sub) (from x "X" (select x".foo"))) ; queries append by default: > (display (to-sql (from a (my-sub))))
select
x.foo
from X x
; use a subquery to prevent appending: > (display (to-sql (from a (subquery (my-sub)))))
select a.*
from (
select
x.foo
from X x) a
(from one-row (subquery "select 1 as ONE_ROW") (join a "A" 'LeftJoin (join-on a".ID = 3")) (join b "B" 'LeftJoin (join-on b".ID = 9")) (select (coalesce (scalar a".Name") (scalar b".Name") "neither A nor B was found")))
value
(or/c clause? join-type?)
value
The name binding? was a poor choice. TODO Better names would be attached-join? and detached-join?; and join? would be true if either is true.
> (displayln (to-sql (from ignored-id (join x "X" (define y (join y "Y")) (join z "Z") (define (what-is it) (list (if (binding? it) (select it".is-binding") (select it".is-not-binding")) (if (join? it) (select it".is-join") (select it".is-not-join")) (if (source? it) (select it".is-source") (select it".is-not-source")))) (what-is x) (what-is y) (what-is z)))))
select
x.is-not-binding
, x.is-not-join
, x.is-source
, y.is-not-binding
, y.is-join
, y.is-not-source
, z.is-binding
, z.is-not-join
, z.is-not-source
from X x
inner join Z z
on 1=1
inner join Y y
on 1=1
value
4.4 Terminology
"The Item of/s the Copy" means "the Item that belongs to the Copy, and take note that there is at most 1 such Item." The /s refers to the singular nature of this relationship.
"The Copies of/g the Item" means "the group of Copies that belongs to the Item, and take note that there is exactly 1 such group. (But the group may be empty.)" The /g refers to the grouped nature of this relationship.
"The Copies of/p the Item" means "the Copies that belong to the Item, and take note that each Copy will make an individual appearance in the result set." The /p refers to the plural nature of this relationship.
4.5 #lang plisqin
TODO write this section.
** Dots are read differently: foo.bar gets read as foo .bar. You can use bars to suppress this: foo.bar.
** What gets required by default in #lang plisqin.
** The {curly-brace rewrite rules}. Note that a+b is not the same as a + b.
Pattern |
| Result |
| Notes |
LITERAL |
| 'LITERAL |
| When LITERAL is one of null, asc, desc |
x .y |
| {y x} |
| Also works for x.y which is read as x .y |
x ?? y |
| {coalesce x y} |
| |
x || y |
| {|| x y} |
| String concatenation. TODO think about this precedence... |
x OP y |
| {OP x y} |
| When OP is one of *, / |
x OP y |
| {OP x y} |
| When OP is one of +, - |
x CMP y |
| {CMP x y} |
| When CMP is one of =, <>, like, not-like, is, is-not, <, <=, >, >= |
TODO put examples here, to make the motivation clear. Such as {order-by desc x.Foo} is rewritten to (order-by 'desc (Foo x))