cl-2sql

Lisp to SQL compiler for Common Lisp
git clone https://logand.com/git/cl-2sql.git/
Log | Files | Refs | README | LICENSE

commit fcc0be4d35710dc0c49bde833ff23b471e671d09
Author: Tomas Hlavaty <tom@logand.com>
Date:   Sun, 31 Jul 2011 05:56:34 +0200

Initial commit

Diffstat:
AREADME | 21+++++++++++++++++++++
Acl-2sql.asd | 13+++++++++++++
Acore.lisp | 45+++++++++++++++++++++++++++++++++++++++++++++
Amacros.lisp | 181+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apackages.lisp | 13+++++++++++++
5 files changed, 273 insertions(+), 0 deletions(-)

diff --git a/README b/README @@ -0,0 +1,21 @@ +- meta-syntax with keywords (small, fast, not extensible) +- syntax with macros (M-. etc) + +- programmable and evolvable sql syntax +- embracing many sql dialects + +- no custom reader + - http://clsql.b9.com/manual/csql-find.html + - hu.dwim.rdbms +- not keyword based + - http://marijnhaverbeke.nl/postmodern/s-sql.html + - completion, M-. +- not class based + - bloat (rdbms) +- the above => arbitrary magic + - we are defining syntax, right? + - make use of lisp then (macros) + +- join proper prefix + +- minimal core meta-syntax and user macro layer diff --git a/cl-2sql.asd b/cl-2sql.asd @@ -0,0 +1,13 @@ +;; -*- lisp; -*- + +(asdf:defsystem :cl-2sql + :description "cl-2sql -- Lisp to SQL compiler for Common Lisp." + :version "" + :author "Tomas Hlavaty" + :maintainer "Tomas Hlavaty" + :licence "" + :depends-on (:split-sequence) + :serial t + :components ((:file "packages") + (:file "core") + (:file "macros"))) diff --git a/core.lisp b/core.lisp @@ -0,0 +1,45 @@ +(in-package :2sql) + +(defun princ-string (string stream) + (write-char #\' stream) + (princ string stream) ;; TODO escape + (write-char #\' stream)) + +(defun princ-symbol (x stream) + (format stream "~(~{~a_~}~)" ;; TODO . + (split-sequence:split-sequence #\. (symbol-name x)))) + +(defun 2sql (form stream &optional (princ-symbol 'princ-symbol)) + (let ((lvars nil)) + (labels ((rec (x) + (if (atom x) + (etypecase x + (string (princ-string x stream)) + (keyword (princ x stream)) + (symbol (funcall princ-symbol x stream)) + (integer (princ x stream))) + (ecase (car x) + (:lvar + (push (cdr x) lvars) + (format stream ":~d" (length lvars))) + (:par + (write-char #\( stream) + (rec (cons :lst (cons nil (cdr x)))) + (write-char #\) stream)) + (:lst + (let ((sep (cadr x)) + (i 0)) + (dolist (x (cddr x)) + (when (plusp i) + (princ (or sep " ") stream)) + (rec x) + (incf i)))))))) + (rec form) + lvars))) + +(defun 2sql-string (form &optional (princ-symbol 'princ-symbol)) + (let (lvars) + (values + (with-output-to-string (s) + (setq lvars (2sql (macroexpand form) s princ-symbol))) + lvars))) diff --git a/macros.lisp b/macros.lisp @@ -0,0 +1,181 @@ +(in-package :2sql-macros) + +(cl:defmacro defsyntax (name args &body body) + `(cl:progn + (cl:export ',name) + (cl:defmacro ,name ,args ,@body))) + +(defsyntax lst (sep &body body) + `(:lst ,sep ,@(cl:mapcar + (cl:lambda (x) (cl:if (cl:atom x) x (cl:macroexpand x))) + body))) + +(defsyntax prefix (name &body args) + `(lst nil ,name ,@args)) + +(defsyntax infix (name &body args) + `(lst ,name ,@args)) + +(defsyntax clause (name &body body) + `(lst nil ,name ,@body)) + +(defsyntax not (exp) + `(prefix :not ,exp)) + +(defsyntax between (what left right) + `(lst nil ,what :between ,left :and ,right)) + +(defsyntax + (&body args) + `(infix " + " ,@args)) + +(defsyntax - (&body args) + `(infix " - " ,@args)) + +(defsyntax = (lexp rexp) + `(infix " = " ,lexp ,rexp)) + +(defsyntax and (&body args) + `(infix " AND " ,@args)) + +(defsyntax or (&body args) + `(infix " OR " ,@args)) + +(defsyntax like (text pattern) + `(infix " LIKE " ,text ,pattern)) + +(defsyntax ilike (text pattern) + `(infix " ILIKE " ,text ,pattern)) + +;; (defsyntax relike (text pattern) ;;regexp like +;; `(infix " ILIKE " ,text ,pattern)) + +(defsyntax where (exp) + `(clause :where ,exp)) + +(defsyntax from (&body exp) + `(clause :from ,@exp)) + +(defsyntax cols (cols) + `(lst ", " ,@cols)) + +(defsyntax select (cols &body body) + `(clause :select (cols ,cols) ,@body)) + +(defsyntax delete-from (tab &optional where) + `(clause :delete :from ,tab ,@(cl:when where (cl:list where)))) + +(defsyntax distinct (col) + `(lst nil :distinct ,col)) + +(defsyntax join (kind left right on) + `(lst nil ,left ,kind :join ,right :on ,on)) + +(defsyntax inner-join (left right on) + `(join :inner ,left ,right ,on)) + +(defsyntax natural-join (left right on) + `(join :natural ,left ,right ,on)) + +(defsyntax cross-join (left right on) + `(join :cross ,left ,right ,on)) + +(defsyntax left-join (left right on) + `(join :left ,left ,right ,on)) + +(defsyntax right-join (left right on) + `(join :right ,left ,right ,on)) + +(defsyntax full-join (left right on) + `(join (lst nil :full :outer) ,left ,right ,on)) + +(defsyntax union (&body select-forms) + `(lst " UNION " ,@select-forms)) + +(defsyntax union-all (&body select-forms) + `(lst " UNION ALL " ,@select-forms)) + +(defsyntax intersect (&body select-forms) + `(lst " INTERSECT " ,@select-forms)) + +(defsyntax intersect-all (&body select-forms) + `(lst " INTERSECT ALL " ,@select-forms)) + +(defsyntax except (&body select-forms) + `(lst " EXCEPT " ,@select-forms)) + +(defsyntax except-all (&body select-forms) + `(lst " EXCEPT " ,@select-forms)) + +(defsyntax drop-if-exists (name kind if-exists &body body) + `(clause :drop ,kind ,@(cl:when if-exists '(:if :exists)) ,name ,@body)) + +(defsyntax drop-table (name &optional if-exists cascade) + `(drop-if-exists ,name :table ,if-exists ,@(cl:when cascade '(:cascade)))) + +(defsyntax drop-view (name &optional if-exists) + `(drop-if-exists ,name :view ,if-exists)) + +(defsyntax drop-index (name &optional if-exists) + `(drop-if-exists ,name :index ,if-exists)) + +(defsyntax drop-sequence (name &optional if-exists) + `(drop-if-exists ,name :view ,if-exists)) + +(defsyntax create-index (name unique tab &body cols) + `(clause :create ,@(cl:when unique '(:unique)) :index ,name + :on ,tab (par ,@cols))) + +(defsyntax insert-into (tab cols vals) + `(clause :insert :into ,tab (par ,@cols) :values (par ,@vals))) + +(defsyntax create-sequence (name &optional temp) + `(clause :create ,@(cl:when temp '(:temporary)) :sequence ,name)) + +(defsyntax curval (seq) + `(function :curval ,seq)) + +(defsyntax nextval (seq) + `(function :nextval ,seq)) + +(defsyntax setval (seq val &optional current) + `(function :curval ,seq ,val ,@(cl:unless current '(:false)))) + +(defsyntax cond (&body cases) + `(lst nil :case + ,@(cl:loop + :for (c b) :in cases + :appending (cl:if (cl:eq cl:t c) + `(:else ,b) + `(:when ,c :then ,b))) + :end)) + +(defsyntax zerop (number) + `(= 0 ,number)) + +(defsyntax par (&body body) + `(:par ,@(cl:mapcar + (cl:lambda (x) (cl:if (cl:atom x) x (cl:macroexpand x))) body))) + +(defsyntax function (name &body args) + `(lst nil ,name (par (lst ", " ,@args)))) + +(defsyntax count (x) + `(function :count ,x)) + +(defsyntax asc (exp) + `(prefix :asc ,exp)) + +(defsyntax desc (exp) + `(prefix :desc ,exp)) + +(defsyntax as (tab alias) + `(lst nil ,tab ,alias)) + +;; order-by group-by having +;; min max avg sum + +(defsyntax lvar (name &optional type) + `(:lvar ,name ,@(cl:when type (cl:list type)))) + +;; (defsyntax type (&body body) +;; `(:type ,@body)) diff --git a/packages.lisp b/packages.lisp @@ -0,0 +1,13 @@ +(in-package :cl-user) + +(defpackage 2sql + (:use :cl) + (:export #:2sql #:2sql-string)) + +(defpackage 2sql-macros + (:use) + (:nicknames :q) ;; TODO really? + (:import-from :cl #:&optional #:&body #:nil)) + +(defpackage 2sql-tests + (:use :cl :2sql))