a database layer insipred by caqti and ecto

fix(query): add missing space before JOIN clauses in SQL generation

The SQL generator was concatenating query parts with empty string,
causing malformed SQL like 'FROM usersINNER JOIN' instead of
'FROM users INNER JOIN'.

Added regression test to prevent future occurrences.

+1 -1
dune-project
··· 1 1 (lang dune 3.20) 2 2 3 3 (name repodb) 4 - (version 0.2.1) 4 + (version 0.2.2) 5 5 (source 6 6 ; (tangled @gdiazlo.tngl.sh/hcs) 7 7 (uri git+https://tangled.org/gdiazlo.tngl.sh/repodb))
+1 -1
lib/query.ml
··· 212 212 @ join_strs 213 213 @ [ where_str; group_str; having_str; order_str; limit_str; offset_str ] 214 214 in 215 - String.concat "" (List.filter (fun s -> s <> "") parts) 215 + String.concat " " (List.filter (fun s -> s <> "") parts) 216 216 | Insert -> 217 217 let table_str = Schema.table_name query.table in 218 218 let cols_str = String.concat ", " query.insert_columns in
+1 -1
repodb-postgresql.opam
··· 1 1 # This file is generated by dune, edit dune-project instead 2 2 opam-version: "2.0" 3 - version: "0.2.1" 3 + version: "0.2.2" 4 4 synopsis: "PostgreSQL driver for repodb" 5 5 description: 6 6 "PostgreSQL driver implementation for repodb using the postgresql-ocaml library."
+1 -1
repodb-sqlite.opam
··· 1 1 # This file is generated by dune, edit dune-project instead 2 2 opam-version: "2.0" 3 - version: "0.2.1" 3 + version: "0.2.2" 4 4 synopsis: "SQLite driver for repodb" 5 5 description: 6 6 "SQLite driver implementation for repodb using the sqlite3-ocaml library."
+1 -1
repodb.opam
··· 1 1 # This file is generated by dune, edit dune-project instead 2 2 opam-version: "2.0" 3 - version: "0.2.1" 3 + version: "0.2.2" 4 4 synopsis: "Ecto-like database toolkit for OCaml" 5 5 description: 6 6 "repodb is a database toolkit inspired by Elixir's Ecto. It provides type-safe query building, schema definitions, changesets for data validation, and database migrations."
+27
test/test_query.ml
··· 80 80 let sql = Query.to_sql q in 81 81 Alcotest.(check bool) "has LEFT JOIN" true (String.length sql > 0) 82 82 83 + let contains_substring haystack needle = 84 + let needle_len = String.length needle in 85 + let haystack_len = String.length haystack in 86 + if needle_len > haystack_len then false 87 + else 88 + let rec check i = 89 + if i > haystack_len - needle_len then false 90 + else if String.sub haystack i needle_len = needle then true 91 + else check (i + 1) 92 + in 93 + check 0 94 + 95 + let test_inner_join_spacing () = 96 + let comments_table = Schema.table "comments" in 97 + let q = 98 + Query.from posts_table 99 + |> Query.inner_join ~on:Expr.(int 1 = int 1) comments_table 100 + in 101 + let sql = Query.to_sql q in 102 + let has_bad_spacing = contains_substring sql "postsINNER" in 103 + Alcotest.(check bool) 104 + "no missing space before INNER JOIN" false has_bad_spacing; 105 + let has_proper_spacing = contains_substring sql "posts INNER JOIN comments" in 106 + Alcotest.(check bool) 107 + "proper spacing: 'posts INNER JOIN comments'" true has_proper_spacing 108 + 83 109 let test_on_conflict_do_nothing () = 84 110 let q = 85 111 Query.insert_into posts_table ··· 104 130 ("delete_from", `Quick, test_delete_from); 105 131 ("join", `Quick, test_join); 106 132 ("left_join", `Quick, test_left_join); 133 + ("inner_join_spacing", `Quick, test_inner_join_spacing); 107 134 ("on_conflict_do_nothing", `Quick, test_on_conflict_do_nothing); 108 135 ]