(* Copyright (C) 2022--2025 Petter A. Urkedal * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or (at your * option) any later version, with the LGPL-3.0 Linking Exception. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * and the LGPL-3.0 Linking Exception along with this library. If not, see * and , respectively. *) module Make (Ground : Testlib.Sig.Ground) = struct open Ground open Ground.Fiber.Infix let env driver_info = function | "engine_innodb" -> (match Caqti_driver_info.dialect_tag driver_info with | `Mysql -> Caqti_query.L" ENGINE = InnoDB" | _ -> Caqti_query.S[]) | _ -> raise Not_found let create_reqs = List.map Caqti_template.Create.(direct T.(unit -->. unit)) [ "DROP TABLE IF EXISTS caqti_test_publication"; "DROP TABLE IF EXISTS caqti_test_genre"; "CREATE TABLE caqti_test_genre \ (id INTEGER PRIMARY KEY, \ genre VARCHAR(40) UNIQUE NOT NULL CHECK (genre != ''))\ $(engine_innodb)"; "CREATE TABLE caqti_test_publication \ (title VARCHAR(160) UNIQUE NOT NULL, \ author TEXT NOT NULL, \ genre_id INTEGER NOT NULL, \ FOREIGN KEY (genre_id) REFERENCES caqti_test_genre (id))\ $(engine_innodb)"; "INSERT INTO caqti_test_genre VALUES (1, 'fiction'), (2, 'nonfiction')"; "INSERT INTO caqti_test_publication VALUES ('Fiction', 'N.N.', 2)" ] let not_null_violation_req = Caqti_template.Create.(static T.(unit -->. unit)) "INSERT INTO caqti_test_genre VALUES (NULL, NULL)" let unique_violation_req = Caqti_template.Create.(static T.(unit -->. unit)) "INSERT INTO caqti_test_genre VALUES (3, 'fiction')" let foreign_key_violation_req = Caqti_template.Create.(static T.(unit -->. unit)) "INSERT INTO caqti_test_publication \ VALUES ('Unclassified', 'N.N.', 0)" let check_violation_req = Caqti_template.Create.(static T.(unit -->. unit)) "INSERT INTO caqti_test_genre VALUES (3, '')" (* Missing: * - Restrict violation is only mapped for PostgreSQL, but even there it is * likely unused (deleting a restricted FK casuse FK violation. * - Exclusion violation is also only mapped for PostgreSQL. *) let check_restrict_violation_req = Caqti_template.Create.(static T.(unit -->. unit)) "DELETE FROM caqti_test_genre WHERE id = 2" (* TODO: exclusion *) let harness (module Db : CONNECTION) = List_result_fiber.iter_s (fun req -> Db.exec req ()) create_reqs >>= or_fail let make_test_case (expected_cause, req) = let test (module Db : CONNECTION) = Db.exec req () >|= function | Ok () -> Alcotest.fail "Error not reported." | Error (`Request_failed _ | `Response_failed _ as err) -> let actual_cause = Caqti_error.cause err in (* Skip for sqlite3 < 5.2.0 due to missing extended error code. *) let open Caqti_template.Version.Infix in (match Db.dialect with | Caqti_template.Dialect.Sqlite {server_version; _} -> if server_version <=* "5.2" then Alcotest.skip () | _ -> ()); Alcotest.(check string) "cause" (Caqti_error.show_cause expected_cause) (Caqti_error.show_cause actual_cause) | Error err -> Alcotest.failf "Unexpected error: %a" Caqti_error.pp err in (Caqti_error.show_cause expected_cause, `Quick, test) let test_cases = ("harness", `Quick, harness) :: List.map make_test_case [ `Not_null_violation, not_null_violation_req; `Unique_violation, unique_violation_req; `Foreign_key_violation, foreign_key_violation_req; `Check_violation, check_violation_req; ] end