# Getting Started ## Installation Add repodb to your project: ```bash # Core library + SQLite opam install repodb repodb-sqlite # Or with PostgreSQL opam install repodb repodb-postgresql ``` In your `dune` file: ```lisp (executable (name myapp) (libraries repodb repodb_sqlite)) ; or repodb_postgresql ``` ## Connecting to a Database ### SQLite ```ocaml open Repodb let () = match Repodb_sqlite.connect "myapp.db" with | Error e -> failwith (Repodb_sqlite.error_message e) | Ok conn -> (* Use connection *) Repodb_sqlite.close conn ``` ### PostgreSQL ```ocaml open Repodb let conninfo = "host=localhost dbname=myapp user=postgres password=secret" let () = match Repodb_postgresql.connect conninfo with | Error e -> failwith (Repodb_postgresql.error_message e) | Ok conn -> (* Use connection *) Repodb_postgresql.close conn ``` ## Creating the Repo Module The `Repo` module is parameterized by your driver: ```ocaml (* For SQLite *) module Repo = Repodb.Repo.Make(Repodb_sqlite) (* For PostgreSQL *) module Repo = Repodb.Repo.Make(Repodb_postgresql) ``` ## Defining Your First Schema ```ocaml open Repodb (* Table definition *) let users_table = Schema.table "users" (* Record type *) type user = { id : int; name : string; email : string; age : int; } (* Field definitions with getters/setters *) let id_field = Field.make ~table_name:"users" ~name:"id" ~ty:Types.int ~get:(fun u -> u.id) ~set:(fun v u -> { u with id = v }) ~primary_key:true () let name_field = Field.make ~table_name:"users" ~name:"name" ~ty:Types.string ~get:(fun u -> u.name) ~set:(fun v u -> { u with name = v }) () let email_field = Field.make ~table_name:"users" ~name:"email" ~ty:Types.string ~get:(fun u -> u.email) ~set:(fun v u -> { u with email = v }) () let age_field = Field.make ~table_name:"users" ~name:"age" ~ty:Types.int ~get:(fun u -> u.age) ~set:(fun v u -> { u with age = v }) () (* Decoder function *) let decode_user row = { id = Driver.row_int row 0; name = Driver.row_text row 1; email = Driver.row_text row 2; age = Driver.row_int row 3; } ``` ## Basic CRUD Operations ### Insert (Type-Safe) Use the `Query_values` module for fully type-safe inserts where the compiler verifies column/value types match: ```ocaml let insert_user conn ~name ~email ~age = let query = Query.insert_into users_table |> Query_values.values3 (name_field, email_field, age_field) (Expr.string name, Expr.string email, Expr.int age) in Repo.exec_query conn query ``` For bulk inserts: ```ocaml let insert_users conn users = let query = Query.insert_into users_table |> Query_values.values3_multi (name_field, email_field, age_field) (List.map (fun (name, email, age) -> (Expr.string name, Expr.string email, Expr.int age)) users) in Repo.exec_query conn query ``` See [Queries - Type-Safe INSERT](queries.md#insert-type-safe) for more details. ### Query All ```ocaml let all_users conn = Repo.all conn ~table:users_table ~decode:decode_user ``` ### Query One by ID ```ocaml let get_user conn id = Repo.get conn ~table:users_table ~id ~decode:decode_user ``` ### Update ```ocaml let update_user_age conn ~id ~new_age = Repo.update conn ~table:users_table ~columns:["age"] ~values:[Driver.Value.int new_age] ~where_column:"id" ~where_value:(Driver.Value.int id) ``` ### Delete ```ocaml let delete_user conn id = Repo.delete conn ~table:users_table ~where_column:"id" ~where_value:(Driver.Value.int id) ``` ## Using the Query DSL (Type-Safe) For more complex queries, use the Query module with your field definitions for full type safety: ```ocaml let adults conn = let query = Query.( from users_table |> where Expr.(column age_field >= int 18) |> order_by ~direction:Asc (Expr.column name_field) |> limit 100 ) in Repo.all_query conn query ~decode:decode_user ``` Using `Expr.column field` instead of `Expr.raw "column_name"` gives you: - **Compile-time type checking** - The compiler ensures you use the right types - **Refactoring safety** - Rename a field and all usages update automatically - **IDE support** - Jump to definition, find usages, etc. See [Queries](queries.md) for the full Query DSL reference. ## Using Changesets Validate data before inserting: ```ocaml let create_user_changeset params = let empty_user = { id = 0; name = ""; email = ""; age = 0 } in Changeset.create empty_user |> Changeset.cast params ~fields:[name_field; email_field; age_field] |> Changeset.validate_required [name_field; email_field] |> Changeset.validate_format email_field ~pattern:"^[^@]+@[^@]+$" |> Changeset.validate_number age_field ~greater_than_or_equal:0 let insert_with_validation conn params = let cs = create_user_changeset params in if Changeset.is_valid cs then let user = Changeset.data cs in insert_user conn ~name:user.name ~email:user.email ~age:user.age else Error (Error.Validation_failed (Changeset.error_messages cs)) ``` ## Next Steps - [Schemas](schemas.md) - Learn more about schema definitions - [Changesets](changesets.md) - Deep dive into validation - [Queries](queries.md) - Master the query DSL - [Associations](associations.md) - Define relationships