2010-05-02 32 views
6

Tôi đang sử dụng clojure.contrib.sql để tìm nạp một số bản ghi từ cơ sở dữ liệu SQLite.Các khối Iterator trong Clojure?

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

Bây giờ, tôi không thực sự muốn thực hiện toàn bộ chuỗi trước khi trở về từ chức năng (tức là tôi muốn giữ nó lười biếng), nhưng nếu tôi trở res trực tiếp hoặc quấn nó một số loại wrapper lười biếng (ví dụ tôi muốn thực hiện một chuyển đổi map nhất định trên chuỗi kết quả), các liên kết liên quan đến SQL sẽ được đặt lại và kết nối sẽ bị đóng sau khi tôi quay trở lại, vì vậy việc thực hiện chuỗi sẽ ném một ngoại lệ.

Làm cách nào để có thể đóng toàn bộ chức năng trong một đóng và trả về một loại khối lặp (như yield trong C# hoặc Python)?

Hoặc có cách nào khác để trả về chuỗi lười từ chức năng này không?

Trả lời

0

Tôi chưa bao giờ sử dụng SQLite với Clojure trước đây, nhưng tôi đoán là kết nối có đóng kết nối khi cơ thể của nó đã được đánh giá. Vì vậy, bạn cần tự quản lý kết nối nếu bạn muốn giữ kết nối và đóng nó khi bạn đọc xong các yếu tố mà bạn quan tâm.

+0

Đó là những gì tôi muốn làm, nhưng tôi muốn nó được xử lý tự động bằng cách đóng khối lặp (hoặc một dạng đóng khác để thực hiện giao diện lười biếng). –

7

resultset-seq trả lại có lẽ là đã lười như bạn đang đi để có được. Sự lười biếng chỉ hoạt động miễn là tay cầm mở ra, như bạn đã nói. Không có cách nào xung quanh chuyện này. Bạn không thể đọc từ cơ sở dữ liệu nếu trình quản lý cơ sở dữ liệu bị đóng.

Nếu bạn cần làm I/O và giữ dữ liệu sau khi tay cầm được đóng lại, hãy mở tay cầm, trượt nó nhanh (đánh bại sự lười biếng), đóng chốt và làm việc với kết quả sau đó. Nếu bạn muốn lặp lại trên một số dữ liệu mà không cần giữ tất cả trong bộ nhớ cùng một lúc, sau đó mở xử lý, có được một seq lười biếng trên dữ liệu, doseq trên nó, sau đó đóng xử lý.

Vì vậy, nếu bạn muốn làm một cái gì đó với mỗi hàng (đối với tác dụng phụ) và loại bỏ những kết quả mà không ăn gì cả resultset vào bộ nhớ, sau đó bạn có thể làm điều này:

(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} 

Nếu bạn muốn dữ liệu của bạn để treo xung quanh lâu dài, sau đó bạn cũng có thể slurp tất cả bằng cách sử dụng chức năng read-all-foo của bạn ở trên (do đó đánh bại sự lười biếng). Nếu bạn muốn chuyển đổi dữ liệu, sau đó map qua kết quả sau khi bạn đã tìm nạp tất cả. Tất cả dữ liệu của bạn sẽ có trong bộ nhớ tại thời điểm đó, nhưng chính cuộc gọi map và các biến đổi dữ liệu sau khi tìm nạp của bạn sẽ bị chậm.

3

Đó là trong thực tế có thể để thêm một "chấm dứt tác dụng phụ" tới chuỗi lười biếng, được thực hiện một lần, khi toàn bộ chuỗi được tiêu thụ lần đầu tiên:

(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 

Tôi không chắc chắn tôi sẽ sử dụng điều này để đóng kết nối DB, mặc dù tôi cho rằng thực tế tốt nhất là không giữ kết nối DB vô thời hạn và việc kết thúc cuộc gọi kết thúc ở cuối chuỗi kết quả của bạn sẽ không chỉ giữ vào kết nối lâu hơn mức cần thiết, nhưng cũng mở ra khả năng chương trình của bạn sẽ thất bại vì một lý do không liên quan mà không bao giờ đóng kết nối. Vì vậy, đối với kịch bản này, tôi thường sẽ chỉ slurp trong tất cả các dữ liệu. Như Brian nói, bạn có thể lưu trữ tất cả ở một nơi nào đó chưa được xử lý, hơn là thực hiện bất kỳ biến đổi nào một cách uể oải, vì vậy bạn sẽ ổn khi bạn không cố gắng lấy một tập dữ liệu khổng lồ trong một đoạn.Nhưng sau đó tôi không biết chính xác hoàn cảnh của bạn, do đó, nếu nó có ý nghĩa từ quan điểm của bạn, bạn chắc chắn có thể gọi một chức năng kết nối đóng ở cuối đuôi của chuỗi kết quả của bạn. Như Michiel Borkent đã chỉ ra, bạn sẽ không thể sử dụng with-connection nếu bạn muốn thực hiện việc này.

+0

Đó là một mẹo rất hay ... –

0

Không có cách nào để tạo hàm hoặc macro "ở trên cùng" của with-connectionwith-query-results để thêm sự lười biếng. Cả hai đóng kết nối và ResultSet tương ứng, khi dòng điều khiển rời khỏi phạm vi lexical.

Như Michal đã nói, sẽ không có vấn đề gì khi tạo ra một seq lười biếng, đóng kết quả của nó và kết nối một cách uể oải. Như ông cũng nói, nó sẽ không là một ý tưởng tốt, trừ khi bạn có thể đảm bảo rằng các trình tự cuối cùng đã được hoàn thành.

Một giải pháp khả thi có thể là:

(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 
)) 

này sẽ cho phép ví dụ giữ cho bộ kết quả mở cho đến khi yêu cầu hiện tại kết thúc.