summaryrefslogtreecommitdiff
path: root/priv/repo/migrations/20200508092434_update_counter_cache_table.exs
blob: 73834486851d1dac6d6eaf5fea95c298ff5631fd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
defmodule Pleroma.Repo.Migrations.UpdateCounterCacheTable do
  use Ecto.Migration

  @function_name "update_status_visibility_counter_cache"
  @trigger_name "status_visibility_counter_cache_trigger"

  def up do
    execute("drop trigger if exists #{@trigger_name} on activities")
    execute("drop function if exists #{@function_name}()")
    drop_if_exists(unique_index(:counter_cache, [:name]))
    drop_if_exists(table(:counter_cache))

    create_if_not_exists table(:counter_cache) do
      add(:instance, :string, null: false)
      add(:direct, :bigint, null: false, default: 0)
      add(:private, :bigint, null: false, default: 0)
      add(:unlisted, :bigint, null: false, default: 0)
      add(:public, :bigint, null: false, default: 0)
    end

    create_if_not_exists(unique_index(:counter_cache, [:instance]))

    """
    CREATE OR REPLACE FUNCTION #{@function_name}()
    RETURNS TRIGGER AS
    $$
      DECLARE
        hostname character varying(255);
        visibility_new character varying(64);
        visibility_old character varying(64);
        actor character varying(255);
      BEGIN
      IF TG_OP = 'DELETE' THEN
        actor := OLD.actor;
      ELSE
        actor := NEW.actor;
      END IF;
      hostname := split_part(actor, '/', 3);
      IF TG_OP = 'INSERT' THEN
        visibility_new := activity_visibility(NEW.actor, NEW.recipients, NEW.data);
        IF NEW.data->>'type' = 'Create'
            AND visibility_new IN ('public', 'unlisted', 'private', 'direct') THEN
          EXECUTE format('INSERT INTO "counter_cache" ("instance", %1$I) VALUES ($1, 1)
                          ON CONFLICT ("instance") DO
                          UPDATE SET %1$I = "counter_cache".%1$I + 1', visibility_new)
                          USING hostname;
        END IF;
        RETURN NEW;
      ELSIF TG_OP = 'UPDATE' THEN
        visibility_new := activity_visibility(NEW.actor, NEW.recipients, NEW.data);
        visibility_old := activity_visibility(OLD.actor, OLD.recipients, OLD.data);
        IF (NEW.data->>'type' = 'Create')
            AND (OLD.data->>'type' = 'Create')
            AND visibility_new != visibility_old
            AND visibility_new IN ('public', 'unlisted', 'private', 'direct') THEN
          EXECUTE format('UPDATE "counter_cache" SET
                          %1$I = greatest("counter_cache".%1$I - 1, 0),
                          %2$I = "counter_cache".%2$I + 1
                          WHERE "instance" = $1', visibility_old, visibility_new)
                          USING hostname;
        END IF;
        RETURN NEW;
      ELSIF TG_OP = 'DELETE' THEN
        IF OLD.data->>'type' = 'Create' THEN
          visibility_old := activity_visibility(OLD.actor, OLD.recipients, OLD.data);
          EXECUTE format('UPDATE "counter_cache" SET
                          %1$I = greatest("counter_cache".%1$I - 1, 0)
                          WHERE "instance" = $1', visibility_old)
                          USING hostname;
        END IF;
        RETURN OLD;
      END IF;
      END;
    $$
    LANGUAGE 'plpgsql';
    """
    |> execute()

    execute("DROP TRIGGER IF EXISTS #{@trigger_name} ON activities")

    """
    CREATE TRIGGER #{@trigger_name}
    BEFORE
      INSERT
      OR UPDATE of recipients, data
      OR DELETE
    ON activities
    FOR EACH ROW
      EXECUTE PROCEDURE #{@function_name}();
    """
    |> execute()
  end

  def down do
    execute("DROP TRIGGER IF EXISTS #{@trigger_name} ON activities")
    execute("DROP FUNCTION IF EXISTS #{@function_name}()")
    drop_if_exists(unique_index(:counter_cache, [:instance]))
    drop_if_exists(table(:counter_cache))

    create_if_not_exists table(:counter_cache) do
      add(:name, :string, null: false)
      add(:count, :bigint, null: false, default: 0)
    end

    create_if_not_exists(unique_index(:counter_cache, [:name]))

    """
    CREATE OR REPLACE FUNCTION #{@function_name}()
    RETURNS TRIGGER AS
    $$
      DECLARE
      BEGIN
      IF TG_OP = 'INSERT' THEN
          IF NEW.data->>'type' = 'Create' THEN
            EXECUTE 'INSERT INTO counter_cache (name, count) VALUES (''status_visibility_' || activity_visibility(NEW.actor, NEW.recipients, NEW.data) || ''', 1) ON CONFLICT (name) DO UPDATE SET count = counter_cache.count + 1';
          END IF;
          RETURN NEW;
      ELSIF TG_OP = 'UPDATE' THEN
          IF (NEW.data->>'type' = 'Create') and (OLD.data->>'type' = 'Create') and activity_visibility(NEW.actor, NEW.recipients, NEW.data) != activity_visibility(OLD.actor, OLD.recipients, OLD.data) THEN
             EXECUTE 'INSERT INTO counter_cache (name, count) VALUES (''status_visibility_' || activity_visibility(NEW.actor, NEW.recipients, NEW.data) || ''', 1) ON CONFLICT (name) DO UPDATE SET count = counter_cache.count + 1';
             EXECUTE 'update counter_cache SET count = counter_cache.count - 1 where count > 0 and name = ''status_visibility_' || activity_visibility(OLD.actor, OLD.recipients, OLD.data) || ''';';
          END IF;
          RETURN NEW;
      ELSIF TG_OP = 'DELETE' THEN
          IF OLD.data->>'type' = 'Create' THEN
            EXECUTE 'update counter_cache SET count = counter_cache.count - 1 where count > 0 and name = ''status_visibility_' || activity_visibility(OLD.actor, OLD.recipients, OLD.data) || ''';';
          END IF;
          RETURN OLD;
      END IF;
      END;
    $$
    LANGUAGE 'plpgsql';
    """
    |> execute()

    """
    CREATE TRIGGER #{@trigger_name} BEFORE INSERT OR UPDATE of recipients, data OR DELETE ON activities
    FOR EACH ROW
    EXECUTE PROCEDURE #{@function_name}();
    """
    |> execute()
  end
end