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 if match?(%User{}, for_user) and resolve, do: User.get_or_fetch(query)
20 Repo.transaction(fn ->
21 Ecto.Adapters.SQL.query(Repo, "select set_limit(0.25)", [])
24 |> search_query(for_user)
31 defp search_query(query, for_user) do
35 |> boost_search_rank_query(for_user)
37 |> order_by(desc: :search_rank)
39 |> maybe_restrict_local(for_user)
42 defp union_query(query) do
43 fts_subquery = fts_search_subquery(query)
44 trigram_subquery = trigram_search_subquery(query)
46 from(s in trigram_subquery, union_all: ^fts_subquery)
49 defp distinct_query(q) do
50 from(s in subquery(q), order_by: s.search_type, distinct: s.id)
53 # unauthenticated users can only search local activities
54 defp maybe_restrict_local(q, %User{}), do: q
55 defp maybe_restrict_local(q, _), do: where(q, [u], u.local == true)
57 defp boost_search_rank_query(query, nil), do: query
59 defp boost_search_rank_query(query, for_user) do
60 friends_ids = User.get_friends_ids(for_user)
61 followers_ids = User.get_followers_ids(for_user)
63 from(u in subquery(query),
68 CASE WHEN (?) THEN (?) * 1.3
69 WHEN (?) THEN (?) * 1.2
70 WHEN (?) THEN (?) * 1.1
73 u.id in ^friends_ids and u.id in ^followers_ids,
77 u.id in ^followers_ids,
85 defp fts_search_subquery(term, query \\ User) do
88 |> String.replace(~r/\W+/, " ")
91 |> Enum.map(&(&1 <> ":*"))
102 setweight(to_tsvector('simple', regexp_replace(?, '\\W', ' ', 'g')), 'A') ||
103 setweight(to_tsvector('simple', regexp_replace(coalesce(?, ''), '\\W', ' ', 'g')), 'B'),
104 to_tsquery('simple', ?),
116 (setweight(to_tsvector('simple', regexp_replace(?, '\\W', ' ', 'g')), 'A') ||
117 setweight(to_tsvector('simple', regexp_replace(coalesce(?, ''), '\\W', ' ', 'g')), 'B')) @@ to_tsquery('simple', ?)
124 |> User.restrict_deactivated()
127 defp trigram_search_subquery(term) do
131 # ^1 gives 'Postgrex expected a binary, got 1' for some weird reason
132 search_type: fragment("?", 1),
135 "similarity(?, trim(? || ' ' || coalesce(?, '')))",
141 where: fragment("trim(? || ' ' || coalesce(?, '')) % ?", u.nickname, u.name, ^term)
143 |> User.restrict_deactivated()