improvement: Cache always selected fields and use mapsets for building select list (#1428)

* Add attribute_names function on Info that returns MapSet

And use that when calculating select lists.
This commit is contained in:
pinetops 2024-09-01 17:06:51 -04:00 committed by GitHub
parent cdbb0c4608
commit 2c754f90a6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 43 additions and 32 deletions

View file

@ -845,42 +845,37 @@ defmodule Ash.Query do
""" """
def select(query, fields, opts \\ []) do def select(query, fields, opts \\ []) do
query = new(query) query = new(query)
fields = List.wrap(fields) existing_fields = Ash.Resource.Info.attribute_names(query.resource)
fields = MapSet.new(List.wrap(fields))
{fields, non_existent} = valid_fields = MapSet.intersection(fields, existing_fields)
Enum.split_with(fields, &Ash.Resource.Info.attribute(query.resource, &1))
query = query =
Enum.reduce(non_existent, query, fn field, query -> if MapSet.size(valid_fields) != MapSet.size(fields) do
Ash.Query.add_error( MapSet.difference(fields, existing_fields)
query, |> Enum.reduce(query, fn field, query ->
Ash.Error.Query.NoSuchAttribute.exception(resource: query.resource, attribute: field) Ash.Query.add_error(
) query,
end) Ash.Error.Query.NoSuchAttribute.exception(resource: query.resource, attribute: field)
)
end)
else
query
end
always_select = always_select =
query.resource valid_fields
|> Ash.Resource.Info.attributes() |> MapSet.union(Ash.Resource.Info.always_selected_attribute_names(query.resource))
|> Enum.filter(& &1.always_select?) |> MapSet.union(MapSet.new(Ash.Resource.Info.primary_key(query.resource)))
|> Enum.map(& &1.name)
if opts[:replace?] do new_select =
%{ if opts[:replace?] do
query always_select
| select: else
Enum.uniq(fields ++ Ash.Resource.Info.primary_key(query.resource) ++ always_select) MapSet.union(MapSet.new(query.select || []), always_select)
} end
else
%{ %{query | select: MapSet.to_list(new_select)}
query
| select:
Enum.uniq(
fields ++
(query.select || []) ++
Ash.Resource.Info.primary_key(query.resource) ++ always_select
)
}
end
end end
@doc """ @doc """

View file

@ -649,6 +649,11 @@ defmodule Ash.Resource.Info do
Extension.get_entities(resource, [:attributes]) Extension.get_entities(resource, [:attributes])
end end
@spec attribute_names(Spark.Dsl.t() | Ash.Resource.t()) :: MapSet.t()
def attribute_names(resource) do
Extension.get_persisted(resource, :attribute_names)
end
@doc "Returns all attributes of a resource with lazy non-matching-defaults" @doc "Returns all attributes of a resource with lazy non-matching-defaults"
@spec lazy_non_matching_default_attributes( @spec lazy_non_matching_default_attributes(
Spark.Dsl.t() | Ash.Resource.t(), Spark.Dsl.t() | Ash.Resource.t(),
@ -769,6 +774,11 @@ defmodule Ash.Resource.Info do
end end
end end
@spec always_selected_attribute_names(Spark.Dsl.t() | Ash.Resource.t()) :: MapSet.t()
def always_selected_attribute_names(resource) do
Extension.get_persisted(resource, :always_selected_attribute_names)
end
@doc "Returns all attributes, aggregates, calculations and relationships of a resource" @doc "Returns all attributes, aggregates, calculations and relationships of a resource"
@spec fields( @spec fields(
Spark.Dsl.t() | Ash.Resource.t(), Spark.Dsl.t() | Ash.Resource.t(),

View file

@ -20,7 +20,7 @@ defmodule Ash.Resource.Transformers.AttributesByName do
|> Map.put(to_string(name), attr) |> Map.put(to_string(name), attr)
end) end)
attribute_names = Enum.map(attributes, & &1.name) attribute_names = Enum.map(attributes, & &1.name) |> MapSet.new()
create_attributes_with_static_defaults = create_attributes_with_static_defaults =
attributes attributes
@ -61,6 +61,11 @@ defmodule Ash.Resource.Transformers.AttributesByName do
(is_function(attribute.update_default) or match?({_, _, _}, attribute.update_default)) (is_function(attribute.update_default) or match?({_, _, _}, attribute.update_default))
end) end)
always_selected_attribute_names =
Enum.filter(attributes, & &1.always_select?)
|> Enum.map(& &1.name)
|> MapSet.new()
{:ok, {:ok,
persist( persist(
dsl_state, dsl_state,
@ -74,7 +79,8 @@ defmodule Ash.Resource.Transformers.AttributesByName do
update_attributes_with_static_defaults: update_attributes_with_static_defaults, update_attributes_with_static_defaults: update_attributes_with_static_defaults,
update_attributes_with_non_matching_lazy_defaults: update_attributes_with_non_matching_lazy_defaults:
update_attributes_with_non_matching_lazy_defaults, update_attributes_with_non_matching_lazy_defaults,
update_attributes_with_matching_defaults: update_attributes_with_matching_defaults update_attributes_with_matching_defaults: update_attributes_with_matching_defaults,
always_selected_attribute_names: always_selected_attribute_names
} }
)} )}
end end