Skip to content

Commit a0fb9dd

Browse files
authored
Merge pull request fukamachi#150 from fukamachi/cursor
Cursor support (only for PostgreSQL)
2 parents 596b074 + e31f7a5 commit a0fb9dd

File tree

5 files changed

+118
-31
lines changed

5 files changed

+118
-31
lines changed

mito-core.asd

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
:version "0.2.0"
33
:author "Eitaro Fukamachi"
44
:license "LLGPL"
5-
:depends-on ((:version "dbi" "0.10.0")
5+
:depends-on ((:version "dbi" "0.11.1")
66
"sxql"
77
"cl-ppcre"
88
"closer-mop"

qlfile.lock

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
("cl-dbi" .
66
(:class qlot/source/ql:source-ql-upstream
77
:initargs nil
8-
:version "ql-upstream-2ff41f0706180e140a31b844da4f0272e1a281cd"
8+
:version "ql-upstream-f58761b4da39e0559fcfbd744fa6f024182c6d94"
99
:remote-url "https://github.com/fukamachi/cl-dbi.git"))
1010
("cl-mysql" .
1111
(:class qlot/source/ql:source-ql-upstream

src/core/dao.lisp

+56-7
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,16 @@
2828
#:last-insert-id
2929
#:execute-sql
3030
#:retrieve-by-sql
31-
#:table-exists-p)
31+
#:table-exists-p
32+
#:ensure-sql)
3233
(:import-from #:mito.logger
3334
#:with-sql-logging)
3435
(:import-from #:mito.util
36+
#:lispify
3537
#:unlispify
3638
#:symbol-name-literally
37-
#:ensure-class)
39+
#:ensure-class
40+
#:execute-with-retry)
3841
(:import-from #:trivia
3942
#:match
4043
#:guard)
@@ -58,7 +61,8 @@
5861
#:count-dao
5962
#:recreate-table
6063
#:ensure-table-exists
61-
#:deftable))
64+
#:deftable
65+
#:do-cursor))
6266
(in-package #:mito.dao)
6367

6468
(defun foreign-value (obj slot)
@@ -198,6 +202,33 @@
198202
(update-dao obj)
199203
(insert-dao obj))))
200204

205+
(defstruct mito-cursor
206+
cursor
207+
fields
208+
class)
209+
210+
(defun select-by-sql-as-cursor (class sql &key binds)
211+
(multiple-value-bind (sql yield-binds)
212+
(ensure-sql sql)
213+
(let* ((cursor (dbi:make-cursor *connection* sql))
214+
(cursor (execute-with-retry cursor (or binds yield-binds))))
215+
(make-mito-cursor :cursor cursor
216+
:fields (mapcar (lambda (column-name)
217+
(intern (lispify (string-upcase column-name)) :keyword))
218+
(dbi.driver:query-fields cursor))
219+
:class class))))
220+
221+
(defun fetch-dao-from-cursor (cursor)
222+
(let ((fields (mito-cursor-fields cursor))
223+
(row (dbi:fetch (mito-cursor-cursor cursor)
224+
:format :values)))
225+
(when row
226+
(apply #'make-dao-instance (mito-cursor-class cursor)
227+
(loop for field in fields
228+
for value in row
229+
collect field
230+
collect value)))))
231+
201232
(defun select-by-sql (class sql &key binds)
202233
(mapcar (lambda (result)
203234
(apply #'make-dao-instance class result))
@@ -305,6 +336,8 @@
305336
(expand-op arg class)) args)))
306337
(otherwise object))))
307338

339+
(defparameter *want-cursor* nil)
340+
308341
(defmacro select-dao (class &body clauses)
309342
(with-gensyms (sql clause results include-classes foreign-class)
310343
(once-only (class)
@@ -327,10 +360,12 @@
327360
(dolist (,clause (list ,@clauses))
328361
(when ,clause
329362
(add-child ,sql ,clause)))
330-
(let ((,results (select-by-sql ,class ,sql)))
331-
(dolist (,foreign-class (remove-duplicates ,include-classes))
332-
(include-foreign-objects ,foreign-class ,results))
333-
(values ,results ,sql))))))))))
363+
(if *want-cursor*
364+
(select-by-sql-as-cursor ,class ,sql)
365+
(let ((,results (select-by-sql ,class ,sql)))
366+
(dolist (,foreign-class (remove-duplicates ,include-classes))
367+
(include-foreign-objects ,foreign-class ,results))
368+
(values ,results ,sql)))))))))))
334369

335370
(defun where-and (fields-and-values class)
336371
(when fields-and-values
@@ -417,3 +452,17 @@
417452
,@(unless (find :conc-name options :key #'car)
418453
`((:conc-name ,(intern (format nil "~@:(~A-~)" name) (symbol-package name)))))
419454
,@options))
455+
456+
(defmacro do-cursor ((dao select &optional index) &body body)
457+
(with-gensyms (main cursor)
458+
`(flet ((,main ()
459+
(let* ((*want-cursor* t)
460+
(,cursor ,select))
461+
(loop ,@(and index `(for ,index from 0))
462+
for ,dao = (fetch-dao-from-cursor ,cursor)
463+
while ,dao
464+
do (progn ,@body)))))
465+
(if (dbi:in-transaction *connection*)
466+
(,main)
467+
(dbi:with-transaction *connection*
468+
(,main))))))

src/core/db.lisp

+19-22
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,18 @@ Note that DBI:PREPARE-CACHED is added CL-DBI v0.9.5.")
115115
:format :plist)
116116
t))))
117117

118+
(defun sxql-to-sql (sql)
119+
(with-quote-char (sxql:yield sql)))
120+
121+
(defun ensure-sql (sql)
122+
(etypecase sql
123+
(string sql)
124+
((or sql-statement
125+
composed-statement
126+
;; For UNION [ALL]
127+
conjunctive-op)
128+
(sxql-to-sql sql))))
129+
118130
(defgeneric execute-sql (sql &optional binds)
119131
(:method ((sql string) &optional binds)
120132
(check-connected)
@@ -124,10 +136,9 @@ Note that DBI:PREPARE-CACHED is added CL-DBI v0.9.5.")
124136
(query-row-count query))))
125137
(:method ((sql sql-statement) &optional binds)
126138
(declare (ignore binds))
127-
(with-quote-char
128-
(multiple-value-bind (sql binds)
129-
(sxql:yield sql)
130-
(execute-sql sql binds)))))
139+
(multiple-value-bind (sql binds)
140+
(sxql-to-sql sql)
141+
(execute-sql sql binds))))
131142

132143
(defun lispified-fields (query)
133144
(mapcar (lambda (field)
@@ -203,25 +214,11 @@ Note that DBI:PREPARE-CACHED is added CL-DBI v0.9.5.")
203214
(:plist t)
204215
(otherwise nil)))))
205216
(retrieve-from-query query format))))
206-
(:method ((sql sql-statement) &rest args &key binds &allow-other-keys)
207-
(assert (null binds))
208-
(with-quote-char
209-
(multiple-value-bind (sql binds)
210-
(sxql:yield sql)
211-
(apply #'retrieve-by-sql sql :binds binds args))))
212-
(:method ((sql composed-statement) &rest args &key binds &allow-other-keys)
213-
(assert (null binds))
214-
(with-quote-char
215-
(multiple-value-bind (sql binds)
216-
(sxql:yield sql)
217-
(apply #'retrieve-by-sql sql :binds binds args))))
218-
;; For UNION [ALL]
219-
(:method ((sql conjunctive-op) &rest args &key binds &allow-other-keys)
217+
(:method (sql &rest args &key binds &allow-other-keys)
220218
(assert (null binds))
221-
(with-quote-char
222-
(multiple-value-bind (sql binds)
223-
(sxql:yield sql)
224-
(apply #'retrieve-by-sql sql :binds binds args)))))
219+
(multiple-value-bind (sql binds)
220+
(ensure-sql sql)
221+
(apply #'retrieve-by-sql sql :binds binds args))))
225222

226223
(defun acquire-advisory-lock (conn id)
227224
(funcall

t/dao.lisp

+41
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,47 @@
240240

241241
(dolist (class-name '(user-setting user tweet friend-relationship tweet2))
242242
(setf (find-class class-name) nil))
243+
244+
(disconnect-toplevel))
245+
246+
(deftest cursor
247+
(setf *connection* (connect-to-testdb :postgres))
248+
(when (find-class 'user nil)
249+
(setf (find-class 'user) nil))
250+
(defclass user ()
251+
((name :col-type :text
252+
:initarg :name))
253+
(:metaclass dao-table-class))
254+
(mito:execute-sql "DROP TABLE IF EXISTS \"user\"")
255+
(mito:ensure-table-exists 'user)
256+
(mito:create-dao 'user :name "Eitaro")
257+
(mito:create-dao 'user :name "Btaro")
258+
(mito:create-dao 'user :name "Charlie")
259+
(dbi:with-transaction *connection*
260+
(let* ((mito.dao::*want-cursor* t)
261+
(cursor (mito.dao:select-dao 'user
262+
(where (:like :name "%aro")))))
263+
(ok (typep cursor 'mito.dao::mito-cursor))
264+
(let ((row (mito.dao::fetch-dao-from-cursor cursor)))
265+
(ok (typep row 'user))
266+
(ok (equal (slot-value row 'name) "Eitaro")))
267+
(let ((row (mito.dao::fetch-dao-from-cursor cursor)))
268+
(ok (typep row 'user))
269+
(ok (equal (slot-value row 'name) "Btaro")))
270+
(ok (null (mito.dao::fetch-dao-from-cursor cursor)))))
271+
272+
(let ((records '()))
273+
(do-cursor (dao (mito.dao:select-dao 'user) i)
274+
(push (cons i dao) records)
275+
(when (<= 1 i)
276+
(return)))
277+
(ok (= (length records) 2))
278+
(ok (every (lambda (record)
279+
(typep (cdr record) 'user))
280+
records)))
281+
282+
(when (find-class 'user nil)
283+
(setf (find-class 'user) nil))
243284
(disconnect-toplevel))
244285

245286
(deftest foreign-slots

0 commit comments

Comments
 (0)