Ecto: Merge `select` or `select_merge` into a possibly `nil` value (left join)

Asking for help! I’m struggling with a left join merge with a virtual src field coming from another join:

|> select([cs, f, c, s], %{f.fragment_name => %{c | src: s.src}})


* (BadMapError) expected a map, got: nil
    (ecto 3.10.2) lib/ecto/repo/queryable.ex:391: anonymous fn/2 in Ecto.Repo.Queryable.process_update/5
    (elixir 1.14.4) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
    (ecto 3.10.2) lib/ecto/repo/queryable.ex:391: Ecto.Repo.Queryable.process_update/5
    (ecto 3.10.2) lib/ecto/repo/queryable.ex:404: anonymous fn/4 in Ecto.Repo.Queryable.process_kv/4
    (elixir 1.14.4) lib/enum.ex:1780: Enum."-map_reduce/3-lists^mapfoldl/2-0-"/3
    (ecto 3.10.2) lib/ecto/repo/queryable.ex:363: Ecto.Repo.Queryable.process/4
    (ecto 3.10.2) lib/ecto/repo/queryable.ex:283: anonymous fn/3 in Ecto.Repo.Queryable.postprocessor/4
    (elixir 1.14.4) lib/enum.ex:1658: Enum."-map/2-lists^map/1-0-"/2
    (ecto 3.10.2) lib/ecto/repo/queryable.ex:235: Ecto.Repo.Queryable.execute/4
    (ecto 3.10.2) lib/ecto/repo/queryable.ex:19: Ecto.Repo.Queryable.all/3

I understand it’s coming from c being nil sometimes. How would I get %{c | src: s.src} for non-nil and nil for all the rest cases?

|> select([cs, f, c, s], %{f.f_fragment_name => c})
|> select_merge([cs, f, c, s], %{f.f_fragment_name => %{c | src: s.src}})

has the same outcome.

I’m not sure this will help as I don’t know the whole query and never written one where I’m updating a specific map key, but map/2 is useful for left joins. It will work when given a nil source and merge when given the same source multiple times.

|> select([cs, f, c, s], %{f.fragment_name => map(c, [:src])})

Thank you @sodapopcan, I unfortunately couldn’t manage to make Ecto know where :src is coming from in that expression, it doesn’t seem like allowing to specify s.src which is what the data sits it.

For now I ended up doing

      |> select([cs, f, c, s], %{{f.f_fragment_name, :val} => c})
      |> select_merge([cs, f, c, s], %{{f.f_fragment_name, :src} => s.src})

and then reducing with substituting src with the one coming from matching {name, :src} key. Not to most brilliant solution, but I have to reduce anyway, because I’m initially creating a nested structure, a list of maps, which I need to flatten a bit.

map seems to be useful, I’ll keep an eye on it!

I was afraid of that but didn’t know how to articulate it as I haven’t been in this exact situation before. Glad you found a solution!