septeql

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

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)))