object: add support for preloading objects when walking an activity graph in normal form

This commit is contained in:
William Pitcock 2019-03-22 23:34:47 +00:00
parent 8b18955a59
commit 62bccddde0
2 changed files with 86 additions and 1 deletions

View file

@ -7,6 +7,7 @@ defmodule Pleroma.Activity do
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Notification alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Repo alias Pleroma.Repo
import Ecto.Query import Ecto.Query
@ -33,6 +34,18 @@ defmodule Pleroma.Activity do
field(:recipients, {:array, :string}) field(:recipients, {:array, :string})
has_many(:notifications, Notification, on_delete: :delete_all) has_many(:notifications, Notification, on_delete: :delete_all)
# Attention: this is a fake relation, don't try to preload it blindly and expect it to work!
# The foreign key is embedded in a jsonb field.
#
# To use it, you probably want to do an inner join and a preload:
#
# ```
# |> join(:inner, [activity], o in Object,
# fragment("(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)", o.data, activity.data))
# |> preload([activity, object], [object: object])
# ```
has_one(:object, Object, on_delete: :nothing, foreign_key: :id)
timestamps() timestamps()
end end
@ -49,6 +62,21 @@ def get_by_id(id) do
Repo.get(Activity, id) Repo.get(Activity, id)
end end
def get_by_id_with_object(id) do
from(activity in Activity,
where: activity.id == ^id,
inner_join: o in Object,
on:
fragment(
"(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)",
o.data,
activity.data
),
preload: [object: o]
)
|> Repo.one()
end
def by_object_ap_id(ap_id) do def by_object_ap_id(ap_id) do
from( from(
activity in Activity, activity in Activity,
@ -76,7 +104,7 @@ def create_by_object_ap_id(ap_ids) when is_list(ap_ids) do
) )
end end
def create_by_object_ap_id(ap_id) do def create_by_object_ap_id(ap_id) when is_binary(ap_id) do
from( from(
activity in Activity, activity in Activity,
where: where:
@ -90,6 +118,8 @@ def create_by_object_ap_id(ap_id) do
) )
end end
def create_by_object_ap_id(_), do: nil
def get_all_create_by_object_ap_id(ap_id) do def get_all_create_by_object_ap_id(ap_id) do
Repo.all(create_by_object_ap_id(ap_id)) Repo.all(create_by_object_ap_id(ap_id))
end end
@ -101,6 +131,36 @@ def get_create_by_object_ap_id(ap_id) when is_binary(ap_id) do
def get_create_by_object_ap_id(_), do: nil def get_create_by_object_ap_id(_), do: nil
def create_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do
from(
activity in Activity,
where:
fragment(
"coalesce((?)->'object'->>'id', (?)->>'object') = ?",
activity.data,
activity.data,
^to_string(ap_id)
),
where: fragment("(?)->>'type' = 'Create'", activity.data),
inner_join: o in Object,
on:
fragment(
"(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)",
o.data,
activity.data
),
preload: [object: o]
)
end
def create_by_object_ap_id_with_object(_), do: nil
def get_create_by_object_ap_id_with_object(ap_id) do
ap_id
|> create_by_object_ap_id_with_object()
|> Repo.one()
end
def normalize(obj) when is_map(obj), do: Activity.get_by_ap_id(obj["id"]) def normalize(obj) when is_map(obj), do: Activity.get_by_ap_id(obj["id"])
def normalize(ap_id) when is_binary(ap_id), do: Activity.get_by_ap_id(ap_id) def normalize(ap_id) when is_binary(ap_id), do: Activity.get_by_ap_id(ap_id)
def normalize(_), do: nil def normalize(_), do: nil

View file

@ -14,6 +14,8 @@ defmodule Pleroma.Object do
import Ecto.Query import Ecto.Query
import Ecto.Changeset import Ecto.Changeset
require Logger
schema "objects" do schema "objects" do
field(:data, :map) field(:data, :map)
@ -38,6 +40,29 @@ def get_by_ap_id(ap_id) do
Repo.one(from(object in Object, where: fragment("(?)->>'id' = ?", object.data, ^ap_id))) Repo.one(from(object in Object, where: fragment("(?)->>'id' = ?", object.data, ^ap_id)))
end end
# If we pass an Activity to Object.normalize(), we can try to use the preloaded object.
# Use this whenever possible, especially when walking graphs in an O(N) loop!
def normalize(%Activity{object: %Object{} = object}), do: object
# Catch and log Object.normalize() calls where the Activity's child object is not
# preloaded.
def normalize(%Activity{data: %{"object" => %{"id" => ap_id}}}) do
Logger.info(
"Object.normalize() called without preloaded object (#{ap_id}). Consider preloading the object!"
)
normalize(ap_id)
end
def normalize(%Activity{data: %{"object" => ap_id}}) do
Logger.info(
"Object.normalize() called without preloaded object (#{ap_id}). Consider preloading the object!"
)
normalize(ap_id)
end
# Old way, try fetching the object through cache.
def normalize(%{"id" => ap_id}), do: normalize(ap_id) def normalize(%{"id" => ap_id}), do: normalize(ap_id)
def normalize(ap_id) when is_binary(ap_id), do: get_cached_by_ap_id(ap_id) def normalize(ap_id) when is_binary(ap_id), do: get_cached_by_ap_id(ap_id)
def normalize(_), do: nil def normalize(_), do: nil