6

clojure.contrib.sqlSQLiteデータベースからいくつかのレコードをフェッチするために使用しています。

(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 を実行し、ハンドルを閉じた後もデータを保持する必要がある場合は、ハンドルを開き、すばやく丸呑みし (怠惰を克服)、ハンドルを閉じて、後で結果を処理します。一度にすべてをメモリに保持せずに一部のデータを反復処理する場合は、ハンドルを開き、データに対して遅延 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すべてをフェッチした後に結果を調べます。その時点でデータはすべてメモリ内にmapありますが、呼び出し自体とフェッチ後のデータ変換は遅延します。

于 2010-05-02T16:07:32.253 に答える
3

実際、シーケンス全体が初めて消費されたときに、遅延シーケンスに「終了の副作用」を追加して、1 回実行することができます。

(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

ただし、これを使用してDB接続を閉じるかどうかはわかりません-DB接続を無期限に保持せず、結果の遅延シーケンスの最後に接続終了呼び出しを配置することがベストプラクティスと見なされると思います厳密に必要以上に長く接続を保持するだけでなく、接続を閉じることなく、無関係な理由でプログラムが失敗する可能性も開きます。したがって、このシナリオでは、通常、すべてのデータを丸呑みします。ブライアンが言うように、変換を遅延して実行するよりも、未処理の場所にすべて保存できるため、非常に巨大なデータセットを 1 つのチャンクに取り込もうとしない限り問題ありません。

しかし、あなたの正確な状況がわからないので、あなたの観点からそれが理にかなっていれば、結果シーケンスの末尾で接続を閉じる関数を確実に呼び出すことができます。Michiel Borkent が指摘しているように、with-connectionこれを行いたい場合は使用できません。

于 2010-05-02T17:13:58.600 に答える
0

私はこれまでClojureでSQLiteを使用したことがありませんが、with-connectionは、本体が評価されたときに接続を閉じると思います。したがって、接続を開いたままにしておきたい場合は、接続を自分で管理し、関心のある要素を読み終えたら接続を閉じる必要があります。

于 2010-05-02T14:42:23.147 に答える
0

「上に」関数またはマクロを作成し、遅延を追加する方法はwith-connectionありません。制御フローがレキシカルスコープwith-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 に答える