What's the right way to cast Repo.all results into the correct struct?

I wrote a query joining two tables

    search_query =
      from(
        c in Chunk,
        inner_join: r in Source,
        on: c.source_id == r.id,
        select: %{
          id: c.id,
          content_jsonpath: c.content_jsonpath,
          rank: fragment("RANK() OVER (ORDER BY ?)", cosine_distance(c.embedding, ^vector)),
          source_id: c.source_id,
          content: fragment("jsonb_path_query(?, ?::jsonpath)", r.content, c.content_jsonpath)
        },
        order_by: [asc: cosine_distance(c.embedding, ^vector)],
        limit: ^top_n
      )

    results =
      Repo.all(search_query)
      |> IO.inspect(label: "Semantic Search")
      |> Enum.map(fn chunk ->
        %ChunkSearchResult{}
        |> ChunkSearchResult.changeset(chunk)
        |> Ecto.Changeset.apply_changes()
      end)

and I casted the results into ChunkSearchResult. This works, but I’m wondering if I’m doing this right. How do folks typically type their query results when a custom select: {} clause is used?

Here’s the ChunkSearchResult:

defmodule SightxrApi.Chunks.ChunkSearchResult do
  use Ecto.Schema
  import Ecto.Changeset

  schema "chunks" do
    field :source_id, :integer
    field :content_jsonpath, :string
    field :content, :string
    field :rank, :integer
  end

  @doc false
  def changeset(post, attrs) do
    post
    |> cast(attrs, [:source_id, :content_jsonpath, :content, :rank])
    |> validate_required([:source_id, :content_jsonpath, :content, :rank])
  end
end
2 Likes

A struct is just a map with a __struct__ key, you can write it directly in the the select:

7 Likes

Ohhh! Ecto syntax surprises me every day. :slight_smile:

1 Like