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

View file

@ -649,6 +649,11 @@ defmodule Ash.Resource.Info do
Extension.get_entities(resource, [:attributes])
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"
@spec lazy_non_matching_default_attributes(
Spark.Dsl.t() | Ash.Resource.t(),
@ -769,6 +774,11 @@ defmodule Ash.Resource.Info do
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"
@spec fields(
Spark.Dsl.t() | Ash.Resource.t(),

View file

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