create type "public"."thread_attachment_kind" as enum ('vote', 'emoji');

create type "public"."thread_attachment_vote_direction" as enum ('up', 'down');

alter type "public"."thread_kind" rename to "thread_kind__old_version_to_be_dropped";

create type "public"."thread_kind" as enum ('feed', 'message_feed', 'message', 'post_feed', 'post', 'comment', 'symbolic');

create table "public"."thread_attachment" (
    "id" integer generated always as identity not null,
    "uid" uuid not null default gen_random_uuid(),
    "thread" integer not null,
    "kind" thread_attachment_kind not null
);


create table "public"."thread_attachment_audit" (
    "thread_attachment" integer not null,
    "kind" audit_kind not null,
    "actor" integer not null
);


create table "public"."thread_attachment_emoji" (
    "id" integer generated always as identity not null,
    "uid" uuid not null default gen_random_uuid(),
    "thread_attachment" integer not null,
    "emoji" text not null
);


create table "public"."thread_attachment_emoji_audit" (
    "thread_attachment_emoji" integer not null,
    "kind" audit_kind not null,
    "actor" integer not null
);


create table "public"."thread_attachment_vote" (
    "id" integer generated always as identity not null,
    "uid" uuid not null default gen_random_uuid(),
    "thread_attachment" integer not null,
    "direction" thread_attachment_vote_direction not null
);


create table "public"."thread_attachment_vote_audit" (
    "thread_attachment_vote" integer not null,
    "kind" audit_kind not null,
    "actor" integer not null
);


create table "public"."thread_audit" (
    "thread" integer not null,
    "kind" audit_kind not null,
    "actor" integer not null
);


create table "public"."thread_feed" (
    "id" integer generated always as identity not null,
    "uid" uuid not null default gen_random_uuid(),
    "deleted" boolean not null default false,
    "thread" integer not null
);


create table "public"."thread_feed_audit" (
    "thread_feed" integer not null,
    "kind" audit_kind not null,
    "actor" integer not null
);


alter table "public"."thread" alter column kind type "public"."thread_kind" using kind::text::"public"."thread_kind";

drop type "public"."thread_kind__old_version_to_be_dropped";

alter table "public"."thread" add column "deleted" boolean not null default false;

CREATE UNIQUE INDEX thread_attachment_emoji_pkey ON public.thread_attachment_emoji USING btree (id);

CREATE UNIQUE INDEX thread_attachment_pkey ON public.thread_attachment USING btree (id);

CREATE UNIQUE INDEX thread_attachment_vote_pkey ON public.thread_attachment_vote USING btree (id);

CREATE UNIQUE INDEX thread_feed_pkey ON public.thread_feed USING btree (id);

alter table "public"."thread_attachment" add constraint "thread_attachment_pkey" PRIMARY KEY using index "thread_attachment_pkey";

alter table "public"."thread_attachment_emoji" add constraint "thread_attachment_emoji_pkey" PRIMARY KEY using index "thread_attachment_emoji_pkey";

alter table "public"."thread_attachment_vote" add constraint "thread_attachment_vote_pkey" PRIMARY KEY using index "thread_attachment_vote_pkey";

alter table "public"."thread_feed" add constraint "thread_feed_pkey" PRIMARY KEY using index "thread_feed_pkey";

alter table "public"."thread_attachment" add constraint "thread_attachment_thread_fkey" FOREIGN KEY (thread) REFERENCES thread(id) not valid;

alter table "public"."thread_attachment" validate constraint "thread_attachment_thread_fkey";

alter table "public"."thread_attachment_audit" add constraint "thread_attachment_audit_actor_fkey" FOREIGN KEY (actor) REFERENCES usr(id) not valid;

alter table "public"."thread_attachment_audit" validate constraint "thread_attachment_audit_actor_fkey";

alter table "public"."thread_attachment_audit" add constraint "thread_attachment_audit_thread_attachment_fkey" FOREIGN KEY (thread_attachment) REFERENCES thread_attachment(id) not valid;

alter table "public"."thread_attachment_audit" validate constraint "thread_attachment_audit_thread_attachment_fkey";

alter table "public"."thread_attachment_emoji" add constraint "thread_attachment_emoji_thread_attachment_fkey" FOREIGN KEY (thread_attachment) REFERENCES thread_attachment(id) not valid;

alter table "public"."thread_attachment_emoji" validate constraint "thread_attachment_emoji_thread_attachment_fkey";

alter table "public"."thread_attachment_emoji_audit" add constraint "thread_attachment_emoji_audit_actor_fkey" FOREIGN KEY (actor) REFERENCES usr(id) not valid;

alter table "public"."thread_attachment_emoji_audit" validate constraint "thread_attachment_emoji_audit_actor_fkey";

alter table "public"."thread_attachment_emoji_audit" add constraint "thread_attachment_emoji_audit_thread_attachment_emoji_fkey" FOREIGN KEY (thread_attachment_emoji) REFERENCES thread_attachment_emoji(id) not valid;

alter table "public"."thread_attachment_emoji_audit" validate constraint "thread_attachment_emoji_audit_thread_attachment_emoji_fkey";

alter table "public"."thread_attachment_vote" add constraint "thread_attachment_vote_thread_attachment_fkey" FOREIGN KEY (thread_attachment) REFERENCES thread_attachment(id) not valid;

alter table "public"."thread_attachment_vote" validate constraint "thread_attachment_vote_thread_attachment_fkey";

alter table "public"."thread_attachment_vote_audit" add constraint "thread_attachment_vote_audit_actor_fkey" FOREIGN KEY (actor) REFERENCES usr(id) not valid;

alter table "public"."thread_attachment_vote_audit" validate constraint "thread_attachment_vote_audit_actor_fkey";

alter table "public"."thread_attachment_vote_audit" add constraint "thread_attachment_vote_audit_thread_attachment_vote_fkey" FOREIGN KEY (thread_attachment_vote) REFERENCES thread_attachment_vote(id) not valid;

alter table "public"."thread_attachment_vote_audit" validate constraint "thread_attachment_vote_audit_thread_attachment_vote_fkey";

alter table "public"."thread_audit" add constraint "thread_audit_actor_fkey" FOREIGN KEY (actor) REFERENCES usr(id) not valid;

alter table "public"."thread_audit" validate constraint "thread_audit_actor_fkey";

alter table "public"."thread_audit" add constraint "thread_audit_thread_fkey" FOREIGN KEY (thread) REFERENCES thread(id) not valid;

alter table "public"."thread_audit" validate constraint "thread_audit_thread_fkey";

alter table "public"."thread_feed" add constraint "thread_feed_thread_fkey" FOREIGN KEY (thread) REFERENCES thread(id) not valid;

alter table "public"."thread_feed" validate constraint "thread_feed_thread_fkey";

alter table "public"."thread_feed_audit" add constraint "thread_feed_audit_actor_fkey" FOREIGN KEY (actor) REFERENCES usr(id) not valid;

alter table "public"."thread_feed_audit" validate constraint "thread_feed_audit_actor_fkey";

alter table "public"."thread_feed_audit" add constraint "thread_feed_audit_thread_feed_fkey" FOREIGN KEY (thread_feed) REFERENCES thread_feed(id) not valid;

alter table "public"."thread_feed_audit" validate constraint "thread_feed_audit_thread_feed_fkey";

set check_function_bodies = off;

CREATE OR REPLACE FUNCTION public.do_thread_attachment_audit()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
declare
  audit_kind public.audit_kind;
  id int;
begin
  if (TG_OP = 'INSERT') then
    id := NEW.id;
    audit_kind := 'create';
  elsif (TG_OP = 'UPDATE') then
    id := OLD.id;
    audit_kind := 'modify';
  end if;
  insert into public.usr_audit
    (kind, usr, actor  )
  values
    (audit_kind, id, 1);
  return NEW;
end;
$function$
;

CREATE OR REPLACE FUNCTION public.do_thread_attachment_emoji_audit()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
declare
  audit_kind public.audit_kind;
  id int;
begin
  if (TG_OP = 'INSERT') then
    id := NEW.id;
    audit_kind := 'create';
  elsif (TG_OP = 'UPDATE') then
    id := OLD.id;
    audit_kind := 'modify';
  end if;
  insert into public.usr_audit
    (kind, usr, actor  )
  values
    (audit_kind, id, 1);
  return NEW;
end;
$function$
;

CREATE OR REPLACE FUNCTION public.do_thread_attachment_emoji_immutable_columns()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
begin
  if OLD.id <> NEW.id then
    raise exception 'public.thread_attachment_emoji.id is immutable' using errcode = 'restrict_violation';
  elsif OLD.uid <> NEW.uid then
    raise exception 'public.thread_attachment_emoji.uid is immutable' using errcode = 'restrict_violation';
  elsif OLD.thread_attachment <> NEW.thread_attachment then
    raise exception 'public.thread_attachment_emoji.thread_attachment is immutable' using errcode = 'restrict_violation';
  elsif OLD.emoji <> NEW.emoji then
    raise exception 'public.thread_attachment_emoji.emoji is immutable' using errcode = 'restrict_violation';
  end if;

  return NEW;
end;
$function$
;

CREATE OR REPLACE FUNCTION public.do_thread_attachment_emoji_soft_delete()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
begin
  insert into public.thread_attachment_emoji_audit
    (thread_attachment_emoji, kind, actor)
  values
    (OLD.id, 'delete', 1);

  update public.thread_attachment_emoji
  set deleted = true
  where id = OLD.id;

  return null;
end;
$function$
;

CREATE OR REPLACE FUNCTION public.do_thread_attachment_immutable_columns()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
begin
  if OLD.id <> NEW.id then
    raise exception 'public.thread_attachment.id is immutable' using errcode = 'restrict_violation';
  elsif OLD.uid <> NEW.uid then
    raise exception 'public.thread_attachment.uid is immutable' using errcode = 'restrict_violation';
  elsif OLD.thread <> NEW.thread then
    raise exception 'public.thread_attachment.thread is immutable' using errcode = 'restrict_violation';
  elsif OLD.kind <> NEW.kind then
    raise exception 'public.thread_attachment.kind is immutable' using errcode = 'restrict_violation';
  end if;

  return NEW;
end;
$function$
;

CREATE OR REPLACE FUNCTION public.do_thread_attachment_soft_delete()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
begin
  insert into public.thread_attachment_audit
    (thread_attachment, kind, actor)
  values
    (OLD.id, 'delete', 1);

  update public.thread_attachment
  set deleted = true
  where id = OLD.id;

  return null;
end;
$function$
;

CREATE OR REPLACE FUNCTION public.do_thread_attachment_vote_audit()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
declare
  audit_kind public.audit_kind;
  id int;
begin
  if (TG_OP = 'INSERT') then
    id := NEW.id;
    audit_kind := 'create';
  elsif (TG_OP = 'UPDATE') then
    id := OLD.id;
    audit_kind := 'modify';
  end if;
  insert into public.usr_audit
    (kind, usr, actor  )
  values
    (audit_kind, id, 1);
  return NEW;
end;
$function$
;

CREATE OR REPLACE FUNCTION public.do_thread_attachment_vote_immutable_columns()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
begin
  if OLD.id <> NEW.id then
    raise exception 'public.thread_attachment_vote.id is immutable' using errcode = 'restrict_violation';
  elsif OLD.uid <> NEW.uid then
    raise exception 'public.thread_attachment_vote.uid is immutable' using errcode = 'restrict_violation';
  elsif OLD.thread_attachment <> NEW.thread_attachment then
    raise exception 'public.thread_attachment_vote.thread_attachment is immutable' using errcode = 'restrict_violation';
  elsif OLD.direction <> NEW.direction then
    raise exception 'public.thread_attachment_vote.direction is immutable' using errcode = 'restrict_violation';
  end if;

  return NEW;
end;
$function$
;

CREATE OR REPLACE FUNCTION public.do_thread_attachment_vote_soft_delete()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
begin
  insert into public.thread_attachment_vote_audit
    (thread_attachment_vote, kind, actor)
  values
    (OLD.id, 'delete', 1);

  update public.thread_attachment_vote
  set deleted = true
  where id = OLD.id;

  return null;
end;
$function$
;

CREATE OR REPLACE FUNCTION public.do_thread_audit()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
declare
  audit_kind public.audit_kind;
  id int;
begin
  if (TG_OP = 'INSERT') then
    id := NEW.id;
    audit_kind := 'create';
  elsif (TG_OP = 'UPDATE') then
    id := OLD.id;
    audit_kind := 'modify';
  end if;
  insert into public.usr_audit
    (kind, usr, actor  )
  values
    (audit_kind, id, 1);
  return NEW;
end;
$function$
;

CREATE OR REPLACE FUNCTION public.do_thread_feed_audit()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
declare
  audit_kind public.audit_kind;
  id int;
begin
  if (TG_OP = 'INSERT') then
    id := NEW.id;
    audit_kind := 'create';
  elsif (TG_OP = 'UPDATE') then
    id := OLD.id;
    audit_kind := 'modify';
  end if;
  insert into public.usr_audit
    (kind, usr, actor  )
  values
    (audit_kind, id, 1);
  return NEW;
end;
$function$
;

CREATE OR REPLACE FUNCTION public.do_thread_feed_immutable_columns()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
begin
  if OLD.id <> NEW.id then
    raise exception 'public.thread_feed.id is immutable' using errcode = 'restrict_violation';
  elsif OLD.uid <> NEW.uid then
    raise exception 'public.thread_feed.uid is immutable' using errcode = 'restrict_violation';
  elsif OLD.thread <> NEW.thread then
    raise exception 'public.thread_feed.thread is immutable' using errcode = 'restrict_violation';
  elsif OLD.community <> NEW.community then
    raise exception 'public.thread_feed.community is immutable' using errcode = 'restrict_violation';
  end if;

  return NEW;
end;
$function$
;

CREATE OR REPLACE FUNCTION public.do_thread_feed_soft_delete()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
begin
  insert into public.thread_feed_audit
    (thread_feed, kind, actor)
  values
    (OLD.id, 'delete', 1);

  update public.thread_feed
  set deleted = true
  where id = OLD.id;

  return null;
end;
$function$
;

CREATE OR REPLACE FUNCTION public.do_thread_immutable_columns()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
begin
  if OLD.id <> NEW.id then
    raise exception 'public.thread.id is immutable' using errcode = 'restrict_violation';
  elsif OLD.uid <> NEW.uid then
    raise exception 'public.thread.uid is immutable' using errcode = 'restrict_violation';
  elsif OLD.kind <> NEW.kind then
    raise exception 'public.thread.kind is immutable' using errcode = 'restrict_violation';
  end if;

  return NEW;
end;
$function$
;

CREATE OR REPLACE FUNCTION public.do_thread_soft_delete()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
begin
  insert into public.thread_audit
    (thread, kind, actor)
  values
    (OLD.id, 'delete', 1);

  update public.thread
  set deleted = true
  where id = OLD.id;

  return null;
end;
$function$
;

CREATE OR REPLACE FUNCTION public.do_usr_immutable_columns()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
begin
  if OLD.id <> NEW.id then
    raise exception 'public.usr.id is immutable' using errcode = 'restrict_violation';
  elsif OLD.uid <> NEW.uid then
    raise exception 'public.usr.uid is immutable' using errcode = 'restrict_violation';
  end if;

  return NEW;
end;
$function$
;

CREATE OR REPLACE FUNCTION public.mark_columns_immutable(schema text, table_ text, columns text[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
declare
  col text;
  qualified_name text := concat(schema, '.', table_);
  do_table_immutable_columns text := concat('do_', table_, '_immutable_columns');
  trigger_table_immutable_columns text := concat('trigger_', table_, '_immutable_columns');
  create_do_table_immutable_columns text;
  create_trigger_table_immutable_columns text;
begin
  create_do_table_immutable_columns := concat( create_do_table_immutable_columns
                                             , 'create function ', schema, '.', do_table_immutable_columns, E'() returns trigger language plpgsql as \$\$\n'
                                             , E'begin\n'
                                             , '  if OLD.', columns[1], ' <> NEW.', columns[1], E' then\n'
                                             , E'    raise exception \'', qualified_name, '.', columns[1], E' is immutable\' using errcode = \'restrict_violation\';\n'
                                             );
  foreach col in array columns[2:] loop
    create_do_table_immutable_columns := concat( create_do_table_immutable_columns
                                               , E'  elsif OLD.', col, ' <> NEW.', col, E' then\n'
                                               , E'    raise exception \'', qualified_name, '.', col, E' is immutable\' using errcode = \'restrict_violation\';\n'
                                               );
  end loop;

  create_do_table_immutable_columns := concat( create_do_table_immutable_columns
                                             , E'  end if;\n'
                                             , E'\n'
                                             , E'  return NEW;\n'
                                             , E'end;\n'
                                             , E'\$\$;\n'
                                             );

  create_trigger_table_immutable_columns := concat( create_trigger_table_immutable_columns
                                                  , 'create trigger ', trigger_table_immutable_columns, E'\n'
                                                  , 'before update on ', qualified_name, E'\n'
                                                  , 'for each row execute function ', do_table_immutable_columns, '();'
                                                  );

  execute create_do_table_immutable_columns;
  execute create_trigger_table_immutable_columns;
end;
$function$
;

CREATE TRIGGER trigger_thread_audit AFTER INSERT OR UPDATE ON public.thread FOR EACH ROW EXECUTE FUNCTION do_thread_audit();

CREATE TRIGGER trigger_thread_immutable_columns BEFORE UPDATE ON public.thread FOR EACH ROW EXECUTE FUNCTION do_thread_immutable_columns();

CREATE TRIGGER trigger_thread_soft_delete BEFORE DELETE ON public.thread FOR EACH ROW EXECUTE FUNCTION do_thread_soft_delete();

CREATE TRIGGER trigger_thread_attachment_audit AFTER INSERT OR UPDATE ON public.thread_attachment FOR EACH ROW EXECUTE FUNCTION do_thread_attachment_audit();

CREATE TRIGGER trigger_thread_attachment_immutable_columns BEFORE UPDATE ON public.thread_attachment FOR EACH ROW EXECUTE FUNCTION do_thread_attachment_immutable_columns();

CREATE TRIGGER trigger_thread_attachment_soft_delete BEFORE DELETE ON public.thread_attachment FOR EACH ROW EXECUTE FUNCTION do_thread_attachment_soft_delete();

CREATE TRIGGER trigger_thread_attachment_emoji_audit AFTER INSERT OR UPDATE ON public.thread_attachment_emoji FOR EACH ROW EXECUTE FUNCTION do_thread_attachment_emoji_audit();

CREATE TRIGGER trigger_thread_attachment_emoji_immutable_columns BEFORE UPDATE ON public.thread_attachment_emoji FOR EACH ROW EXECUTE FUNCTION do_thread_attachment_emoji_immutable_columns();

CREATE TRIGGER trigger_thread_attachment_emoji_soft_delete BEFORE DELETE ON public.thread_attachment_emoji FOR EACH ROW EXECUTE FUNCTION do_thread_attachment_emoji_soft_delete();

CREATE TRIGGER trigger_thread_attachment_vote_audit AFTER INSERT OR UPDATE ON public.thread_attachment_vote FOR EACH ROW EXECUTE FUNCTION do_thread_attachment_vote_audit();

CREATE TRIGGER trigger_thread_attachment_vote_immutable_columns BEFORE UPDATE ON public.thread_attachment_vote FOR EACH ROW EXECUTE FUNCTION do_thread_attachment_vote_immutable_columns();

CREATE TRIGGER trigger_thread_attachment_vote_soft_delete BEFORE DELETE ON public.thread_attachment_vote FOR EACH ROW EXECUTE FUNCTION do_thread_attachment_vote_soft_delete();

CREATE TRIGGER trigger_thread_feed_audit AFTER INSERT OR UPDATE ON public.thread_feed FOR EACH ROW EXECUTE FUNCTION do_thread_feed_audit();

CREATE TRIGGER trigger_thread_feed_immutable_columns BEFORE UPDATE ON public.thread_feed FOR EACH ROW EXECUTE FUNCTION do_thread_feed_immutable_columns();

CREATE TRIGGER trigger_thread_feed_soft_delete BEFORE DELETE ON public.thread_feed FOR EACH ROW EXECUTE FUNCTION do_thread_feed_soft_delete();

CREATE TRIGGER trigger_usr_immutable_columns BEFORE UPDATE ON public.usr FOR EACH ROW EXECUTE FUNCTION do_usr_immutable_columns();
