2010-05-02 19 views
6

Estoy usando clojure.contrib.sql para obtener algunos registros de una base de datos SQLite.bloques iterador en Clojure?

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

Ahora, yo realmente no quiere darse cuenta de toda la secuencia antes de volver de la función (es decir, quiero mantenerlo perezoso), pero si vuelvo res directa o envolverla algún tipo de envoltorio perezoso (por ejemplo, quiero hacer una determinada transformación de map en la secuencia de resultados), los enlaces relacionados con SQL se restablecerán y la conexión se cerrará después de que regrese, por lo que al realizar la secuencia arrojaré una excepción.

¿Cómo puedo encerrar toda la función en un cierre y devolver un tipo de bloque iterador (como yield en C# o Python)?

¿O existe otra forma de devolver una secuencia diferida de esta función?

Respuesta

0

Nunca antes había usado SQLite con Clojure, pero mi conjetura es que con la conexión se cierra la conexión cuando se ha evaluado su cuerpo. Por lo tanto, necesita administrar la conexión usted mismo si desea mantenerla abierta y cerrarla cuando termine de leer los elementos que le interesan.

+0

Eso es lo que quiero hacer, pero quiero que se maneje automágicamente con un cierre de bloque iterador (u otra forma de cierre que implemente una interfaz seq perezosa). –

7

La resultset-seq que with-query-results vuelve probablemente ya sea tan vaga como usted va Llegar. La pereza solo funciona mientras el mango esté abierto, como dijiste. No hay forma de evitar esto. No puede leer desde una base de datos si el identificador de la base de datos está cerrado.

Si necesita hacer E/S y conservar los datos después de que se cierra el asa, abra el asa, sorbuela rápidamente (derrotando la pereza), cierre el asa y trabaje con los resultados después. Si desea iterar sobre algunos datos sin mantener todo en la memoria de una vez, abra el identificador, obtenga un seq perezoso sobre los datos, doseq sobre él, luego cierre el controlador.

Así que si quieres hacer algo con cada fila (por efectos secundarios) y descartar los resultados sin comer todo el conjunto de resultados en la memoria, entonces se podría hacer esto:

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

Si desea que sus datos para esperar a largo plazo, entonces también puede sorberlo todo usando su función read-all-foo arriba (derrotando así la pereza). Si desea transformar los datos, entonces map sobre los resultados después de que haya recuperado todo. Sus datos estarán todos en la memoria en ese punto, pero la llamada map y sus transformaciones de datos posteriores a la búsqueda serán flojas.

3

De hecho, es posible añadir una "terminación efecto secundario" a una secuencia perezosa, a ser ejecutado una vez, cuando la secuencia entera se consume por primera vez:

(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 

No estoy Sin embargo, creo que usaría esto para cerrar una conexión de base de datos. Creo que se considera una mejor práctica no mantener una conexión de base de datos indefinidamente y poner la llamada de cierre de conexión al final de la secuencia de resultados diferida no solo se mantendría. a la conexión más tiempo de lo estrictamente necesario, pero también abre la posibilidad de que su programa falle por una razón no relacionada sin cerrar la conexión. Por lo tanto, para este escenario, normalmente solo sorbería en todos los datos. Como dice Brian, puedes almacenarlo todo en algún lugar sin procesar, que realizar las transformaciones de forma perezosa, por lo que deberías estar bien siempre y cuando no intentes obtener un conjunto de datos realmente grande en un solo trozo.

Pero no conozco sus circunstancias exactas, por lo que si tiene sentido desde su punto de vista, definitivamente puede llamar a una función de cierre de conexión en el final de su secuencia de resultados. Como señala Michiel Borkent, no podrías usar with-connection si quisieras hacer esto.

+0

Eso es un truco muy bueno ... –

0

No hay forma de crear una función o macro "en la parte superior" de with-connection y with-query-results para agregar lazyness. Ambos cierran su Connection y ResultSet respectivamente, cuando el flujo de control sale del alcance léxica.

Como dijo Michal, no sería ningún problema crear un seq perezoso, cerrando su ResultSet y Connection perezosamente. Como también dijo, no sería una buena idea, a menos que pueda garantizar que las secuencias finalmente se terminen.

una solución factible podría ser:

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

Esto permitiría, por ejemplo, manteniendo los resultados abiertos hasta que finalice la solicitud actual.