(module sql-select-internals-unit mzscheme
(require (lib "unitsig.ss")
(lib "time.ss" "srfi" "19"))
(require (file "../base.ss")
(file "../query-core.ss")
(file "../type.ss")
(file "sql-sig.ss"))
(provide sql-select-internals@)
(define sql-select-internals@
(unit/sig sql-select-internals^
(import sql-quote^)
(define (display-select-sql select out)
(let* ([what (select-what select)]
[from (select-from select)]
[where (select-where select)]
[group (select-group select)]
[order (select-order select)]
[limit (select-limit select)]
[offset (select-offset select)]
[from-fields (select-from-fields select)])
(display "SELECT " out)
(display-what-sql what from-fields out)
(display " FROM " out)
(display-from-sql from from-fields out)
(when where
(display " WHERE " out)
(display-expr-sql where from-fields out))
(unless (null? group)
(display " GROUP BY " out)
(display-group-sql group from-fields out))
(unless (null? order)
(display " ORDER BY " out)
(display-order-sql order from-fields out))
(when limit
(display " LIMIT " out)
(display limit out))
(when offset
(display " OFFSET " out)
(display offset out))))
(define (display-what-sql what declared out)
(let loop ([what what] [first? #t])
(unless (null? what)
(unless first? (display ", " out))
(display-what-sql/field (car what) declared out)
(loop (cdr what) #f))))
(define (display-from-sql from declared out)
(cond [(table? from) (display-from-sql/table from declared out)]
[(join? from) (display-from-sql/join from declared out)]
[(select? from) (display-from-sql/select from declared out)]
[else (raise-exn exn:fail:contract
(format "Expected (U table join select), received ~a" from))]))
(define (display-expr-sql expr declared out)
(cond [(string? expr) (display (quote-data type:text expr) out)]
[(integer? expr) (display (quote-data type:integer expr) out)]
[(symbol? expr) (display (quote-data type:symbol expr) out)]
[(boolean? expr) (display (quote-data type:boolean expr) out)]
[(time? expr) (display (quote-data type:time-tai expr) out)]
[(field? expr) (display-expr-sql/field expr declared out)]
[(expr? expr) (display-expr-sql/expr expr declared out)]
[(select? expr) (display-expr-sql/select expr declared out)]
[else (raise-exn exn:fail:contract
(format "Expected (U expr field select), received ~a" expr))]))
(define (display-group-sql group declared out)
(let loop ([group group] [first? #t])
(unless (null? group)
(let ([head (car group)]
[tail (cdr group)])
(unless first?
(display ", " out))
(display-field-sql head declared out)
(loop tail #f)))))
(define (display-order-sql order declared out)
(let loop ([order order] [first? #t])
(unless (null? order)
(let ([head (car order)]
[tail (cdr order)])
(unless first?
(display ", " out))
(display-order-sql/order head declared out)
(loop tail #f)))))
(define (display-what-sql/field field declared out)
(if (member field declared)
(display (quote-id (named-alias field)) out)
(begin (display-field-sql field declared out)
(display " AS " out)
(display (quote-id (named-alias field)) out))))
(define (display-from-sql/table table declared out)
(display (quote-id (table-name table)) out)
(display " AS " out)
(display (quote-id (named-alias table)) out))
(define (display-from-sql/join join declared out)
(let ([op (join-op join)]
[left (join-left join)]
[right (join-right join)]
[on (join-on join)])
(display "(" out)
(display-from-sql left declared out)
(cond [(eq? op 'inner) (display " INNER JOIN " out)]
[(eq? op 'left) (display " LEFT JOIN " out)]
[(eq? op 'right) (display " RIGHT JOIN " out)]
[(eq? op 'outer) (display " OUTER JOIN " out)]
[else (raise-exn exn:fail:contract
(format "Join operator: expected (U 'inner 'outer 'left 'right), received ~a" op))])
(display-from-sql right declared out)
(unless (eq? op 'outer)
(display " ON " out)
(display-expr-sql on declared out))
(display ")" out)))
(define (display-from-sql/select select declared out)
(display "(" out)
(display-select-sql select out)
(display ") AS " out)
(display (quote-id (named-alias select)) out))
(define (display-expr-sql/field field declared out)
(cond [(member field declared)
(display (quote-id (named-alias field)) out)]
[(field? field)
(display (quote-id (named-alias (field-table field))) out)
(display "." out)
(display (quote-id (field-name field)) out)]
[else (raise-exn exn:fail:snooze
(format "Locally declared aggregate functions cannot be referenced in expressions: ~a" field))]))
(define (display-expr-sql/expr expr declared out)
(let ([op (expr-op expr)]
[args (expr-args expr)])
(cond [(eq? op 'and) (display-expr-sql/infix args " AND " declared out)]
[(eq? op 'or) (display-expr-sql/infix args " OR " declared out)]
[(eq? op 'not) (display-expr-sql/outfix (car args) "NOT " #f declared out)]
[(eq? op '=) (display-expr-sql/infix args " = " declared out)]
[(eq? op '<>) (display-expr-sql/infix args " <> " declared out)]
[(eq? op '<) (display-expr-sql/infix args " < " declared out)]
[(eq? op '>) (display-expr-sql/infix args " > " declared out)]
[(eq? op '<=) (display-expr-sql/infix args " <= " declared out)]
[(eq? op '>=) (display-expr-sql/infix args " >= " declared out)]
[(eq? op 'like) (display-expr-sql/infix args " LIKE " declared out)]
[(eq? op 'match) (display-expr-sql/infix args " ~ " declared out)]
[(eq? op 'match-ci) (display-expr-sql/infix args " ~* " declared out)]
[(eq? op 'null?) (display-expr-sql/outfix (car args) #f " IS NULL" declared out)]
[(eq? op 'in) (display-expr-sql/in (car args) (cadr args) declared out)]
[else (raise-exn exn:fail:snooze
(format "Expression: unrecognised operator: ~a" op))])))
(define (display-expr-sql/select select declared out)
(display "(" out)
(display-select-sql select out)
(display ")" out))
(define (display-expr-sql/infix args delim declared out)
(display "(" out)
(let loop ([args args] [first? #t])
(unless (null? args)
(unless first? (display delim out))
(display-expr-sql (car args) declared out)
(loop (cdr args) #f)))
(display ")" out))
(define (display-expr-sql/outfix arg prefix suffix declared out)
(display "(" out)
(when prefix (display prefix out))
(display-expr-sql arg declared out)
(when suffix (display suffix out))
(display ")" out))
(define (display-expr-sql/in field source declared out)
(display "(" out)
(display-expr-sql field declared out)
(display " IN (" out)
(if (select? source)
(display-select-sql source out)
(let loop ([items source])
(unless (null? items)
(display-expr-sql (car items) declared out)
(unless (null? (cdr items))
(display ", " out))
(loop (cdr items)))))
(display "))" out))
(define (display-order-sql/order order declared out)
(let* ([arg (order-arg order)]
[dir (order-dir order)]
[alias (named-alias arg)])
(display-field-sql arg declared out)
(if (eq? dir 'asc)
(display " ASC" out)
(display " DESC" out))))
(define (display-field-sql field declared out)
(if (member field declared)
(display (quote-id (named-alias field)) out)
(if (aggregate? field)
(let ([op (aggregate-op field)]
[arg (aggregate-arg field)])
(cond [(eq? op 'count) (display "COUNT(" out)]
[(eq? op 'count*) (display "COUNT(" out)]
[(eq? op 'max) (display "MAX(" out)]
[(eq? op 'min) (display "MIN(" out)]
[(eq? op 'average) (display "AVERAGE(" out)]
[else (raise-exn exn:fail:contract
(format "Expected (U 'count 'count* 'min 'max 'average), received ~a" op))])
(cond [(field? arg)
(display-field-sql arg declared out)
(display ")" out)]
[(named? arg)
(display (quote-id (named-alias arg)) out)
(display ".*)" out)]
[(not arg)
(display "*)" out)]
[else
(raise-exn exn:fail:snooze
(format "Aggregate: expected (aggregate (U field named #f)), received ~a" field))]))
(begin (display (quote-id (named-alias (field-table field))) out)
(display "." out)
(display (quote-id (field-name field)) out)))))
))
)