-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsqlite3.ml
More file actions
409 lines (337 loc) · 10.2 KB
/
Copy pathsqlite3.ml
File metadata and controls
409 lines (337 loc) · 10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
module Weak_store = struct
type 'a t =
{ mutable w : 'a Weak.t ;
mutable free : int ;
finalise : 'a -> unit }
let create f =
{ w = Weak.create 8 ; free = 0 ; finalise = f }
let register s v =
Gc.finalise s.finalise v ;
let len = Weak.length s.w in
assert (len > 0) ;
if s.free < len
then begin
Weak.set s.w s.free (Some v) ;
s.free <- s.free + 1
end
else begin
let i = ref 0 in
let full = ref true in
while !full && !i < Weak.length s.w do
full := Weak.check s.w !i ;
if !full then incr i
done ;
if !full
then begin
let n_s = Weak.create (2 * len) in
Weak.blit s.w 0 n_s 0 len ;
s.w <- n_s ;
s.free <- len + 1 ;
i := len
end ;
Weak.set s.w !i (Some v)
end
let clear s =
for i = 0 to Weak.length s.w - 1 do
match Weak.get s.w i with
| Some v -> s.finalise v
| None -> ()
done
end
type db
type stmt
type argument
type sql_type = [`INTEGER|`FLOAT|`TEXT|`BLOB|`NULL]
type sql_value = [
| `INT of int
| `INT64 of int64
| `FLOAT of float
| `TEXT of string
| `BLOB of string
| `VALUE of argument
| `NULL ]
type error_code =
| ERROR
| INTERNAL
| PERM
| ABORT
| BUSY
| LOCKED
| NOMEM
| READONLY
| INTERRUPT
| IOERR
| CORRUPT
| NOTFOUND
| FULL
| CANTOPEN
| PROTOCOL
| EMPTY
| SCHEMA
| TOOBIG
| CONSTRAINT
| MISMATCH
| MISUSE
| NOLFS
| AUTH
| FORMAT
| RANGE
| NOTADB
exception Error of error_code * string
let init =
Callback.register_exception "mlsqlite3_exn" (Error (ERROR, ""))
external open_db : string -> db = "ml_sqlite3_open"
external _close_db : db -> unit = "ml_sqlite3_close"
external set_stmt_store : db -> stmt Weak_store.t option -> unit = "ml_sqlite3_set_stmt_store"
external get_stmt_store : db -> stmt Weak_store.t = "ml_sqlite3_get_stmt_store"
external finalize_stmt : stmt -> unit = "ml_sqlite3_finalize_noerr"
let stmt_store db =
try get_stmt_store db
with Not_found ->
let s = Weak_store.create finalize_stmt in
set_stmt_store db (Some s) ;
s
let register_stmt db stmt =
Weak_store.register (stmt_store db) stmt
let close_db db =
begin
try Weak_store.clear (get_stmt_store db)
with Not_found -> ()
end ;
_close_db db
external _compileoption_get : int -> string option = "ml_sqlite3_compileoption_get"
let compileoption_get () =
let rec get i =
match _compileoption_get i with
| Some o -> o :: get (i+1)
| None -> [] in
get 0
external interrupt : db -> unit = "ml_sqlite3_interrupt"
external is_complete : string -> bool = "ml_sqlite3_complete"
external _version : unit -> string = "ml_sqlite3_version"
let version = _version ()
external last_insert_rowid : db -> int64 = "ml_sqlite3_last_insert_rowid"
external changes : db -> int = "ml_sqlite3_changes"
external total_changes : db -> int = "ml_sqlite3_total_changes"
external get_autocommit : db -> bool = "ml_sqlite3_get_autocommit"
external sleep : int -> unit = "ml_sqlite3_sleep"
external busy_set : db -> (int -> [`FAIL|`RETRY]) -> unit
= "ml_sqlite3_busy_handler"
external busy_unset : db -> unit = "ml_sqlite3_busy_handler_unset"
external busy_timeout : db -> int -> unit = "ml_sqlite3_busy_timeout"
external trace_set : db -> (string -> unit) -> unit = "ml_sqlite3_trace"
external trace_unset : db -> unit = "ml_sqlite3_trace_unset"
external progress_handler_set : db -> int -> (unit -> unit) -> unit
= "ml_sqlite3_progress_handler"
external progress_handler_unset : db -> unit = "ml_sqlite3_progress_handler_unset"
external prepare : db -> string -> int -> stmt option * int = "ml_sqlite3_prepare"
external reset : stmt -> unit = "ml_sqlite3_reset"
external expired : stmt -> bool = "ml_sqlite3_expired"
external step : stmt -> [`DONE|`ROW] = "ml_sqlite3_step"
external bind : stmt -> int -> sql_value -> unit = "ml_sqlite3_bind"
external bind_parameter_count : stmt -> int = "ml_sqlite3_bind_parameter_count"
external bind_parameter_index : stmt -> string -> int = "ml_sqlite3_bind_parameter_index"
external bind_parameter_name : stmt -> int -> string = "ml_sqlite3_bind_parameter_name"
external clear_bindings : stmt -> unit = "ml_sqlite3_clear_bindings"
external column_blob : stmt -> int -> string = "ml_sqlite3_column_blob"
external column_double : stmt -> int -> float = "ml_sqlite3_column_double"
external column_int : stmt -> int -> int = "ml_sqlite3_column_int"
external column_int64 : stmt -> int -> int64 = "ml_sqlite3_column_int64"
external column_text : stmt -> int -> string = "ml_sqlite3_column_text"
external column_type : stmt -> int -> sql_type = "ml_sqlite3_column_type"
external data_count : stmt -> int = "ml_sqlite3_data_count"
external column_count : stmt -> int = "ml_sqlite3_column_count"
external column_name : stmt -> int -> string = "ml_sqlite3_column_name"
external column_decltype : stmt -> int -> string = "ml_sqlite3_column_decltype"
external value_blob : argument -> string = "ml_sqlite3_value_blob"
external value_double : argument -> float = "ml_sqlite3_value_double"
external value_int : argument -> int = "ml_sqlite3_value_int"
external value_int64 : argument -> int64 = "ml_sqlite3_value_int64"
external value_text : argument -> string = "ml_sqlite3_value_text"
external value_type : argument -> sql_type = "ml_sqlite3_value_type"
external _create_function :
db -> string -> int -> (argument array -> sql_value) -> unit
= "ml_sqlite3_create_function"
let create_fun_N db name f =
_create_function db name (-1) f
let create_fun_0 db name f =
_create_function db name 0 (fun _ -> f ())
let create_fun_1 db name f =
_create_function db name 1 (fun args -> f args.(0))
let create_fun_2 db name f =
_create_function db name 2 (fun args -> f args.(0) args.(1))
let create_fun_3 db name f =
_create_function db name 3 (fun args -> f args.(0) args.(1) args.(2))
external delete_function : db -> string -> unit = "ml_sqlite3_delete_function"
(* Higher-level functions manipulating statements *)
type ('a, 'b) fmt = ('b, unit, string, 'a) format4 -> 'b
(* Prepare only the first statement of the SQL string *)
let rec _prepare_one db off sql =
if off >= String.length sql
then failwith "Sqlite3.prepare_one: empty statement" ;
match prepare db sql off with
| Some stmt, _ ->
register_stmt db stmt ;
stmt
| None, nxt ->
_prepare_one db nxt sql
let prepare_one db sql =
_prepare_one db 0 sql
let prepare_one_f db fmt =
Printf.kprintf (_prepare_one db 0) fmt
(* Loop over all the statements in a SQL string *)
let _fold_prepare ?(final=false) db sql f init =
let rec loop acc off =
if off >= String.length sql
then acc
else
match prepare db sql off with
| Some stmt, nxt ->
register_stmt db stmt ;
let acc =
try f acc stmt
with exn when final ->
finalize_stmt stmt ;
raise exn in
if final then finalize_stmt stmt ;
loop acc nxt
| None, nxt ->
loop acc nxt in
loop init 0
let fold_prepare db sql f init =
_fold_prepare db sql f init
let fold_prepare_f db fmt =
Printf.kprintf (_fold_prepare db) fmt
(* Bind SQL values to statements *)
let do_bind stmt = function
| [] -> []
| l ->
let n = bind_parameter_count stmt in
let rec proc i = function
| v :: tl when i <= n ->
bind stmt i v ;
proc (i+1) tl
| l -> l in
proc 1 l
let _fold_prepare_bind ?final db sql bindings f init =
let bindings = ref bindings in
_fold_prepare
?final db sql
(fun acc stmt ->
bindings := do_bind stmt !bindings ;
f acc stmt)
init
let fold_prepare_bind db sql bindings f init =
_fold_prepare_bind db sql bindings f init
let fold_prepare_bind_f db fmt =
Printf.kprintf (_fold_prepare_bind db) fmt
(* Execute statements *)
let rec do_step stmt =
match step stmt with
| `DONE -> ()
| `ROW -> do_step stmt
let exec db sql =
_fold_prepare
~final:true
db sql
(fun () stmt -> do_step stmt)
()
let exec_f db fmt =
Printf.kprintf (exec db) fmt
let exec_v db sql data =
_fold_prepare_bind
~final:true
db sql data
(fun () stmt -> do_step stmt)
()
let exec_fv db fmt =
Printf.kprintf (exec_v db) fmt
(* Execute statements and get some results back *)
let rec fold_step f acc stmt =
match step stmt with
| `DONE -> acc
| `ROW ->
let acc =
try f acc stmt
with exn ->
reset stmt ;
raise exn in
fold_step f acc stmt
let fetch db sql f init =
_fold_prepare
db sql
(fold_step f) init
let fetch_f db fmt =
Printf.kprintf (fetch db) fmt
let fetch_v db sql data f init =
_fold_prepare_bind
db sql data
(fold_step f) init
let fetch_fv db fmt =
Printf.kprintf (fetch_v db) fmt
(* Reset-Bind-Step *)
let bind_and_exec stmt bindings =
reset stmt ;
ignore (do_bind stmt bindings) ;
do_step stmt
let bind_fetch stmt bindings f init =
reset stmt ;
ignore (do_bind stmt bindings) ;
fold_step f init stmt
let sql_escape s =
let n = ref 0 in
let len = String.length s in
for i = 0 to len - 1 do
let c = String.unsafe_get s i in
if c = '\'' then incr n
done ;
if !n = 0
then s
else begin
let n_len = len + !n in
let o = String.create n_len in
let j = ref 0 in
for i = 0 to len - 1 do
let c = String.unsafe_get s i in
if c = '\'' then begin
String.unsafe_set o !j '\'' ;
incr j
end ;
String.unsafe_set o !j c ;
incr j
done ;
assert (!j = n_len) ;
o
end
let char_of_hex v =
if v < 0xa
then Char.chr (v + Char.code '0')
else Char.chr (v - 0xa + Char.code 'a')
let hex_enc s =
let len = String.length s in
let o = String.create (2 * len) in
for i = 0 to len - 1 do
let c = int_of_char s.[i] in
let hi = c lsr 4 in
o.[2*i] <- char_of_hex hi ;
let lo = c land 0xf in
o.[2*i + 1] <- char_of_hex lo
done ;
o
let blob_escape = hex_enc
let string_of_transaction = function
| `DEFERRED -> "DEFERRED"
| `IMMEDIATE -> "IMMEDIATE"
| `EXCLUSIVE -> "EXCLUSIVE"
let transaction ?kind db f =
begin
let sql =
match kind with
| None -> "BEGIN"
| Some t -> "BEGIN " ^ string_of_transaction t in
exec db sql
end ;
try let r = f db in exec db "COMMIT" ; r
with exn ->
begin try exec db "ROLLBACK" with _ -> () end ;
raise exn