aboutsummaryrefslogtreecommitdiff
path: root/transform/sql.scm
blob: a8962c8029b4b4e3805b680c45c1a19f92968e7c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
;;; Database helpers
;;;
;;; Most of these functions should have been a part of
;;; guile-dbi. Never too late to contribute upstream!

(define-module (transform sql)
  #:use-module (srfi srfi-1)
  #:use-module (srfi srfi-26)
  #:use-module (ice-9 match)
  #:use-module (dbi dbi)
  #:export (select-query
            call-with-database
            call-with-target-database
            sql-exec
            sql-fold
            sql-map
            sql-for-each
            sql-find))

;; A half-baked macro to make SQL SELECT queries a bit more
;; S-expression friendly
(define-syntax select-query
  (lambda (x)
    (syntax-case x ()
      ((_ fields tables raw-forms ...)
       #`(string-append "SELECT "
                        #,(syntax-case #'fields (distinct)
                            ((distinct _ ...)
                             "DISTINCT ")
                            (_ ""))
                        #,(string-join (filter-map (match-lambda
                                                     ('distinct #f)
                                                     (((query alias))
                                                      (format #f "~a AS ~a" query alias))
                                                     ((table column)
                                                      (format #f "~a.~a" table column))
                                                     ((table column alias)
                                                      (format #f "~a.~a AS ~a" table column alias))
                                                     (field-spec
                                                      (error "Invalid field specification" field-spec)))
                                                   (syntax->datum #'fields))
                                       ", ")
                        " FROM "
                        #,(string-join (map (match-lambda
                                              ((join table condition)
                                               (format #f "~a ~a ~a"
                                                       (case join
                                                         ((join) "JOIN")
                                                         ((left-join) "LEFT JOIN")
                                                         ((inner-join) "INNER JOIN")
                                                         (else (error "Invalid join operator" join)))
                                                       table condition))
                                              ((? symbol? table)
                                               (symbol->string table))
                                              (table-spec
                                               (error "Invalid table specification" table-spec)))
                                            (syntax->datum #'tables))
                                       " ")
                        #,(syntax-case #'(raw-forms ...) ()
                            (() "")
                            (_ " "))
                        raw-forms ...))
      (_ (error "Invalid SQL select query" (syntax->datum x))))))

(define (call-with-database backend connection-string proc)
  (let ((db #f))
    (dynamic-wind (lambda ()
                    (set! db (dbi-open backend connection-string)))
                  (cut proc db)
                  (cut dbi-close db))))

(define (database-check-status db)
  (match (dbi-get_status db)
    ((code . str)
     (unless (zero? code)
       (error str)))))

(define (sql-exec db statement)
  (dbi-query db statement)
  (database-check-status db))

(define (sql-fold proc init db statement)
  (sql-exec db statement)
  (let loop ((result init))
    (let ((row (dbi-get_row db)))
      (if row
          (loop (proc row result))
          result))))

(define (sql-map proc db statement)
  (sql-fold (lambda (row result)
              (cons (proc row) result))
            (list) db statement))

(define (sql-for-each proc db statement)
  (sql-fold (lambda (row _)
              (proc row))
            #f db statement))

(define (sql-find db statement)
  (sql-exec db statement)
  (dbi-get_row db))

(define (call-with-target-database connection-settings proc)
  (call-with-database "mysql" (string-join
                               (list (assq-ref connection-settings 'sql-username)
                                     (assq-ref connection-settings 'sql-password)
                                     (assq-ref connection-settings 'sql-database)
                                     "tcp"
                                     (assq-ref connection-settings 'sql-host)
                                     (number->string
                                      (assq-ref connection-settings 'sql-port)))
                               ":")
                      proc))