6

clojure.contrib.sql用来从 SQLite 数据库中获取一些记录。

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

现在,我真的不想在从函数返回之前实现整个序列(即我想让它保持惰性),但是如果我res直接返回或包装某种惰性包装器(例如我想map结果序列的转换),SQL相关的绑定将在我返回后被重置并关闭连接,因此实现序列将抛出异常。

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

还是有另一种方法可以从此函数返回惰性序列?

4

4 回答 4

7

返回的resultset-seq那个with-query-results可能已经像你要得到的那样懒惰了。正如你所说,只要把手打开,懒惰只会起作用。没有办法解决这个问题。如果数据库句柄已关闭,则无法从数据库中读取。

如果您需要在句柄关闭后进行 I/O 并保留数据,则打开句柄,快速 slurp(打败懒惰),关闭句柄,然后处理结果。如果您想遍历某些数据而不一次将其全部保存在内存中,请打开句柄,获取数据的惰性序列,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在您获取所有数据后对结果进行转换。那时您的数据都将在内存中,但map调用本身和您的获取后数据转换将是惰性的。

于 2010-05-02T16:07:32.253 回答
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

不过,我不确定我是否会用它来关闭数据库连接——我认为最好的做法是不要无限期地保持数据库连接,并将关闭连接的调用放在你的懒惰结果序列的末尾不仅会比严格必要的时间更长时间地保持连接,而且还会导致您的程序由于不相关的原因而失败而不会关闭连接。因此,对于这种情况,我通常只会吞下所有数据。正如布赖恩所说,您可以将其全部存储在未处理的地方,而不是懒惰地执行任何转换,所以只要您不试图将一个非常大的数据集拉入一个块中,您就应该没问题。

但是我不知道你的确切情况,所以如果从你的角度来看它是有意义的,你绝对可以在你的结果序列的末尾调用一个连接关闭函数。with-connection正如 Michiel Borkent 指出的那样,如果您想这样做,您将无法使用。

于 2010-05-02T17:13:58.600 回答
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 ~@body)]
      ;;; 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
  ))

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

于 2010-10-17T19:09:41.867 回答
0

我以前从未将 SQLite 与 Clojure 一起使用,但我的猜测是 with-connection 在评估它的主体时会关闭连接。因此,如果您想保持连接打开,则需要自己管理连接,并在阅读完您感兴趣的元素后关闭它。

于 2010-05-02T14:42:23.147 回答