{"id":20072219,"url":"https://github.com/bluzky/querie","last_synced_at":"2025-05-05T20:33:09.787Z","repository":{"id":48570619,"uuid":"306199572","full_name":"bluzky/querie","owner":"bluzky","description":"Compose Ecto query from the client side","archived":false,"fork":false,"pushed_at":"2021-07-20T03:28:31.000Z","size":254,"stargazers_count":22,"open_issues_count":1,"forks_count":1,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-09-17T17:56:18.179Z","etag":null,"topics":["ecto","elixir","phoenix","query-builder"],"latest_commit_sha":null,"homepage":"","language":"Elixir","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/bluzky.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}},"created_at":"2020-10-22T02:19:40.000Z","updated_at":"2024-06-12T05:42:21.000Z","dependencies_parsed_at":"2022-09-26T20:01:06.735Z","dependency_job_id":null,"html_url":"https://github.com/bluzky/querie","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bluzky%2Fquerie","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bluzky%2Fquerie/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bluzky%2Fquerie/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bluzky%2Fquerie/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bluzky","download_url":"https://codeload.github.com/bluzky/querie/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224468184,"owners_count":17316325,"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","phoenix","query-builder"],"created_at":"2024-11-13T14:38:59.667Z","updated_at":"2024-11-13T14:39:00.344Z","avatar_url":"https://github.com/bluzky.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv style=\"text-align: center\"\u003e\n\u003cimg src=\"docs/logo.png\" width=\"200\"\u003e\n\u003ch3\u003eCompose Ecto query from the client side\u003c/h3\u003e\n\u003c/div\u003e\n\n### Install\n\nAdd to your `mix.exs` file:\n\n```elixir\n[\n\t{:querie, \"~\u003e 1.0\"}\n]\n```\n\n\n### This is what `Query` does\nIt turns this:\n```\nhttp://localhost:4000/products?name__icontains=milk\u0026price__ge=32\u0026rating__sort=desc\n```\n\nInto this\n```elixir\nfrom(p in Product, where: ilike(p.name, \"%milk%\") and p.price \u003e= 32, order_by: [desc: :rating])\n```\n\nCool, right?\n\n## Table of content\n-  [What is Querie?](#what-is-querie-)\n- [What Querie can do?](#what-querie-can-do-)\n- [How to use Querie?](#how-to-use-querie-)\n  - [Install](#install)\n  - [Query on a single table](#query-on-a-single-table)\n  - [Sort query result](#sort-query-result)\n  - [Query `between`](#query--between-)\n  - [Join tables](#query-reference-tables)\n  - [Custom join field](#custom-join-field)\n- [Build filter in your code](#filter-directly-from-your-code)\n- [Supported operators](#supported-operators)\n\n\n## What is `Querie`?\nQuerie is a library that help you to build the query directly from the URL parameters without writing to much code.\nIf you want to add more filter criteria? Don't worry, you only need to change the filter schema.\n\n## What `Querie` can do?\n\n- Build Ecto Query dynamically\n- Query reference tables\n- Support common query operator: `\u003e` `\u003e=` `\u003c` `\u003c=` `=` `not` `like` `ilike` `between` `is_nil`\n- Support sort query\n\n**Especially Querie does not use macro** 😇\n\n## How to use `Querie`?\n### Query on a single table\n\nThere are 3 steps to make it work\n**1. Define a filter schema**\n\n\u003e Schema is a map which define:\\* data type of field, so it can be parsed correctly\n\u003e\n\u003e - which field can be filter, other extra fields are skip\n\u003e - which tables are referenced and how to query referenced tables\n\nFor example you have a Post schema:\n\n```elixir\ndefmodule Example.Content.Post do\n  use Ecto.Schema\n  import Ecto.Changeset\n\n  def state_enum(), do: ~w(draft published archived trash)\n\n  schema \"posts\" do\n    field(:content, :string)\n    field(:state, :string, default: \"draft\")\n    field(:title, :string)\n    field(:view_count, :integer, default: 0)\n    belongs_to(:category, Example.PostMeta.Category)\n    belongs_to(:author, Example.Account.User)\n  end\nend\n```\n\nAnd you want to filter the `Post` by `title`, `state`, `view_count`. This is the schema:\n\n```elixir\n@schema %{\n    title: :string,\n    state: :string, # short form\n    view_count: [type: :integer] # long form\n}\n```\n\n**2. Parse request parameters and build the query**\nUse `Querie.parse/2` to parse request parameters with your schema\n\n```elixir\nalias Example.Content.Post\n\ndef index(conn, params) do\n    with {:ok, filter} \u003c- Querie.parse(@schema, params) do\n\t query = Querie.filter(Post, filter)\n\t # Or you can pass a query like this\n\t # query = from(p in Post, where: ....)\n\t # query = Querie.filter(query, filter)\n\t posts = Repo.all(query)\n\t # do the rendering here\n    else\n    {:error, errors} -\u003e\n\t IO.puts(inspect(errors)\n\t # or do anything with error\n\t # error is a list of tuple {field, message}\n    end\nend\n```\n\n**3. Build the URL query**\nParameter must follow this format: `[field_name]__[operator]=[value]`. If no operator is specified, by defaut `=` operator is used.\nSupported operators are listed below.\n\nFor example you want to filter `Post` which:\n\n- `title` contains `elixir`\n- `state` is `published`\n- `view_count` \u003e= 100\n\nURL query string would be: `?title__icontains=elixir\u0026state=published\u0026view_count__ge=100`\n\n### Sort query result\n\nFollow this format to sort by field: `\u003cfield\u003e__sort=\u003casc|desc\u003e`\n\nFor example you want to sort by `title` ascending, add this to query: `title__sort=asc`\n\nSimple, right?\n\nYou can set default sort order and sort priority for each field:\n\n```elixir\n%{\n    view_count: [type: :integer, sort_default: :desc, sort_priority: 1]\n    title: [type: :string, sort_default: :asc, sort_priority: 2]\n}\n```\n\nField with smaller `sort_priority` smaller is sorted first\n\n### Query `between`\n\n`Query` supports query between `min` and `max` value. It translates `between` to ` \u003e min and \u003c max`. And inclusive version is `ibetween` which translated to ` \u003e= min and \u003c= max`\n\nYou don’t have to modify your schema to use `between`.\nFrom client you can send between value in 3 forms:\n\n- value with separator: `view_count__between=20,60`\n- array of 2 value: `view_count__between[]=20\u0026view_count__between[]=60`\n- map value with `min` and `max`: `view_count__between[min]=20\u0026view_count__between[max]=60`\n\nIf `min` or `max` is omitted, it will use one compare operator.\n\n### Query reference tables\n\nFor example, the `Post` schema above references to 2 other schemas: `User` and `Category` you can filter with conditions on those 2 schema.\n\nThis is the schema for `User`\n\n```elixir\ndefmodule Example.Account.User do\n  use Ecto.Schema\n  import Ecto.Changeset\n\n  schema \"users\" do\n    field(:email, :string)\n    field(:first_name, :string)\n    field(:last_name, :string)\n  end\n```\n\n**1. Update your schema**\n\n```elixir\nalias Example.Account.User\n\n@schema %{\n    title: :string,\n    state: :string,\n    view_count: [type: :integer],\n    author: [\n\t\ttype: :ref, # this references to another schema\n\t\tmodel: User, # which schema to query\n\t\tschema: %{ # define filter schema for User\n\t\t\temail: :string\n\t\t}\n\t  ]\n}\n\n```\n\n**2. Update your query**\nFor example you want to query `Post` by author whose `email` contains `sam` the query would be: `?author__ref[email__icontains]=sam`\n\n### Custom join field\n\nYou can specify custom join field with 2 options:\n\n- `foreign_key` default is `[field]_id`. In the example above, it is `author_id`\n- `references` is the key to join on the other table. Default is `id`\n\n\n## Filter directly from your code\n\nYou can build filter for `Querie` directly from your code\n\n```elixir\nfilters = %{\n    title: {:ilike, \"elixir\"},\n    tag: \"elixir\", # query with = operator,\n    category_id: [1, 2], # query with in operator,\n    view_count: {:between, [10, 50]}\n}\n\nQuerie.filter(Post, filters}\n```\n\n## Supported operators\n\nThis is list of supported operators with mapping key word.\n\n| operator          | mapping keyword |\n| ----------------- | --------------- |\n| =                 | `is` or omit    |\n| !=                | `ne`     |\n| \u003e                 | `gt`            |\n| \u003e=                | `ge`            |\n| \u003c                 | `lt`            |\n| \u003c=                | `le`            |\n| like              | `contains` or `like`      |\n| ilike             | `icontains` or `ilike`     |\n| between           | `between`       |\n| inclusive between | `ibetween`      |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbluzky%2Fquerie","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbluzky%2Fquerie","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbluzky%2Fquerie/lists"}