diff --git a/README.markdown b/README.markdown index 3d89b3e..d5930a0 100644 --- a/README.markdown +++ b/README.markdown @@ -608,6 +608,24 @@ Since `insert-dao`, `update-dao` and `delete-dao` are defined as generic functio ;=> # ``` +### Iteration (Experimental) + +`do-select` is a macro to iterate over results from SELECT one by one. It's the same as `cl:loop`, but it uses CURSOR for PostgreSQL, which can reduce memory usage since it won't load whole results on memory. + +```common-lisp +(do-select (dao (select-dao 'user + (where (:< "2024-07-01" :created_at)))) + ;; Can be a more complex condition + (when (equal (user-name dao) "Eitaro") + (return dao))) + +;; Same but without using CURSOR +(loop for dao in (select-dao 'user + (where (:< "2024-07-01" :created_at))) + when (equal (user-name dao) "Eitaro") + do (return dao)) +``` + ## Installation ```common-lisp diff --git a/src/core/dao.lisp b/src/core/dao.lisp index 0dc3803..3d14cca 100644 --- a/src/core/dao.lisp +++ b/src/core/dao.lisp @@ -62,7 +62,7 @@ #:recreate-table #:ensure-table-exists #:deftable - #:do-cursor)) + #:do-select)) (in-package #:mito.dao) (defun foreign-value (obj slot) @@ -453,16 +453,28 @@ `((:conc-name ,(intern (format nil "~@:(~A-~)" name) (symbol-package name))))) ,@options)) -(defmacro do-cursor ((dao select &optional index) &body body) - (with-gensyms (main cursor) - `(flet ((,main () - (let* ((*want-cursor* t) - (,cursor ,select)) - (loop ,@(and index `(for ,index from 0)) - for ,dao = (fetch-dao-from-cursor ,cursor) - while ,dao - do (progn ,@body))))) - (if (dbi:in-transaction *connection*) - (,main) - (dbi:with-transaction *connection* - (,main)))))) +(defmacro do-select ((dao select &optional index) &body body) + (with-gensyms (main main-body select-fn cursor i) + `(block nil + (labels ((,main-body (,dao ,(or index i)) + ,@(and (not index) + `((declare (ignore ,i)))) + ,@body) + (,select-fn () ,select) + (,main () + (case (dbi:connection-driver-type *connection*) + (:postgres + (let ((,cursor (let ((*want-cursor* t)) + (,select-fn)))) + (loop ,@(and index `(for ,i from 0)) + for ,dao = (fetch-dao-from-cursor ,cursor) + while ,dao + do (,main-body ,dao ,i)))) + (otherwise + (loop ,@(and index `(for ,i from 0)) + for ,dao in (,select-fn) + do (,main-body ,dao ,i)))))) + (if (dbi:in-transaction *connection*) + (,main) + (dbi:with-transaction *connection* + (,main))))))) diff --git a/t/dao.lisp b/t/dao.lisp index 744a266..e9f1cdb 100644 --- a/t/dao.lisp +++ b/t/dao.lisp @@ -270,8 +270,8 @@ (ok (null (mito.dao::fetch-dao-from-cursor cursor))))) (let ((records '())) - (do-cursor (dao (mito.dao:select-dao 'user) i) - (push (cons i dao) records) + (do-select (user (mito.dao:select-dao 'user) i) + (push (cons i user) records) (when (<= 1 i) (return))) (ok (= (length records) 2))