Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redundant ecto queries #127

Open
sam701 opened this issue Jul 3, 2021 · 2 comments
Open

Redundant ecto queries #127

sam701 opened this issue Jul 3, 2021 · 2 comments

Comments

@sam701
Copy link

sam701 commented Jul 3, 2021

I'm trying to plug this awesome dataloader module. It's working as expected so far but I'm observing redundant ecto queries in the following scenario. Here are absinthe objects of my graphql schema:

  object :topic do
    field :id, :id
    field :title, :string

    field :posts, list_of(:post), resolve: dataloader(:db)

    field :created_at, :string
    field :created_by, :user, resolve: dataloader(:db)
    field :updated_at, :string
    field :updated_by, :user, resolve: dataloader(:db)
  end


  object :post do
    field :id, :id

    field :content, :string

    field :created_at, :string
    field :created_by, :user, resolve: dataloader(:db)
    field :updated_at, :string
    field :updated_by, :user, resolve: dataloader(:db)
  end

When I fire the query below I observe 4 queries select * from users....:

query {
  topic {
   title created_by { name } updated_by { name }
   posts {
      content created_by { name } updated_by { name }
   }
  }
}

Is there a way to define the absinthe objects so that there will be just one query for the users?

EDIT: the "hack" below is wrong (see my comment).
I managed to reduce the number of user queries by two using this “hack”

field :updated_by, :user, resolve: dataloader(:db, :created_by)

But is there a proper way to specify a unique key for all four fields?

@sam701
Copy link
Author

sam701 commented Jul 12, 2021

The "hack" I described above is wrong. It worked only because created_by and updated_by was the same user. I will edit the issue description.

I came up with the following solution:

  defp batch_loader(source, entity, id_field) do
    fn parent, _args, %{context: %{loader: loader}} ->
      id = Map.get(parent, id_field)
      loader
        |> Dataloader.load(source, entity, id)
        |> Absinthe.Resolution.Helpers.on_load(fn loader ->
          result = Dataloader.get(loader, source, entity, id)
          {:ok, result}
        end)
    end
  end
  def user_loader(source, id_field), do: batch_loader(source, User, id_field)

and then in the schema:

    field :created_by, :user, resolve: user_loader(:db, :created_by_id)
    field :updated_by, :user, resolve: user_loader(:db, :updated_by_id)

This works. But the question now is if this can be done automatically by Dataloader.Ecto? Or are there any reasons why it can/should not be done?

@benwilson512
Copy link
Contributor

The blocker here is whether Dataloader can "see through" associations on a schema to determine that they have equivalent SQL that will be run. Not sure if this is doable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants