defmodule Noizu.Weaviate.GraphQL.Aggregate do defstruct [ class: nil, where: nil, group_by: nil, near_text: nil, near_vector: nil, near_object: nil, object_limit: nil, limit: nil, tenant: nil, fields: [] ] def where(%Noizu.Weaviate.GraphQL.Aggregate{} = this, value) do %{this | where: value} end def group_by(%Noizu.Weaviate.GraphQL.Aggregate{} = this, value) when is_list(value) do %{this | group_by: value} end def near_text(%Noizu.Weaviate.GraphQL.Aggregate{} = this, value) do %{this | near_text: value} end def near_vector(%Noizu.Weaviate.GraphQL.Aggregate{} = this, value) do %{this | near_vector: value} end def near_object(%Noizu.Weaviate.GraphQL.Aggregate{} = this, value) do %{this | near_object: value} end def object_limit(%Noizu.Weaviate.GraphQL.Aggregate{} = this, value) do %{this | object_limit: value} end def limit(%Noizu.Weaviate.GraphQL.Aggregate{} = this, value) do %{this | limit: value} end def tenant(%Noizu.Weaviate.GraphQL.Aggregate{} = this, value) do %{this | tenant: value} end @doc """ Add a field specification for aggregation. Example: field(agg, %{name: "wordCount", type: :int, aggregations: [:count, :mean, :maximum]}) """ def field(%Noizu.Weaviate.GraphQL.Aggregate{} = this, field_spec) do update_in(this, [Access.key(:fields)], &([field_spec | &1])) end @doc """ Add meta { count } to the aggregate query. """ def meta_count(%Noizu.Weaviate.GraphQL.Aggregate{} = this) do field(this, %{name: :meta, type: :meta, aggregations: [:count]}) end @doc false def render_aggregation(:topOccurrences), do: "topOccurrences { value occurs }" def render_aggregation({:topOccurrences, limit}), do: "topOccurrences(limit: #{limit}) { value occurs }" def render_aggregation(:pointingTo), do: "pointingTo" def render_aggregation(:groupedBy), do: "groupedBy { path value }" def render_aggregation(agg), do: "#{agg}" @doc false def render_field(%{name: :meta, type: :meta, aggregations: aggs}) do inner = Enum.map(aggs, &render_aggregation/1) |> Enum.join(" ") "meta { #{inner} }" end def render_field(%{name: name, aggregations: aggs}) do inner = Enum.map(aggs, &render_aggregation/1) |> Enum.join(" ") "#{name} { #{inner} }" end defimpl Jason.Encoder do defp nest(string, prefix) do prepared = String.trim(string) |> String.split("\n") |> Enum.join("\n#{prefix}") prepared end def encode(this, _opts) do class = case this.class do v when is_bitstring(v) -> v v when is_atom(v) -> v.__class__ end class_attributes = [] |> then(& this.where && ["where: #{nest(Jason.encode!(this.where), " ")}" | &1] || &1) |> then(& this.near_text && [Jason.encode!(this.near_text) | &1] || &1) |> then(& this.near_vector && [Jason.encode!(this.near_vector) | &1] || &1) |> then(& this.near_object && [Jason.encode!(this.near_object) | &1] || &1) |> then(& this.group_by && ["groupBy: #{inspect(this.group_by)}" | &1] || &1) |> then(& this.object_limit && [{:objectLimit, this.object_limit} | &1] || &1) |> then(& this.limit && [{:limit, this.limit} | &1] || &1) |> then(& this.tenant && ["tenant: #{inspect(this.tenant)}" | &1] || &1) |> Enum.map(fn ({k, v}) -> "#{k}: #{Noizu.Weaviate.GraphQL.encode_value(v)}" (k) -> k end) |> Enum.join(",\n") |> case do "" -> nil v -> v end fields = this.fields |> Enum.reverse() |> Enum.map(&Noizu.Weaviate.GraphQL.Aggregate.render_field/1) # Add groupedBy output when group_by is set fields = if this.group_by do fields ++ ["groupedBy { path value }"] else fields end fields_str = Enum.join(fields, "\n") query = if class_attributes do """ { Aggregate { #{class} ( #{nest(class_attributes, " ")} ) { #{nest(fields_str, " ")} } } } """ else """ { Aggregate { #{class} { #{nest(fields_str, " ")} } } } """ end |> String.trim() """ {\"query\": #{inspect(query)}} """ |> String.trim() end end end