fuelen

fuelen

Hi all!

Just want to share a small code snippet which allows writing CASE expressions using macro which is similar to cond.
Here is an example of usage:

import Ecto.Extension.Query.API
import Ecto.Query

from users in "users",
  select: %{
    email: users.email,
    role_label:
      cond_ do
        users.role == "director" -> "DIRECTOR!"
        users.inserted_at < ago(6, "month") -> "OLD!"
        true -> type(users.role, :string)
      end
  }

generated SQL:

SELECT u0."email", CASE WHEN u0."role" = 'director' THEN 'DIRECTOR!' WHEN u0."inserted_at" < $1::timestamp + (-6::decimal::numeric * interval '1 month') THEN 'OLD!' WHEN TRUE THEN u0."role"::varchar END FROM "users" AS u0

and implementation:

defmodule Ecto.Extension.Query.API do
  defmacro cond_(do: block) do
    bindings =
      block
      |> Enum.reduce([], fn
        {:->, _, [[clause], branch]}, acc ->
          [branch, clause | acc]
      end)
      |> Enum.reverse()

    bindings_number = length(bindings)

    sql =
      IO.iodata_to_binary([
        "CASE",
        List.duplicate(" WHEN ? THEN ?", div(bindings_number, 2)),
        " END"
      ])

    quote do
      fragment(unquote(sql), unquote_splicing(bindings))
    end
  end
end

I hope someone will find this useful :slight_smile:

Showing Posts 1 to 3

hauleth

hauleth

Super nice. Once upon a time I was thinking about doing exactly the same magic.

Small suggestion for improvement - define from macro in your module and then traverse the query to replace all cond with your implementation. This should result with clearer API :wink:

axelson

axelson

Scenic Core Team

This is a neat improvement to ecto’s sql!

But the downside to this is that (if I’m following correctly) this improvement would no longer be composable with other ecto DSL improvements since only one person can define from

fuelen

fuelen OP

Well, this is my first attempt to implement @hauleth idea. Some kind of composability is still possible

defmodule CustomEctoFrom do
  defmacro __using__(map) do
    quote do
      require Ecto.Query

      defmacro from(expr, kw) do
        kw = CustomEctoFrom.traverse(kw, unquote(map))

        quote do
          Ecto.Query.from(unquote(expr), unquote(kw))
        end
      end
    end
  end

  def traverse({function, args}, map) when is_atom(function) do
    {function, traverse(args, map)}
  end

  def traverse({function, meta, args}, map) do
    function =
      case Map.fetch(map, function) do
        {:ok, aliased_to} -> aliased_to
        :error -> function
      end

    {traverse(function, map), meta, traverse(args, map)}
  end

  def traverse(kw, map) when is_list(kw) do
    for elem <- kw, do: traverse(elem, map)
  end

  def traverse(term, _map), do: term
end

defmodule OtherModuleWithExtensions do
  defmacro concat(a, b) do
    quote do
      fragment("? || ?", unquote(a), unquote(b))
    end
  end
end

defmodule Test do
  import Ecto.Extension.Query.API
  import OtherModuleWithExtensions
  use CustomEctoFrom, %{cond: :cond_, <>: :concat}

  def test do
    from users in "users",
      select: %{
        full_name: users.first_name <> " " <> users.last_name,
        email: users.email,
        role_label:
          cond do
            users.role == "director" -> "DIRECTOR!"
            users.inserted_at < ago(6, "month") -> "OLD!"
            true -> type(users.role, :string)
          end,
        id: users.id
      }
  end
end
— All posts loaded —

Where Next? Top

Trending in Guides/Tuts Top

krasenyp
You probably already know that &lt;span&gt;{nil}&lt;/span&gt; in a HEEX template produces &lt;span&gt; &lt;/span&gt; when rendered. I fin...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews