@@ -34,89 +34,28 @@ The initial position depends on the context that this pattern is used in, it can
3434
3535----
3636
37- You can explore the usage of this recipe by going through a simple database paging example.
38-
39- To run the example you'll need to use leinigen and add the following dependencies to your project.clj file.
40-
41- 1. [org.clojure/java.jdbc "0.3.0-alpha4"]
42- 2. [org.hsqldb/hsqldb "2.3.0"]
43-
44-
45- Some helper functions and setup are required, paste the below code into your repl.
37+ You can explore the usage of this recipe by going through a simple file reading example.
4638
4739[source,clojure]
4840----
41+ (use 'clojure.pprint)
4942
50- (require 'clojure.java.jdbc)
5143(defn load-example []
52-
53- (def spec {:classname "org.hsqldb.jdbcDriver"
54- :subprotocol "hsqldb"
55- :subname "mem;sql.syntax_ora=true;create=true"
56- :user "sa"
57- :password ""
58- })
59-
60- ;create the database table
61- (clojure.java.jdbc/with-connection spec
62- (clojure.java.jdbc/transaction
63- (try
64- (do
65- (clojure.java.jdbc/create-table
66- "users"
67- [:id "BIGINT" "NOT NULL" "PRIMARY KEY"]
68- [:name "VARCHAR(20)" "NOT NULL"]
69- [:age "BIGINT" "NOT NULL"])
70- (clojure.java.jdbc/do-commands "CREATE INDEX users_index1 ON users(id,name)"))
71-
72- (catch Exception e (prn " ignore errors") ))))
73- ;truncate all previous data
74- (clojure.java.jdbc/with-connection spec
75- (clojure.java.jdbc/transaction (clojure.java.jdbc/do-commands "truncate table users")))
76-
77- ;insert 1000 records
78- (clojure.java.jdbc/with-connection spec
79- (clojure.java.jdbc/transaction
80- (dotimes [id 1000]
81- (clojure.java.jdbc/insert-values
82- :users
83- [:id :name :age]
84- [id (str id "-name") 20]))))
85-
86-
87- ;helper function that does a query
88- (defn do-query [q max]
89- (clojure.java.jdbc/with-connection spec
90- (clojure.java.jdbc/transaction
91- (clojure.java.jdbc/with-query-results rs [q]
92- (vec (take max rs))))))
93-
94-
95- ;the db state with helper closures
96- {:spec spec
97- :query (fn [from max]
98- (clojure.java.jdbc/with-connection spec
99- (clojure.java.jdbc/transaction
100- (clojure.java.jdbc/with-query-results rs [
101- (str "select * from (select id,name,age, ROWNUM() rnum from (select id,name,age from users order by id asc) a where ROWNUM() <= "
102- (+ from max)
103- ") WHERE rnum >= " from)]
104- (vec (take max rs))
105- ))))
106-
107- })
108-
109- ----
110-
111- The aim of this example is to show how you can use this recipe to select from the users table as if it was a single sequence without
112- having to care about closing connections when pulling the data.
113-
114- [source,clojure]
115- ----
116- (use 'clojure.pprint)
44+ "Methods that will build and read from a RandomAccessFile"
45+ (let [file "testfile.txt"
46+ get-bytes (fn [pos max]
47+ ;reads the file from the line position pos
48+ (with-open [raf (java.io.RandomAccessFile. file "r")]
49+ (if (> pos 0) (.seek raf pos))
50+ (doall (take max (repeatedly #(.readByte raf))))))
51+ ]
52+
53+ (with-open [writer (clojure.java.io/writer file)]
54+ (doseq [x (range 1000) ] (.write writer x)))
55+ {:file file :bytes get-bytes} ))
11756
11857(defn buffered-select [f-select init-pos]
119- "Creates a lazy sequence of messages for this datasource "
58+ "Creates a lazy sequence from f-select "
12059 (letfn [
12160 (m-seq [buff pos]
12261 (if-let [buff2 (if (empty? buff) (f-select pos) buff)]
@@ -129,49 +68,12 @@ having to care about closing connections when pulling the data.
12968
13069;normally you'd make the max argument much bigger for performance reasons,
13170;here its set to 5 to show how connecting and quering is done.
132- (defn select [pos] ((:query db) pos 5 ) )
133-
134- ;the start pos is 1 because in sql the position 0 is 1.
135- (def users (buffered-select select 1))
136-
137- (pprint (take 20 users))
138-
139- ;; -> {:rnum 20, :age 20, :name "25-name", :id 25})
140-
141- ;; -> ({:rnum 1, :age 20, :name "0-name", :id 0}
142- ;; -> {:rnum 2, :age 20, :name "1-name", :id 1}
143- ;; -> {:rnum 3, :age 20, :name "2-name", :id 2}
144- ;; -> {:rnum 4, :age 20, :name "3-name", :id 3}
145- ;; -> {:rnum 5, :age 20, :name "4-name", :id 4}
146- ;; -> {:rnum 6, :age 20, :name "5-name", :id 5}
147- ;; -> {:rnum 7, :age 20, :name "6-name", :id 6}
148- ;; -> {:rnum 8, :age 20, :name "7-name", :id 7}
149- ;; -> {:rnum 9, :age 20, :name "8-name", :id 8}
150- ;; -> {:rnum 10, :age 20, :name "9-name", :id 9}
151- ;; -> {:rnum 11, :age 20, :name "10-name", :id 10}
152- ;; -> {:rnum 12, :age 20, :name "11-name", :id 11}
153- ;; -> {:rnum 13, :age 20, :name "12-name", :id 12}
154- ;; -> {:rnum 14, :age 20, :name "13-name", :id 13}
155- ;; -> {:rnum 15, :age 20, :name "14-name", :id 14}
156- ;; -> {:rnum 16, :age 20, :name "15-name", :id 15}
157- ;; -> {:rnum 17, :age 20, :name "16-name", :id 16}
158- ;; -> {:rnum 18, :age 20, :name "17-name", :id 17}
159- ;; -> {:rnum 19, :age 20, :name "18-name", :id 18}
160- ;; -> {:rnum 20, :age 20, :name "19-name", :id 19})
161-
162- ;print the name of the first 30 users
163- (pprint (take 10 (map :name users)))
164-
165- ;; -> ("0-name"
166- ;; -> "1-name"
167- ;; -> "2-name"
168- ;; -> "3-name"
169- ;; -> "4-name"
170- ;; -> "5-name"
171- ;; -> "6-name"
172- ;; -> "7-name"
173- ;; -> "8-name"
174- ;; -> "9-name")
71+ (defn select [pos] ((:bytes db) pos 5 ) )
72+
73+ (def bts (buffered-select select 0))
74+
75+ (pprint (take 20 bts))
76+ ;; -> (0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19)
17577
17678
17779----
@@ -180,23 +82,12 @@ having to care about closing connections when pulling the data.
18082===== Discussion
18183
18284The solution showed is a simple pattern (monad) with simple constructs, and its usage applies directly to database queries and network io,
183- and more generally to all resources that are accessed using a connection.
85+ and more generally to all resources that are accessed using a connection or resource interface (as shown in the example) .
18486
185- The select function connects, queries and closes a connection, then returns a sequence. The sequence can then be consumed by the using code
186- and after the buffer has been consumed the select function is called again. To the user of the function it appears as if the sequence is
87+ The select function connects (opens a file) , queries (sees to the position and reads max amount of bytes) and closes a connection (closes the file), then returns a sequence.
88+ The sequence can then be consumed by the using code and after the buffer has been consumed the select function is called again. To the user of the function it appears as if the sequence is
18789one huge sequence over which higher order functions like map filter partition can be applied.
18890
91+ The pattern also helps unroll the typical with-open pattern; where work must be done inside the scope of another function and cannot be pulled, to something more pull orientated and functional.
18992
190- The pattern also helps unroll the typical pattern:
191-
192- [source,clojure]
193- ----
194- (with-open [resource get-resource] (do-work-on-resource resource) )
195- ----
196-
197- where work must be done inside the scope of another function and cannot be pulled, to something more pull orientated and functional:
19893
199- [source,clojure]
200- ----
201- (do-work-unit (get-resource))
202- ----
0 commit comments