Repository: elixir-ecto/ecto
Pull request #4503 (2024-08-27 09:48:08)

Add `ecto_query: :preload` option to preload query
This pull request, based on [this discussion](https://groups.google.com/g/elixir-ecto/c/Y_S6u0WWecc), adds an option to a preload query so that they can be filtered out when using [Multi tenancy with foreign keys](https://hexdocs.pm/ecto/multi-tenancy-with-foreign-keys.html#adding-org_id-to-read-operations)

This will also change migrations to pass `opts[:ecto_query] = :schema_migration` instead of `opts[:schema_migration] = true`


Patch:
diff --git a/guides/howtos/Multi tenancy with foreign keys.md b/guides/howtos/Multi tenancy with foreign keys.md
index c3633550..cffdde6c 100644
--- a/guides/howtos/Multi tenancy with foreign keys.md
+++ b/guides/howtos/Multi tenancy with foreign keys.md
@@ -27,7 +27,7 @@ defmodule MyApp.Repo do
   @impl true
   def prepare_query(_operation, query, opts) do
     cond do
-      opts[:skip_org_id] || opts[:schema_migration] ->
+      opts[:skip_org_id] || opts[:ecto_query] in [:schema_migrations, :preload] ->
         {query, opts}
 
       org_id = opts[:org_id] ->
@@ -44,7 +44,7 @@ Now we can pass `:org_id` to all READ operations, such as `get`, `get_by`, `prel
 
   * if you explicitly set `:skip_org_id` to true, it won't require an `:org_id`. This reduces the odds of a developer forgetting to scope their queries, which can accidentally expose private data to other users
 
-  * if the `:schema_migration` option is set. This means the repository operation was issued by Ecto itself when migrating our database and we don't want to apply an `org_id` to them
+  * if the `:ecto_query` option is set. This means the repository operation was issued by Ecto itself, with value `:schema_migration` when migrating our database, or `:preload` when issuing a preload query, and we don't want to apply an `org_id` to them
 
 Still, setting the `org_id` for every operation is cumbersome and error prone. We will be better served if all operations attempt to set an `org_id`.
 
diff --git a/lib/ecto/repo/preloader.ex b/lib/ecto/repo/preloader.ex
index 3dc5b37f..376decc0 100644
--- a/lib/ecto/repo/preloader.ex
+++ b/lib/ecto/repo/preloader.ex
@@ -50,7 +50,15 @@ defmodule Ecto.Repo.Preloader do
     normalize_and_preload_each([struct], repo_name, preloads, opts[:take], %{}, tuplet) |> hd()
   end
 
-  defp normalize_and_preload_each(structs, repo_name, preloads, take, query_assocs, tuplet) do
+  defp normalize_and_preload_each(
+         structs,
+         repo_name,
+         preloads,
+         take,
+         query_assocs,
+         {adapter_meta, opts}
+       ) do
+    tuplet = {adapter_meta, Keyword.put(opts, :ecto_query, :preload)}
     preloads = normalize(preloads, take, preloads)
     preload_each(structs, repo_name, preloads, query_assocs, tuplet)
   rescue


Test patch:
diff --git a/test/ecto/repo_test.exs b/test/ecto/repo_test.exs
index 19c3f620..e244d7da 100644
--- a/test/ecto/repo_test.exs
+++ b/test/ecto/repo_test.exs
@@ -2162,7 +2162,7 @@ defmodule Ecto.RepoTest do
 
     test "preload" do
       PrepareRepo.preload(%MySchemaWithAssoc{parent_id: 1}, :parent, hello: :world)
-      assert_received {:all, query, _}
+      assert_received {:all, query, [ecto_query: :preload, hello: :world]}
       assert query.from.source == {"my_parent", Ecto.RepoTest.MyParent}
     end