(module sql-quote-unit mzscheme
(require (lib "etc.ss")
(lib "pregexp.ss")
(lib "unitsig.ss")
(lib "list.ss" "srfi" "1")
(lib "string.ss" "srfi" "13")
(lib "time.ss" "srfi" "19")
(lib "vector-lib.ss" "srfi" "43"))
(require (file "spgsql-ssl/spgsql.ss")
(file "../base.ss")
(file "../era.ss")
(file "../type.ss")
(file "../generic/sql-sig.ss")
(prefix generic: (file "../generic/sql-quote-unit.ss")))
(provide sql-quote@)
(define sql-quote@
(let ([mixin@
(unit/sig sql-quote^
(import (generic : sql-quote^))
(define (quote-id identifier)
(if (symbol? identifier)
(string-append "\"" (symbol->string identifier) "\"")
(string-append "\"" identifier "\"")))
(define (quote-data type data)
(let ([base (type-base type)])
(cond [(eq? base type:id)
(cond [(integer? data) (number->string data)]
[(equal? data (type-null type)) "NULL"]
[else (raise-exn exn:fail:snooze
(format "Expected data of type (U integer #f), given ~a" data))])]
[(eq? base type:revision)
(cond [(integer? data) (number->string data)]
[(equal? data (type-null type)) "NULL"]
[else (raise-exn exn:fail:snooze
(format "Expected data of type (U integer #f), given ~a" data))])]
[(eq? base type:text)
(cond [(string? data) (string-append "'" (regexp-replace* #rx"'" data "''") "'")]
[(equal? data (type-null type)) "NULL"]
[else (raise-exn exn:fail:snooze
(format "Expected data of type (U string #f), given ~a" data))])]
[(eq? base type:integer)
(cond [(integer? data) (number->string data)]
[(equal? data (type-null type)) "NULL"]
[else (raise-exn exn:fail:snooze
(format "Expected data of type (U integer #f), given ~a" data))])]
[(eq? base type:real)
(cond [(real? data) (number->string (exact->inexact data))]
[(equal? data (type-null type)) "NULL"]
[else (raise-exn exn:fail:snooze
(format "Expected data of type (U real #f), given ~a" data))])]
[(eq? base type:symbol)
(cond [(symbol? data) (string-append "'" (regexp-replace* #rx"'" (symbol->string data) "''") "'")]
[(equal? data (type-null type)) "NULL"]
[else (raise-exn exn:fail:snooze
(format "Expected data of type (U symbol #f), given ~a\n" data))])]
[(eq? base type:boolean)
(cond [(eq? data #t) "true"]
[(eq? data #f) "false"]
[else (raise-exn exn:fail:snooze
(format "Expected data of type (U #t #f), given ~a\n" data))])]
[(eq? base type:time-tai)
(cond [(time? data)
(cond [(eq? (time-type data) time-tai)
(string-append (date->string (time-tai->date data 0) "'~Y-~m-~d ~H:~M:~S.")
(string-pad (date->string (time-tai->date data) "~N'") 10 #\0))]
[(eq? (time-type data) time-utc)
(let ([data (time-utc->time-tai data)])
(string-append (date->string (time-tai->date data 0) "'~Y-~m-~d ~H:~M:~S.")
(string-pad (date->string (time-tai->date data) "~N'") 10 #\0)))]
[else (raise-exn exn:fail:snooze
(format "Expected data of type (U time-tai time-utc #f), given ~a\n" data))])]
[(equal? data (type-null type)) "NULL"]
[else (raise-exn exn:fail:snooze
(format "Expected data of type (U time-tai time-utc #f), given ~a\n" data))])])))
(define (unquote-data type data)
(let ([base (type-base type)])
(cond [(eq? base type:text)
(cond [(sql-null? data) #f]
[(bytes? data) (bytes->string/utf-8 data)]
[(string? data) data]
[else (raise-exn exn:fail:snooze (format "Cannot parse ~a: ~s" type data))])]
[(or (eq? base type:id)
(eq? base type:revision)
(eq? base type:integer)
(eq? base type:real))
(cond [(sql-null? data) #f]
[(bytes? data) (string->number (bytes->string/utf-8 data))]
[(string? data) (string->number data)]
[else (raise-exn exn:fail:snooze (format "Cannot parse ~a: ~s" type data))])]
[(eq? base type:symbol)
(cond [(sql-null? data) #f]
[(bytes? data) (string->symbol (bytes->string/utf-8 data))]
[(string? data) (string->symbol data)]
[else (raise-exn exn:fail:snooze (format "Cannot parse ~a: ~s" type data))])]
[(eq? base type:boolean)
(cond [(sql-null? data) #f]
[(equal? data #"t") #t]
[(equal? data "t") #t]
[else #f])]
[(eq? base type:time-tai)
(cond [(sql-null? data) #f]
[(bytes? data) (unquote-time-tai (bytes->string/utf-8 data))]
[(string? data) (unquote-time-tai data)]
[else (raise-exn exn:fail:snooze (format "Cannot parse ~a: ~s" type data))])])))
(define iso-8601-regexp-1 (pregexp "^([0-9]{4})-([0-9]{2})-([0-9]{2})[ T]([0-9]{2}):([0-9]{2}):([0-9]{2})"))
(define iso-8601-regexp-2 (pregexp "\\.([0-9]{1,9})$"))
(define (unquote-time-tai data)
(let ([matches1 (pregexp-match iso-8601-regexp-1 data)]
[matches2 (pregexp-match iso-8601-regexp-2 data)])
(if matches1
(date->time-tai (apply make-srfi:date
(append
(if matches2
(list (string->number (string-pad-right (cadr matches2) 9 #\0)))
(list 0))
(map string->number (reverse (cdr matches1)))
(list 0))))
(raise-exn exn:fail:snooze
(format "Expected ISO 8601 formatted string, received ~a" data)))))
(define (make-data-unquoter types)
(let ([types (list->vector types)])
(lambda (source)
(if source
(vector-map (lambda (index type val)
(unquote-data type val))
types
source)
#f))))
)])
(compound-unit/sig
(import)
(link (original : sql-quote^ (generic:sql-quote@))
(variation : sql-quote^ (mixin@ original)))
(export (open variation)))))
)