Use timestamp-based IDs
This change introduces timestamp-based (Snowflake/Cornflake/Waspflake) identifier.
This commit is contained in:
parent
d627c4c261
commit
efec568d43
29
Rakefile
29
Rakefile
|
@ -430,3 +430,32 @@ namespace :justask do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
namespace :db do
|
||||
namespace :schema do
|
||||
task :create_timestampid_function do
|
||||
conn = ActiveRecord::Base.connection
|
||||
have_func = conn.execute("SELECT EXISTS(SELECT * FROM pg_proc WHERE proname = 'gen_timestamp_id');").values.first.first
|
||||
next if have_func
|
||||
|
||||
statement = File.read(File.join(__dir__, 'db/migrate/20200704163504_use_timestamped_ids.rb')).match(/<~SQL(?<stmt>.+)SQL$/m)[:stmt]
|
||||
conn.execute(statement)
|
||||
end
|
||||
|
||||
task :create_id_sequences do
|
||||
conn = ActiveRecord::Base.connection
|
||||
|
||||
# required for the timestampid function to work properly
|
||||
%i[questions answers comments smiles comment_smiles users].each do |tbl|
|
||||
conn.execute("CREATE SEQUENCE IF NOT EXISTS #{tbl}_id_seq")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# create timestampid before loading schema
|
||||
Rake::Task['db:schema:load'].enhance ['db:schema:create_timestampid_function']
|
||||
# create id_sequences after loading schema
|
||||
Rake::Task['db:schema:load'].enhance do
|
||||
Rake::Task['db:schema:create_id_sequences'].invoke
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'securerandom'
|
||||
|
||||
# This migration changes the IDs of several tables from serial to a
|
||||
# timestamped/"snowflake" one.
|
||||
#
|
||||
# Instead of "snowflakes" we shall call those IDs Cornflakes or Waspflakes
|
||||
# instead.
|
||||
#
|
||||
# Implementation somewhat lifted from Mastodon.
|
||||
class UseTimestampedIds < ActiveRecord::Migration[5.2]
|
||||
def up
|
||||
# PL/pgSQL is just spicy pascal
|
||||
# don't @ me
|
||||
execute <<~SQL
|
||||
CREATE or replace FUNCTION gen_timestamp_id(tblname text) RETURNS bigint AS $$
|
||||
DECLARE
|
||||
timepart bigint;
|
||||
seqpart bigint;
|
||||
BEGIN
|
||||
timepart := (date_part('epoch', now()) * 1000)::bigint << 16;
|
||||
seqpart := ('x' || substr(md5(tblname ||
|
||||
'#{SecureRandom.hex(16)}' ||
|
||||
timepart::text), 1, 4))::bit(16)::bigint;
|
||||
RETURN timepart | ((seqpart + nextval(tblname || '_id_seq')) & 65535);
|
||||
END;
|
||||
$$ LANGUAGE plpgsql VOLATILE;
|
||||
SQL
|
||||
|
||||
# we need to migrate related columns to bigints for this to work
|
||||
{
|
||||
question: %i[answers inboxes],
|
||||
answer: %i[comments smiles subscriptions],
|
||||
comment: %i[comment_smiles],
|
||||
user: %i[announcements answers comment_smiles comments inboxes list_members lists moderation_comments moderation_votes questions reports services smiles subscriptions themes users_roles],
|
||||
|
||||
# polymorphic tables go brrr
|
||||
recipient: %i[notifications],
|
||||
source: %i[relationships],
|
||||
target: %i[notifications relationships reports],
|
||||
}.each do |ref, tbls|
|
||||
tbls.each do |tbl|
|
||||
say "Migrating #{tbl}.#{ref}_id to bigint"
|
||||
change_column(tbl, :"#{ref}_id", :bigint)
|
||||
end
|
||||
end
|
||||
|
||||
%i[questions answers comments smiles comment_smiles users].each do |tbl|
|
||||
say "Migrating #{tbl} to use timestamped ids"
|
||||
change_column(tbl, :id, :bigint, default: -> { "gen_timestamp_id('#{tbl}'::text)" })
|
||||
end
|
||||
end
|
||||
end
|
64
db/schema.rb
64
db/schema.rb
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 2020_05_25_145144) do
|
||||
ActiveRecord::Schema.define(version: 2020_07_04_163504) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
@ -27,11 +27,11 @@ ActiveRecord::Schema.define(version: 2020_05_25_145144) do
|
|||
t.index ["user_id"], name: "index_announcements_on_user_id"
|
||||
end
|
||||
|
||||
create_table "answers", id: :serial, force: :cascade do |t|
|
||||
create_table "answers", id: :bigint, default: -> { "gen_timestamp_id('answers'::text)" }, force: :cascade do |t|
|
||||
t.text "content"
|
||||
t.integer "question_id"
|
||||
t.bigint "question_id"
|
||||
t.integer "comment_count", default: 0, null: false
|
||||
t.integer "user_id"
|
||||
t.bigint "user_id"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.integer "smile_count", default: 0, null: false
|
||||
|
@ -39,9 +39,9 @@ ActiveRecord::Schema.define(version: 2020_05_25_145144) do
|
|||
t.index ["user_id", "created_at"], name: "index_answers_on_user_id_and_created_at"
|
||||
end
|
||||
|
||||
create_table "comment_smiles", id: :serial, force: :cascade do |t|
|
||||
t.integer "user_id"
|
||||
t.integer "comment_id"
|
||||
create_table "comment_smiles", id: :bigint, default: -> { "gen_timestamp_id('comment_smiles'::text)" }, force: :cascade do |t|
|
||||
t.bigint "user_id"
|
||||
t.bigint "comment_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["comment_id"], name: "index_comment_smiles_on_comment_id"
|
||||
|
@ -49,10 +49,10 @@ ActiveRecord::Schema.define(version: 2020_05_25_145144) do
|
|||
t.index ["user_id"], name: "index_comment_smiles_on_user_id"
|
||||
end
|
||||
|
||||
create_table "comments", id: :serial, force: :cascade do |t|
|
||||
create_table "comments", id: :bigint, default: -> { "gen_timestamp_id('comments'::text)" }, force: :cascade do |t|
|
||||
t.string "content"
|
||||
t.integer "answer_id"
|
||||
t.integer "user_id"
|
||||
t.bigint "answer_id"
|
||||
t.bigint "user_id"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.integer "smile_count", default: 0, null: false
|
||||
|
@ -61,8 +61,8 @@ ActiveRecord::Schema.define(version: 2020_05_25_145144) do
|
|||
end
|
||||
|
||||
create_table "inboxes", id: :serial, force: :cascade do |t|
|
||||
t.integer "user_id"
|
||||
t.integer "question_id"
|
||||
t.bigint "user_id"
|
||||
t.bigint "question_id"
|
||||
t.boolean "new"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
|
@ -71,14 +71,14 @@ ActiveRecord::Schema.define(version: 2020_05_25_145144) do
|
|||
|
||||
create_table "list_members", id: :serial, force: :cascade do |t|
|
||||
t.integer "list_id", null: false
|
||||
t.integer "user_id", null: false
|
||||
t.bigint "user_id", null: false
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.index ["list_id", "user_id"], name: "index_list_members_on_list_id_and_user_id", unique: true
|
||||
end
|
||||
|
||||
create_table "lists", id: :serial, force: :cascade do |t|
|
||||
t.integer "user_id", null: false
|
||||
t.bigint "user_id", null: false
|
||||
t.string "name"
|
||||
t.string "display_name"
|
||||
t.boolean "private", default: true
|
||||
|
@ -89,7 +89,7 @@ ActiveRecord::Schema.define(version: 2020_05_25_145144) do
|
|||
|
||||
create_table "moderation_comments", id: :serial, force: :cascade do |t|
|
||||
t.integer "report_id"
|
||||
t.integer "user_id"
|
||||
t.bigint "user_id"
|
||||
t.string "content"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
|
@ -98,7 +98,7 @@ ActiveRecord::Schema.define(version: 2020_05_25_145144) do
|
|||
|
||||
create_table "moderation_votes", id: :serial, force: :cascade do |t|
|
||||
t.integer "report_id", null: false
|
||||
t.integer "user_id", null: false
|
||||
t.bigint "user_id", null: false
|
||||
t.boolean "upvote", default: false, null: false
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
|
@ -109,19 +109,19 @@ ActiveRecord::Schema.define(version: 2020_05_25_145144) do
|
|||
|
||||
create_table "notifications", id: :serial, force: :cascade do |t|
|
||||
t.string "target_type"
|
||||
t.integer "target_id"
|
||||
t.integer "recipient_id"
|
||||
t.bigint "target_id"
|
||||
t.bigint "recipient_id"
|
||||
t.boolean "new"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
end
|
||||
|
||||
create_table "questions", id: :serial, force: :cascade do |t|
|
||||
create_table "questions", id: :bigint, default: -> { "gen_timestamp_id('questions'::text)" }, force: :cascade do |t|
|
||||
t.string "content"
|
||||
t.boolean "author_is_anonymous"
|
||||
t.string "author_name"
|
||||
t.string "author_email"
|
||||
t.integer "user_id"
|
||||
t.bigint "user_id"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.integer "answer_count", default: 0, null: false
|
||||
|
@ -129,8 +129,8 @@ ActiveRecord::Schema.define(version: 2020_05_25_145144) do
|
|||
end
|
||||
|
||||
create_table "relationships", id: :serial, force: :cascade do |t|
|
||||
t.integer "source_id"
|
||||
t.integer "target_id"
|
||||
t.bigint "source_id"
|
||||
t.bigint "target_id"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.index ["source_id", "target_id"], name: "index_relationships_on_source_id_and_target_id", unique: true
|
||||
|
@ -140,8 +140,8 @@ ActiveRecord::Schema.define(version: 2020_05_25_145144) do
|
|||
|
||||
create_table "reports", id: :serial, force: :cascade do |t|
|
||||
t.string "type", null: false
|
||||
t.integer "target_id", null: false
|
||||
t.integer "user_id", null: false
|
||||
t.bigint "target_id", null: false
|
||||
t.bigint "user_id", null: false
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.boolean "deleted", default: false
|
||||
|
@ -162,7 +162,7 @@ ActiveRecord::Schema.define(version: 2020_05_25_145144) do
|
|||
|
||||
create_table "services", id: :serial, force: :cascade do |t|
|
||||
t.string "type", null: false
|
||||
t.integer "user_id", null: false
|
||||
t.bigint "user_id", null: false
|
||||
t.string "uid"
|
||||
t.string "access_token"
|
||||
t.string "access_secret"
|
||||
|
@ -172,9 +172,9 @@ ActiveRecord::Schema.define(version: 2020_05_25_145144) do
|
|||
t.index ["user_id"], name: "index_services_on_user_id"
|
||||
end
|
||||
|
||||
create_table "smiles", id: :serial, force: :cascade do |t|
|
||||
t.integer "user_id"
|
||||
t.integer "answer_id"
|
||||
create_table "smiles", id: :bigint, default: -> { "gen_timestamp_id('smiles'::text)" }, force: :cascade do |t|
|
||||
t.bigint "user_id"
|
||||
t.bigint "answer_id"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.index ["answer_id"], name: "index_smiles_on_answer_id"
|
||||
|
@ -183,8 +183,8 @@ ActiveRecord::Schema.define(version: 2020_05_25_145144) do
|
|||
end
|
||||
|
||||
create_table "subscriptions", id: :serial, force: :cascade do |t|
|
||||
t.integer "user_id", null: false
|
||||
t.integer "answer_id", null: false
|
||||
t.bigint "user_id", null: false
|
||||
t.bigint "answer_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.boolean "is_active", default: true
|
||||
|
@ -192,7 +192,7 @@ ActiveRecord::Schema.define(version: 2020_05_25_145144) do
|
|||
end
|
||||
|
||||
create_table "themes", id: :serial, force: :cascade do |t|
|
||||
t.integer "user_id", null: false
|
||||
t.bigint "user_id", null: false
|
||||
t.integer "primary_color", default: 6174129
|
||||
t.integer "primary_text", default: 16777215
|
||||
t.integer "danger_color", default: 14431557
|
||||
|
@ -219,7 +219,7 @@ ActiveRecord::Schema.define(version: 2020_05_25_145144) do
|
|||
t.index ["user_id", "created_at"], name: "index_themes_on_user_id_and_created_at"
|
||||
end
|
||||
|
||||
create_table "users", id: :serial, force: :cascade do |t|
|
||||
create_table "users", id: :bigint, default: -> { "gen_timestamp_id('users'::text)" }, force: :cascade do |t|
|
||||
t.string "email", default: "", null: false
|
||||
t.string "encrypted_password", default: "", null: false
|
||||
t.string "reset_password_token"
|
||||
|
|
Loading…
Reference in New Issue