zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

query.ex (10880B)


      1 # Zenflows is designed to implement the Valueflows vocabulary,
      2 # written and maintained by srfsh <info@dyne.org>.
      3 # Copyright (C) 2021-2023 Dyne.org foundation <foundation@dyne.org>.
      4 #
      5 # This program is free software: you can redistribute it and/or modify
      6 # it under the terms of the GNU Affero General Public License as published by
      7 # the Free Software Foundation, either version 3 of the License, or
      8 # (at your option) any later version.
      9 #
     10 # This program is distributed in the hope that it will be useful,
     11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
     12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13 # GNU Affero General Public License for more details.
     14 #
     15 # You should have received a copy of the GNU Affero General Public License
     16 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
     17 
     18 defmodule Zenflows.VF.Proposal.Query do
     19 @moduledoc false
     20 
     21 import Ecto.Query
     22 
     23 alias Ecto.{Changeset, Queryable}
     24 alias Zenflows.DB.{ID, Page, Schema, Validate}
     25 alias Zenflows.VF.Proposal
     26 
     27 @spec all(Page.t()) :: {:ok, Queryable.t()} | {:error, Changeset.t()}
     28 def all(%{filter: nil}), do: {:ok, Proposal}
     29 def all(%{filter: params}) do
     30 	with {:ok, filters} <- all_validate(params) do
     31 		{:ok, Enum.reduce(filters, Proposal, &all_f(&2, &1)) |> distinct([x], x.id)}
     32 	end
     33 end
     34 
     35 @spec all_f(Queryable.t(), {atom(), term()}) :: Queryable.t()
     36 defp all_f(q, {:primary_intents_resource_inventoried_as_conforms_to, v}) do
     37 	q
     38 	|> join(:primary_intents_resource_inventoried_as)
     39 	|> where([primary_intents_resource_inventoried_as: r], r.conforms_to_id in ^v)
     40 end
     41 defp all_f(q, {:or_primary_intents_resource_inventoried_as_conforms_to, v}) do
     42 	q
     43 	|> join(:primary_intents_resource_inventoried_as)
     44 	|> or_where([primary_intents_resource_inventoried_as: r], r.conforms_to_id in ^v)
     45 end
     46 defp all_f(q, {:primary_intents_resource_inventoried_as_primary_accountable, v}) do
     47 	q
     48 	|> join(:primary_intents_resource_inventoried_as)
     49 	|> where([primary_intents_resource_inventoried_as: r], r.primary_accountable_id in ^v)
     50 end
     51 defp all_f(q, {:or_primary_intents_resource_inventoried_as_primary_accountable, v}) do
     52 	q
     53 	|> join(:primary_intents_resource_inventoried_as)
     54 	|> or_where([primary_intents_resource_inventoried_as: r], r.primary_accountable_id in ^v)
     55 end
     56 defp all_f(q, {:primary_intents_resource_inventoried_as_classified_as, v}) do
     57 	q
     58 	|> join(:primary_intents_resource_inventoried_as)
     59 	|> where([primary_intents_resource_inventoried_as: r], fragment("? @> ?", r.classified_as, ^v))
     60 end
     61 defp all_f(q, {:or_primary_intents_resource_inventoried_as_classified_as, v}) do
     62 	q
     63 	|> join(:primary_intents_resource_inventoried_as)
     64 	|> or_where([primary_intents_resource_inventoried_as: r], fragment("? @> ?", r.classified_as, ^v))
     65 end
     66 defp all_f(q, {:primary_intents_resource_inventoried_as_name, v}) do
     67 	q
     68 	|> join(:primary_intents_resource_inventoried_as)
     69 	|> where([primary_intents_resource_inventoried_as: r], ilike(r.name, ^"%#{v}%"))
     70 end
     71 defp all_f(q, {:or_primary_intents_resource_inventoried_as_name, v}) do
     72 	q
     73 	|> join(:primary_intents_resource_inventoried_as)
     74 	|> or_where([primary_intents_resource_inventoried_as: r], ilike(r.name, ^"%#{v}%"))
     75 end
     76 defp all_f(q, {:primary_intents_resource_inventoried_as_note, v}) do
     77 	q
     78 	|> join(:primary_intents_resource_inventoried_as)
     79 	|> where([primary_intents_resource_inventoried_as: r], ilike(r.note, ^"%#{v}%"))
     80 end
     81 defp all_f(q, {:or_primary_intents_resource_inventoried_as_note, v}) do
     82 	q
     83 	|> join(:primary_intents_resource_inventoried_as)
     84 	|> or_where([primary_intents_resource_inventoried_as: r], ilike(r.note, ^"%#{v}%"))
     85 end
     86 defp all_f(q, {:primary_intents_resource_inventoried_as_id, v}) do
     87 	q
     88 	|> join(:primary_intents_resource_inventoried_as)
     89 	|> where([primary_intents_resource_inventoried_as: r], r.id in ^v)
     90 end
     91 defp all_f(q, {:or_primary_intents_resource_inventoried_as_id, v}) do
     92 	q
     93 	|> join(:primary_intents_resource_inventoried_as)
     94 	|> or_where([primary_intents_resource_inventoried_as: r], r.id in ^v)
     95 end
     96 defp all_f(q, {:status, v}) do
     97 	cond = case v do
     98 		:refused -> dynamic([intents: i, satisfactions: s], fragment("every(?)", i.finished) and count(s.id) == 0)
     99 		:accepted -> dynamic([intents: i, satisfactions: s], count(i.id) == count(s.id))
    100 		:pending -> dynamic([intents: i, satisfactions: s], not fragment("every(?)", i.finished) and count(i.id) != count(s.id))
    101 	end
    102 	q
    103 	|> join(:inner, [x], pi in assoc(x, :publishes), as: :proposed_intents)
    104 	|> join(:inner, [proposed_intents: pi], i in assoc(pi, :publishes), as: :intents)
    105 	|> join(:left, [intents: i], s in assoc(i, :satisfied_by), as: :satisfactions)
    106 	|> group_by([x], x.id)
    107 	|> having(^cond)
    108 end
    109 defp all_f(q, {:or_status, v}) do
    110 	cond = case v do
    111 		:refused -> dynamic([intents: i, satisfactions: s], fragment("every(?)", i.finished) and count(s.id) == 0)
    112 		:accepted -> dynamic([intents: i, satisfactions: s], count(i.id) == count(s.id))
    113 		:pending -> dynamic([intents: i, satisfactions: s], not fragment("every(?)", i.finished) and count(i.id) != count(s.id))
    114 	end
    115 	q
    116 	|> join(:inner, [x], pi in assoc(x, :publishes), as: :proposed_intents)
    117 	|> join(:inner, [proposed_intents: pi], i in assoc(pi, :publishes), as: :intents)
    118 	|> join(:left, [intents: i], s in assoc(i, :satisfied_by), as: :satisfactions)
    119 	|> group_by([x], x.id)
    120 	|> or_having(^cond)
    121 end
    122 defp all_f(q, {:not_status, v}) do
    123 	cond = case v do
    124 		:refused -> dynamic([intents: i, satisfactions: s], not (fragment("every(?)", i.finished) and count(s.id) == 0))
    125 		:accepted -> dynamic([intents: i, satisfactions: s], not (count(i.id) == count(s.id)))
    126 		:pending -> dynamic([intents: i, satisfactions: s], not (not fragment("every(?)", i.finished) and count(i.id) != count(s.id)))
    127 	end
    128 	q
    129 	|> join(:inner, [x], pi in assoc(x, :publishes), as: :proposed_intents)
    130 	|> join(:inner, [proposed_intents: pi], i in assoc(pi, :publishes), as: :intents)
    131 	|> join(:left, [intents: i], s in assoc(i, :satisfied_by), as: :satisfactions)
    132 	|> group_by([x], x.id)
    133 	|> having(^cond)
    134 end
    135 
    136 # join primary_intents
    137 @spec join(Queryable.t(), atom()) :: Queryable.t()
    138 defp join(q, :primary_intents) do
    139 	if has_named_binding?(q, :primary_intents),
    140 		do: q,
    141 		else: join(q, :inner, [x], pi in assoc(x, :primary_intents), as: :primary_intents)
    142 end
    143 # join resource_inventoried_as through primary_intents above
    144 defp join(q, :primary_intents_resource_inventoried_as) do
    145 	q = join(q, :primary_intents)
    146 	if has_named_binding?(q, :primary_intents_resource_inventoried_as),
    147 		do: q,
    148 		else: join(q, :inner, [primary_intents: pi], r in assoc(pi, :resource_inventoried_as),
    149 			as: :primary_intents_resource_inventoried_as)
    150 end
    151 
    152 @spec all_validate(Schema.params())
    153 	:: {:ok, Changeset.data()} | {:error, Changeset.t()}
    154 defp all_validate(params) do
    155 	{%{}, %{
    156 		primary_intents_resource_inventoried_as_conforms_to: {:array, ID},
    157 		or_primary_intents_resource_inventoried_as_conforms_to: {:array, ID},
    158 		primary_intents_resource_inventoried_as_primary_accountable: {:array, ID},
    159 		or_primary_intents_resource_inventoried_as_primary_accountable: {:array, ID},
    160 		primary_intents_resource_inventoried_as_classified_as: {:array, :string},
    161 		or_primary_intents_resource_inventoried_as_classified_as: {:array, :string},
    162 		primary_intents_resource_inventoried_as_name: :string,
    163 		or_primary_intents_resource_inventoried_as_name: :string,
    164 		primary_intents_resource_inventoried_as_note: :string,
    165 		or_primary_intents_resource_inventoried_as_note: :string,
    166 		primary_intents_resource_inventoried_as_id: {:array, ID},
    167 		or_primary_intents_resource_inventoried_as_id: {:array, ID},
    168 		status: {:parameterized, Ecto.Enum, Ecto.Enum.init(values: ~w[pending accepted refused]a)},
    169 		or_status: {:parameterized, Ecto.Enum, Ecto.Enum.init(values: ~w[pending accepted refused]a)},
    170 		not_status: {:parameterized, Ecto.Enum, Ecto.Enum.init(values: ~w[pending accepted refused]a)},
    171 	}}
    172 	|> Changeset.cast(params, ~w[
    173 		primary_intents_resource_inventoried_as_conforms_to
    174 		or_primary_intents_resource_inventoried_as_conforms_to
    175 		primary_intents_resource_inventoried_as_primary_accountable
    176 		or_primary_intents_resource_inventoried_as_primary_accountable
    177 		primary_intents_resource_inventoried_as_classified_as
    178 		or_primary_intents_resource_inventoried_as_classified_as
    179 		primary_intents_resource_inventoried_as_name
    180 		or_primary_intents_resource_inventoried_as_name
    181 		primary_intents_resource_inventoried_as_note
    182 		or_primary_intents_resource_inventoried_as_note
    183 		primary_intents_resource_inventoried_as_id
    184 		or_primary_intents_resource_inventoried_as_id
    185 		status or_status not_status
    186 	]a)
    187 	|> Validate.class(:primary_intents_resource_inventoried_as_conforms_to)
    188 	|> Validate.class(:or_primary_intents_resource_inventoried_as_conforms_to)
    189 	|> Validate.exist_nand([:primary_intents_resource_inventoried_as_conforms_to,
    190 		:or_primary_intents_resource_inventoried_as_conforms_to])
    191 	|> Validate.class(:primary_intents_resource_inventoried_as_primary_accountable)
    192 	|> Validate.class(:or_primary_intents_resource_inventoried_as_primary_accountable)
    193 	|> Validate.exist_nand([:primary_intents_resource_inventoried_as_primary_accountable,
    194 		:or_primary_intents_resource_inventoried_as_primary_accountable])
    195 	|> Validate.class(:primary_intents_resource_inventoried_as_classified_as)
    196 	|> Validate.class(:or_primary_intents_resource_inventoried_as_classified_as)
    197 	|> Validate.exist_nand([:primary_intents_resource_inventoried_as_classified_as,
    198 		:or_primary_intents_resource_inventoried_as_classified_as])
    199 	|> Validate.escape_like(:primary_intents_resource_inventoried_as_name)
    200 	|> Validate.escape_like(:or_primary_intents_resource_inventoried)
    201 	|> Validate.name(:primary_intents_resource_inventoried_as_name)
    202 	|> Validate.name(:or_primary_intents_resource_inventoried_as_name)
    203 	|> Validate.exist_nand([:primary_intents_resource_inventoried_as_name,
    204 		:or_primary_intents_resource_inventoried_as_name])
    205 	|> Validate.escape_like(:primary_intents_resource_inventoried_as_note)
    206 	|> Validate.escape_like(:or_primary_intents_resource_inventoried)
    207 	|> Validate.note(:primary_intents_resource_inventoried_as_note)
    208 	|> Validate.note(:or_primary_intents_resource_inventoried_as_note)
    209 	|> Validate.exist_nand([:primary_intents_resource_inventoried_as_note,
    210 		:or_primary_intents_resource_inventoried_as_note])
    211 	|> Validate.class(:primary_intents_resource_inventoried_as_id)
    212 	|> Validate.class(:or_primary_intents_resource_inventoried_as_id)
    213 	|> Validate.exist_nand([:primary_intents_resource_inventoried_as_id,
    214 		:or_primary_intents_resource_inventoried_as_id])
    215 	|> Validate.exist_nand([:status, :or_status, :not_status])
    216 	|> Changeset.apply_action(nil)
    217 end
    218 
    219 @spec status(Schema.id()) :: Queryable.t()
    220 def status(id) do
    221 	from p in Proposal,
    222 		where: p.id == ^id,
    223 		join: pi in assoc(p, :publishes),
    224 		join: i in assoc(pi, :publishes),
    225 		left_join: s in assoc(i, :satisfied_by),
    226 		select: {fragment("every(?)", i.finished), count(i.id), count(s.id)}
    227 end
    228 end