2010-05-02 69 views
6

我正在使用clojure.contrib.sql从SQLite数据库中获取一些记录。Clojure中的迭代器块?

(defn read-all-foo [] 
    (with-connection *db* 
    (with-query-results res ["select * from foo"] 
     (into [] res)))) 

现在,我真的不希望在函数返回(即我希望保持它的懒惰)之前实现全序列,但如果我回到res直接或包裹起某种懒包装的(例如,我想对结果序列进行一定的map转换),SQL相关的绑定将被重置,并且在我返回后连接将被关闭,因此实现该序列将引发异常。

如何将整个函数封装在闭包中并返回一种迭代器块(如C#或Python中的yield)?

或者还有另一种方法从这个函数返回一个懒惰的序列?

回答

0

我从来没有在Clojure中使用过SQLite,但我的猜测是,with-connection在评估正文时会关闭连接。因此,如果您想保持打开状态,则需要自行管理连接,并在完成对感兴趣的元素的阅读后关闭它。

+0

这就是我想要做的,但我希望它通过迭代器块闭包(或实现懒惰seq接口的某种其他形式的闭包)自动处理。 – 2010-05-02 14:50:35

7

with-query-results返回的值可能已经很慢要得到。正如你所说,只要手柄开着,懒惰就会起作用。这是没有办法的。如果数据库句柄已关闭,则无法从数据库读取数据。

如果您需要在关闭手柄后进行I/O操作并保留数据,请打开手柄,快速滑动(消除懒惰),关闭手柄,然后处理结果。如果你想迭代一些数据而不一次全部保存在内存中,那么打开句柄,在数据上获得一个惰性seq,doseq,然后关闭句柄。

所以,如果你想要做的每行的东西(的副作用),丢弃结果没有吃整个结果集到内存中,然后你可以这样做:如果你想让你的数据

(defn do-something-with-all-foo [f] 
    (let [sql "select * from foo"] 
    (with-connection *db* 
     (with-query-results res [sql] 
     (doseq [row res] 
      (f row)))))) 

user> (do-something-with-all-foo println) 
{:id 1} 
{:id 2} 
{:id 3} 
nil 

;; transforming the data as you go 
user> (do-something-with-all-foo #(println (assoc % :bar :baz))) 
{:id 1, :bar :baz} 
{:id 2, :bar :baz} 
{:id 3, :bar :baz} 

长期存在,那么你最好还是使用上面的read-all-foo函数(这样就可以打败懒惰)。如果您想要转换数据,则在您将所有数据提取完毕后对结果执行map。您的数据将全部在内存中,但调用本身并且您的Post-fetch数据转换将是懒惰的。

3

它实际上可能是“终止副作用”添加到一个懒序列,当整个序列被消耗在第一时间被执行一次,:

(def s (lazy-cat (range 10) (do (println :foo) nil))) 

(first s) 
; => returns 0, prints out nothing 

(doall (take 10 s)) 
; => returns (0 1 2 3 4 5 6 7 8 9), prints nothing 

(last s) 
; => returns 9, prints :foo 

(doall s) 
; => returns (0 1 2 3 4 5 6 7 8 9), prints :foo 
; or rather, prints :foo if it it's the first time s has been 
; consumed in full; you'll have to redefine it if you called 
; (last s) earlier 

我不是当然,我会用它来关闭数据库连接,但我认为最好的做法是不要无限期地保持数据库连接,并将连接关闭调用放在惰性结果序列的末尾,直到连接的时间超过严格必要的时间,而且还会打开您的程序因不相关的原因失败而无法关闭连接的可能性。因此,对于这种情况,我通常只是在所有数据中啜泣。正如Brian所说,你可以将它存储在未经处理的地方,而不是懒惰地执行任何转换,所以只要不试图在一个块中插入真正巨大的数据集,就应该没问题。

但是我不知道你的具体情况,所以如果从你的观点来看有意义的话,你可以在结果序列的尾部明确地调用连接关闭函数。正如Michiel Borkent指出的那样,如果你想这样做,你将无法使用with-connection

+0

这是一个非常好的技巧... – 2010-05-03 10:52:22

0

没有办法在with-connectionwith-query-results的顶部创建函数或宏以增加懒惰。当控制流程离开词汇范围时,它们都分别关闭它们的Connection和ResultSet。正如Michal所说,创建一个懒惰的seq,关闭它的ResultSet和Connection是没问题的。正如他也说过的,这不是一个好主意,除非你能保证序列最终完成。

一种可行的解决方案可能是:

(def *deferred-resultsets*) 
(defmacro with-deferred-close [&body] 
    (binding [*deferred-resultsets* (atom #{})] 
    (let [ret# (do [email protected])] 
     ;;; close resultsets 
     ret#)) 
(defmacro with-deferred-results [bind-form sql & body] 
    (let [resultset# (execute-query ...)] 
    (swap! *deferred-resultsets* conj resultset#) 
    ;;; execute body, similar to with-query-results 
    ;;; but leave resultset open 
)) 

这将允许例如保持结果集打开,直到当前请求完成。