2019-01-09 10:08:24 -05:00
defmodule Pleroma.Repo.Migrations.UsersAndActivitiesFlakeId do
use Ecto.Migration
2019-01-23 05:21:52 -05:00
alias Pleroma.Clippy
require Integer
import Ecto.Query
alias Pleroma.Repo
2019-01-09 10:08:24 -05:00
# This migrates from int serial IDs to custom Flake:
# 1- create a temporary uuid column
# 2- fill this column with compatibility ids (see below)
# 3- remove pkeys constraints
# 4- update relation pkeys with the new ids
# 5- rename the temporary column to id
# 6- re-create the constraints
2019-06-30 21:08:07 -04:00
def up do
2019-01-09 10:08:24 -05:00
# Old serial int ids are transformed to 128bits with extra padding.
# The application (in `Pleroma.FlakeId`) handles theses IDs properly as integers; to keep compatibility
# with previously issued ids.
2019-10-08 08:16:39 -04:00
# execute "update activities set external_id = CAST( LPAD( TO_HEX(id), 32, '0' ) AS uuid);"
# execute "update users set external_id = CAST( LPAD( TO_HEX(id), 32, '0' ) AS uuid);"
2019-01-09 10:08:24 -05:00
2019-01-23 05:21:52 -05:00
clippy = start_clippy_heartbeats ( )
2019-01-21 07:10:48 -05:00
# Lock both tables to avoid a running server to meddling with our transaction
2019-10-08 08:16:39 -04:00
execute ( " LOCK TABLE activities; " )
execute ( " LOCK TABLE users; " )
2019-01-21 07:10:48 -05:00
2019-10-08 08:16:39 -04:00
execute ( """
2019-01-23 06:09:21 -05:00
ALTER TABLE activities
DROP CONSTRAINT activities_pkey CASCADE ,
ALTER COLUMN id DROP default ,
ALTER COLUMN id SET DATA TYPE uuid USING CAST ( LPAD ( TO_HEX ( id ) , 32 , ' 0 ' ) AS uuid ) ,
ADD PRIMARY KEY ( id ) ;
2019-10-08 08:16:39 -04:00
""" )
2019-01-09 10:08:24 -05:00
2019-10-08 08:16:39 -04:00
execute ( """
2019-01-23 06:09:21 -05:00
ALTER TABLE users
DROP CONSTRAINT users_pkey CASCADE ,
ALTER COLUMN id DROP default ,
ALTER COLUMN id SET DATA TYPE uuid USING CAST ( LPAD ( TO_HEX ( id ) , 32 , ' 0 ' ) AS uuid ) ,
ADD PRIMARY KEY ( id ) ;
2019-10-08 08:16:39 -04:00
""" )
2019-01-09 10:08:24 -05:00
2019-10-08 08:16:39 -04:00
execute (
" UPDATE users SET info = jsonb_set(info, '{pinned_activities}', array_to_json(ARRAY(select jsonb_array_elements_text(info->'pinned_activities')))::jsonb); "
)
2019-01-23 05:22:31 -05:00
2019-01-09 10:08:24 -05:00
# Fkeys:
# Activities - Referenced by:
# TABLE "notifications" CONSTRAINT "notifications_activity_id_fkey" FOREIGN KEY (activity_id) REFERENCES activities(id) ON DELETE CASCADE
# Users - Referenced by:
# TABLE "filters" CONSTRAINT "filters_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
# TABLE "lists" CONSTRAINT "lists_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
# TABLE "notifications" CONSTRAINT "notifications_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
# TABLE "oauth_authorizations" CONSTRAINT "oauth_authorizations_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(id)
# TABLE "oauth_tokens" CONSTRAINT "oauth_tokens_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(id)
# TABLE "password_reset_tokens" CONSTRAINT "password_reset_tokens_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(id)
# TABLE "push_subscriptions" CONSTRAINT "push_subscriptions_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
# TABLE "websub_client_subscriptions" CONSTRAINT "websub_client_subscriptions_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(id)
2019-10-08 08:16:39 -04:00
execute ( """
2019-01-23 06:09:21 -05:00
ALTER TABLE notifications
ALTER COLUMN activity_id SET DATA TYPE uuid USING CAST ( LPAD ( TO_HEX ( activity_id ) , 32 , ' 0 ' ) AS uuid ) ,
ADD CONSTRAINT notifications_activity_id_fkey FOREIGN KEY ( activity_id ) REFERENCES activities ( id ) ON DELETE CASCADE ;
2019-10-08 08:16:39 -04:00
""" )
2019-01-09 10:08:24 -05:00
2019-10-08 08:16:39 -04:00
for table <-
~w( notifications filters lists oauth_authorizations oauth_tokens password_reset_tokens push_subscriptions websub_client_subscriptions ) do
execute ( """
2019-01-23 06:09:21 -05:00
ALTER TABLE #{table}
ALTER COLUMN user_id SET DATA TYPE uuid USING CAST ( LPAD ( TO_HEX ( user_id ) , 32 , ' 0 ' ) AS uuid ) ,
ADD CONSTRAINT #{table}_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
2019-10-08 08:16:39 -04:00
""" )
2019-01-09 10:08:24 -05:00
end
2019-01-24 10:15:13 -05:00
flush ( )
2019-01-23 05:21:52 -05:00
stop_clippy_heartbeats ( clippy )
2019-01-09 10:08:24 -05:00
end
2019-01-23 05:21:52 -05:00
2019-06-30 21:08:07 -04:00
def down , do : :ok
2019-01-23 05:21:52 -05:00
defp start_clippy_heartbeats ( ) do
2019-10-08 08:16:39 -04:00
count = from ( a in " activities " , select : count ( a . id ) ) |> Repo . one! ( )
2019-01-23 05:21:52 -05:00
2019-01-24 10:15:13 -05:00
if count > 5000 do
2019-01-23 05:21:52 -05:00
heartbeat_interval = :timer . minutes ( 2 ) + :timer . seconds ( 30 )
2019-10-08 08:16:39 -04:00
all_tips =
Clippy . tips ( ) ++
[
" The migration is still running, maybe it's time for another “tea”? " ,
" Happy rabbits practice a cute behavior known as a \n “binky:” they jump up in the air \n and twist \n and spin around! " ,
" Nothing and everything. \n \n I still work. " ,
" Pleroma runs on a Raspberry Pi! \n \n … but this migration will take forever if you \n actually run on a raspberry pi " ,
" Status? Stati? Post? Note? Toot? \n Repeat? Reboost? Boost? Retweet? Retoot?? \n \n I-I'm confused. "
]
heartbeat = fn heartbeat , runs , all_tips , tips ->
tips =
if Integer . is_even ( runs ) do
tips = if tips == [ ] , do : all_tips , else : tips
[ tip | tips ] = Enum . shuffle ( tips )
Clippy . puts ( tip )
tips
else
IO . puts (
" \n -- #{ DateTime . to_string ( DateTime . utc_now ( ) ) } Migration still running, please wait… \n "
)
tips
end
2019-01-23 05:21:52 -05:00
:timer . sleep ( heartbeat_interval )
heartbeat . ( heartbeat , runs + 1 , all_tips , tips )
end
2019-10-08 08:16:39 -04:00
Clippy . puts ( [
2019-01-23 05:21:52 -05:00
[ :red , :bright , " It looks like you are running an older instance! " ] ,
[ " " ] ,
[ :bright , " This migration may take a long time " , :reset , " -- so you probably should " ] ,
2019-01-24 10:15:13 -05:00
[ " go drink a cofe, or a tea, or a beer, a whiskey, a vodka, " ] ,
2019-01-23 05:21:52 -05:00
[ " while it runs to deal with your temporary fediverse pause! " ]
2019-10-08 08:16:39 -04:00
] )
2019-01-23 05:21:52 -05:00
:timer . sleep ( heartbeat_interval )
2019-10-08 08:16:39 -04:00
spawn_link ( fn -> heartbeat . ( heartbeat , 1 , all_tips , [ ] ) end )
2019-01-23 05:21:52 -05:00
end
end
defp stop_clippy_heartbeats ( pid ) do
if pid do
Process . unlink ( pid )
Process . exit ( pid , :kill )
2019-10-08 08:16:39 -04:00
Clippy . puts ( [ [ :green , :bright , " Hurray!! " , " " , " " , " Migration completed! " ] ] )
2019-01-23 05:21:52 -05:00
end
end
2019-01-09 10:08:24 -05:00
end