commit 870a499c1fbef28d728e8d312a3161c472036c70
parent c4b3eb2b27ea83d774a27dbaceafc12904901190
Author: Tomas Hlavaty <tom@logand.com>
Date:   Tue,  5 Mar 2013 22:05:50 +0100
added dbquery for common lisp
Diffstat:
| A | dbquery.asd |  |  | 36 | ++++++++++++++++++++++++++++++++++++ | 
| A | dbquery.lisp |  |  | 135 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
2 files changed, 171 insertions(+), 0 deletions(-)
diff --git a/dbquery.asd b/dbquery.asd
@@ -0,0 +1,36 @@
+;;; -*- lisp; -*-
+
+;;; Copyright (C) 2013 Tomas Hlavaty <tom@logand.com>
+;;;
+;;; Permission is hereby granted, free of charge, to any person
+;;; obtaining a copy of this software and associated documentation
+;;; files (the "Software"), to deal in the Software without
+;;; restriction, including without limitation the rights to use, copy,
+;;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;;; of the Software, and to permit persons to whom the Software is
+;;; furnished to do so, subject to the following conditions:
+;;;
+;;; The above copyright notice and this permission notice shall be
+;;; included in all copies or substantial portions of the Software.
+;;;
+;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+;;; DEALINGS IN THE SOFTWARE.
+
+(defpackage :dbquery-system
+  (:use :asdf :cl))
+
+(in-package :dbquery-system)
+
+(defsystem :dbquery
+  :description "dbquery for Common Lisp"
+  :author "Tomas Hlavaty <tom@logand.com>"
+  :maintainer "Tomas Hlavaty <tom@logand.com>"
+  :licence "MIT"
+  :serial t
+  :components ((:file "dbquery")))
diff --git a/dbquery.lisp b/dbquery.lisp
@@ -0,0 +1,135 @@
+;;; Copyright (C) 2013 Tomas Hlavaty <tom@logand.com>
+;;;
+;;; Permission is hereby granted, free of charge, to any person
+;;; obtaining a copy of this software and associated documentation
+;;; files (the "Software"), to deal in the Software without
+;;; restriction, including without limitation the rights to use, copy,
+;;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;;; of the Software, and to permit persons to whom the Software is
+;;; furnished to do so, subject to the following conditions:
+;;;
+;;; The above copyright notice and this permission notice shall be
+;;; included in all copies or substantial portions of the Software.
+;;;
+;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+;;; DEALINGS IN THE SOFTWARE.
+
+(defpackage :dbquery
+  (:use :cl))
+
+(in-package :dbquery)
+
+(defun call-with-program-output (command fn)
+  (let ((x (ccl:run-program (car command) (cdr command) :output :stream)))
+    (funcall fn (ccl:external-process-output-stream x))
+    #+nil
+    (unwind-protect (funcall fn (ccl:external-process-output-stream x))
+      (ccl:external-process-status ))))
+
+(defmacro with-program-output ((stream command) &body body)
+  `(call-with-program-output ,command (lambda (,stream) ,@body)))
+
+#+nil
+(time
+ (with-program-output (s (list "./dbquery-sqlite"
+                               "sqlite.db"
+                               "select 123, 1, 0, null, 12.34, 'hello'"))
+   (read s)))
+
+#+nil
+(time
+ (with-program-output (s (list "./dbquery-pg"
+                               "dbname='pokus' user='tomas'"
+                               "select 123, 1, 0, null, 12.34, 'hello'"))
+   (read s)))
+
+(defun make-concurrent-queue ()
+  (let ((x (cons nil nil))
+        (l (ccl:make-lock 'concurrent-queue-lock))
+        (s (ccl:make-semaphore)))
+    (setf (car x) x)
+    (lambda (&optional (value nil valuep))
+      (if valuep
+          (let ((y (cons value nil)))
+            (ccl:with-lock-grabbed (l)
+              (setf (cdar x) y
+                    (car x) y)
+              (ccl:signal-semaphore s))
+            value)
+          (do (done z)
+              (done z)
+            (ccl:wait-on-semaphore s)
+            (ccl:with-lock-grabbed (l)
+              (unless (eq x (car x))
+                (setq done t
+                      z (pop (cdr x)))
+                (unless (cdr x)
+                  (setf (car x) x)))))))))
+
+;; (setq q (make-concurrent-queue))
+;; (funcall q 1)
+;; (funcall q 2)
+;; (funcall q 3)
+;; (funcall q)
+
+(defun make-program-server (command args writer reader)
+  (let ((p (ccl:run-program command
+                            args
+                            :input :stream
+                            :output :stream
+                            :sharing :external
+                            :wait nil)))
+    (assert (eq :running (ccl:external-process-status p)))
+    (let ((wq (make-concurrent-queue)))
+      (ccl:process-run-function
+       'program-server-writer
+       (let ((s (ccl:external-process-input-stream p)))
+         (lambda ()
+           (do (x)
+               ((not (setq x (funcall wq)))
+                (close s))
+             (funcall writer x s)))))
+      (let ((l (ccl:make-lock 'program-server-lock))
+            (s (ccl:external-process-output-stream p)))
+        (lambda (&optional query)
+          (ccl:with-lock-grabbed (l)
+            (when wq
+              (cond
+                (query
+                 (funcall wq query)
+                 (funcall reader s))
+                (t (funcall wq nil)
+                   (setq wq nil)
+                   (ccl::external-process-wait p)
+                   (multiple-value-bind (status code) (ccl:external-process-status p)
+                     (assert (eq :exited status))
+                     (assert (zerop code))))))))))))
+
+(defun dbquery-writer (value stream)
+  (write-line value stream)
+  (finish-output stream))
+
+(defun dbquery-reader (stream) ;; TODO raise backend errors!
+  (let (*read-eval*)
+    (prog1 (read stream nil nil)
+      (assert (char= #\newline (read-char stream))))))
+
+(defun make-pg-server (command connection-info)
+  (make-program-server command (list connection-info) 'dbquery-writer 'dbquery-reader))
+
+;; (setq c (make-pg-server "cat" "-"))
+;; (funcall c "123")
+;; (funcall c nil)
+;; (funcall c)
+
+;; (setq c (make-pg-server "./dbquery-pg" "dbname='pokus' user='tomas'"))
+;; (funcall c "select 1, 2+3")
+;; (time (funcall c "select 4, 'hello'"))
+;; (funcall c nil)
+;; (funcall c)