Skip to content

Commit 0329139

Browse files
committed
Improved handling of user function exceptions
1 parent aa92399 commit 0329139

File tree

9 files changed

+87
-43
lines changed

9 files changed

+87
-43
lines changed

CHANGES.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
2015-09-02: Major API change that is compatible with major release series 2:
2+
3+
It is now possible to return errors from user-defined SQL-functions
4+
by simply raising (arbitrary) exceptions. This somewhat
5+
tricky internal change eliminates the need for Data.ERROR and
6+
reestablishes compatibility with major release series 2.
7+
8+
Sorry for the churn, but the more elegant solution was not
9+
quite obvious!
10+
111
2015-08-29: Added user function error handling (major API change).
212

313
Thanks to Joseph Young for this patch!

_oasis

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
OASISFormat: 0.4
22
Name: sqlite3
3-
Version: 3.0.0
3+
Version: 4.0.0
44
Synopsis: sqlite3-ocaml - SQLite3 bindings
55
Description: sqlite3-ocaml is an OCaml library with bindings to the
66
SQLite3 client API. Sqlite3 is a self-contained, serverless,
@@ -41,7 +41,7 @@ Library sqlite3
4141
CCOpt: -g -O2 -fPIC -DPIC
4242
if flag(strict) && ccomp_type(cc)
4343
CCOpt+: -Wall -pedantic -Wextra -Wunused -Wno-long-long
44-
CCLib: -lsqlite3
44+
CCLib: -lsqlite3 -lpthread
4545

4646

4747
# Tests

lib/META

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# OASIS_START
2-
# DO NOT EDIT (digest: fc7db74acb0529e253896e714e0a428b)
3-
version = "3.0.0"
2+
# DO NOT EDIT (digest: 5c96ba55072fdd9b0770a2d5872a8c6d)
3+
version = "4.0.0"
44
description = "sqlite3-ocaml - SQLite3 bindings"
55
archive(byte) = "sqlite3.cma"
66
archive(byte, plugin) = "sqlite3.cma"

lib/sqlite3.ml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,13 +112,12 @@ module Data = struct
112112
| FLOAT of float
113113
| TEXT of string
114114
| BLOB of string
115-
| ERROR of string
116115

117116
let to_string = function
118117
| NONE | NULL -> ""
119118
| INT i -> Int64.to_string i
120119
| FLOAT f -> string_of_float f
121-
| TEXT t | BLOB t | ERROR t -> t
120+
| TEXT t | BLOB t -> t
122121

123122
let to_string_debug = function
124123
| NONE -> "NONE"
@@ -127,7 +126,6 @@ module Data = struct
127126
| FLOAT f -> sprintf "FLOAT <%f>" f
128127
| TEXT t -> sprintf "TEXT <%S>" t
129128
| BLOB b -> sprintf "BLOB <%d>" (String.length b)
130-
| ERROR e -> sprintf "ERROR <%S>" e
131129
end
132130

133131
type header = string

lib/sqlite3.mli

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,6 @@ module Data : sig
142142
| FLOAT of float
143143
| TEXT of string
144144
| BLOB of string
145-
| ERROR of string
146145

147146
val to_string : t -> string
148147
(** [to_string data] converts [data] to a string. Both [NONE] and

lib/sqlite3_stubs.c

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include <caml/signals.h>
3838

3939
#include <sqlite3.h>
40+
#include <pthread.h>
4041

4142
#if __GNUC__ >= 3
4243
# define inline inline __attribute__ ((always_inline))
@@ -105,6 +106,46 @@ typedef struct stmt_wrap {
105106
} stmt_wrap;
106107

107108

109+
/* Handling of exceptions in user-defined SQL-functions */
110+
111+
/* For propagating exceptions from user-defined SQL-functions */
112+
static pthread_key_t user_exception_key;
113+
114+
typedef struct user_exception { value exn; } user_exception;
115+
116+
static inline void create_user_exception(value exn)
117+
{
118+
user_exception *user_exn = malloc(sizeof(user_exception));
119+
user_exn->exn = exn;
120+
caml_register_global_root(&user_exn->exn);
121+
pthread_setspecific(user_exception_key, user_exn);
122+
}
123+
124+
static inline void destroy_user_exception(void *user_exc_)
125+
{
126+
user_exception *user_exn = user_exc_;
127+
caml_remove_global_root(&user_exn->exn);
128+
free(user_exn);
129+
}
130+
131+
static inline void maybe_raise_user_exception(int rc)
132+
{
133+
if (rc == SQLITE_ERROR) {
134+
user_exception *user_exn = pthread_getspecific(user_exception_key);
135+
136+
if (user_exn != NULL) {
137+
CAMLparam0();
138+
CAMLlocal1(v_exn);
139+
v_exn = user_exn->exn;
140+
destroy_user_exception(user_exn);
141+
pthread_setspecific(user_exception_key, NULL);
142+
caml_raise(v_exn);
143+
CAMLnoreturn;
144+
}
145+
}
146+
}
147+
148+
108149
/* Macros to access the wrapper structures stored in the custom blocks */
109150

110151
#define Sqlite3_val(x) (*((db_wrap **) (Data_custom_val(x))))
@@ -120,11 +161,11 @@ static value *caml_sqlite3_RangeError = NULL;
120161
static inline void raise_with_two_args(value v_tag, value v_arg1, value v_arg2)
121162
{
122163
CAMLparam3(v_tag, v_arg1, v_arg2);
123-
value v_exc = caml_alloc_small(3, 0);
124-
Field(v_exc, 0) = v_tag;
125-
Field(v_exc, 1) = v_arg1;
126-
Field(v_exc, 2) = v_arg2;
127-
caml_raise(v_exc);
164+
value v_exn = caml_alloc_small(3, 0);
165+
Field(v_exn, 0) = v_tag;
166+
Field(v_exn, 1) = v_arg1;
167+
Field(v_exn, 2) = v_arg2;
168+
caml_raise(v_exn);
128169
CAMLnoreturn;
129170
}
130171

@@ -215,6 +256,7 @@ CAMLprim value caml_sqlite3_init(value __unused v_unit)
215256
caml_sqlite3_InternalError = caml_named_value("Sqlite3.InternalError");
216257
caml_sqlite3_Error = caml_named_value("Sqlite3.Error");
217258
caml_sqlite3_RangeError = caml_named_value("Sqlite3.RangeError");
259+
pthread_key_create(&user_exception_key, destroy_user_exception);
218260
return Val_unit;
219261
}
220262

@@ -521,6 +563,7 @@ CAMLprim value caml_sqlite3_exec(value v_db, value v_maybe_cb, value v_sql)
521563
caml_leave_blocking_section();
522564

523565
if (rc == SQLITE_ABORT) caml_raise(*cbx.exn);
566+
maybe_raise_user_exception(rc);
524567

525568
CAMLreturn(Val_rc(rc));
526569
}
@@ -570,6 +613,7 @@ CAMLprim value caml_sqlite3_exec_no_headers(value v_db, value v_cb, value v_sql)
570613
caml_leave_blocking_section();
571614

572615
if (rc == SQLITE_ABORT) caml_raise(*cbx.exn);
616+
maybe_raise_user_exception(rc);
573617

574618
CAMLreturn(Val_rc(rc));
575619
}
@@ -633,6 +677,8 @@ CAMLprim value caml_sqlite3_exec_not_null(value v_db, value v_cb, value v_sql)
633677
if (*cbx.exn != 0) caml_raise(*cbx.exn);
634678
else raise_sqlite3_Error("Null element in row");
635679
}
680+
maybe_raise_user_exception(rc);
681+
636682
CAMLreturn(Val_rc(rc));
637683
}
638684

@@ -692,6 +738,8 @@ CAMLprim value caml_sqlite3_exec_not_null_no_headers(
692738
if (*cbx.exn != 0) caml_raise(*cbx.exn);
693739
else raise_sqlite3_Error("Null element in row");
694740
}
741+
maybe_raise_user_exception(rc);
742+
695743
CAMLreturn(Val_rc(rc));
696744
}
697745

@@ -847,7 +895,6 @@ CAMLprim value caml_sqlite3_bind(value v_stmt, value v_index, value v_data)
847895
String_val(v_field),
848896
caml_string_length(v_field),
849897
SQLITE_TRANSIENT));
850-
case 4 : return Val_rc(SQLITE_ERROR);
851898
}
852899
}
853900
return Val_rc(SQLITE_ERROR);
@@ -1002,14 +1049,15 @@ static inline value caml_sqlite3_wrap_values(int argc, sqlite3_value **args)
10021049
}
10031050
}
10041051

1005-
static inline void exception_result(sqlite3_context *ctx)
1052+
static inline void exception_result(sqlite3_context *ctx, value v_exn)
10061053
{
10071054
sqlite3_result_error(ctx, "OCaml callback raised an exception", -1);
1055+
create_user_exception(v_exn);
10081056
}
10091057

10101058
static inline void set_sqlite3_result(sqlite3_context *ctx, value v_res)
10111059
{
1012-
if (Is_exception_result(v_res)) exception_result(ctx);
1060+
if (Is_exception_result(v_res)) exception_result(ctx, v_res);
10131061
else if (Is_long(v_res)) sqlite3_result_null(ctx);
10141062
else {
10151063
value v = Field(v_res, 0);
@@ -1024,9 +1072,6 @@ static inline void set_sqlite3_result(sqlite3_context *ctx, value v_res)
10241072
sqlite3_result_blob(
10251073
ctx, String_val(v), caml_string_length(v), SQLITE_TRANSIENT);
10261074
break;
1027-
case 4 :
1028-
sqlite3_result_error(ctx, String_val(v), caml_string_length(v));
1029-
break;
10301075
default :
10311076
sqlite3_result_error(ctx, "unknown value returned by callback", -1);
10321077
}
@@ -1064,7 +1109,7 @@ static inline void caml_sqlite3_user_function_step(
10641109
}
10651110
v_args = caml_sqlite3_wrap_values(argc, argv);
10661111
v_res = caml_callback2_exn(Field(data->v_fun, 2), agg_ctx->v_acc, v_args);
1067-
if (Is_exception_result(v_res)) exception_result(ctx);
1112+
if (Is_exception_result(v_res)) exception_result(ctx, v_res);
10681113
else agg_ctx->v_acc = v_res;
10691114
caml_enter_blocking_section();
10701115
}

myocamlbuild.ml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
(* OASIS_START *)
2-
(* DO NOT EDIT (digest: 8856c7fbdf56cf3b352c8f24a59be7a0) *)
2+
(* DO NOT EDIT (digest: 1308fb3d48472c2edc4cc51c862b6705) *)
33
module OASISGettext = struct
44
(* # 22 "src/oasis/OASISGettext.ml" *)
55

@@ -651,9 +651,12 @@ let package_default =
651651
])
652652
]);
653653
(["oasis_library_sqlite3_cclib"; "link"],
654-
[(OASISExpr.EBool true, S [A "-cclib"; A "-lsqlite3"])]);
654+
[
655+
(OASISExpr.EBool true,
656+
S [A "-cclib"; A "-lsqlite3"; A "-cclib"; A "-lpthread"])
657+
]);
655658
(["oasis_library_sqlite3_cclib"; "ocamlmklib"; "c"],
656-
[(OASISExpr.EBool true, S [A "-lsqlite3"])])
659+
[(OASISExpr.EBool true, S [A "-lsqlite3"; A "-lpthread"])])
657660
];
658661
includes = [("test", ["lib"])]
659662
}
@@ -663,7 +666,7 @@ let conf = {MyOCamlbuildFindlib.no_automatic_syntax = false}
663666

664667
let dispatch_default = MyOCamlbuildBase.dispatch_default conf package_default;;
665668

666-
# 667 "myocamlbuild.ml"
669+
# 670 "myocamlbuild.ml"
667670
(* OASIS_STOP *)
668671

669672
let read_lines_from_cmd ~max_lines cmd =

setup.ml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
(* setup.ml generated for the first time by OASIS v0.3.0 *)
22

33
(* OASIS_START *)
4-
(* DO NOT EDIT (digest: 51146b2c34f38bae10732375ae42aa0e) *)
4+
(* DO NOT EDIT (digest: 1b6fe2a99f7d4b7e0a8fcaf32a49dc1a) *)
55
(*
66
Regenerated by OASIS v0.4.5
77
Visit http://oasis.forge.ocamlcore.org for more information and
@@ -6981,7 +6981,7 @@ let setup_t =
69816981
alpha_features = [];
69826982
beta_features = [];
69836983
name = "sqlite3";
6984-
version = "3.0.0";
6984+
version = "4.0.0";
69856985
license =
69866986
OASISLicense.DEP5License
69876987
(OASISLicense.DEP5Unit
@@ -7097,7 +7097,8 @@ let setup_t =
70977097
"-Wno-long-long"
70987098
])
70997099
];
7100-
bs_cclib = [(OASISExpr.EBool true, ["-lsqlite3"])];
7100+
bs_cclib =
7101+
[(OASISExpr.EBool true, ["-lsqlite3"; "-lpthread"])];
71017102
bs_dlllib = [(OASISExpr.EBool true, [])];
71027103
bs_dllpath = [(OASISExpr.EBool true, [])];
71037104
bs_byteopt = [(OASISExpr.EBool true, [])];
@@ -7511,14 +7512,14 @@ let setup_t =
75117512
};
75127513
oasis_fn = Some "_oasis";
75137514
oasis_version = "0.4.5";
7514-
oasis_digest = Some "µ¢]]±\1486t\133\000&¿º M£";
7515+
oasis_digest = Some "Ý7ö\022\139Kuâeùû_¥Ó\019?";
75157516
oasis_exec = None;
75167517
oasis_setup_args = [];
75177518
setup_update = false
75187519
};;
75197520

75207521
let setup () = BaseSetup.setup setup_t;;
75217522

7522-
# 7523 "setup.ml"
7523+
# 7524 "setup.ml"
75237524
(* OASIS_STOP *)
75247525
let () = setup ();;

test/test_error.ml

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,6 @@ open Sqlite3
33
(* Tests our ability to return an error from a user defined function *)
44
let () =
55
let db = db_open "t" in
6-
create_fun0 db "MYERROR" (fun () -> Data.ERROR "This function always errors");
6+
create_fun0 db "MYERROR" (fun () -> failwith "This function always errors");
77
let res = exec db "SELECT MYERROR();" in
8-
match res with
9-
| Rc.ERROR -> print_endline (errmsg db)
10-
| x -> prerr_endline ("Should have thrown an error: " ^ Rc.to_string x)
11-
12-
(* Insures that we can't bind an error to a query *)
13-
let () =
14-
let db = db_open "t" in
15-
let _ : Rc.t = exec db "CREATE TABLE foo (val text);" in
16-
let s = Sqlite3.prepare db "INSERT INTO foo values (?);" in
17-
let res = Sqlite3.bind s 1 (Sqlite3.Data.ERROR "Should be impossible") in
18-
match res with
19-
| Rc.ERROR -> print_endline ("Bind threw an error")
20-
| x -> prerr_endline ("Should have thrown an error: " ^ Rc.to_string x)
8+
prerr_endline ("Should have thrown an error: " ^ Rc.to_string res)

0 commit comments

Comments
 (0)