31
31
bind /2 , bind /3 ,
32
32
fetchone /1 ,
33
33
fetchall /1 ,
34
+ fetchall /2 ,
34
35
column_names /1 , column_names /2 ,
35
36
column_types /1 , column_types /2 ,
36
37
close /1 , close /2 ]).
37
38
38
39
-export ([q /2 , q /3 , map /3 , foreach /3 ]).
39
40
40
41
-define (DEFAULT_TIMEOUT , 5000 ).
42
+ -define (DEFAULT_CHUNK_SIZE , 5000 ).
41
43
42
44
% %
43
45
-type connection () :: {connection , reference (), term ()}.
@@ -123,19 +125,19 @@ foreach(F, Sql, Connection) ->
123
125
Row :: tuple (),
124
126
ColumnNames :: tuple ().
125
127
foreach_s (F , Statement ) when is_function (F , 1 ) ->
126
- case try_step (Statement , 0 ) of
127
- '$done' -> ok ;
128
+ case try_multi_step (Statement , 1 , [] , 0 ) of
129
+ { '$done' , []} -> ok ;
128
130
{error , _ } = E -> F (E );
129
- {row , Row } ->
131
+ {rows , [ Row | []] } ->
130
132
F (Row ),
131
133
foreach_s (F , Statement )
132
134
end ;
133
135
foreach_s (F , Statement ) when is_function (F , 2 ) ->
134
136
ColumnNames = column_names (Statement ),
135
- case try_step (Statement , 0 ) of
136
- '$done' -> ok ;
137
+ case try_multi_step (Statement , 1 , [] , 0 ) of
138
+ { '$done' , []} -> ok ;
137
139
{error , _ } = E -> F ([], E );
138
- {row , Row } ->
140
+ {rows , [ Row | []] } ->
139
141
F (ColumnNames , Row ),
140
142
foreach_s (F , Statement )
141
143
end .
@@ -147,59 +149,82 @@ foreach_s(F, Statement) when is_function(F, 2) ->
147
149
ColumnNames :: tuple (),
148
150
Type :: term ().
149
151
map_s (F , Statement ) when is_function (F , 1 ) ->
150
- case try_step (Statement , 0 ) of
151
- '$done' -> [];
152
+ case try_multi_step (Statement , 1 , [] , 0 ) of
153
+ { '$done' , []} -> [];
152
154
{error , _ } = E -> F (E );
153
- {row , Row } ->
155
+ {rows , [ Row | []] } ->
154
156
[F (Row ) | map_s (F , Statement )]
155
157
end ;
156
158
map_s (F , Statement ) when is_function (F , 2 ) ->
157
159
ColumnNames = column_names (Statement ),
158
- case try_step (Statement , 0 ) of
159
- '$done' -> [];
160
+ case try_multi_step (Statement , 1 , [] , 0 ) of
161
+ { '$done' , []} -> [];
160
162
{error , _ } = E -> F ([], E );
161
- {row , Row } ->
163
+ {rows , [ Row | []] } ->
162
164
[F (ColumnNames , Row ) | map_s (F , Statement )]
163
165
end .
164
166
165
167
% %
166
- -spec fetchone (statement ()) -> tuple ().
168
+ % % -spec fetchone(statement()) -> tuple().
167
169
fetchone (Statement ) ->
168
- case try_step (Statement , 0 ) of
169
- '$done' -> ok ;
170
+ case try_multi_step (Statement , 1 , [] , 0 ) of
171
+ { '$done' , []} -> ok ;
170
172
{error , _ } = E -> E ;
171
- {row , Row } -> Row
173
+ {rows , [ Row | []] } -> Row
172
174
end .
173
175
174
- % %
176
+ % % @doc Fetch all records
177
+ % % @param Statement is prepared sql statement
178
+ % % @spec fetchall(statement()) -> list(tuple()) | {error, term()}.
175
179
-spec fetchall (statement ()) ->
176
180
list (tuple ()) |
177
181
{error , term ()}.
178
182
fetchall (Statement ) ->
179
- case try_step (Statement , 0 ) of
180
- '$done' ->
181
- [];
182
- {error , _ } = E -> E ;
183
- {row , Row } ->
184
- case fetchall (Statement ) of
185
- {error , _ } = E -> E ;
186
- Rest -> [Row | Rest ]
187
- end
183
+ fetchall (Statement , ? DEFAULT_CHUNK_SIZE ).
184
+
185
+ % % @doc Fetch all records
186
+ % % @param Statement is prepared sql statement
187
+ % % @param ChunkSize is a count of rows to read from sqlite and send to erlang process in one bulk.
188
+ % % Decrease this value if rows are heavy. Default value is 5000 (DEFAULT_CHUNK_SIZE).
189
+ % % @spec fetchall(statement()) -> list(tuple()) | {error, term()}.
190
+ -spec fetchall (statement (), pos_integer ()) ->
191
+ list (tuple ()) |
192
+ {error , term ()}.
193
+ fetchall (Statement , ChunkSize ) ->
194
+ case fetchall_internal (Statement , ChunkSize , []) of
195
+ {'$done' , Rows } -> lists :reverse (Rows );
196
+ {error , _ } = E -> E
197
+ end .
198
+
199
+ % % return rows in revers order
200
+ -spec fetchall_internal (statement (), pos_integer (), list (tuple ())) ->
201
+ {'$done' , list (tuple ())} |
202
+ {error , term ()}.
203
+ fetchall_internal (Statement , ChunkSize , Rest ) ->
204
+ case try_multi_step (Statement , ChunkSize , Rest , 0 ) of
205
+ {rows , Rows } -> fetchall_internal (Statement , ChunkSize , Rows );
206
+ Else -> Else
188
207
end .
189
208
190
- % % Try the step, when the database is busy,
191
- -spec try_step (statement (), non_neg_integer ()) ->
192
- '$done' |
193
- term ().
194
- try_step (_Statement , Tries ) when Tries > 5 ->
209
+ % % Try a number of steps, when the database is busy,
210
+ % % return rows in revers order
211
+ -spec try_multi_step (statement (), pos_integer (), list (tuple ()), non_neg_integer ()) ->
212
+ {rows , list (tuple ())} |
213
+ {'$done' , list (tuple ())} |
214
+ {error , term ()}.
215
+ try_multi_step (_Statement , _ChunkSize , _Rest , Tries ) when Tries > 5 ->
195
216
throw (too_many_tries );
196
- try_step (Statement , Tries ) ->
197
- case esqlite3 :step (Statement ) of
198
- '$busy' ->
217
+ try_multi_step (Statement , ChunkSize , Rest , Tries ) ->
218
+ case multi_step (Statement , ChunkSize ) of
219
+ {'$busy' , Rows } -> % % core can fetch a number of rows (rows < ChunkSize) per 'multi_step' call and then get busy...
220
+ erlang :display ({" busy" , Tries }),
199
221
timer :sleep (100 * Tries ),
200
- try_step (Statement , Tries + 1 );
201
- Something ->
202
- Something
222
+ try_multi_step (Statement , ChunkSize , Rows ++ Rest , Tries + 1 );
223
+ {rows , Rows } ->
224
+ {rows , Rows ++ Rest };
225
+ {'$done' , Rows } ->
226
+ {'$done' , Rows ++ Rest };
227
+ Else -> Else
203
228
end .
204
229
205
230
% % @doc Execute Sql statement, returns the number of affected rows.
@@ -280,7 +305,29 @@ step(Stmt) ->
280
305
-spec step (term (), timeout ()) -> tuple () | '$busy' | '$done' .
281
306
step ({statement , Stmt , {connection , _ , Conn }}, Timeout ) ->
282
307
Ref = make_ref (),
283
- ok = esqlite3_nif :step (Conn , Stmt , Ref , self ()),
308
+ ok = esqlite3_nif :multi_step (Conn , Stmt , 1 , Ref , self ()),
309
+ case receive_answer (Ref , Timeout ) of
310
+ {rows , [Row | []]} -> {row , Row };
311
+ {'$done' , []} -> '$done' ;
312
+ {'$busy' , []} -> '$busy' ;
313
+ Else -> Else
314
+ end .
315
+
316
+ % % make multiple sqlite steps per call
317
+ % % return rows in reverse order
318
+ multi_step (Stmt , ChunkSize ) ->
319
+ multi_step (Stmt , ChunkSize , ? DEFAULT_TIMEOUT ).
320
+
321
+ % % make multiple sqlite steps per call
322
+ % % return rows in reverse order
323
+ -spec multi_step (term (), pos_integer (), timeout ()) ->
324
+ {rows , list (tuple ())} |
325
+ {'$busy' , list (tuple ())} |
326
+ {'$done' , list (tuple ())} |
327
+ {error , term ()}.
328
+ multi_step ({statement , Stmt , {connection , _ , Conn }}, ChunkSize , Timeout ) ->
329
+ Ref = make_ref (),
330
+ ok = esqlite3_nif :multi_step (Conn , Stmt , ChunkSize , Ref , self ()),
284
331
receive_answer (Ref , Timeout ).
285
332
286
333
% % @doc Reset the prepared statement back to its initial state.
0 commit comments