Potential bug for context-based multi-tenancy and many-to-many relationships

Hello, I think I found a bug in the handling of tenancy.

I have a resource that manages context-based multi-tenancy in my app called
Platform.Accounts.Client. It implements Ash.ToTenant so I pass the record
into the tenant param rather than the string value of the tenant. This has
worked everywhere in my app except for this one circumstance I found today.

I have a resource called Platform.Tracking.EntityType, which has a has_many
relationship called field_sets. Each of those has a many_to_many relationship
called fields.

If I try to load the data like so, I get an error:

Tracking.get_entity_type!(params["entity_type_id"],
  load: [field_sets: :fields],
  # This is the tenant record (not the string value)
  tenant: socket.assigns.current_tenant,
  actor: socket.assigns.current_user
)

The stacktrace for the error looks like:

[error] ** (FunctionClauseError) no function clause matching in Ecto.Adapters.Postgres.Connection.quote_name/1
    (ecto_sql 3.11.3) lib/ecto/adapters/postgres/connection.ex:1803: Ecto.Adapters.Postgres.Connection.quote_name(#Platform.Accounts.Client<__meta__: #Ecto.Schema.Metadata<:loaded, "clients">, id: "0190db98-e47a-7ce3-a41e-f28dc15873d5", name: #Ash.CiString<"CLIENT NAME">, slug: #Ash.CiString<"client-slug">, active: true, description: nil, inserted_at: ~U[2024-07-22 17:59:08.666898Z], updated_at: ~U[2024-07-22 17:59:08.666898Z], aggregates: %{}, calculations: %{}, ...>)
    (ecto_sql 3.11.3) lib/ecto/adapters/postgres/connection.ex:1801: Ecto.Adapters.Postgres.Connection.quote_name/2
    (ecto_sql 3.11.3) lib/ecto/adapters/postgres/connection.ex:1139: Ecto.Adapters.Postgres.Connection.create_name/3
    (ecto_sql 3.11.3) lib/ecto/adapters/postgres/connection.ex:1118: Ecto.Adapters.Postgres.Connection.create_names/4
    (ecto_sql 3.11.3) lib/ecto/adapters/postgres/connection.ex:1114: Ecto.Adapters.Postgres.Connection.create_names/2
    (ecto_sql 3.11.3) lib/ecto/adapters/postgres/connection.ex:161: Ecto.Adapters.Postgres.Connection.all/2
    (ecto_sql 3.11.3) lib/ecto/adapters/postgres.ex:132: Ecto.Adapters.Postgres.prepare/2
    (ecto 3.11.2) lib/ecto/query/planner.ex:182: Ecto.Query.Planner.query_without_cache/4
    (ecto 3.11.2) lib/ecto/query/planner.ex:152: Ecto.Query.Planner.query_prepare/6
    (ecto 3.11.2) lib/ecto/query/planner.ex:127: Ecto.Query.Planner.query_with_cache/8
    (ecto 3.11.2) lib/ecto/repo/queryable.ex:214: Ecto.Repo.Queryable.execute/4
    (ecto 3.11.2) lib/ecto/repo/queryable.ex:19: Ecto.Repo.Queryable.all/3
    (ash_postgres 2.1.6) lib/data_layer.ex:973: AshPostgres.DataLayer.run_query_with_lateral_join/4
    (ash 3.2.0) lib/ash/actions/read/read.ex:2419: Ash.Actions.Read.run_query/4
    (ash 3.2.0) lib/ash/actions/read/read.ex:448: anonymous fn/5 in Ash.Actions.Read.do_read/4
    (ash 3.2.0) lib/ash/actions/read/read.ex:786: Ash.Actions.Read.maybe_in_transaction/3
    (ash 3.2.0) lib/ash/actions/read/read.ex:249: Ash.Actions.Read.do_run/3
    (ash 3.2.0) lib/ash/actions/read/read.ex:66: anonymous fn/3 in Ash.Actions.Read.run/3
    (ash 3.2.0) lib/ash/actions/read/read.ex:65: Ash.Actions.Read.run/3
    (ash 3.2.0) lib/ash/actions/read/relationships.ex:379: anonymous fn/2 in Ash.Actions.Read.Relationships.do_fetch_related_records/3
    (elixir 1.17.2) lib/enum.ex:1711: anonymous fn/3 in Enum.map/2
    (elixir 1.17.2) lib/enum.ex:4423: anonymous fn/3 in Enum.map/2
    (elixir 1.17.2) lib/stream.ex:1879: anonymous fn/3 in Enumerable.Stream.reduce/3
    (elixir 1.17.2) lib/enum.ex:4858: Enumerable.List.reduce/3
    (elixir 1.17.2) lib/stream.ex:1891: Enumerable.Stream.do_each/4
    (elixir 1.17.2) lib/enum.ex:4423: Enum.map/2
    (ash 3.2.0) lib/ash/actions/read/relationships.ex:43: Ash.Actions.Read.Relationships.fetch_related_records/2
    (ash 3.2.0) lib/ash/actions/read/relationships.ex:24: Ash.Actions.Read.Relationships.load/3
    (ash 3.2.0) lib/ash/actions/read/read.ex:271: Ash.Actions.Read.do_run/3
    (ash 3.2.0) lib/ash/actions/read/read.ex:66: anonymous fn/3 in Ash.Actions.Read.run/3
    (ash 3.2.0) lib/ash/actions/read/read.ex:65: Ash.Actions.Read.run/3
    (ash 3.2.0) lib/ash/actions/read/relationships.ex:514: anonymous fn/3 in Ash.Actions.Read.Relationships.do_fetch_related_records/3
    (elixir 1.17.2) lib/enum.ex:1711: anonymous fn/3 in Enum.map/2
    (elixir 1.17.2) lib/enum.ex:4423: anonymous fn/3 in Enum.map/2
    (elixir 1.17.2) lib/stream.ex:1879: anonymous fn/3 in Enumerable.Stream.reduce/3
    (elixir 1.17.2) lib/enum.ex:4858: Enumerable.List.reduce/3
    (elixir 1.17.2) lib/stream.ex:1891: Enumerable.Stream.do_each/4
    (elixir 1.17.2) lib/enum.ex:4423: Enum.map/2
    (ash 3.2.0) lib/ash/actions/read/relationships.ex:43: Ash.Actions.Read.Relationships.fetch_related_records/2
    (ash 3.2.0) lib/ash/actions/read/relationships.ex:24: Ash.Actions.Read.Relationships.load/3
    (ash 3.2.0) lib/ash/actions/read/read.ex:271: Ash.Actions.Read.do_run/3
    (ash 3.2.0) lib/ash/actions/read/read.ex:66: anonymous fn/3 in Ash.Actions.Read.run/3
    (ash 3.2.0) lib/ash/actions/read/read.ex:65: Ash.Actions.Read.run/3
    (ash 3.2.0) lib/ash.ex:1977: Ash.do_read_one/3
    (ash 3.2.0) lib/ash.ex:1926: Ash.read_one/2
    (ash 3.2.0) lib/ash.ex:1902: Ash.read_one!/2
    (platform 0.1.0) deps/ash/lib/ash/code_interface.ex:634: Platform.Tracking.get_entity_type!/3
    (platform 0.1.0) lib/platform_web/live/tracking/entity_live/index.ex:104: PlatformWeb.Tracking.EntityLive.Index.handle_params/3
    (phoenix_live_view 1.0.0-rc.6) lib/phoenix_live_view/utils.ex:451: anonymous fn/5 in Phoenix.LiveView.Utils.call_handle_params!/5
    (telemetry 1.2.1) /Users/mc/src/mylanconnolly/platform/deps/telemetry/src/telemetry.erl:321: :telemetry.span/3
    (phoenix_live_view 1.0.0-rc.6) lib/phoenix_live_view/static.ex:282: Phoenix.LiveView.Static.call_mount_and_handle_params!/5
    (phoenix_live_view 1.0.0-rc.6) lib/phoenix_live_view/static.ex:116: Phoenix.LiveView.Static.render/3
    (phoenix_live_view 1.0.0-rc.6) lib/phoenix_live_view/controller.ex:39: Phoenix.LiveView.Controller.live_render/3
    (phoenix 1.7.14) lib/phoenix/router.ex:484: Phoenix.Router.__call__/5
    (platform 0.1.0) lib/platform_web/endpoint.ex:1: PlatformWeb.Endpoint.plug_builder_call/2
    (platform 0.1.0) deps/plug/lib/plug/debugger.ex:136: PlatformWeb.Endpoint."call (overridable 3)"/2
    (platform 0.1.0) lib/platform_web/endpoint.ex:1: PlatformWeb.Endpoint.call/2
    (phoenix 1.7.14) lib/phoenix/endpoint/sync_code_reload_plug.ex:22: Phoenix.Endpoint.SyncCodeReloadPlug.do_call/4
    (bandit 1.5.5) lib/bandit/pipeline.ex:124: Bandit.Pipeline.call_plug!/2
    (bandit 1.5.5) lib/bandit/pipeline.ex:36: Bandit.Pipeline.run/4

If I either pass the string value of the tenant into the tenant param or
simply not load the fields relationship, the error goes away so it kinda looks
to me like it’s just not properly setting the tenant in some part of the code.
I tried looking through it but I wasn’t sure where that would be.

For now I can convert the tenant to the string value but I thought I’d bring it
up in case this isn’t expected behavior.

Thanks!

I’ve got the issue reproduced in a test :slight_smile: Will push a fixed version of ash_postgres up soon.

Fixed in version 2.1.14 of ash_postgres

I just updated to 2.1.14 and it works like a charm now. Thanks so much, I can’t believe how quick that turnaround was!

1 Like