{"id":15225207,"url":"https://github.com/andreypopp/sqlpp","last_synced_at":"2025-09-13T14:45:30.342Z","repository":{"id":219611661,"uuid":"749459682","full_name":"andreypopp/sqlpp","owner":"andreypopp","description":"relation query language, with typed embedding into OCaml","archived":false,"fork":false,"pushed_at":"2025-02-23T16:10:55.000Z","size":193,"stargazers_count":17,"open_issues_count":0,"forks_count":1,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-09T19:09:08.362Z","etag":null,"topics":["ocaml","relational-database","sql"],"latest_commit_sha":null,"homepage":"","language":"OCaml","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/andreypopp.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-01-28T16:45:19.000Z","updated_at":"2025-02-23T16:10:59.000Z","dependencies_parsed_at":"2024-03-06T08:45:51.449Z","dependency_job_id":"7eca6831-24eb-4dba-a057-497fe77a397a","html_url":"https://github.com/andreypopp/sqlpp","commit_stats":null,"previous_names":["andreypopp/sqlpp"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreypopp%2Fsqlpp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreypopp%2Fsqlpp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreypopp%2Fsqlpp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreypopp%2Fsqlpp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/andreypopp","download_url":"https://codeload.github.com/andreypopp/sqlpp/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248094993,"owners_count":21046770,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ocaml","relational-database","sql"],"created_at":"2024-09-28T18:05:08.564Z","updated_at":"2025-04-09T19:09:18.921Z","avatar_url":"https://github.com/andreypopp.png","language":"OCaml","readme":"# sqlpp\n\n**WARNING: EXPERIMENTAL, DO NOT USE**\n\nan extension to sql with typed embedding into ocaml \n\n## motivation\n\nembedding sql into ocaml is notoriously hard and results in very complex and\nunergonomic apis, especially if some form of query composition and reuse is\npresent.\n\nsqlpp takes another approach. it extends sql, the language, to recover\ncomposability, so the typed embedding into ocaml results in a simpler api.\n\nit is worth stating that, sqlpp is just a conservative and backward compatible\nextension to sql. it's not a new language.\n\n## project structure\n\nproject structure:\n- `sqlpp` implements sqlpp language parser, analyzer and generic SQL printer\n- `sqlpp_ppx` implements typed embedding into ocaml as ppx\n- `sqlpp_manage` database management interface\n- `sqlpp_sqlite` Sqlite dialect/driver\n- `sqlpp_sqlite_manage` database management interface for Sqlite\n- `sqlpp_mariadb` MariaDB dialect/driver, uses lwt for I/O\n- `sqlpp_postgresql` PostgreSQL dialect/driver, uses lwt for I/O\n\nexample project structure:\n- `test/sqlpp_sqlite/db.ml` defines the database schema, also acts as a ppx\n- `test/sqlpp_sqlite/main.ml` the application itself, also database management interface\n\n## features\n\n### query parameters\n\nquery can have parameters:\n\n```sql\nselect id, name\nfrom users\nwhere id = ?id\n```\n\n### variant query parameters\n\nthe `match ?PARAM with` construct can match on variant query parameters and\ndrive SQL query generation:\n\n```sql\nselect id, name\nfrom users\nwhere \n  match ?where with\n  | by_id ?id -\u003e id = ?id\n  | by_name ?name -\u003e name = ?name\n  end\n```\n\n### nested scopes\n\nthe `select ...` syntax allows to build new expressions within a query:\n\n```sql\ncreate query users_with_invited_users as\nselect ...\nfrom users\njoin (select parent_id, ... from users group by parent_id) as invited_users\non users.id = invited_users.parent_id\n```\n\nthe usage looks like this:\n\n```sql\nselect\n  users.id,\n  invited_users.count(1) as num_invited,\n  invited_users.argMax(id, created_at) as last_invited\nfrom users_with_invited_users\n```\n\n### named expression bindings\n\nthe `with EXPR as NAME` synax predefines an expression for further usage:\n\n```sql\nselect\n  with deleted_at is not null and not is_disabled as is_active,\n  is_active\nfrom users\n```\n\nsuch expressions won't be queried unless they are used\n\n### named scope bindings\n\nconsider query like this:\n\n```sql\nselect ...\nfrom (select ... from users) as u\n:- (from (users: users) as u)\n```\n\nthe `:- SCOPE` at the bottom shows the scope query has, now with the following\n`withscope SCOPE as NAME` syntax one can alias scopes as well:\n\n```sql\nselect \n  withscope u.users as users\nfrom (select ... from users) as u\n:- (from users)\n```\n\nand another, more elaborate, example:\n\n```sql\nselect \n  withscope q.users as users,\n  withscope q.profiles as profiles\nfrom (\n  select\n    withscope u.users as users\n  from (select ... from users) as u\n  join profiles\n) q\n:- (from users, from profiles)\n```\n\n### reusable fieldsets\n\nthe `create fieldset` declaration defines a reusable fieldset:\n\n```sql\ncreate fieldset users(from users as u) as\nselect\n  u.id as user_id,\n  u.name as user_name,\n  u.created_at as user_created_at\n```\n\nwhich then could be used as:\n\n```sql\nselect \n  with ...users(users)\nfrom users\n:- (user_id int, user_name string user_created_at int)\n```\n\nnow consider a more elaborate example:\n\n```sql\ncreate fieldset profiles(from profiles as p) as\nselect\n  p.email as profile_email\n\ncreate fieldset users_agg(from users as u) as\nselect\n  u.count(1) as count,\n  u.max(created_at) as last_created\n\nselect \n  with ...users(q.users), \n  with ...profiles(q.profiles),\n  with q.invited_users as invited_users,\nfrom (\n  select with u.useres as users\n  from (from users) u\n  join profiles\n  on u.users.id = profiles.user_id\n  join (\n    select parent_id, ...users_agg(users)\n    from users\n    group by parent_id\n  ) as invited_users\n  on u.users.id = invited_users.parent_id\n) as q\n:- (\n     user_id int,\n     user_name string,\n     user_created_at int,\n     profile_email string,\n     from (count int, last_created int) as invited_users\n   )\n```\n\nwe used `with ...FIELDSET(..)` syntax so far, but it's also possible to drop\nthe `with` and make fieldset actually select the fields. this is useful in case\nyou have several queries selecting similar data.\n\n### (todo) optional joins\n\n## ocaml ppx\n\nocaml embedding is implemented via ppx, there are following forms available\n\n### `[%exec QUERY]` execute a query\n\ndefine a query which doesn't fetch any data:\n\n```ocaml\nlet create_todo = [%exec \"INSERT INTO todos(text) VALUES(?text)\"]\nlet () = create_todo db ~text:\"ship it\"\n```\n\n### `[%fetch_list QUERY]` fetch a list of tuples\n\ndefine a query to fetch a list of tuples:\n\n```ocaml\nlet all_todos = [%exec \"SELECT id, text, completed FROM todos\"]\nList.iter (all_todos db) ~f:(fun (id, text, completed) -\u003e\n    print_endline (Printf.sprintf \"id=%i text=%s completed=%b\" id text completed)\n```\n\n### `[%fetch_list QUERY ~record:row]` fetch a list of records\n\ndefine a query to fetch a list of records:\n\n```ocaml\ntype todo = {id: int; text: string; completed: bool}\nlet all_todos = [%exec \"SELECT id, text, completed FROM todos\" ~record:todo]\nList.iter (all_todos db) ~f:(fun t -\u003e\n    print_endline (Printf.sprintf \"id=%i text=%s completed=%b\" t.id t.text t.completed)\n```\n\n### `[%fetch_option QUERY]` maybe fetch a tuple value\n\ndefine a query to fetch an optional tuple value:\n\n```ocaml\nlet last = [%exec \"SELECT id, text, completed FROM todos\n                   ORDER BY created DESC\n                   LIMIT 1\"]\nlet () =\n  match last with\n  | None -\u003e print_endline \"no todos\"\n  | Some (id, text, completed) -\u003e \n    print_endline (Printf.sprintf \"id=%i text=%s completed=%b\" id text completed)\n```\n\n### `[%fetch_option QUERY ~record:row]` maybe fetch a record value\n\ndefine a query to fetch an optional record value:\n\n```ocaml\ntype todo = {id: int; text: string; completed: bool}\nlet last = [%exec \"SELECT id, text, completed FROM todos\n                   ORDER BY created DESC\n                   LIMIT 1\" ~record:todo]\nlet () =\n  match last with\n  | None -\u003e print_endline \"no todos\"\n  | Some t -\u003e \n    print_endline (Printf.sprintf \"id=%i text=%s completed=%b\" t.id t.text t.completed)\n```\n\n## bugs\n\n- when instantiating a query, scopes within the `Expr_in` are not being copied\n  fresh\n\n## references\n\n- [HTSQL](https://www.htsql.org)\n- [FunSQL.jl](https://github.com/MechanicalRabbit/FunSQL.jl)\n- [sqlgg](https://github.com/ygrek/sqlgg)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandreypopp%2Fsqlpp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fandreypopp%2Fsqlpp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandreypopp%2Fsqlpp/lists"}