-
Notifications
You must be signed in to change notification settings - Fork 21
feat: add JSON type support #69
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
ab06286
0a778bf
6696126
d78e358
d59d162
76672d7
36d3f98
8252f60
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ type value = | |
| | `String of string | ||
| | `Bytes of bytes | ||
| | `Time of Time.t | ||
| | `Json of string | ||
| ] | ||
|
|
||
| type t = | ||
|
|
@@ -65,7 +66,7 @@ let to_time field kind = | |
| ; kind | ||
| } | ||
|
|
||
| type to_string = [`Decimal | `New_decimal | `String | `Var_string | `Bit] | ||
| type to_string = [`Decimal | `New_decimal | `String | `Var_string | `Bit | `Json] | ||
| type to_blob = [`Tiny_blob | `Blob | `Medium_blob | `Long_blob] | ||
| type to_time = [`Time | `Date | `Datetime | `Timestamp] | ||
|
|
||
|
|
@@ -85,6 +86,7 @@ let convert field typ unsigned = | |
| | `Long_long, false -> `Int (Int64.to_int (cast_to int64_t field)) | ||
| | `Float, _ -> `Float (cast_to float field) | ||
| | `Double, _ -> `Float (cast_to double field) | ||
| | `Json, _ -> `Json (Bytes.to_string (to_bytes field)) | ||
| | #to_string, _ -> `String (Bytes.to_string (to_bytes field)) | ||
| | #to_blob, _ -> `Bytes (to_bytes field) | ||
| | #to_time as t, _ -> `Time (to_time field t) | ||
|
|
@@ -124,6 +126,11 @@ let time field = | |
| | `Time t -> t | ||
| | _ -> err field ~info:"a time value" | ||
|
|
||
| let json field = | ||
| match value field with | ||
| | `Json j -> j | ||
| | _ -> err field ~info:"a json value" | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the test case you take into account the possibility that the database server returns a string (or bytes, as I've when testing). That would be needed here, as well. But if you agree to the above, this definition can be omitted. |
||
| let int_opt field = | ||
| match value field with | ||
| | `Int i -> Some i | ||
|
|
@@ -153,3 +160,9 @@ let time_opt field = | |
| | `Time t -> Some t | ||
| | `Null -> None | ||
| | _ -> err field ~info:"a nullable time value" | ||
|
|
||
| let json_opt field = | ||
| match value field with | ||
| | `Json j -> Some j | ||
| | `Null -> None | ||
| | _ -> err field ~info:"a nullable json value" | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -92,6 +92,7 @@ struct | |
| | `String s -> sprintf "(%S : string)" s | ||
| | `Bytes s -> sprintf "(%S : bytes)" (Bytes.to_string s) | ||
| | `Time t -> string_of_timestamp t | ||
| | `Json j -> sprintf "(%S : json)" j | ||
|
|
||
| let equal_float x x' = | ||
| abs_float (x -. x') /. (abs_float (x +. x') +. epsilon_float) < 1e-6 | ||
|
|
@@ -117,6 +118,8 @@ struct | |
| | `Bytes s, `Bytes s' -> s = s' | ||
| | `Bytes _, _ | _, `Bytes _ -> false | ||
| | `Time t, `Time t' -> equal_time t t' | ||
| | `Json j, `Json j' -> j = j' | ||
| | `Json _, _ | _, `Json _ -> false | ||
|
|
||
| let assert_field_equal v v' = | ||
| if not (equal_field v v') then begin | ||
|
|
@@ -345,10 +348,102 @@ struct | |
| in | ||
| (test_integer, test_bigint) | ||
|
|
||
| let test_json () = | ||
| connect () >>= or_die "connect" >>= fun dbh -> | ||
|
|
||
| (* Create a test table with JSON column *) | ||
| M.prepare dbh | ||
| "CREATE TEMPORARY TABLE ocaml_mariadb_json_test (id integer PRIMARY KEY AUTO_INCREMENT, data JSON)" | ||
| >>= or_die "prepare create json table" | ||
| >>= fun create_table_stmt -> | ||
| execute_no_data create_table_stmt >>= fun () -> | ||
|
|
||
| (* Test inserting JSON data *) | ||
| M.prepare dbh "INSERT INTO ocaml_mariadb_json_test (data) VALUES (?)" | ||
| >>= or_die "prepare insert json" | ||
| >>= fun insert_stmt -> | ||
|
|
||
| (* Test various JSON types *) | ||
| let test_cases = [ | ||
| {|{"name": "John", "age": 30}|}; | ||
| {|[1, 2, 3, "four"]|}; | ||
| {|"simple string"|}; | ||
| {|42|}; | ||
| {|true|}; | ||
| {|null|} | ||
| ] in | ||
|
|
||
| (* Insert all test cases *) | ||
| iter_s_list (fun json_data -> | ||
| M.Stmt.execute insert_stmt [| `Json json_data |] >>= or_die "insert json" | ||
| >|= fun _ -> () | ||
| ) test_cases >>= fun () -> | ||
|
|
||
| (* Select and verify we can retrieve JSON data *) | ||
| M.prepare dbh "SELECT id, data FROM ocaml_mariadb_json_test ORDER BY id" | ||
| >>= or_die "prepare select json" | ||
| >>= fun select_stmt -> | ||
| M.Stmt.execute select_stmt [||] >>= or_die "execute select json" >>= fun res -> | ||
|
|
||
| (* Verify we can fetch and access JSON fields *) | ||
| let rec verify_rows count = | ||
| M.Res.fetch (module M.Row.Array) res >>= or_die "fetch json row" >>= function | ||
| | Some row -> | ||
| assert (Array.length row = 2); | ||
| (* Test that we can access the JSON field using different methods *) | ||
| let json_value = match M.Field.value row.(1) with | ||
| | `Json j -> j | ||
| | `String s -> s (* TiDB/MySQL might return as string *) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm also seeing |
||
| | _ -> failwith "Expected JSON or String field" | ||
| in | ||
| (* Verify we got some data back *) | ||
| assert (String.length json_value > 0); | ||
|
|
||
| (* Test accessor functions *) | ||
| let json_direct = M.Field.json row.(1) in | ||
| let json_opt = M.Field.json_opt row.(1) in | ||
| assert (json_opt = Some json_direct); | ||
| assert (String.length json_direct > 0); | ||
|
|
||
| verify_rows (count + 1) | ||
| | None -> | ||
| (* We should have retrieved all our test cases *) | ||
| assert (count = List.length test_cases); | ||
| return () | ||
| in | ||
|
|
||
| verify_rows 0 >>= fun () -> | ||
|
|
||
| (* Test JSON functions if supported (optional) *) | ||
| (try | ||
| M.prepare dbh "SELECT JSON_TYPE(data) FROM ocaml_mariadb_json_test LIMIT 1" | ||
| >>= or_die "prepare json type" | ||
| >>= fun json_func_stmt -> | ||
| M.Stmt.execute json_func_stmt [||] >>= or_die "execute json type" >>= fun res -> | ||
| M.Res.fetch (module M.Row.Array) res >>= or_die "fetch json type" >>= function | ||
| | Some row -> | ||
| let json_type = match M.Field.value row.(0) with | ||
| | `Json j -> j | ||
| | `String s -> s | ||
| | _ -> failwith "Expected JSON or String from JSON_TYPE" | ||
| in | ||
| (* JSON_TYPE should return something like "OBJECT", "ARRAY", etc. *) | ||
| assert (String.length json_type > 0); | ||
| M.Stmt.close json_func_stmt >>= or_die "close json func stmt" | ||
| | None -> return () | ||
| with | ||
| | _ -> return () (* JSON functions might not be supported in all versions *) | ||
| ) >>= fun () -> | ||
|
|
||
| M.Stmt.close select_stmt >>= or_die "close select stmt" >>= fun () -> | ||
| M.Stmt.close insert_stmt >>= or_die "close insert stmt" >>= fun () -> | ||
| M.close dbh | ||
|
|
||
| let main () = | ||
| test_server_properties () >>= fun () -> | ||
| test_insert_id () >>= fun () -> | ||
| test_txn () >>= fun () -> | ||
| test_json () >>= fun () -> | ||
| test_many_select () >>= fun () -> | ||
| test_integer () >>= fun () -> test_bigint () | ||
| end | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it not be better to use the
`Stringcase for JSON, as well? I didn't write the original code, but the intention seem to me that thevaluetype only contains the cases which have different representation on the OCaml side, e.g. omitting the various integer variants.