2010-05-02 1 views
6

Я использую clojure.contrib.sql для извлечения некоторых записей из базы данных SQLite.Блоки Iterator в Clojure?

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

Теперь, я не хочу, чтобы понять всю последовательность до возвращения из функции (то есть я хочу, чтобы держать его ленивым), но если я вернусь res непосредственно или обернуть его какой-то ленивой обертку (например, я хочу сделать некое преобразование map в последовательности результатов), привязки, связанные с SQL, будут сброшены, и соединение будет закрыто после того, как я вернусь, поэтому реализация последовательности вызовет исключение.

Как я могу заключить целую функцию в замыкание и вернуть вид блока итератора (например, в C# или Python)?

Или есть другой способ вернуть ленивую последовательность из этой функции?

ответ

0

Я никогда раньше не использовал SQLite с Clojure, но я предполагаю, что при подключении соединение закрывается, когда его тело было оценено. Таким образом, вам необходимо самостоятельно управлять соединением, если вы хотите сохранить его открытым, и закрыть его, когда вы закончите чтение интересующих вас элементов.

+0

Это то, что я хочу сделать, но я хочу, чтобы он автоматически обрабатывался закрытием блока итератора (или некоторой другой формой замыкания, которая реализует ленивый интерфейс seq). –

7

resultset-seq, что with-query-results возвращается, вероятно, уже так же ленив, как вы собираетесь получить. Как вы сказали, лента работает только до тех пор, пока ручка открыта. Нет никакого способа обойти это. Вы не можете читать из базы данных, если дескриптор базы данных закрыт.

Если вам необходимо выполнить ввод-вывод и сохранить данные после закрытия ручка, откройте ручку, быстро разберите ее (победите лень), закройте ручку и выполните последующие результаты. Если вы хотите перебрать некоторые данные, не сохраняя их все в памяти сразу, откройте ручку, получите ленивый 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 и ваши преобразования данных после получения будут ленивыми.

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, если хотите сделать это.

+0

Это очень хороший трюк ... –

0

Невозможно создать функцию или макрос «сверху» with-connection и with-query-results, чтобы добавить ленивость. Оба закрывают их Connection и ResultSet соответственно, когда поток управления оставляет лексическую область лексикой.

Как сказал Михаль, было бы непросто создать ленивый 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 
)) 

Это позволит, например, сохраняя результаты, пока не будет завершен текущий запрос.