septeql.lisp (9531B)
1 (cl:in-package :septeql) 2 3 ;;;; literals 4 5 (defun lisp-to-sql-string (string) 6 "Does necessary quoting and/or escaping of STRING such that it can be 7 interpolated into a SQL statement as a string literal" 8 (with-output-to-string (o) 9 (princ "\'" o) 10 (loop for c across string do 11 (case c 12 ((#\' #\\) (princ c o) (princ c o)) 13 (t (princ c o)))) 14 (princ "\'" o))) 15 16 (defun lisp-to-sql-name (symbol) 17 "Transform the lisp SYMBOL into an identifier that the database will like." 18 (substitute #\_ #\- (symbol-name symbol))) 19 20 (defun sql-name-to-lisp (string) 21 "Tranform the database name STRING into a lisp-friendly keyword." 22 (intern (substitute #\- #\_ (string-upcase string)) :keyword)) 23 24 25 ;;;; parsing expressions that return scalar values. 26 27 (defparameter *infix-syntax* '(+ - / * = > < >= <= <> like ilike is and 28 or |::| ~ ~* ! !*) 29 "Functions to be transformed to infix syntax. Must be column-expr * column-expr => column-expr") 30 31 (defun infixize (op args) 32 (labels ((i (args) 33 (format nil "(~A ~A ~A)" 34 (if (> (length args) 2) 35 (i (butlast args)) 36 (first args)) 37 op (car (last args))))) 38 (i args))) 39 40 (defvar *scalar-translations* (make-hash-table :test 'equal)) 41 42 (defun translate-scalar (expr) 43 (typecase expr 44 (string (lisp-to-sql-string expr)) 45 (complex (error "Don't know how to translate complex numbers to SQL")) 46 (integer (format nil "~S" expr)) 47 (real (format nil "~S" (coerce expr 'single-float))) 48 (null "NULL") 49 (boolean "TRUE") 50 ; (keyword (lisp-to-sql-string (string expr))) 51 (symbol (lisp-to-sql-name expr)) 52 (cons 53 (destructuring-bind (op &rest args) expr 54 (cond 55 ((member op *infix-syntax* :test 'string-equal) 56 (infixize op (mapcar #'translate-scalar args))) 57 (t 58 (let ((func (gethash (symbol-name op) *scalar-translations*))) 59 (if func 60 (apply func args) 61 (format nil "~A(~{~A~^,~})" 62 (lisp-to-sql-name op ) 63 (mapcar #'translate-scalar args)))))))))) 64 65 (defun make-scalar-translator (name lambda) 66 (setf (gethash (symbol-name name) *scalar-translations*) lambda)) 67 68 (defmacro define-scalar-translator (name args &body forms) 69 `(make-scalar-translator (quote ,name) (lambda ,args ,@forms))) 70 71 (define-scalar-translator position (needle haystack) 72 (format nil "position(~A in ~A)" 73 (translate-scalar needle) 74 (translate-scalar haystack))) 75 76 77 (define-scalar-translator as (column-expr alias) 78 (format nil "~A AS ~A" (translate-scalar column-expr) (lisp-to-sql-name alias))) 79 80 (define-scalar-translator cast (column-expr type) 81 (format nil "cast(~A as ~A)" (translate-scalar column-expr) 82 (lisp-to-sql-name type))) 83 84 85 (define-scalar-translator case (&rest clauses) 86 (with-output-to-string (o) 87 (princ "(CASE " o) 88 (loop for (expr result) in clauses 89 if (and (symbolp expr) (member expr '(t else) :test #'string-equal)) 90 do (format o "ELSE ~A " (translate-scalar result)) 91 else do (format o "WHEN ~A THEN ~A " 92 (translate-scalar expr) 93 (translate-scalar result))) 94 (princ "END) " o))) 95 96 ;; SQL's CASE is more like CL's COND, so define the latter as an alias 97 (define-scalar-translator cond (&rest clauses) 98 (translate-scalar `(case ,@clauses))) 99 100 ;;; we *could* use the zero-length symbol for string concatenation, 101 ;;; relying on the printer to render it as ``||'', but that's arguably 102 ;;; kinda tacky. So, ++ for sticking strings together 103 104 (define-scalar-translator ++ (&rest columns) 105 (infixize "||" (mapcar #'translate-scalar columns))) 106 107 (define-scalar-translator in (column-expr choices) 108 (format nil "~A IN (~{~A~^,~})" (translate-scalar column-expr) 109 (mapcar #'translate-scalar choices))) 110 111 112 ;;;; and relations 113 114 115 ;;; this models a sql SELECT statement 116 ;;; XXX we don't support HAVING, mostly becaue I don't understand it 117 (defclass sql () 118 ((distinct :initarg :distinct) 119 (attributes :initarg :attributes) 120 (from :initarg :from ) 121 (where :initarg :where) 122 (group-by :initarg :group-by) 123 (order-by :initarg :order-by) 124 (start :initarg :start) ; LIMIT and OFFSET are 125 (end :initarg :end) ; computed with these 126 )) 127 128 (defmacro sv (ob slot &optional (default nil default-p)) 129 (if default-p 130 `(if (slot-boundp ,ob ,slot) (slot-value ,ob ,slot) ,default) 131 `(slot-value ,ob ,slot))) 132 133 (defun any-slot-bound-p (ob slots) 134 (some (lambda (s) (slot-boundp ob s)) slots)) 135 136 (defvar *rel-translations* (make-hash-table :test 'equal)) 137 138 (defun parse-relation (s) 139 (etypecase s 140 (cons (destructuring-bind (op &rest args) s 141 (apply (gethash (symbol-name op) *rel-translations*) args))) 142 (symbol (make-instance 'sql :from s)))) 143 144 (defmacro define-translator (op args &body forms) 145 `(setf (gethash (symbol-name ',op) *rel-translations*) 146 (lambda ,args ,@forms))) 147 148 (define-translator select (relation predicate) 149 (let ((s (parse-relation relation))) 150 (when (any-slot-bound-p s '(start end group-by)) 151 (setf s (make-instance 'sql :from s))) 152 (if (slot-boundp s 'where) 153 (setf (sv s 'where) `(and ,(sv s 'where) ,predicate)) 154 (setf (sv s 'where) predicate)) 155 s)) 156 157 (define-translator project (attributes relation) 158 (let ((s (parse-relation relation))) 159 (setf (sv s 'attributes) attributes) 160 s)) 161 162 (define-translator join (rel1 rel2 expr) 163 (let ((l (parse-relation rel1)) 164 (r (parse-relation rel2))) 165 (when (any-slot-bound-p l '(start end group-by attributes order-by where)) 166 ;; _any_ slot other than FROM 167 (setf l (make-instance 'sql :from l))) 168 (when (any-slot-bound-p r '(start end group-by attributes order-by where)) 169 (setf r (make-instance 'sql :from r))) 170 (make-instance 'sql :from `(join ,(sv l 'from) ,(sv r 'from) ,expr)))) 171 172 (define-translator left-join (rel1 rel2 expr) 173 (let* ((j (parse-relation `(join ,rel1 ,rel2 ,expr))) 174 (from (sv j 'from))) 175 (setf (sv j 'from) `(left-join ,@(cdr from))) 176 j)) 177 178 (define-translator distinct (rel) 179 (let ((r (parse-relation rel))) 180 (setf (sv r 'distinct) t) 181 r)) 182 183 (define-translator group (aggregates dividers relation) 184 (let ((l (parse-relation relation))) 185 (when (any-slot-bound-p l '(start end group-by attributes order-by)) 186 (setf l (make-instance 'sql :from l))) 187 (setf (sv l 'attributes) 188 (append aggregates dividers) 189 (sv l 'group-by) dividers) 190 l)) 191 192 193 (define-translator order (relation attribute &optional direction) 194 (let ((sql (parse-relation relation))) 195 (when (any-slot-bound-p sql '(order-by start end)) 196 (setf sql (make-instance 'sql :from sql))) 197 (setf (slot-value sql 'order-by) 198 (cons attribute (or direction :asc))) 199 sql)) 200 201 (define-translator rename (relation name) 202 (let ((sql (parse-relation relation))) 203 (cond ((any-slot-bound-p sql 204 '(start end group-by attributes order-by where)) 205 ;; _any_ slot other than FROM 206 (make-instance 'sql :from `(rename ,sql ,name))) 207 (t 208 (setf (sv sql 'from) 209 `(rename ,(sv sql 'from) ,name)) 210 sql)))) 211 212 (defun min~ (&rest args) 213 (let ((real-args (remove-if #'not args))) 214 (and (car real-args) (apply #'min real-args)))) 215 216 (define-translator limit (relation start end) 217 (let ((sql (parse-relation relation))) 218 (let ((start (max start (sv sql 'start 0))) 219 (end (min~ end (sv sql 'end nil)))) 220 ;; the effect of nested LIMIT clauses is to use the narrowest ones 221 (setf (sv sql 'start) start 222 (sv sql 'end) end) 223 sql))) 224 225 ;;; This is a less useful function than its name would suggest. 226 ;;; It's really only used to give a somewhat sensible name to a 227 ;;; subselect where the user hasn't provided any 228 (defun sql-name (sql) 229 (cond ((typep sql 'sql) (sql-name (sv sql 'from))) 230 ((symbolp sql) (lisp-to-sql-name sql)) 231 ((eql (car sql) 'join) (gensym)) 232 ((eql (car sql) 'rename) (third sql)) 233 (t (error "no name")))) 234 235 (defun from-clause-as-string (from) 236 (cond ((typep from 'sql) 237 (format nil "(~A) ~A " (as-string from) (sql-name from))) 238 ((symbolp from) (lisp-to-sql-name from)) 239 ((eql (car from) 'rename) 240 (let ((rel (second from)) 241 (name (lisp-to-sql-name (third from)))) 242 (if (typep rel 'sql) 243 (format nil "(~A) ~A " (as-string rel) name) 244 (format nil "~A ~A " (from-clause-as-string rel) name)))) 245 ((eql (car from) 'join) 246 (format nil "(~A JOIN ~A ON ~A)" 247 (from-clause-as-string (second from)) 248 (from-clause-as-string (third from)) 249 (translate-scalar (fourth from)))) 250 ((eql (car from) 'left-join) 251 (format nil "(~A LEFT JOIN ~A ON ~A)" 252 (from-clause-as-string (second from)) 253 (from-clause-as-string (third from)) 254 (translate-scalar (fourth from)))) 255 (t (error "buggered if I know")))) 256 257 (defun as-string (sql) 258 (with-output-to-string (s) 259 (format s "SELECT ~A ~{~A~^,~} " 260 (if (sv sql 'distinct nil) "DISTINCT" "") 261 (or (mapcar #'translate-scalar (sv sql 'attributes nil)) '(*))) 262 (format s "FROM ~A " (from-clause-as-string (sv sql 'from))) 263 (let* ((nilg (gensym)) 264 (w (sv sql 'where nilg))) 265 (unless (eql w nilg) (format s "WHERE ~A " (translate-scalar w)))) 266 (let ((group (sv sql 'group-by nil))) 267 (when group (format s "GROUP BY ~{~A~^,~} " 268 (mapcar #'translate-scalar group)))) 269 (let ((order (sv sql 'order-by nil))) 270 (when order (format s "ORDER BY ~A ~A " 271 (translate-scalar (car order)) 272 (symbol-name (cdr order))))) 273 (let* ((offset (sv sql 'start 0)) 274 (end (sv sql 'end nil))) 275 (unless (zerop offset) (format s "OFFSET ~A " offset)) 276 (if end (format s "LIMIT ~A " (- end offset)))))) 277 278 (defun to-sql (relation) 279 (as-string (parse-relation relation)))