1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.User.Search do
10 def search(query, opts \\ []) do
11 resolve = Keyword.get(opts, :resolve, false)
12 for_user = Keyword.get(opts, :for_user)
14 # Strip the beginning @ off if there is a query
15 query = String.trim_leading(query, "@")
17 maybe_resolve(resolve, for_user, query)
20 Repo.transaction(fn ->
21 Ecto.Adapters.SQL.query(Repo, "select set_limit(0.25)", [])
24 |> search_query(for_user)
31 defp maybe_resolve(true, %User{}, query) do
32 User.get_or_fetch(query)
35 defp maybe_resolve(true, _, query) do
36 unless restrict_local?(), do: User.get_or_fetch(query)
39 defp maybe_resolve(_, _, _), do: :noop
41 defp search_query(query, for_user) do
45 |> boost_search_rank_query(for_user)
47 |> order_by(desc: :search_rank)
49 |> maybe_restrict_local(for_user)
52 defp restrict_local? do
53 Pleroma.Config.get([:instance, :limit_unauthenticated_to_local_content], true)
56 defp union_query(query) do
57 fts_subquery = fts_search_subquery(query)
58 trigram_subquery = trigram_search_subquery(query)
60 from(s in trigram_subquery, union_all: ^fts_subquery)
63 defp distinct_query(q) do
64 from(s in subquery(q), order_by: s.search_type, distinct: s.id)
67 # unauthenticated users can only search local activities
68 defp maybe_restrict_local(q, %User{}), do: q
70 defp maybe_restrict_local(q, _) do
71 if restrict_local?() do
72 where(q, [u], u.local == true)
78 defp boost_search_rank_query(query, nil), do: query
80 defp boost_search_rank_query(query, for_user) do
81 friends_ids = User.get_friends_ids(for_user)
82 followers_ids = User.get_followers_ids(for_user)
84 from(u in subquery(query),
89 CASE WHEN (?) THEN 0.5 + (?) * 1.3
90 WHEN (?) THEN 0.5 + (?) * 1.2
91 WHEN (?) THEN (?) * 1.1
94 u.id in ^friends_ids and u.id in ^followers_ids,
98 u.id in ^followers_ids,
106 defp fts_search_subquery(term, query \\ User) do
109 |> String.replace(~r/\W+/, " ")
112 |> Enum.map(&(&1 <> ":*"))
123 setweight(to_tsvector('simple', regexp_replace(?, '\\W', ' ', 'g')), 'A') ||
124 setweight(to_tsvector('simple', regexp_replace(coalesce(?, ''), '\\W', ' ', 'g')), 'B'),
125 to_tsquery('simple', ?),
137 (setweight(to_tsvector('simple', regexp_replace(?, '\\W', ' ', 'g')), 'A') ||
138 setweight(to_tsvector('simple', regexp_replace(coalesce(?, ''), '\\W', ' ', 'g')), 'B')) @@ to_tsquery('simple', ?)
145 |> User.restrict_deactivated()
148 defp trigram_search_subquery(term) do
152 # ^1 gives 'Postgrex expected a binary, got 1' for some weird reason
153 search_type: fragment("?", 1),
156 "similarity(?, trim(? || ' ' || coalesce(?, '')))",
162 where: fragment("trim(? || ' ' || coalesce(?, '')) % ?", u.nickname, u.name, ^term)
164 |> User.restrict_deactivated()