{"id":23014802,"url":"https://github.com/alexdesousa/ayesql","last_synced_at":"2025-05-15T20:03:38.349Z","repository":{"id":32989396,"uuid":"148910953","full_name":"alexdesousa/ayesql","owner":"alexdesousa","description":"Library for using raw SQL in Elixir","archived":false,"fork":false,"pushed_at":"2024-11-08T22:30:28.000Z","size":146,"stargazers_count":146,"open_issues_count":5,"forks_count":14,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-05-12T03:09:28.236Z","etag":null,"topics":["ecto","elixir","postgresql","postgrex","raw-sql","yesql"],"latest_commit_sha":null,"homepage":null,"language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alexdesousa.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","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},"funding":{"github":["alexdesousa"]}},"created_at":"2018-09-15T14:49:10.000Z","updated_at":"2025-05-01T22:49:57.000Z","dependencies_parsed_at":"2024-10-23T01:16:41.269Z","dependency_job_id":"d3346de7-6529-4a27-bd3b-f42886c92bb0","html_url":"https://github.com/alexdesousa/ayesql","commit_stats":{"total_commits":52,"total_committers":8,"mean_commits":6.5,"dds":0.5,"last_synced_commit":"dc089bd73310d0fa7b001757d91baa8eca8a5332"},"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexdesousa%2Fayesql","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexdesousa%2Fayesql/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexdesousa%2Fayesql/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexdesousa%2Fayesql/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alexdesousa","download_url":"https://codeload.github.com/alexdesousa/ayesql/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254414493,"owners_count":22067271,"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":["ecto","elixir","postgresql","postgrex","raw-sql","yesql"],"created_at":"2024-12-15T11:05:28.331Z","updated_at":"2025-05-15T20:03:37.449Z","avatar_url":"https://github.com/alexdesousa.png","language":"Elixir","funding_links":["https://github.com/sponsors/alexdesousa"],"categories":[],"sub_categories":[],"readme":"# AyeSQL\n\n![Build status](https://github.com/alexdesousa/ayesql/actions/workflows/checks.yml/badge.svg) [![Hex pm](http://img.shields.io/hexpm/v/ayesql.svg?style=flat)](https://hex.pm/packages/ayesql) [![hex.pm downloads](https://img.shields.io/hexpm/dt/ayesql.svg?style=flat)](https://hex.pm/packages/ayesql) [![Coverage Status](https://coveralls.io/repos/github/alexdesousa/ayesql/badge.svg?branch=master)](https://coveralls.io/github/alexdesousa/ayesql?branch=master)\n\n\u003e **Aye** _/ʌɪ/_ _exclamation (archaic dialect)_: said to express assent; yes.\n\n_AyeSQL_ is a library for using raw SQL.\n\n## Overview\n\nInspired by Clojure library [Yesql](https://github.com/krisajenkins/yesql),\n_AyeSQL_ tries to find a middle ground between strings with raw SQL queries and\nSQL DSLs. This library aims to:\n\n- Keep SQL in SQL files.\n- Generate easy to use Elixir functions for every query.\n- Parameterize queries using maps and keyword lists.\n- Allow query composablity.\n- Work out-of-the-box with PostgreSQL using\n  [Ecto](https://github.com/elixir-ecto/ecto_sql) or\n  [Postgrex](https://github.com/elixir-ecto/postgrex).\n- Work out-of-the-box with DuckDB using\n  [Duckdbex](https://github.com/AlexR2D2/duckdbex).\n\nIf you want to know more about AyeSQL:\n\n- [Small example](#small-example)\n- [Syntax](#syntax)\n\n  + [Naming queries](#naming-queries)\n  + [Parameters](#parameters)\n  + [Mandatory parameters](#mandatory-parameters)\n  + [Query composition](#query-composition)\n  + [Optional fragments](#optional-fragments)\n  + [IN statement](#in-statement)\n  + [Subqueries and subfragments](#subqueries-and-subfragments)\n\n- [Installation](#installation)\n\nAnd the following additional links provide more information about the library:\n\n- [Full Documentation](https://hexdocs.pm/ayesql)\n- [AyeSQL: Writing Raw SQL in Elixir](https://thebroken.link/ayesql-writing-raw-sql-in-elixir/)\n- [Why raw SQL?](https://hexdocs.pm/ayesql/why-raw-sql.html)\n- [Dynamic queries with EEx](https://hexdocs.pm/ayesql/dynamic-queries-with-eex.html)\n- [Adding support to other databases](https://hexdocs.pm/ayesql/query-runners.html)\n\n## Small Example\n\nIn AyeSQL, the equivalent would be to create an SQL file with the query e.g.\n`queries.sql`:\n\n```sql\n-- file: queries.sql\n-- name: get_avg_clicks\n-- docs: Gets average click count.\n    WITH computed_dates AS (\n      SELECT datetime::date AS date\n      FROM generate_series(\n        current_date - :days::interval, -- Named parameter :days\n        current_date - interval '1 day',\n        interval '1 day'\n      )\n    )\n  SELECT dates.date AS day, count(clicks.id) AS count\n    FROM computed_date AS dates\n         LEFT JOIN clicks AS clicks ON date(clicks.inserted_at) = dates.date\n   WHERE clicks.link_id = :link_id -- Named parameter :link_id\nGROUP BY dates.date\nORDER BY dates.date;\n```\n\nIn Elixir, we would load all the queries in this file by creating the following\nmodule:\n\n```elixir\n# file: lib/queries.ex\ndefmodule Queries do\n  use AyeSQL, repo: MyRepo\n\n  defqueries(\"queries.sql\") # File name with relative path to SQL file.\nend\n```\n\nor using the macro `defqueries/3`:\n\n```elixir\n# file: lib/queries.ex\nimport AyeSQL, only: [defqueries: 3]\n\ndefqueries(Queries, \"queries.sql\", repo: MyRepo)\n```\n\n\u003e **Note**: The file name used in `defqueries` macro should be relative to the\n\u003e file where the macro is used.\n\nBoth approaches will create a module called `Queries` with all the queries\ndefined in `queries.sql`.\n\nAnd then we could execute the query as follows:\n\n```elixir\niex\u003e params = [\n...\u003e   link_id: 42,\n...\u003e   days: %Postgrex.Interval{secs: 864_000} # 10 days\n...\u003e ]\niex\u003e Queries.get_avg_clicks(params)\n{:ok,\n  [\n    %{day: ..., count: ...},\n    %{day: ..., count: ...},\n    %{day: ..., count: ...},\n    ...\n  ]\n}\n```\n\n## Syntax\n\nAn SQL file can have as many queries as you want as long as they are named.\n\nFor the following sections we'll assume we have:\n\n- `lib/my_repo.ex` which is an `Ecto` repo called `MyRepo`.\n- `lib/queries.sql` with SQL queries.\n- `lib/queries.ex` with the following structure:\n\n    ```elixir\n    import AyeSQL, only: [defqueries: 3]\n\n    defqueries(Queries, \"queries.sql\", repo: MyRepo)\n    ```\n\n### Naming Queries\n\nFor naming queries, we add a comment with the keyword `-- name: ` followed by\nthe name of the function e.g the following query would generate the function\n`Queries.get_hostnames/2`:\n\n```sql\n-- name: get_hostnames\nSELECT hostname FROM server\n```\n\nAdditionally, we could also add documentation for the query by adding a comment\nwith the keyword `-- docs: ` followed by the query's documentation e.g:\n\n```sql\n-- name: get_hostnames\n-- docs: Gets hostnames from the servers.\nSELECT hostname FROM server\n```\n\n\u003e Important: if the function does not have `-- docs: ` it won't have\n\u003e documentation e.g. `@doc false`.\n\n### Parameters\n\nThere are two types of parameters:\n\n- Mandatory: for passing parameters to a query. They start with `:` e.g.\n  `:hostname`.\n- Optional: for query composability. They start with `:_` e.g. `:_order_by`.\n\nAdditionally, any query in a file can be accessed with its name adding `:` at\nthe front e.g `:get_hostnames`.\n\n### Mandatory Parameters\n\nLet's say we want to get the name of an operative system by architecture:\n\n```sql\n-- name: get_os_by_architecture\n-- docs: Gets operative system's name by a given architecture.\nSELECT name\n  FROM operative_system\n WHERE architecture = :architecture\n```\n\nThe previous query would generate the function\n`Queries.get_os_by_architecture/2` that can be called as:\n\n```elixir\niex\u003e Queries.get_os_by_architecture(architecture: \"AMD64\")\n{:ok,\n  [\n    %{name: \"Debian Buster\"},\n    %{name: \"Windows 10\"},\n    ...\n  ]\n}\n```\n\n### Query Composition\n\nNow if we would like to get hostnames by architecture we could compose queries\nby doing the following:\n\n```sql\n-- name: get_os_by_architecture\n-- docs: Gets operative system's name by a given architecture.\nSELECT name\n  FROM operative_system\n WHERE architecture = :architecture\n\n-- name: get_hostnames_by_architecture\n-- docs: Gets hostnames by architecture.\nSELECT hostname\n  FROM servers\n WHERE os_name IN ( :get_os_by_architecture )\n```\n\nThe previous query would generate the function\n`Queries.get_hostnames_by_architecture/2` that can be called as:\n\n```elixir\niex\u003e Queries.get_hostnames_by_architecture(architecture: \"AMD64\")\n{:ok,\n  [\n    %{hostname: \"server0\"},\n    %{hostname: \"server1\"},\n    ...\n  ]\n}\n```\n\n### Optional Fragments\n\nLet's say that now we need to order ascending or descending by hostname by\nusing an optional `:_order_by` parameter e.g:\n\n```sql\n-- name: get_os_by_architecture\n-- docs: Gets operative system's name by a given architecture.\nSELECT name\n  FROM operative_system\n WHERE architecture = :architecture\n\n-- name: get_hostnames_by_architecture\n-- docs: Gets hostnames by architecture.\nSELECT hostname\n  FROM servers\n WHERE os_name IN ( :get_os_by_architecture )\n :_order_by\n\n-- name: ascending\nORDER BY hostname ASC\n\n-- name: descending\nORDER BY hostname DESC\n```\n\nThe previous query could be called as before:\n\n```elixir\niex\u003e Queries.get_hostnames_by_architecture(architecture: \"AMD64\")\n{:ok,\n  [\n    %{hostname: \"Barcelona\"},\n    %{hostname: \"Granada\"},\n    %{hostname: \"Madrid\"},\n    ...\n  ]\n}\n```\n\nor by order ascending:\n\n```elixir\niex\u003e params = [architecture: \"AMD64\", _order_by: :ascending]\niex\u003e Queries.get_hostnames_by_architecture(params)\n{:ok,\n  [\n    %{hostname: \"Barcelona\"},\n    %{hostname: \"Madrid\"},\n    %{hostname: \"Granada\"},\n    ...\n  ]\n}\n```\n\nor descending:\n\n```elixir\niex\u003e params = [architecture: \"AMD64\", _order_by: :descending]\niex\u003e Queries.get_hostnames_by_architecture(params)\n{:ok,\n  [\n    %{hostname: \"Zaragoza\"},\n    %{hostname: \"Madrid\"},\n    %{hostname: \"Granada\"},\n    ...\n  ]\n}\n```\n\n\u003e Important: A query can be called by name e.g. `:descending` if it's defined\n\u003e in the same SQL file. Otherwise, we need to pass the function instead e.g.\n\u003e `Queries.descending/2`\n\u003e\n\u003e ```elixir\n\u003e iex\u003e params = [architecture: \"AMD64\", _order_by: \u0026Queries.descending/2]\n\u003e iex\u003e Queries.get_hostnames_by_architecture(params)\n\u003e {:ok,\n\u003e   [\n\u003e     %{hostname: \"Zaragoza\"},\n\u003e     %{hostname: \"Madrid\"},\n\u003e     %{hostname: \"Granada\"},\n\u003e     ...\n\u003e   ]\n\u003e }\n\u003e ```\n\n### IN Statement\n\nLists in SQL might be tricky. That's why AyeSQL supports a special type for\nthem e.g:\n\nLet's say we have the following query:\n\n```sql\n-- name: get_os_by_hostname\n-- docs: Gets hostnames and OS names given a list of hostnames.\nSELECT hostname, os_name\n  FROM servers\n WHERE hostname IN (:hostnames)\n```\n\nIt is possible to do the following:\n\n```elixir\niex\u003e params = [hostnames: {:in, [\"server0\", \"server1\", \"server2\"]}]\niex\u003e Server.get_os_by_hostname(params)\n{:ok,\n  [\n    %{hostname: \"server0\", os_name: \"Debian Buster\"},\n    %{hostname: \"server1\", avg_ram: \"Windows 10\"},\n    %{hostname: \"server2\", avg_ram: \"Minix 3\"}\n  ]\n}\n```\n\n### Subqueries and Subfragments\n\nSubqueries can be composed directly, as show before, or via the `:inner` tuple\ne.g. let's say we need to get the adults order by name in ascending order and\nage in descending order:\n\n```sql\n-- name: ascending\nASC\n\n-- name: descending\nDESC\n\n-- name: by_age\nage :order_direction\n\n-- name: by_name\nname :order_direction\n\n-- name: get_adults\n-- docs: Gets adults.\nSELECT name, age\n  FROM person\n WHERE age \u003e= 18\nORDER BY :order_by\n```\n\nThen our code in elixir would be:\n\n```elixir\niex\u003e order_by = [\n...\u003e   by_name: [order_direction: :ascending],\n...\u003e   by_age: [order_direction: :descending]\n...\u003e ]\niex\u003e Queries.get_adults(order_by: {:inner, order_by, \", \"})\n{:ok,\n  [\n    %{name: \"Alice\", age: 42},\n    %{name: \"Bob\", age: 21},\n    ...\n  ]\n}\n```\n\n\u003e **Note**: If you're using this level of composability and it fits your use\n\u003e case, consider using either:\n\u003e - [Ecto](https://hexdocs.pm/ecto/Ecto.html)\n\u003e - [EEx templates](https://hexdocs.pm/ayesql/dynamic-queries-with-eex.html)\n\n## Installation\n\nAyeSQL is available as a Hex package. To install, add it to your\ndependencies in your `mix.exs` file:\n\n```elixir\ndef deps do\n  [{:ayesql, \"~\u003e 1.1\"}]\nend\n```\n\nIf you're going to use any of the provided query runners, then you should add\ntheir dependencies as well:\n\n- Add `:ecto_sql` for `AyeSQL.Runner.Ecto` (default runner).\n- Add `:postgrex` for `AyeSQL.Runner.Postgrex`.\n- Add `duckdbex` for `AyeSQL.Runner.Duckdbex`.\n- Add `:ecto_sql` and `:postgrex` for running queries using `Ecto` in a\n  `PostgreSQL` database.\n\n## Author\n\nAlexander de Sousa.\n\n## License\n\nAyeSQL is released under the MIT License. See the LICENSE file for further\ndetails.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexdesousa%2Fayesql","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falexdesousa%2Fayesql","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexdesousa%2Fayesql/lists"}