{"id":18021190,"url":"https://github.com/tizoc/ppx_pgsql","last_synced_at":"2025-03-26T22:30:36.925Z","repository":{"id":66301773,"uuid":"83909335","full_name":"tizoc/ppx_pgsql","owner":"tizoc","description":"Syntax extension for embedded SQL queries using PG'OCaml.","archived":false,"fork":false,"pushed_at":"2020-09-04T17:57:06.000Z","size":87,"stargazers_count":54,"open_issues_count":5,"forks_count":10,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-03-22T14:22:08.697Z","etag":null,"topics":["ocaml","postgres","ppx","sql"],"latest_commit_sha":null,"homepage":"","language":"OCaml","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tizoc.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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":"2017-03-04T16:56:35.000Z","updated_at":"2025-02-01T10:40:50.000Z","dependencies_parsed_at":"2023-02-20T21:46:26.031Z","dependency_job_id":null,"html_url":"https://github.com/tizoc/ppx_pgsql","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tizoc%2Fppx_pgsql","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tizoc%2Fppx_pgsql/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tizoc%2Fppx_pgsql/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tizoc%2Fppx_pgsql/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tizoc","download_url":"https://codeload.github.com/tizoc/ppx_pgsql/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245747398,"owners_count":20665782,"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","postgres","ppx","sql"],"created_at":"2024-10-30T06:09:00.642Z","updated_at":"2025-03-26T22:30:36.915Z","avatar_url":"https://github.com/tizoc.png","language":"OCaml","readme":"# ppx_pgsql\n\nSyntax extension for embedded SQL queries using PG'OCaml.\n\n## How it works\n\nExpressions of the form `[%sqlf \u003csql string\u003e]` are validated and converted into a function that will execute the query. The generated function will take each named argument (denoted as names starting with the dollar symbol, e.g. `$parameter_name`) as a keyword argument with the same name, and a database handle as the last argument.\n\n### Example:\n\n```ocaml\n  let update_account =\n    [%sqlf {|\n      UPDATE accounts\n         SET email = $email, account_type = $account_type\n       WHERE account_id = $account_id\n   RETURNING account_id, created_at, account_type, email\n    |}]\n```\n\nThe type of `update_account` is:\n\n```ocaml\nemail:string -\u003e\naccount_type:string -\u003e\naccount_id:int64 -\u003e\n(string, bool) Hashtbl.t Pg_store_helpers.PGOCaml.t -\u003e\n(int64 * CalendarLib.Calendar.t * string * string) list\nPGOCaml.monad\n```\n\n## Named arguments syntax\n\n- `$name` - normal value\n- `$@name` - list expression value\n- `$?name` - option value (None becomes NULL)\n- `$@?name` - option list expression value\n\n## To install\n\n```\nopam pin add ppx_pgsql -k git https://github.com/tizoc/ppx_pgsql.git\n```\n\n## Some tips\n\n### Views and NULL-able heuristic\n\nThis rewriter tries its best to figure out which columns are NULL-able and which are not, but sometimes it fails to do so.\n\nOne case is with columns in views, which will be assumed to always be NULL-able.\n\nTo fix this, you can alter the view metadata contained in the `pg_attribute` table, and set `attnotnull` to `true`:\n\n```sql\nUPDATE pg_attribute SET attnotnull = 't'\n WHERE attrelid IN (\n   SELECT oid FROM pg_class\n    WHERE relname = 'name_of_view');\n```\n\n### Outer joins, and using `COALESCE`\n\nWhen performing joins, columns that on the original table are qualified as not NULL-able, may become NULL-able, this will make the heuristic fail.\n\nOne workaround is to create a view for such query, and then use the trick described above.\n\n[Another option](https://github.com/tizoc/ppx_pgsql/issues/4#issuecomment-479106321) it to use the `COALESCE` function to force the column to be NULL-able:\n\n```sql\n-- Given these tables\nCREATE TABLE authors (id serial PRIMARY KEY, name varchar(255) NOT NULL);\nINSERT INTO authors (id, name) VALUES (1, 'John Doe');\nCREATE TABLE books (id serial PRIMARY KEY, title varchar(255) NOT NULL, author int NOT NULL REFERENCES authors(id) ON DELETE CASCADE);\n\n-- This join could result in NULL values for books.title\nSELECT\n authors.name,\n coalesce(books.title) -- inferred as NULL-able now\nFROM authors\nLEFT OUTER JOIN books ON books.author = authors.id\n```\n\nCredit to @NightBlues for coming up with this solution.\n\n### IN/NOT IN operator when using a possibly empty dynamic list of values\n\nUsing list expressions to build `IN`/`NOT IN` query expresions (`IN $@name` or `NOT IN $@name`) is not encouraged when the list of values is dynamic and has the potential of being empty.\n\nThe problem with doing so is that the list may be empty, resulting in an invalud query being generated (`IN ()` and `NOT IN ()` are not valid SQL). What is worse, this failure will happen at runtime.\nAdditionaly, by doing so with lists of varying length, a new prepared statement will be created at runtime for each one of the lengths.\n\nAn alternative is to use the `ANY` and `ALL` operators with arrays:\n\n```sql\n-- This\nSELECT COUNT(*) FROM users\nWHERE id IN $@user_ids_list\n\n-- Becomes\nSELECT COUNT(*) FROM users\nWHERE id = ANY($user_ids_list::int[])\n\n-- And this\nSELECT COUNT(*) FROM users\nWHERE id NOT IN $@user_ids_list\n\n-- Becomes\nSELECT COUNT(*) FROM users\nWHERE id \u003c\u003e ALL($user_ids_list::int[])\n```\n\nAnother option is to use the `unnest` array function and an inner `SELECT`:\n\n```sql\nSELECT COUNT(*) FROM users\nWHERE id IN (SELECT unnest($user_ids_list::int[]))\n\nSELECT COUNT(*) FROM users\nWHERE id NOT IN (SELECT unnest($user_ids_list::int[]))\n```","funding_links":[],"categories":["Databases"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftizoc%2Fppx_pgsql","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftizoc%2Fppx_pgsql","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftizoc%2Fppx_pgsql/lists"}