#lang racket/base
(require racket/match
(prefix-in srfi: srfi/19)
"sql-data.rkt")
(provide parse-char1
parse-decimal
parse-date
parse-time
parse-time-tz
parse-timestamp
parse-timestamp-tz
parse-interval)
(define (parse-char1 s)
(string-ref s 0))
(define (parse-decimal s)
(cond [(equal? s "NaN") +nan.0]
[(regexp-match #rx"^-?([0-9]*)$" s)
=> (lambda (m)
(string->number s))]
[(regexp-match #rx"^-?([0-9]*)\\.([0-9]*)$" s)
=> (lambda (m)
(+ (string->number (cadr m))
(parse-exact-fraction (caddr m))))]
[else (parse-error "numeric" s)]))
(define (parse-exact-fraction s)
(/ (string->number s)
(expt 10 (string-length s))))
(define (parse-date d)
(srfi-date->sql-date
(srfi:string->date d "~Y-~m-~d")))
(define time/ns-rx #rx"^[0-9]*:[0-9]*:[0-9]*\\.([0-9]*)")
(define timestamp/ns-rx #rx"^.* [0-9]*:[0-9]*:[0-9]*\\.([0-9]*)")
(define (ns-of t rx)
(let ([m (regexp-match rx t)])
(if m
(* #e1e9 (parse-exact-fraction (cadr m)))
0)))
(define (parse-time t)
(srfi-date->sql-time
(srfi:string->date t "~k:~M:~S")
(ns-of t time/ns-rx)))
(define (parse-time-tz t)
(srfi-date->sql-time-tz
(srfi:string->date t "~k:~M:~S~z")
(ns-of t time/ns-rx)))
(define (parse-timestamp t)
(srfi-date->sql-timestamp
(srfi:string->date t "~Y-~m-~d ~k:~M:~S")
(ns-of t timestamp/ns-rx)))
(define (parse-timestamp-tz t)
(srfi-date->sql-timestamp-tz
(srfi:string->date t "~Y-~m-~d ~k:~M:~S~z")
(ns-of t timestamp/ns-rx)))
(define interval-rx
(regexp
(string-append "^"
"(?:(-?[0-9]*) years? *)?"
"(?:(-?[0-9]*) mons? *)?"
"(?:(-?[0-9]*) days? *)?"
"(?:(-?)([0-9]*):([0-9]*):([0-9]*)(?:\\.([0-9]*))?)?"
"$")))
(define (parse-interval s)
(define (to-num m)
(if m (string->number m) 0))
(define match-result (regexp-match interval-rx s))
(match match-result
[(list _whole years months days tsign hours mins secs fsec)
(let* ([years (to-num years)]
[months (to-num months)]
[days (to-num days)]
[sg (if (equal? tsign "-") - +)]
[hours (sg (to-num hours))]
[mins (sg (to-num mins))]
[secs (sg (to-num secs))]
[nsecs (if fsec
(let ([flen (string-length fsec)])
(* (string->number (substring fsec 0 (min flen 9)))
(expt 10 (- 9 (min flen 9)))))
0)])
(sql-interval years months days hours mins secs nsecs))]))
(define (parse-error type rep)
(error 'query* "internal error: cannot parse as SQL type ~s: ~e"
type rep))
(provide marshal-decimal
marshal-date
marshal-time
marshal-time-tz
marshal-timestamp
marshal-timestamp-tz
marshal-interval
exact->decimal-string
exact->scaled-integer)
(define (marshal-decimal f i n)
(cond [(not (real? n))
(marshal-error f i "numeric" n)]
[(eqv? n +nan.0)
"NaN"]
[(or (eqv? n +inf.0) (eqv? n -inf.0))
(marshal-error f i "numeric" n)]
[(or (integer? n) (inexact? n))
(number->string n)]
[(exact? n)
(or (exact->decimal-string n)
(number->string (exact->inexact n)))]))
(define (exact->decimal-string n)
(let* ([whole-part (truncate n)]
[fractional-part (- (abs n) (abs whole-part))]
[scaled (exact->scaled-integer fractional-part)])
(and scaled
(let* ([ma (car scaled)]
[ex (cdr scaled)]
[ma-str (number->string ma)])
(if (zero? ex)
(number->string whole-part)
(string-append (number->string whole-part)
"."
(make-string (- ex (string-length ma-str)) #\0)
ma-str))))))
(define (exact->scaled-integer n)
(let* ([whole-part (truncate n)]
[fractional-part (- (abs n) (abs whole-part))]
[den (denominator fractional-part)])
(let*-values ([(den* fives) (factor-out den 5)]
[(den** twos) (factor-out den* 2)])
(and (= 1 den**)
(let ([tens (max fives twos)])
(cons (* n (expt 10 tens)) tens))))))
(define (factor-out-v1 n factor)
(define (loop n acc)
(let-values ([(q r) (quotient/remainder n factor)])
(if (zero? r)
(loop q (add1 acc))
(values n acc))))
(loop n 0))
(define (factor-out n factor)
(define (loop n factor)
(if (<= factor n)
(let*-values ([(q n) (loop n (* factor factor))]
[(q* r) (quotient/remainder q factor)])
(if (zero? r)
(values q* (+ n n 1))
(values q (+ n n))))
(values n 0)))
(loop n factor))
(define (marshal-date f i d)
(unless (sql-date? d)
(marshal-error f i "date" d))
(srfi:date->string (sql-datetime->srfi-date d) "~Y-~m-~d"))
(define (marshal-time f i t)
(unless (sql-time? t)
(marshal-error f i "time" t))
(srfi:date->string (sql-datetime->srfi-date t) "~k:~M:~S.~N"))
(define (marshal-time-tz f i t)
(unless (sql-time? t)
(marshal-error f i "time" t))
(srfi:date->string (sql-datetime->srfi-date t) "~k:~M:~S.~N~z"))
(define (marshal-timestamp f i t)
(unless (sql-timestamp? t)
(marshal-error f i "timestamp" t))
(srfi:date->string (sql-datetime->srfi-date t) "~Y-~m-~d ~k:~M:~S.~N"))
(define (marshal-timestamp-tz f i t)
(unless (sql-timestamp? t)
(marshal-error f i "timestamp" t))
(srfi:date->string (sql-datetime->srfi-date t) "~Y-~m-~d ~k:~M:~S.~N~z"))
(define (marshal-interval f i t)
(define (tag num unit)
(if (zero? num) "" (format "~a ~a " num unit)))
(match t
[(sql-interval years months days hours minutes seconds nanoseconds)
(string-append (tag years "years")
(tag months "months")
(tag days "days")
(tag hours "hours")
(tag minutes "minutes")
(tag seconds "seconds")
(tag (quotient nanoseconds 1000) "microseconds"))]
[else
(marshal-error f i "interval" t)]))
(define (marshal-error f i type datum)
(error f "cannot marshal as SQL type ~s: ~e"
type datum))