timestamps()
end
+ def normalize_name(name) do
+ name
+ |> String.downcase()
+ |> String.trim()
+ end
+
def get_by_name(name) do
- from(h in Hashtag)
- |> where([h], fragment("name = ?::citext", ^String.downcase(name)))
- |> Repo.one()
+ Repo.get_by(Hashtag, name: normalize_name(name))
end
def get_or_create_by_name(name) when is_bitstring(name) do
end
def get_or_create_by_names(names) when is_list(names) do
- names = Enum.map(names, &String.downcase/1)
+ names = Enum.map(names, &normalize_name/1)
timestamp = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
structs =
try do
with {:ok, %{query_op: hashtags}} <-
Multi.new()
- |> Multi.insert_all(:insert_all_op, Hashtag, structs, on_conflict: :nothing)
+ |> Multi.insert_all(:insert_all_op, Hashtag, structs,
+ on_conflict: :nothing,
+ conflict_target: :name
+ )
|> Multi.run(:query_op, fn _repo, _changes ->
- {:ok,
- Repo.all(from(ht in Hashtag, where: ht.name in fragment("?::citext[]", ^names)))}
+ {:ok, Repo.all(from(ht in Hashtag, where: ht.name in ^names))}
end)
|> Repo.transaction() do
{:ok, hashtags}
def changeset(%Hashtag{} = struct, params) do
struct
|> cast(params, [:name])
- |> update_change(:name, &String.downcase/1)
+ |> update_change(:name, &normalize_name/1)
|> validate_required([:name])
|> unique_constraint(:name)
end
State.reinit()
update_status(:running)
+ put_stat(:iteration_processed_count, 0)
put_stat(:started_at, NaiveDateTime.utc_now())
data_migration_id = data_migration_id()
max_object_id = Enum.at(object_ids, -1)
put_stat(:max_processed_id, max_object_id)
+ increment_stat(:iteration_processed_count, length(object_ids))
increment_stat(:processed_count, length(object_ids))
increment_stat(:failed_count, length(failed_ids))
increment_stat(:affected_count, chunk_affected_count)
end
defp records_per_second do
- get_stat(:processed_count, 0) / Enum.max([running_time(), 1])
+ get_stat(:iteration_processed_count, 0) / Enum.max([running_time(), 1])
end
defp running_time do
alias Pleroma.Conversation
alias Pleroma.Conversation.Participation
alias Pleroma.Filter
+ alias Pleroma.Hashtag
alias Pleroma.Maps
alias Pleroma.Notification
alias Pleroma.Object
end
defp restrict_embedded_tag_all(query, %{tag_all: [_ | _] = tag_all}) do
- tag_all = Enum.map(tag_all, &String.downcase/1)
-
from(
[_activity, object] in query,
where: fragment("(?)->'tag' \\?& (?)", object.data, ^tag_all)
end
defp restrict_embedded_tag_any(query, %{tag: [_ | _] = tag_any}) do
- tag_any = Enum.map(tag_any, &String.downcase/1)
-
from(
[_activity, object] in query,
where: fragment("(?)->'tag' \\?| (?)", object.data, ^tag_any)
end
defp restrict_embedded_tag_reject_any(query, %{tag_reject: [_ | _] = tag_reject}) do
- tag_reject = Enum.map(tag_reject, &String.downcase/1)
-
from(
[_activity, object] in query,
where: fragment("not (?)->'tag' \\?| (?)", object.data, ^tag_reject)
fragment(
"""
(SELECT array_agg(hashtags.name) FROM hashtags JOIN hashtags_objects
- ON hashtags_objects.hashtag_id = hashtags.id WHERE hashtags.name = ANY(?::citext[])
+ ON hashtags_objects.hashtag_id = hashtags.id WHERE hashtags.name = ANY(?)
AND hashtags_objects.object_id = ?) @> ?
""",
^tags,
end
defp restrict_hashtag_any(query, %{tag: [_ | _] = tags}) do
- # TODO: refactor: debug / experimental feature
- if Config.get([:database, :improved_hashtag_timeline]) == :preselect_hashtag_ids do
- hashtag_ids =
- from(ht in Pleroma.Hashtag,
- where: fragment("name = ANY(?::citext[])", ^tags),
- select: ht.id
+ from(
+ [_activity, object] in query,
+ where:
+ fragment(
+ """
+ EXISTS (SELECT 1 FROM hashtags JOIN hashtags_objects
+ ON hashtags_objects.hashtag_id = hashtags.id WHERE hashtags.name = ANY(?)
+ AND hashtags_objects.object_id = ? LIMIT 1)
+ """,
+ ^tags,
+ object.id
)
- |> Repo.all()
-
- from(
- [_activity, object] in query,
- where:
- fragment(
- """
- EXISTS (
- SELECT 1 FROM hashtags_objects WHERE hashtag_id = ANY(?) AND object_id = ? LIMIT 1)
- """,
- ^hashtag_ids,
- object.id
- )
- )
- else
- from(
- [_activity, object] in query,
- where:
- fragment(
- """
- EXISTS (SELECT 1 FROM hashtags JOIN hashtags_objects
- ON hashtags_objects.hashtag_id = hashtags.id WHERE hashtags.name = ANY(?::citext[])
- AND hashtags_objects.object_id = ? LIMIT 1)
- """,
- ^tags,
- object.id
- )
- )
- end
+ )
end
defp restrict_hashtag_any(query, %{tag: tag}) when is_binary(tag) do
fragment(
"""
NOT EXISTS (SELECT 1 FROM hashtags JOIN hashtags_objects
- ON hashtags_objects.hashtag_id = hashtags.id WHERE hashtags.name = ANY(?::citext[])
+ ON hashtags_objects.hashtag_id = hashtags.id WHERE hashtags.name = ANY(?)
AND hashtags_objects.object_id = ? LIMIT 1)
""",
^tags_reject,
defp maybe_order(query, _), do: query
+ defp normalize_fetch_activities_query_opts(opts) do
+ Enum.reduce([:tag, :tag_all, :tag_reject], opts, fn key, opts ->
+ case opts[key] do
+ value when is_bitstring(value) ->
+ Map.put(opts, key, Hashtag.normalize_name(value))
+
+ value when is_list(value) ->
+ Map.put(opts, key, Enum.map(value, &Hashtag.normalize_name/1))
+
+ _ ->
+ opts
+ end
+ end)
+ end
+
defp fetch_activities_query_ap_ids_ops(opts) do
source_user = opts[:muting_user]
ap_id_relationships = if source_user, do: [:mute, :reblog_mute], else: []
end
def fetch_activities_query(recipients, opts \\ %{}) do
+ opts = normalize_fetch_activities_query_opts(opts)
+
{restrict_blocked_opts, restrict_muted_opts, restrict_muted_reblogs_opts} =
fetch_activities_query_ap_ids_ops(opts)