From 8f5ffa9015c19f1ce9924c5805a5d02d41f1f4f1 Mon Sep 17 00:00:00 2001 From: Brendan Long Date: Mon, 21 Jan 2019 12:46:02 -0700 Subject: [PATCH] Replace the Pgx_async example with a better one This focuses on the parts of the interface that people should actually use, and has a somewhat-reasonable organization for the code. Also made some notes about how to apply that example to Pgx_lwt and Pgx_unix. --- README.md | 19 +- pgx_async/bin/pgx_async_example.ml | 272 ++++++++++++----------------- 2 files changed, 125 insertions(+), 166 deletions(-) diff --git a/README.md b/README.md index 5d7de68..c4e618a 100644 --- a/README.md +++ b/README.md @@ -36,4 +36,21 @@ opam pin add pgx https://github.com/arenadotio/pgx.git ## Examples -See [pgx_async/bin/pgx_async_example.ml](pgx_async/bin/pgx_async_example.ml). +See [pgx_async/bin/pgx_async_example.ml](pgx_async/bin/pgx_async_example.ml) for +a complete example of the high-level functional interface. To translate the +example to Lwt, replace `Pgx_async` with `Pgx_lwt` and `>>| with `>|=`. To +translate it to synchronous IO / standard-library-only, use `Pgx_unix` and +replace both `>>|` and `>>=` with `|>`, or just replace `>>| fun () ->` with `;`. + +I.e. in `Pgx_unix`, you can replace: + +```ocaml +Pgx_async.execute ~params "INSERT INTO ..." +>>| fun () -> +``` + +... with: + +```ocaml +Pgx_unix.execute ~params "INSERT INTO ..."; +``` diff --git a/pgx_async/bin/pgx_async_example.ml b/pgx_async/bin/pgx_async_example.ml index 8598e4d..5c93bd6 100644 --- a/pgx_async/bin/pgx_async_example.ml +++ b/pgx_async/bin/pgx_async_example.ml @@ -2,174 +2,116 @@ open Core open Async -let print_table dbh = - let query_all = "SELECT * FROM Company" in - Pgx_async.simple_query dbh query_all - >>| fun x -> printf !"%{sexp: Pgx.row list list}\n" x +module Employee = struct + let create db = + Pgx_async.simple_query db {| + CREATE TEMPORARY TABLE Employee ( + id SERIAL PRIMARY KEY, + name VARCHAR(100) NOT NULL UNIQUE); + |} + |> Deferred.ignore + + (* This function lets us insert multiple users relatively efficiently *) + let insert_many db names = + let params = + List.map names ~f:(fun name -> + Pgx_async.Value.[ of_string name ]) + in + Pgx_async.execute_many db ~params ~query:{| + INSERT INTO Employee (name) + VALUES ($1) + RETURNING id + |} + >>| List.map ~f:(function + | [[ id ]] -> Pgx.Value.to_int_exn id + | _ -> assert false) + + let insert ~name db = + insert_many db [ name ] + >>| List.hd_exn +end + +module Facility = struct + let create db = + Pgx_async.simple_query db {| + CREATE TEMPORARY TABLE Facility ( + id SERIAL PRIMARY KEY, + name VARCHAR(100) NOT NULL UNIQUE, + director_id INT REFERENCES Employee(id) ON DELETE SET NULL); + + CREATE INDEX facility_director_id ON Facility (director_id); + |} + |> Deferred.ignore + + let insert ~name ?director_id db = + let params = Pgx_async.Value.[ of_string name ; opt of_int director_id ] in + Pgx_async.execute db ~params {| + INSERT INTO Facility (name, director_id) + VALUES ($1, $2) + RETURNING id + |} + >>| function + | [[ id ]] -> Pgx.Value.to_int_exn id + | _ -> assert false + + let all_name_and_director_name db = + Pgx_async.execute db {| + SELECT f.name, e.name + FROM Facility f + LEFT JOIN Employee e ON e.id = f.director_id + |} + >>| List.map ~f:(function + | [ name ; director_name ] -> + Pgx.Value.(to_string_exn name, to_string director_name) + | _ -> assert false) + + let reassign_director db ~director_id ~from_facility_id ~to_facility_id = + (* Note: with_transaction doesn't currently have any special handling + for concurrent queries *) + Pgx_async.with_transaction db @@ fun db -> + let params = Pgx.Value.[ of_int director_id ; of_int from_facility_id ] in + Pgx_async.execute db ~params {| + UPDATE Facility SET director_id = NULL WHERE id = $2 AND director_id = $1 + |} + >>= fun _ -> + let params = Pgx.Value.[ of_int director_id ; of_int to_facility_id ] in + Pgx_async.execute db ~params {| + UPDATE Facility SET director_id = $1 WHERE id = $2 + |} + |> Deferred.ignore +end + +let setup db = + Employee.create db + >>= fun () -> + Facility.create db let main () = - - Pgx_async_test.with_temp_db - (fun dbh ~db_name:_ -> - (** Create the table *) - print_endline "creating table"; - Pgx_async.simple_query dbh - "CREATE TABLE Company( \ - id INT PRIMARY KEY NOT NULL, \ - name TEXT NOT NULL, \ - age INT NOT NULL, \ - address VARCHAR(50), \ - salary REAL)" - - (** Inserting one row with prepare and execute_prepared *) - >>= fun _ -> - print_endline "inserting 1 row"; - let insert_query = "INSERT INTO Company (id, name, age, address, salary) \ - VALUES ($1, $2, $3, $4, $5)" in - let params = [ Some "2" ; Some "John" ; Some "21" ; Some "Pittsburgh" ; Some "100000.0" ] in - Pgx_async.Prepared.(with_prepare dbh ~query:insert_query - ~f:(execute ~params)) - - (** Inserting multiple rows with prepare_execute_prepared_many *) - >>= fun _ -> - print_endline "inserting 2 rows at the same time"; - let params = [[ Some "4" ; Some "Robert" ; Some "21" ; Some "Pittsburgh" ; Some "100000.0"] - ;[ Some "3" ; Some "John" ; Some "23" ; Some "New York" ; Some "30000.0" ]] in - Pgx_async.execute_many dbh ~query:insert_query ~params - - (** Select a single row, with id = 3*) - >>= fun _ -> - print_endline "selecting name with id = 3"; - let query_select_id = "SELECT name FROM Company WHERE id = $1" in - let params = [ Some "3" ] in - Pgx_async.execute dbh query_select_id ~params - >>= fun rows -> - printf !"%{sexp: Pgx.row list}\n" rows; - - (** Alter table by deleting the column address *) - print_endline "altering table by dropping column address"; - let query = "ALTER TABLE Company - DROP COLUMN address" in - Pgx_async.simple_query dbh query - - >>= fun _ -> print_table dbh - - (** adding a new column gender *) - >>= fun _ -> - print_endline "altering table by adding gender column"; - let query = "ALTER TABLE Company - ADD COLUMN gender TEXT" in - Pgx_async.simple_query dbh query - - >>= fun _ -> print_table dbh - - >>= fun _ -> - print_endline "updating John's gender"; - let query = "UPDATE Company - SET gender = $1 - WHERE name = $2" in - let params = [ Some "male" ; Some "John" ] in - Pgx_async.execute dbh query ~params - - >>= fun _ -> print_table dbh - - (** Update a row *) - >>= fun _ -> - print_endline "updating person with id = 4"; - let query = "UPDATE Company - SET name = $1, age = $2 - WHERE id = $3" in - let params = [ Some "Roger" ; Some "34" ; Some "4" ] in - Pgx_async.execute dbh query ~params - - >>= fun _ -> print_table dbh - - (** Delete a row *) - >>= fun _ -> - print_endline "deleting person with id = 4"; - let query = "DELETE FROM Company - WHERE id= $1" in - let params = [ Some "4" ] in - Pgx_async.execute dbh query ~params - - >>= fun _ -> print_table dbh - - (** Begin Transaction using Pgx_async.transact *) - >>= fun () -> - print_endline "start transaction"; - Monitor.try_with (fun () -> - Pgx_async.with_transaction dbh - (fun dbh -> - (** Deleting id = 2 *) - print_endline "deleting person with id = 2"; - let query = "DELETE FROM Company WHERE id = $1" in - let params = [ Some "2" ] in - Pgx_async.execute dbh query ~params - - >>= fun _ -> print_table dbh - - (** if an exception is thrown, rollback will be done *) - >>= fun () -> failwith "example exception" - ) - >>| function - | Ok v -> v - | Error _e -> print_endline "caught exception" - ) - - >>= fun _ -> - print_endline "exception was thrown in transaction, so rollback executed"; - print_table dbh - - (** Begin Transaction using manual transaction *) - >>= fun () -> Pgx_async.begin_work dbh - >>= fun tx -> - print_endline "start transaction"; - - (** Add a guy with id = 5 *) - print_endline "inserting person with id = 5"; - let query = "INSERT INTO Company (id, name, age, salary) VALUES ($1, $2, $3, $4)" in - let params = [ Some "5" ; Some "Alex" ; Some "15" ; Some "60000.0" ] in - Pgx_async.execute dbh query ~params - - >>= fun _ -> - print_endline "commit to inserting person with id = 5"; - - Pgx_async.commit tx - - (** rollback and commit both automatically exit the transaction *) - >>= fun _ -> print_table dbh - - (** begin work again *) - >>= fun () -> Pgx_async.begin_work dbh - >>= fun _ -> - print_endline "start transaction"; - - (** update alex's salary by increasing it *) - print_endline "update alex's salary"; - let query = "UPDATE Company - SET salary = $1 - WHERE name = $2" in - let params = [ Some "70000.0" ; Some "Alex" ] in - Pgx_async.execute dbh query ~params - - >>= fun _ -> print_table dbh - - (** rollback to alex's original salary *) - >>= fun _ -> - print_endline "rollback to alex's original salary"; - Pgx_async.rollback dbh - - >>= fun _ -> print_table dbh - - (** select multiple rows using execute_many *) - >>= fun _ -> - print_endline "selecting multiple lines at same time using execute_many. Make sure order is correct!"; - let params = [[Some "John"; Some "50000"];[Some "Alex"; Some "0"]] in - Pgx_async.execute_many dbh ~query:"SELECT * FROM Company WHERE name = $1 and salary >= $2" ~params - >>| fun x -> printf !"%{sexp: Pgx.row list list}\n" x - ) + Pgx_async.with_conn @@ fun db -> + setup db + >>= fun () -> + let%bind steve_id = Employee.insert ~name:"Steve" db in + (* Parallel queries are not an error, but will execute in serial *) + [ Facility.insert ~name:"Headquarters" ~director_id:steve_id db + ; Facility.insert ~name:"New Office" db ] + |> Deferred.all + >>= function + | [ headquarters_id ; new_office_id ] -> + Facility.all_name_and_director_name db + >>| List.iter ~f:(fun (name, director_name) -> + let director_name = Option.value director_name ~default:"(none)" in + printf "The director of %s is %s\n" name director_name) + >>= fun () -> + print_endline "Re-assigning Steve to the New Office"; + Facility.reassign_director db ~director_id:steve_id ~from_facility_id:headquarters_id ~to_facility_id:new_office_id + >>= fun () -> + Facility.all_name_and_director_name db + >>| List.iter ~f:(fun (name, director_name) -> + let director_name = Option.value director_name ~default:"(none)" in + printf "The director of %s is %s\n" name director_name) + | _ -> assert false let () = - let summary = "example command" in + let summary = "Pgx_async example" in Command.async ~summary (Command.Param.return main) |> Command.run