chore: refresh gramsrv public release
This commit is contained in:
parent
75cebe8dbf
commit
70b6820474
1274 changed files with 378751 additions and 59919 deletions
|
|
@ -12,6 +12,20 @@ services:
|
|||
postgres:
|
||||
image: postgres:17-alpine
|
||||
container_name: telesrv-postgres
|
||||
# 全库已去分区(普通表),max_locks_per_transaction 默认 64 已够;512 仅留余量,对内存近乎零开销。
|
||||
# pg_stat_statements:聚合各 SQL 的累计/平均耗时与调用次数,用于定位 postgres 容器 CPU 热点
|
||||
# (telesrv 是宿主进程,docker stats 里的 CPU 尖峰是本容器)。需 CREATE EXTENSION 后才有视图:
|
||||
# docker exec -i telesrv-postgres psql -U telesrv -d telesrv -c "CREATE EXTENSION IF NOT EXISTS pg_stat_statements;"
|
||||
command:
|
||||
- "postgres"
|
||||
- "-c"
|
||||
- "max_locks_per_transaction=512"
|
||||
- "-c"
|
||||
- "shared_preload_libraries=pg_stat_statements"
|
||||
- "-c"
|
||||
- "pg_stat_statements.track=all"
|
||||
- "-c"
|
||||
- "track_io_timing=on"
|
||||
environment:
|
||||
POSTGRES_DB: telesrv
|
||||
POSTGRES_USER: telesrv
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
-- 0001_init 回滚:按外键依赖逆序删表。
|
||||
DROP TABLE IF EXISTS authorizations;
|
||||
DROP TABLE IF EXISTS users;
|
||||
DROP TABLE IF EXISTS auth_keys;
|
||||
-- 0001_init 回滚(no-op):本文件是全新项目由 0001-0140 迁移链压缩而来的初始 schema。
|
||||
--
|
||||
-- 全新项目明确不保留回退路径(沿用 0011 旧迁移的同一约定)。重置数据库请重建:
|
||||
-- `docker compose -f deploy/docker-compose.yml down -v` 销毁卷后重新迁移,而非 golang-migrate down。
|
||||
-- golang-migrate 执行本文件即把版本回退到 0,不删除任何对象或数据。
|
||||
SELECT 1;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
1
deploy/migrations/0002_channel_topic_read.down.sql
Normal file
1
deploy/migrations/0002_channel_topic_read.down.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS public.channel_topic_read;
|
||||
23
deploy/migrations/0002_channel_topic_read.up.sql
Normal file
23
deploy/migrations/0002_channel_topic_read.up.sql
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
-- per-(user, channel, topic) 独立已读水位。
|
||||
-- 背景:forum 话题已读此前被频道级单一水位 channel_members.read_inbox_max_id 承载,
|
||||
-- 读一个 topic 会污染其它 topic 的未读现算(reply_to_top_id 过滤 + id > 频道级水位)。
|
||||
-- channel_forum_topics 那几个 read_*_max_id 列无 user 维度(PK=channel_id,topic_id),
|
||||
-- 无法承载 per-viewer 已读;故新建本表,topic_id=1 表示 General。
|
||||
CREATE TABLE public.channel_topic_read (
|
||||
channel_id bigint NOT NULL,
|
||||
user_id bigint NOT NULL,
|
||||
topic_id integer NOT NULL,
|
||||
read_inbox_max_id integer DEFAULT 0 NOT NULL,
|
||||
read_outbox_max_id integer DEFAULT 0 NOT NULL,
|
||||
read_inbox_date integer DEFAULT 0 NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT channel_topic_read_pkey PRIMARY KEY (channel_id, user_id, topic_id),
|
||||
CONSTRAINT channel_topic_read_topic_check CHECK (topic_id > 0),
|
||||
CONSTRAINT channel_topic_read_channel_fkey FOREIGN KEY (channel_id)
|
||||
REFERENCES public.channels (id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- outbox 回执反查:某 topic 内某 viewer 自己消息被读到哪 = 该 topic 其他成员 read_inbox_max_id 的最大值。
|
||||
-- (channel_id, topic_id, read_inbox_max_id DESC) 让 MAX 走 index-only,避免每帧全表聚合。
|
||||
CREATE INDEX channel_topic_read_outbox_idx
|
||||
ON public.channel_topic_read (channel_id, topic_id, read_inbox_max_id DESC);
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
DROP TABLE IF EXISTS lang_pack_strings;
|
||||
DROP TABLE IF EXISTS lang_packs;
|
||||
DROP TABLE IF EXISTS dialogs;
|
||||
DROP TABLE IF EXISTS contacts;
|
||||
DROP TABLE IF EXISTS update_states;
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
-- 0002_phase1_business: first-stage business persistence for startup RPCs.
|
||||
--
|
||||
-- 表结构按 telesrv domain/store 边界建模,不引入旧工程依赖。
|
||||
|
||||
CREATE TABLE IF NOT EXISTS update_states (
|
||||
auth_key_id BIGINT PRIMARY KEY REFERENCES auth_keys(auth_key_id) ON DELETE CASCADE,
|
||||
pts INT NOT NULL DEFAULT 0,
|
||||
qts INT NOT NULL DEFAULT 0,
|
||||
date INT NOT NULL DEFAULT 0,
|
||||
seq INT NOT NULL DEFAULT 0,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS contacts (
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
contact_user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
mutual BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (user_id, contact_user_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS contacts_contact_user_id_idx ON contacts (contact_user_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS dialogs (
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
peer_type VARCHAR(16) NOT NULL,
|
||||
peer_id BIGINT NOT NULL,
|
||||
top_message_id INT NOT NULL DEFAULT 0,
|
||||
read_inbox_max_id INT NOT NULL DEFAULT 0,
|
||||
read_outbox_max_id INT NOT NULL DEFAULT 0,
|
||||
unread_count INT NOT NULL DEFAULT 0,
|
||||
unread_mentions_count INT NOT NULL DEFAULT 0,
|
||||
unread_reactions_count INT NOT NULL DEFAULT 0,
|
||||
pinned BOOLEAN NOT NULL DEFAULT false,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (user_id, peer_type, peer_id),
|
||||
CONSTRAINT dialogs_peer_type_check CHECK (peer_type IN ('user'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS dialogs_user_updated_idx ON dialogs (user_id, updated_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS dialogs_user_pinned_idx ON dialogs (user_id, pinned) WHERE pinned;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS lang_packs (
|
||||
lang_pack VARCHAR(32) NOT NULL,
|
||||
lang_code VARCHAR(64) NOT NULL,
|
||||
version INT NOT NULL,
|
||||
strings_count INT NOT NULL DEFAULT 0,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (lang_pack, lang_code)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS lang_pack_strings (
|
||||
lang_pack VARCHAR(32) NOT NULL,
|
||||
lang_code VARCHAR(64) NOT NULL,
|
||||
key VARCHAR(128) NOT NULL,
|
||||
version INT NOT NULL,
|
||||
pluralized BOOLEAN NOT NULL DEFAULT false,
|
||||
value TEXT NOT NULL DEFAULT '',
|
||||
zero_value TEXT NOT NULL DEFAULT '',
|
||||
one_value TEXT NOT NULL DEFAULT '',
|
||||
two_value TEXT NOT NULL DEFAULT '',
|
||||
few_value TEXT NOT NULL DEFAULT '',
|
||||
many_value TEXT NOT NULL DEFAULT '',
|
||||
other_value TEXT NOT NULL DEFAULT '',
|
||||
deleted BOOLEAN NOT NULL DEFAULT false,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (lang_pack, lang_code, key),
|
||||
FOREIGN KEY (lang_pack, lang_code) REFERENCES lang_packs(lang_pack, lang_code) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS lang_pack_strings_pack_version_idx
|
||||
ON lang_pack_strings (lang_pack, lang_code, version);
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
DROP TABLE IF EXISTS temp_auth_key_bindings;
|
||||
DROP TABLE IF EXISTS country_codes;
|
||||
DROP TABLE IF EXISTS countries;
|
||||
DROP TABLE IF EXISTS app_configs;
|
||||
DROP TABLE IF EXISTS account_passwords;
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
-- 0003_startup_config_security: data-backed startup config, countries and account security.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS account_passwords (
|
||||
user_id BIGINT PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
|
||||
has_recovery BOOLEAN NOT NULL DEFAULT false,
|
||||
has_secure_values BOOLEAN NOT NULL DEFAULT false,
|
||||
has_password BOOLEAN NOT NULL DEFAULT false,
|
||||
hint VARCHAR(256) NOT NULL DEFAULT '',
|
||||
email_unconfirmed_pattern VARCHAR(256) NOT NULL DEFAULT '',
|
||||
login_email_pattern VARCHAR(256) NOT NULL DEFAULT '',
|
||||
secure_random BYTEA NOT NULL DEFAULT decode('74656c657372762d746465736b746f702d6465762d7365637572652d72616e64', 'hex'),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS app_configs (
|
||||
client VARCHAR(64) PRIMARY KEY,
|
||||
hash INT NOT NULL,
|
||||
config_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS countries (
|
||||
iso2 VARCHAR(2) PRIMARY KEY,
|
||||
default_name VARCHAR(128) NOT NULL,
|
||||
name VARCHAR(128) NOT NULL DEFAULT '',
|
||||
hidden BOOLEAN NOT NULL DEFAULT false,
|
||||
order_index INT NOT NULL DEFAULT 0,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS country_codes (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
iso2 VARCHAR(2) NOT NULL REFERENCES countries(iso2) ON DELETE CASCADE,
|
||||
country_code VARCHAR(16) NOT NULL,
|
||||
prefixes TEXT[] NOT NULL DEFAULT '{}',
|
||||
patterns TEXT[] NOT NULL DEFAULT '{}',
|
||||
order_index INT NOT NULL DEFAULT 0,
|
||||
UNIQUE (iso2, country_code)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS temp_auth_key_bindings (
|
||||
temp_auth_key_id BIGINT PRIMARY KEY REFERENCES auth_keys(auth_key_id) ON DELETE CASCADE,
|
||||
perm_auth_key_id BIGINT NOT NULL,
|
||||
nonce BIGINT NOT NULL,
|
||||
expires_at INT NOT NULL,
|
||||
encrypted_message BYTEA NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
INSERT INTO app_configs (client, hash, config_json)
|
||||
VALUES ('tdesktop', 1, '{}'::jsonb)
|
||||
ON CONFLICT (client) DO NOTHING;
|
||||
|
||||
INSERT INTO countries (iso2, default_name, name, hidden, order_index)
|
||||
VALUES
|
||||
('US', 'United States', '', false, 10),
|
||||
('CN', 'China', '', false, 20)
|
||||
ON CONFLICT (iso2) DO NOTHING;
|
||||
|
||||
INSERT INTO country_codes (iso2, country_code, prefixes, patterns, order_index)
|
||||
VALUES
|
||||
('US', '1', ARRAY['1'], '{}'::text[], 10),
|
||||
('CN', '86', ARRAY['86'], '{}'::text[], 20)
|
||||
ON CONFLICT (iso2, country_code) DO NOTHING;
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
-- 回滚:恢复不含 forum 话题已读两种事件类型的原始白名单(与 0001 一致)。
|
||||
ALTER TABLE public.user_update_events DROP CONSTRAINT IF EXISTS user_update_events_type_check;
|
||||
ALTER TABLE public.user_update_events ADD CONSTRAINT user_update_events_type_check CHECK (
|
||||
(event_type)::text = ANY (ARRAY[
|
||||
'new_message', 'read_history_inbox', 'read_history_outbox', 'read_message_contents',
|
||||
'edit_message', 'message_reactions', 'message_poll', 'draft_message', 'quick_replies',
|
||||
'new_quick_reply', 'delete_quick_reply', 'quick_reply_message', 'delete_quick_reply_messages',
|
||||
'contacts_reset', 'dialog_pinned', 'pinned_dialogs', 'pinned_messages', 'dialog_unread_mark',
|
||||
'peer_settings', 'peer_story_blocked', 'delete_messages', 'dialog_filter', 'dialog_filter_order',
|
||||
'dialog_filters', 'folder_peers', 'channel_available_messages', 'channel_view_forum_as_messages',
|
||||
'channel_state', 'saved_dialog_pinned', 'pinned_saved_dialogs', 'story', 'read_stories',
|
||||
'sent_story_reaction', 'new_story_reaction', 'noop'
|
||||
]::text[])
|
||||
);
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
-- 修复:forum 话题已读(0002/a180fbc)新增了 user_update_events 事件类型
|
||||
-- read_channel_discussion_inbox / read_channel_discussion_outbox(updateReadChannelDiscussionInbox/
|
||||
-- Outbox 的 durable 多设备同步),但 0001 的 user_update_events_type_check 白名单未包含它们。
|
||||
-- 后果:真 PostgreSQL 上 messages.readDiscussion 首次标已读时 append durable 事件被 CHECK
|
||||
-- 约束拒绝(SQLSTATE 23514)→ RPC 返回 500,且话题已读无法进入 durable / getDifference,
|
||||
-- 用户其它设备收不到 per-topic 已读。memory store 无 CHECK 约束故单测未暴露,真双机才发现。
|
||||
ALTER TABLE public.user_update_events DROP CONSTRAINT IF EXISTS user_update_events_type_check;
|
||||
ALTER TABLE public.user_update_events ADD CONSTRAINT user_update_events_type_check CHECK (
|
||||
(event_type)::text = ANY (ARRAY[
|
||||
'new_message', 'read_history_inbox', 'read_history_outbox', 'read_message_contents',
|
||||
'edit_message', 'message_reactions', 'message_poll', 'draft_message', 'quick_replies',
|
||||
'new_quick_reply', 'delete_quick_reply', 'quick_reply_message', 'delete_quick_reply_messages',
|
||||
'contacts_reset', 'dialog_pinned', 'pinned_dialogs', 'pinned_messages', 'dialog_unread_mark',
|
||||
'peer_settings', 'peer_story_blocked', 'delete_messages', 'dialog_filter', 'dialog_filter_order',
|
||||
'dialog_filters', 'folder_peers', 'channel_available_messages', 'channel_view_forum_as_messages',
|
||||
'channel_state', 'saved_dialog_pinned', 'pinned_saved_dialogs', 'story', 'read_stories',
|
||||
'sent_story_reaction', 'new_story_reaction', 'noop',
|
||||
'read_channel_discussion_inbox', 'read_channel_discussion_outbox'
|
||||
]::text[])
|
||||
);
|
||||
1
deploy/migrations/0004_account_settings.down.sql
Normal file
1
deploy/migrations/0004_account_settings.down.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS public.account_settings;
|
||||
23
deploy/migrations/0004_account_settings.up.sql
Normal file
23
deploy/migrations/0004_account_settings.up.sql
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
-- 账号级单例设置(每用户一行):全局隐私开关、账号自毁期限、敏感内容开关、
|
||||
-- 联系人注册通知静音。此前这些 account.* RPC 为硬编码回显 stub(不持久化):
|
||||
-- account.get/setGlobalPrivacySettings、get/setAccountTTL、get/setContentSettings、
|
||||
-- get/setContactSignUpNotification。本表落地真实持久化。
|
||||
CREATE TABLE public.account_settings (
|
||||
user_id bigint NOT NULL,
|
||||
archive_and_mute_new_noncontact_peers boolean DEFAULT false NOT NULL,
|
||||
keep_archived_unmuted boolean DEFAULT false NOT NULL,
|
||||
keep_archived_folders boolean DEFAULT false NOT NULL,
|
||||
hide_read_marks boolean DEFAULT false NOT NULL,
|
||||
new_noncontact_peers_require_premium boolean DEFAULT false NOT NULL,
|
||||
display_gifts_button boolean DEFAULT false NOT NULL,
|
||||
noncontact_peers_paid_stars bigint DEFAULT 0 NOT NULL,
|
||||
account_ttl_days integer DEFAULT 365 NOT NULL,
|
||||
sensitive_content_enabled boolean DEFAULT false NOT NULL,
|
||||
contact_signup_silent boolean DEFAULT false NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT account_settings_account_ttl_days_check CHECK ((account_ttl_days > 0)),
|
||||
CONSTRAINT account_settings_noncontact_peers_paid_stars_check CHECK ((noncontact_peers_paid_stars >= 0))
|
||||
);
|
||||
|
||||
ALTER TABLE ONLY public.account_settings
|
||||
ADD CONSTRAINT account_settings_pkey PRIMARY KEY (user_id);
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
ALTER TABLE temp_auth_key_bindings
|
||||
DROP COLUMN IF EXISTS temp_session_id;
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
-- 0004_temp_auth_key_binding_session: persist validated bind_auth_key_inner session id.
|
||||
|
||||
ALTER TABLE temp_auth_key_bindings
|
||||
ADD COLUMN IF NOT EXISTS temp_session_id BIGINT NOT NULL DEFAULT 0;
|
||||
1
deploy/migrations/0005_notify_settings.down.sql
Normal file
1
deploy/migrations/0005_notify_settings.down.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS public.notify_settings;
|
||||
24
deploy/migrations/0005_notify_settings.up.sql
Normal file
24
deploy/migrations/0005_notify_settings.up.sql
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
-- per-scope 通知设置(每用户多行):具体 peer / forum 话题 / 三类全局默认
|
||||
-- (users/chats/broadcasts)。此前 account.get/update/resetNotifySettings 为回显
|
||||
-- stub(不持久化),mute 重启即丢、dialog 列表静音状态不正确。本表落地真实持久化。
|
||||
-- 可空 bool/int 列表示“该项未设置=按所属类别继承默认”(与 TL flag-optional 一致)。
|
||||
CREATE TABLE public.notify_settings (
|
||||
owner_user_id bigint NOT NULL,
|
||||
scope_kind text NOT NULL,
|
||||
peer_type text DEFAULT ''::text NOT NULL,
|
||||
peer_id bigint DEFAULT 0 NOT NULL,
|
||||
topic_id integer DEFAULT 0 NOT NULL,
|
||||
show_previews boolean,
|
||||
silent boolean,
|
||||
mute_until integer,
|
||||
stories_muted boolean,
|
||||
stories_hide_sender boolean,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT notify_settings_scope_kind_check CHECK ((scope_kind = ANY (ARRAY['peer'::text, 'users'::text, 'chats'::text, 'broadcasts'::text])))
|
||||
);
|
||||
|
||||
ALTER TABLE ONLY public.notify_settings
|
||||
ADD CONSTRAINT notify_settings_pkey PRIMARY KEY (owner_user_id, scope_kind, peer_type, peer_id, topic_id);
|
||||
|
||||
-- dialog 列表批量读:按 owner + 一组 peer 取整-peer(topic 0)设置。
|
||||
CREATE INDEX notify_settings_owner_peer_idx ON public.notify_settings USING btree (owner_user_id, peer_type, peer_id) WHERE ((scope_kind = 'peer'::text) AND (topic_id = 0));
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
DROP INDEX IF EXISTS messages_owner_date_idx;
|
||||
DROP INDEX IF EXISTS messages_owner_dialog_idx;
|
||||
DROP TABLE IF EXISTS messages;
|
||||
|
||||
DROP INDEX IF EXISTS dialogs_user_top_message_idx;
|
||||
ALTER TABLE dialogs DROP COLUMN IF EXISTS top_message_date;
|
||||
|
||||
DELETE FROM users WHERE id = 777000;
|
||||
ALTER TABLE users
|
||||
DROP COLUMN IF EXISTS support,
|
||||
DROP COLUMN IF EXISTS verified;
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
-- 0005_system_login_messages: official system account and first message persistence.
|
||||
--
|
||||
-- 777000 官方账号与登录消息推送;表结构按 telesrv domain/store 边界建模。
|
||||
|
||||
ALTER TABLE users
|
||||
ADD COLUMN IF NOT EXISTS verified BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN IF NOT EXISTS support BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
INSERT INTO users (id, access_hash, phone, first_name, last_name, username, country_code, verified, support)
|
||||
VALUES (777000, 6599886787491911851, '42777', 'Telegram', '', 'telegram', '', true, true)
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
access_hash = EXCLUDED.access_hash,
|
||||
phone = EXCLUDED.phone,
|
||||
first_name = EXCLUDED.first_name,
|
||||
last_name = EXCLUDED.last_name,
|
||||
username = EXCLUDED.username,
|
||||
verified = EXCLUDED.verified,
|
||||
support = EXCLUDED.support,
|
||||
updated_at = now();
|
||||
|
||||
ALTER TABLE dialogs
|
||||
ADD COLUMN IF NOT EXISTS top_message_date INT NOT NULL DEFAULT 0;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS dialogs_user_top_message_idx
|
||||
ON dialogs (user_id, pinned DESC, top_message_date DESC, top_message_id DESC, peer_id DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS messages (
|
||||
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
owner_user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
peer_type VARCHAR(16) NOT NULL,
|
||||
peer_id BIGINT NOT NULL,
|
||||
from_user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
|
||||
message_date INT NOT NULL,
|
||||
outgoing BOOLEAN NOT NULL DEFAULT false,
|
||||
body TEXT NOT NULL DEFAULT '',
|
||||
entities JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
CONSTRAINT messages_peer_type_check CHECK (peer_type IN ('user'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS messages_owner_dialog_idx
|
||||
ON messages (owner_user_id, peer_type, peer_id, id DESC);
|
||||
CREATE INDEX IF NOT EXISTS messages_owner_date_idx
|
||||
ON messages (owner_user_id, message_date DESC, id DESC);
|
||||
1
deploy/migrations/0006_sticker_collections.down.sql
Normal file
1
deploy/migrations/0006_sticker_collections.down.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS public.user_sticker_collections;
|
||||
18
deploy/migrations/0006_sticker_collections.up.sql
Normal file
18
deploy/migrations/0006_sticker_collections.up.sql
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
-- per-user 个人贴纸/GIF 集合:收藏贴纸 / 最近贴纸 / attach 最近贴纸 / 保存的 GIF。
|
||||
-- 此前 messages.faveSticker/saveRecentSticker/saveGif 未注册(NOT_IMPLEMENTED)、
|
||||
-- getFaved/getRecent/getSavedGifs 返回空 stub。本表落地真实持久化。
|
||||
-- used_at 为入列/最近使用时刻,读取按 used_at DESC(最新在前);document_id 引用
|
||||
-- documents 表,读路径经 Files.GetDocuments 解析为完整文档。
|
||||
CREATE TABLE public.user_sticker_collections (
|
||||
owner_user_id bigint NOT NULL,
|
||||
kind text NOT NULL,
|
||||
document_id bigint NOT NULL,
|
||||
used_at integer DEFAULT 0 NOT NULL,
|
||||
CONSTRAINT user_sticker_collections_kind_check CHECK ((kind = ANY (ARRAY['faved'::text, 'recent'::text, 'recent_attached'::text, 'gif'::text])))
|
||||
);
|
||||
|
||||
ALTER TABLE ONLY public.user_sticker_collections
|
||||
ADD CONSTRAINT user_sticker_collections_pkey PRIMARY KEY (owner_user_id, kind, document_id);
|
||||
|
||||
-- 读取/截断按 (owner, kind, used_at DESC)。
|
||||
CREATE INDEX user_sticker_collections_order_idx ON public.user_sticker_collections USING btree (owner_user_id, kind, used_at DESC);
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
DROP INDEX IF EXISTS update_events_auth_pts_idx;
|
||||
DROP TABLE IF EXISTS update_events;
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
-- 0006_update_events: minimal auth-key update queue for getDifference补偿.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS update_events (
|
||||
auth_key_id BIGINT NOT NULL REFERENCES auth_keys(auth_key_id) ON DELETE CASCADE,
|
||||
pts INT NOT NULL,
|
||||
pts_count INT NOT NULL DEFAULT 1,
|
||||
date INT NOT NULL,
|
||||
event_type VARCHAR(32) NOT NULL,
|
||||
message_id INT REFERENCES messages(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (auth_key_id, pts),
|
||||
CONSTRAINT update_events_type_check CHECK (event_type IN ('new_message'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS update_events_auth_pts_idx
|
||||
ON update_events (auth_key_id, pts);
|
||||
1
deploy/migrations/0007_dialogs_peer_id_index.down.sql
Normal file
1
deploy/migrations/0007_dialogs_peer_id_index.down.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
DROP INDEX IF EXISTS public.dialogs_peer_id_idx;
|
||||
9
deploy/migrations/0007_dialogs_peer_id_index.up.sql
Normal file
9
deploy/migrations/0007_dialogs_peer_id_index.up.sql
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
-- dialogs 按 peer_id 的反向索引。
|
||||
-- dialog_light 失效扇出函数 telesrv_bump_private_dialog_light_for_user 按
|
||||
-- peer_id 过滤 dialogs("哪些用户与该 user 有私聊会话"),但 dialogs 上所有
|
||||
-- 既有索引都以 user_id 前导(主键 + dialogs_user_folder_top_message_idx +
|
||||
-- dialogs_user_pinned_order_idx),该按 peer_id 的查询此前只能 Seq Scan。
|
||||
-- dialogs 有 CHECK (peer_type = 'user'),故单列 (peer_id) 即精确命中、无需
|
||||
-- peer_type 前缀,与 contacts.contacts_contact_user_id_idx 对称。资料变更 /
|
||||
-- premium 到期触发扇出时,从全表扫降为索引扫(随表规模增长收益放大)。
|
||||
CREATE INDEX dialogs_peer_id_idx ON public.dialogs USING btree (peer_id);
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
ALTER TABLE update_events
|
||||
DROP CONSTRAINT IF EXISTS update_events_peer_type_check;
|
||||
|
||||
ALTER TABLE update_events
|
||||
DROP CONSTRAINT IF EXISTS update_events_type_check;
|
||||
|
||||
ALTER TABLE update_events
|
||||
ADD CONSTRAINT update_events_type_check
|
||||
CHECK (event_type IN ('new_message'));
|
||||
|
||||
ALTER TABLE update_events
|
||||
DROP COLUMN IF EXISTS still_unread_count,
|
||||
DROP COLUMN IF EXISTS max_id,
|
||||
DROP COLUMN IF EXISTS peer_id,
|
||||
DROP COLUMN IF EXISTS peer_type;
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
-- 0007_read_history_events: persist readHistory update events for getDifference.
|
||||
--
|
||||
-- updateReadHistoryInbox:真正发生已读推进时递增 pts,并给其它 session / getDifference 留可补偿事件。
|
||||
|
||||
ALTER TABLE update_events
|
||||
ADD COLUMN IF NOT EXISTS peer_type VARCHAR(16),
|
||||
ADD COLUMN IF NOT EXISTS peer_id BIGINT,
|
||||
ADD COLUMN IF NOT EXISTS max_id INT NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS still_unread_count INT NOT NULL DEFAULT 0;
|
||||
|
||||
ALTER TABLE update_events
|
||||
DROP CONSTRAINT IF EXISTS update_events_type_check;
|
||||
|
||||
ALTER TABLE update_events
|
||||
ADD CONSTRAINT update_events_type_check
|
||||
CHECK (event_type IN ('new_message', 'read_history_inbox'));
|
||||
|
||||
ALTER TABLE update_events
|
||||
DROP CONSTRAINT IF EXISTS update_events_peer_type_check;
|
||||
|
||||
ALTER TABLE update_events
|
||||
ADD CONSTRAINT update_events_peer_type_check
|
||||
CHECK (peer_type IS NULL OR peer_type IN ('user'));
|
||||
3
deploy/migrations/0008_message_grouped_id.down.sql
Normal file
3
deploy/migrations/0008_message_grouped_id.down.sql
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
ALTER TABLE public.channel_messages DROP COLUMN IF EXISTS grouped_id;
|
||||
ALTER TABLE public.message_boxes DROP COLUMN IF EXISTS grouped_id;
|
||||
ALTER TABLE public.private_messages DROP COLUMN IF EXISTS grouped_id;
|
||||
6
deploy/migrations/0008_message_grouped_id.up.sql
Normal file
6
deploy/migrations/0008_message_grouped_id.up.sql
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
-- 相册分组 id:同一次 messages.sendMultiMedia 的各条消息共享一个非零 grouped_id,
|
||||
-- 客户端据此把它们渲染成一个相册组。此前 sendMultiMedia 不绑定 grouped_id(各条独立气泡)。
|
||||
-- 镜像 via_bot_id 的三张表:共享私聊主体 + 每 owner 收件箱 + 频道消息。
|
||||
ALTER TABLE public.private_messages ADD COLUMN grouped_id bigint DEFAULT 0 NOT NULL;
|
||||
ALTER TABLE public.message_boxes ADD COLUMN grouped_id bigint DEFAULT 0 NOT NULL;
|
||||
ALTER TABLE public.channel_messages ADD COLUMN grouped_id bigint DEFAULT 0 NOT NULL;
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
-- Revert generated ordinary user ids to the original first-phase base when possible.
|
||||
-- Existing users keep their ids; the next generated id is never moved below MAX(id)+1.
|
||||
|
||||
SELECT setval(
|
||||
pg_get_serial_sequence('users', 'id'),
|
||||
GREATEST((SELECT COALESCE(MAX(id), 0) FROM users), 999999999),
|
||||
true
|
||||
);
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
-- 0008_user_id_sequence_base: move ordinary user ids to the agreed timestamp range.
|
||||
--
|
||||
-- Base: 2026-06-01 00:00:00 Asia/Shanghai => Unix seconds 1780243200.
|
||||
-- Existing users keep their ids; the next generated id is at least this base.
|
||||
|
||||
SELECT setval(
|
||||
pg_get_serial_sequence('users', 'id'),
|
||||
GREATEST((SELECT COALESCE(MAX(id), 0) FROM users), 1780243199),
|
||||
true
|
||||
);
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
-- 0009 rollback: remove second-stage private message pipeline tables.
|
||||
DROP TABLE IF EXISTS dispatch_outbox;
|
||||
DROP TABLE IF EXISTS user_update_events;
|
||||
DROP TABLE IF EXISTS dialogs;
|
||||
DROP TABLE IF EXISTS message_boxes;
|
||||
DROP TABLE IF EXISTS private_messages;
|
||||
|
||||
ALTER TABLE IF EXISTS dialogs_legacy RENAME TO dialogs;
|
||||
ALTER TABLE IF EXISTS messages_legacy RENAME TO messages;
|
||||
|
||||
ALTER TABLE update_states DROP CONSTRAINT IF EXISTS update_states_pkey;
|
||||
ALTER TABLE update_states DROP COLUMN IF EXISTS user_id;
|
||||
ALTER TABLE update_states ADD PRIMARY KEY (auth_key_id);
|
||||
|
|
@ -1,239 +0,0 @@
|
|||
-- 0009_private_message_pipeline: second-stage private text message storage.
|
||||
--
|
||||
-- Large tables are partitioned from the first version of the message module:
|
||||
-- message_boxes/dialogs/user_update_events/dispatch_outbox by owner/target user,
|
||||
-- private_messages by sender user for random_id idempotency locality.
|
||||
|
||||
ALTER TABLE IF EXISTS messages RENAME TO messages_legacy;
|
||||
ALTER TABLE IF EXISTS dialogs RENAME TO dialogs_legacy;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS private_messages (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
|
||||
sender_user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
recipient_user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
random_id BIGINT NOT NULL DEFAULT 0,
|
||||
message_date INT NOT NULL,
|
||||
body TEXT NOT NULL DEFAULT '',
|
||||
entities JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (sender_user_id, id),
|
||||
CONSTRAINT private_messages_nonempty_body CHECK (body <> '')
|
||||
) PARTITION BY HASH (sender_user_id);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS private_messages_sender_random_idx
|
||||
ON private_messages (sender_user_id, random_id)
|
||||
WHERE random_id <> 0;
|
||||
CREATE INDEX IF NOT EXISTS private_messages_recipient_date_idx
|
||||
ON private_messages (recipient_user_id, message_date DESC, id DESC);
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
i int;
|
||||
BEGIN
|
||||
FOR i IN 0..63 LOOP
|
||||
EXECUTE format(
|
||||
'CREATE TABLE IF NOT EXISTS private_messages_p%s PARTITION OF private_messages FOR VALUES WITH (MODULUS 64, REMAINDER %s)',
|
||||
lpad(i::text, 2, '0'),
|
||||
i
|
||||
);
|
||||
END LOOP;
|
||||
END $$;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS message_boxes (
|
||||
owner_user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
box_id INT NOT NULL,
|
||||
private_message_id BIGINT NOT NULL,
|
||||
message_sender_id BIGINT NOT NULL,
|
||||
peer_type VARCHAR(16) NOT NULL,
|
||||
peer_id BIGINT NOT NULL,
|
||||
from_user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
|
||||
message_date INT NOT NULL,
|
||||
outgoing BOOLEAN NOT NULL DEFAULT false,
|
||||
body TEXT NOT NULL DEFAULT '',
|
||||
entities JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
pts INT NOT NULL DEFAULT 0,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (owner_user_id, box_id),
|
||||
UNIQUE (owner_user_id, private_message_id),
|
||||
CONSTRAINT message_boxes_peer_type_check CHECK (peer_type IN ('user')),
|
||||
FOREIGN KEY (message_sender_id, private_message_id)
|
||||
REFERENCES private_messages(sender_user_id, id) ON DELETE CASCADE
|
||||
) PARTITION BY HASH (owner_user_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS message_boxes_dialog_seek_idx
|
||||
ON message_boxes (owner_user_id, peer_type, peer_id, box_id DESC)
|
||||
WHERE NOT deleted;
|
||||
CREATE INDEX IF NOT EXISTS message_boxes_owner_date_idx
|
||||
ON message_boxes (owner_user_id, message_date DESC, box_id DESC)
|
||||
WHERE NOT deleted;
|
||||
CREATE INDEX IF NOT EXISTS message_boxes_private_lookup_idx
|
||||
ON message_boxes (private_message_id, owner_user_id);
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
i int;
|
||||
BEGIN
|
||||
FOR i IN 0..63 LOOP
|
||||
EXECUTE format(
|
||||
'CREATE TABLE IF NOT EXISTS message_boxes_p%s PARTITION OF message_boxes FOR VALUES WITH (MODULUS 64, REMAINDER %s)',
|
||||
lpad(i::text, 2, '0'),
|
||||
i
|
||||
);
|
||||
END LOOP;
|
||||
END $$;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS dialogs (
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
peer_type VARCHAR(16) NOT NULL,
|
||||
peer_id BIGINT NOT NULL,
|
||||
top_message_id INT NOT NULL DEFAULT 0,
|
||||
top_message_date INT NOT NULL DEFAULT 0,
|
||||
read_inbox_max_id INT NOT NULL DEFAULT 0,
|
||||
read_outbox_max_id INT NOT NULL DEFAULT 0,
|
||||
unread_count INT NOT NULL DEFAULT 0,
|
||||
unread_mentions_count INT NOT NULL DEFAULT 0,
|
||||
unread_reactions_count INT NOT NULL DEFAULT 0,
|
||||
pinned BOOLEAN NOT NULL DEFAULT false,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (user_id, peer_type, peer_id),
|
||||
CONSTRAINT dialogs_peer_type_check CHECK (peer_type IN ('user'))
|
||||
) PARTITION BY HASH (user_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS dialogs_user_top_message_idx
|
||||
ON dialogs (user_id, pinned DESC, top_message_date DESC, top_message_id DESC, peer_id DESC);
|
||||
CREATE INDEX IF NOT EXISTS dialogs_user_pinned_idx
|
||||
ON dialogs (user_id, pinned) WHERE pinned;
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
i int;
|
||||
BEGIN
|
||||
FOR i IN 0..63 LOOP
|
||||
EXECUTE format(
|
||||
'CREATE TABLE IF NOT EXISTS dialogs_p%s PARTITION OF dialogs FOR VALUES WITH (MODULUS 64, REMAINDER %s)',
|
||||
lpad(i::text, 2, '0'),
|
||||
i
|
||||
);
|
||||
END LOOP;
|
||||
END $$;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_update_events (
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
pts INT NOT NULL,
|
||||
pts_count INT NOT NULL DEFAULT 1,
|
||||
date INT NOT NULL,
|
||||
event_type VARCHAR(32) NOT NULL,
|
||||
message_box_id INT,
|
||||
peer_type VARCHAR(16),
|
||||
peer_id BIGINT,
|
||||
max_id INT NOT NULL DEFAULT 0,
|
||||
still_unread_count INT NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (user_id, pts),
|
||||
CONSTRAINT user_update_events_type_check CHECK (event_type IN ('new_message', 'read_history_inbox', 'noop')),
|
||||
CONSTRAINT user_update_events_peer_type_check CHECK (peer_type IS NULL OR peer_type IN ('user')),
|
||||
FOREIGN KEY (user_id, message_box_id) REFERENCES message_boxes(owner_user_id, box_id) ON DELETE CASCADE
|
||||
) PARTITION BY HASH (user_id);
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
i int;
|
||||
BEGIN
|
||||
FOR i IN 0..63 LOOP
|
||||
EXECUTE format(
|
||||
'CREATE TABLE IF NOT EXISTS user_update_events_p%s PARTITION OF user_update_events FOR VALUES WITH (MODULUS 64, REMAINDER %s)',
|
||||
lpad(i::text, 2, '0'),
|
||||
i
|
||||
);
|
||||
END LOOP;
|
||||
END $$;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS dispatch_outbox (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
|
||||
target_user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
pts INT NOT NULL,
|
||||
event_type VARCHAR(32) NOT NULL,
|
||||
exclude_session_id BIGINT NOT NULL DEFAULT 0,
|
||||
status VARCHAR(16) NOT NULL DEFAULT 'pending',
|
||||
attempts INT NOT NULL DEFAULT 0,
|
||||
next_attempt_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
last_error TEXT NOT NULL DEFAULT '',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (target_user_id, id),
|
||||
CONSTRAINT dispatch_outbox_status_check CHECK (status IN ('pending', 'dispatching', 'delivered', 'failed')),
|
||||
FOREIGN KEY (target_user_id, pts) REFERENCES user_update_events(user_id, pts) ON DELETE CASCADE
|
||||
) PARTITION BY HASH (target_user_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS dispatch_outbox_pending_idx
|
||||
ON dispatch_outbox (status, next_attempt_at, target_user_id, id)
|
||||
WHERE status = 'pending';
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
i int;
|
||||
BEGIN
|
||||
FOR i IN 0..63 LOOP
|
||||
EXECUTE format(
|
||||
'CREATE TABLE IF NOT EXISTS dispatch_outbox_p%s PARTITION OF dispatch_outbox FOR VALUES WITH (MODULUS 64, REMAINDER %s)',
|
||||
lpad(i::text, 2, '0'),
|
||||
i
|
||||
);
|
||||
END LOOP;
|
||||
END $$;
|
||||
|
||||
ALTER TABLE update_states
|
||||
ADD COLUMN IF NOT EXISTS user_id BIGINT NOT NULL DEFAULT 0;
|
||||
ALTER TABLE update_states DROP CONSTRAINT IF EXISTS update_states_pkey;
|
||||
ALTER TABLE update_states ADD PRIMARY KEY (auth_key_id, user_id);
|
||||
CREATE INDEX IF NOT EXISTS update_states_user_id_idx ON update_states (user_id);
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
r record;
|
||||
private_id bigint;
|
||||
BEGIN
|
||||
IF to_regclass('messages_legacy') IS NULL THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
FOR r IN
|
||||
SELECT id, owner_user_id, peer_type, peer_id, from_user_id, message_date, outgoing, body, entities
|
||||
FROM messages_legacy
|
||||
ORDER BY owner_user_id, id
|
||||
LOOP
|
||||
INSERT INTO private_messages (
|
||||
sender_user_id, recipient_user_id, random_id, message_date, body, entities
|
||||
) VALUES (
|
||||
r.from_user_id,
|
||||
r.owner_user_id,
|
||||
0,
|
||||
r.message_date,
|
||||
r.body,
|
||||
r.entities
|
||||
)
|
||||
RETURNING id INTO private_id;
|
||||
|
||||
INSERT INTO message_boxes (
|
||||
owner_user_id, box_id, private_message_id, message_sender_id, peer_type, peer_id,
|
||||
from_user_id, message_date, outgoing, body, entities
|
||||
) VALUES (
|
||||
r.owner_user_id, r.id, private_id, r.from_user_id, r.peer_type, r.peer_id,
|
||||
r.from_user_id, r.message_date, r.outgoing, r.body, r.entities
|
||||
)
|
||||
ON CONFLICT (owner_user_id, box_id) DO NOTHING;
|
||||
END LOOP;
|
||||
END $$;
|
||||
|
||||
INSERT INTO dialogs (
|
||||
user_id, peer_type, peer_id, top_message_id, top_message_date,
|
||||
read_inbox_max_id, read_outbox_max_id, unread_count,
|
||||
unread_mentions_count, unread_reactions_count, pinned, updated_at
|
||||
)
|
||||
SELECT
|
||||
user_id, peer_type, peer_id, top_message_id, top_message_date,
|
||||
read_inbox_max_id, read_outbox_max_id, unread_count,
|
||||
unread_mentions_count, unread_reactions_count, pinned, updated_at
|
||||
FROM dialogs_legacy
|
||||
ON CONFLICT (user_id, peer_type, peer_id) DO NOTHING;
|
||||
2
deploy/migrations/0009_stars_ledger.down.sql
Normal file
2
deploy/migrations/0009_stars_ledger.down.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
DROP TABLE IF EXISTS public.stars_transactions;
|
||||
DROP TABLE IF EXISTS public.stars_balances;
|
||||
29
deploy/migrations/0009_stars_ledger.up.sql
Normal file
29
deploy/migrations/0009_stars_ledger.up.sql
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
-- Stars 本地账本:per-user 余额 + 交易流水。此前 payments.getStarsStatus 余额恒 0、
|
||||
-- getStarsTransactions 未注册、sendPaidReaction 恒返 BALANCE_TOO_LOW——无任何余额持久化。
|
||||
-- 本地账本(非真实支付):起始余额走惰性首读授予(granted 布尔幂等,新老账号都覆盖、免回填)。
|
||||
-- 借记原子性由 store 层 withTx 保证(SELECT ... FOR UPDATE + CHECK(balance>=0) + UPDATE + INSERT)。
|
||||
CREATE TABLE public.stars_balances (
|
||||
user_id bigint NOT NULL,
|
||||
balance bigint DEFAULT 0 NOT NULL,
|
||||
granted boolean DEFAULT false NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT stars_balances_pkey PRIMARY KEY (user_id),
|
||||
CONSTRAINT stars_balances_balance_nonneg CHECK ((balance >= 0))
|
||||
);
|
||||
|
||||
-- 流水:amount 带符号(贷记 > 0 / 借记 < 0);peer_* 为对手方(grant/topup 等无对手时为空)。
|
||||
CREATE TABLE public.stars_transactions (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
user_id bigint NOT NULL,
|
||||
peer_type text DEFAULT '' NOT NULL,
|
||||
peer_id bigint DEFAULT 0 NOT NULL,
|
||||
amount bigint NOT NULL,
|
||||
reason text DEFAULT 'adjust' NOT NULL,
|
||||
title text DEFAULT '' NOT NULL,
|
||||
description text DEFAULT '' NOT NULL,
|
||||
date integer DEFAULT 0 NOT NULL,
|
||||
CONSTRAINT stars_transactions_pkey PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
-- keyset 分页:WHERE user_id=$1 [AND id < cursor] ORDER BY id DESC LIMIT n。
|
||||
CREATE INDEX stars_transactions_user_id_idx ON public.stars_transactions USING btree (user_id, id DESC);
|
||||
1
deploy/migrations/0010_channel_paid_reactions.down.sql
Normal file
1
deploy/migrations/0010_channel_paid_reactions.down.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS public.channel_message_paid_reactions;
|
||||
17
deploy/migrations/0010_channel_paid_reactions.up.sql
Normal file
17
deploy/migrations/0010_channel_paid_reactions.up.sql
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
-- 频道帖子付费 reaction(messages.sendPaidReaction):每个 reactor 对一条频道消息累计
|
||||
-- 投入的 Stars 星数 + 是否匿名。此前 sendPaidReaction 恒返 BALANCE_TOO_LOW(无账本无累计)。
|
||||
-- 配合 Stars 账本(迁移 0009):rpc 先从账本 Debit,再在此累计;消息上展示 ReactionPaid 总星数
|
||||
-- 与 top reactors 排行。星数为正、按 (channel,message,user) 累加。
|
||||
CREATE TABLE public.channel_message_paid_reactions (
|
||||
channel_id bigint NOT NULL,
|
||||
message_id integer NOT NULL,
|
||||
reactor_user_id bigint NOT NULL,
|
||||
stars bigint DEFAULT 0 NOT NULL,
|
||||
anonymous boolean DEFAULT false NOT NULL,
|
||||
reaction_date integer DEFAULT 0 NOT NULL,
|
||||
CONSTRAINT channel_message_paid_reactions_pkey PRIMARY KEY (channel_id, message_id, reactor_user_id),
|
||||
CONSTRAINT channel_message_paid_reactions_stars_pos CHECK ((stars > 0))
|
||||
);
|
||||
|
||||
-- top reactors 排行按 (channel, message, stars DESC)。
|
||||
CREATE INDEX channel_message_paid_reactions_top_idx ON public.channel_message_paid_reactions USING btree (channel_id, message_id, stars DESC);
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
DROP INDEX IF EXISTS dispatch_outbox_dispatching_stale_idx;
|
||||
DROP INDEX IF EXISTS message_boxes_dialog_date_seek_idx;
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
-- 0010_message_performance_indexes: indexes for second-stage message seek paths.
|
||||
|
||||
CREATE INDEX IF NOT EXISTS message_boxes_dialog_date_seek_idx
|
||||
ON message_boxes (owner_user_id, peer_type, peer_id, message_date DESC, box_id DESC)
|
||||
WHERE NOT deleted;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS dispatch_outbox_dispatching_stale_idx
|
||||
ON dispatch_outbox (status, updated_at, target_user_id, id)
|
||||
WHERE status = 'dispatching';
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
-- 0011 down (no-op): 本迁移清除的是已被取代、运行时零引用的死表
|
||||
-- (update_events / messages_legacy / dialogs_legacy)。
|
||||
--
|
||||
-- 全新项目明确不保留回退路径,故不在此重建这些表;历史结构可查 0002 / 0005 / 0006 / 0007 迁移脚本(保留未删)。
|
||||
-- golang-migrate 执行本文件即把版本回退到 0010,不恢复任何死表或其数据。
|
||||
SELECT 1;
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
-- 0011_drop_dead_tables: 清除已被取代、运行时零引用的遗留表(全新项目,不保留回退)。
|
||||
--
|
||||
-- - update_events : 一阶段 auth_key 维度 update 队列(0006 建 / 0007 扩展),二阶段被
|
||||
-- user_update_events 取代;无任何 query / Go 引用,仅在 sqlc 留下孤儿 model。
|
||||
-- - messages_legacy: 0009 由旧 messages(0005)重命名保留的迁移残骸,数据已迁入 private_messages + message_boxes。
|
||||
-- - dialogs_legacy : 0009 由旧 dialogs(0002)重命名保留的迁移残骸,数据已迁入新 dialogs。
|
||||
--
|
||||
-- 顺序要求:update_events.message_id 外键指向 messages_legacy(原 messages),故先删 update_events。
|
||||
-- 删除后需重跑 `sqlc generate`,models.go 中 UpdateEvent / MessagesLegacy / DialogsLegacy 孤儿 model 会自动消失。
|
||||
|
||||
DROP TABLE IF EXISTS update_events;
|
||||
DROP TABLE IF EXISTS messages_legacy;
|
||||
DROP TABLE IF EXISTS dialogs_legacy;
|
||||
1
deploy/migrations/0011_star_gifts.down.sql
Normal file
1
deploy/migrations/0011_star_gifts.down.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS public.user_star_gifts;
|
||||
24
deploy/migrations/0011_star_gifts.up.sql
Normal file
24
deploy/migrations/0011_star_gifts.up.sql
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
-- Star gift(payments.sendStarsForm + inputInvoiceStarGift):用户花 Stars 给另一用户送礼物
|
||||
-- (贴纸式收藏品)。此前 getStarGifts/getSavedStarGifts 返回空桩、getPaymentForm/sendStarsForm
|
||||
-- 未注册。目录是从已 seed 的 animated_emoji 合成的静态内存表(不入库);本表存「已收到的礼物
|
||||
-- 实例」。配合 Stars 账本(迁移 0009):发礼 Debit、转换回 Stars 时 Credit。
|
||||
-- msg_id 是礼物在 owner 私聊里的服务消息 id(messageActionStarGift),是 save/convert 的身份键
|
||||
-- (对应 inputSavedStarGiftUser.msg_id)。
|
||||
CREATE TABLE public.user_star_gifts (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
owner_user_id bigint NOT NULL,
|
||||
from_user_id bigint DEFAULT 0 NOT NULL,
|
||||
gift_id bigint NOT NULL,
|
||||
msg_id integer NOT NULL,
|
||||
gift_date integer DEFAULT 0 NOT NULL,
|
||||
name_hidden boolean DEFAULT false NOT NULL,
|
||||
unsaved boolean DEFAULT false NOT NULL,
|
||||
converted boolean DEFAULT false NOT NULL,
|
||||
convert_stars bigint DEFAULT 0 NOT NULL,
|
||||
message text DEFAULT '' NOT NULL,
|
||||
CONSTRAINT user_star_gifts_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT user_star_gifts_owner_msg_uniq UNIQUE (owner_user_id, msg_id)
|
||||
);
|
||||
|
||||
-- getSavedStarGifts keyset 分页按 (owner, gift_date DESC, id DESC)。
|
||||
CREATE INDEX user_star_gifts_owner_idx ON public.user_star_gifts USING btree (owner_user_id, gift_date DESC, id DESC);
|
||||
2
deploy/migrations/0012_message_reply_story.down.sql
Normal file
2
deploy/migrations/0012_message_reply_story.down.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE public.message_boxes DROP COLUMN reply_to_story_id;
|
||||
ALTER TABLE public.private_messages DROP COLUMN reply_to_story_id;
|
||||
7
deploy/migrations/0012_message_reply_story.up.sql
Normal file
7
deploy/migrations/0012_message_reply_story.up.sql
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
-- Story 回复(评论):用户对一条 story 回复时,客户端发 messages.sendMessage(reply_to=
|
||||
-- inputReplyToStory{peer, story_id}),消息落入与 story 作者的私聊,并投影为
|
||||
-- messageReplyStoryHeader。此前 telesrv 直接拒(STORY_ID_INVALID)导致评论发送失败。
|
||||
-- 复用已有的 reply_to_* 列族,新增 reply_to_story_id 持久化被回复的 story id(普通消息回复恒 0)。
|
||||
-- 双盒(私聊主体 + per-owner 投影)都需要该列以便重读历史时还原 story 回复头。
|
||||
ALTER TABLE public.private_messages ADD COLUMN reply_to_story_id integer DEFAULT 0 NOT NULL;
|
||||
ALTER TABLE public.message_boxes ADD COLUMN reply_to_story_id integer DEFAULT 0 NOT NULL;
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
-- 0012 down: 恢复宽 status CHECK(含 'delivered')以保持迁移链完整。
|
||||
-- 注意:方案 A 已删除的 delivered 行不可恢复;query 层(DELETE)回退需手动改回 UPDATE,本 down 不涉及代码。
|
||||
ALTER TABLE dispatch_outbox DROP CONSTRAINT IF EXISTS dispatch_outbox_status_check;
|
||||
ALTER TABLE dispatch_outbox ADD CONSTRAINT dispatch_outbox_status_check
|
||||
CHECK (status IN ('pending', 'dispatching', 'delivered', 'failed'));
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
-- 0012_outbox_delete_on_deliver: outbox 投递成功改为直接 DELETE(方案 A),杜绝 delivered 行无限堆积。
|
||||
--
|
||||
-- 配合 query 改动(MarkDispatchDelivered / MarkDispatchDeliveredBatch 由 UPDATE status='delivered' 改为 DELETE):
|
||||
-- 1) 清理改造前堆积的存量 delivered 行;
|
||||
-- 2) 收紧 status CHECK,移除不再使用的 'delivered'(状态机只剩 pending / dispatching / failed)。
|
||||
-- dispatch_outbox 为 HASH 分区表,父表 DELETE / ALTER CONSTRAINT 自动作用于全部分区。
|
||||
|
||||
DELETE FROM dispatch_outbox WHERE status = 'delivered';
|
||||
|
||||
ALTER TABLE dispatch_outbox DROP CONSTRAINT IF EXISTS dispatch_outbox_status_check;
|
||||
ALTER TABLE dispatch_outbox ADD CONSTRAINT dispatch_outbox_status_check
|
||||
CHECK (status IN ('pending', 'dispatching', 'failed'));
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
DROP TRIGGER IF EXISTS users_bot_full_read_model_changed ON public.users;
|
||||
DROP FUNCTION IF EXISTS public.telesrv_notify_bot_full_read_model();
|
||||
25
deploy/migrations/0013_bot_full_read_model_trigger.up.sql
Normal file
25
deploy/migrations/0013_bot_full_read_model_trigger.up.sql
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
-- bot 资料(name/about/description/commands/menu_button)变更都会 bump users.bot_info_version
|
||||
-- (BumpBotInfoVersion,见 bot.sql)。群信息页的 ChannelFull.bot_info 由 RPC 层
|
||||
-- channelFullBotInfoCache 缓存(键=viewer+channel,无法按 bot 定位),其跨实例失效此前只挂在
|
||||
-- channel_base/channel_member 事件上——bot 自身改资料(经 BotFather 本地路径,或其它实例的
|
||||
-- bots.* RPC)不会失效该缓存,导致群信息页里该 bot 的简介/命令陈旧最长 30 分钟(TTL)。
|
||||
--
|
||||
-- 这里给 bot 资料变更单独发一个 'bot_full' read-model 事件;ReadModelChangeListener 收到即
|
||||
-- flush channelFullBotInfoCache(本地 BotFather 路径与跨实例 bots.* RPC 两条更新路径都覆盖)。
|
||||
-- 与既有 user_base 事件(覆盖 RPC 投影/Redis user:base/bot 资料缓存)互补,不改动 user_base 热路径。
|
||||
|
||||
CREATE FUNCTION public.telesrv_notify_bot_full_read_model() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
IF NEW.is_bot AND (OLD.bot_info_version IS DISTINCT FROM NEW.bot_info_version) THEN
|
||||
PERFORM telesrv_bump_read_model_version('bot_full', NEW.id, 'user', NEW.id);
|
||||
END IF;
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER users_bot_full_read_model_changed
|
||||
AFTER UPDATE ON public.users
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION public.telesrv_notify_bot_full_read_model();
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
DROP INDEX IF EXISTS dialogs_user_pinned_order_idx;
|
||||
|
||||
ALTER TABLE dialogs
|
||||
DROP COLUMN IF EXISTS hidden_peer_settings_bar,
|
||||
DROP COLUMN IF EXISTS unread_mark,
|
||||
DROP COLUMN IF EXISTS pinned_order;
|
||||
|
||||
DROP INDEX IF EXISTS contacts_user_name_idx;
|
||||
|
||||
ALTER TABLE contacts
|
||||
DROP COLUMN IF EXISTS stories_hidden,
|
||||
DROP COLUMN IF EXISTS close_friend,
|
||||
DROP COLUMN IF EXISTS note_entities,
|
||||
DROP COLUMN IF EXISTS note,
|
||||
DROP COLUMN IF EXISTS contact_last_name,
|
||||
DROP COLUMN IF EXISTS contact_first_name,
|
||||
DROP COLUMN IF EXISTS contact_phone;
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
-- 0013_contact_profiles_and_dialog_pins: owner-scoped contact profile fields and pin order.
|
||||
--
|
||||
-- contact_* columns are deliberately scoped to (user_id, contact_user_id): they model the
|
||||
-- current account's saved name/phone/note for a peer and must not mutate users global data.
|
||||
|
||||
ALTER TABLE contacts
|
||||
ADD COLUMN IF NOT EXISTS contact_phone VARCHAR(32) NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS contact_first_name VARCHAR(255) NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS contact_last_name VARCHAR(255) NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS note TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS note_entities JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
ADD COLUMN IF NOT EXISTS close_friend BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN IF NOT EXISTS stories_hidden BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS contacts_user_name_idx
|
||||
ON contacts (user_id, contact_first_name, contact_last_name, contact_user_id);
|
||||
|
||||
ALTER TABLE dialogs
|
||||
ADD COLUMN IF NOT EXISTS pinned_order INT NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS unread_mark BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN IF NOT EXISTS hidden_peer_settings_bar BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS dialogs_user_pinned_order_idx
|
||||
ON dialogs (user_id, pinned, pinned_order, top_message_date DESC, top_message_id DESC, peer_id DESC)
|
||||
WHERE pinned;
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
ALTER TABLE user_update_events
|
||||
DROP CONSTRAINT IF EXISTS user_update_events_type_check;
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
ADD CONSTRAINT user_update_events_type_check CHECK (
|
||||
event_type IN ('new_message', 'read_history_inbox', 'noop')
|
||||
);
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
DROP COLUMN IF EXISTS event_bool;
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
-- 0014_settings_update_events: durable updates for contacts/dialog settings.
|
||||
--
|
||||
-- Online push is not enough: offline sessions must recover contact resets,
|
||||
-- dialog pin order changes, manual unread marks, and peer settings changes
|
||||
-- through updates.getDifference.
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
ADD COLUMN IF NOT EXISTS event_bool BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
DROP CONSTRAINT IF EXISTS user_update_events_type_check;
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
ADD CONSTRAINT user_update_events_type_check CHECK (
|
||||
event_type IN (
|
||||
'new_message',
|
||||
'read_history_inbox',
|
||||
'contacts_reset',
|
||||
'dialog_pinned',
|
||||
'pinned_dialogs',
|
||||
'dialog_unread_mark',
|
||||
'peer_settings',
|
||||
'noop'
|
||||
)
|
||||
);
|
||||
1
deploy/migrations/0014_web_pages.down.sql
Normal file
1
deploy/migrations/0014_web_pages.down.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS public.web_pages;
|
||||
18
deploy/migrations/0014_web_pages.up.sql
Normal file
18
deploy/migrations/0014_web_pages.up.sql
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
-- 链接预览(webpage preview)解析缓存。键为规范化 URL 的 63-bit 哈希;同一 URL
|
||||
-- 跨发送/跨实例去重到一行,避免对同一链接重复抓取外网。snapshot 是完整的
|
||||
-- domain.MessageWebPage(含已解析卡片字段与内嵌预览图引用),与消息 media 列同构,
|
||||
-- 读时无需重新抓取或 rehydrate 即可投影为 messageMediaWebPage。
|
||||
--
|
||||
-- web_page_id 由 url_hash 派生(二者同值),使 pending 占位与 done 解析结果携带
|
||||
-- 同一 webPage id,客户端按 id 关联占位与解析(见异步回填阶段)。
|
||||
-- state 冗余出来便于后续按 pending/empty 做清扫与刷新。snapshot 不可变(按内容寻址
|
||||
-- 的预览图 blob 已独立持久化于 photos/blob 存储),跨实例靠 url_hash 唯一约束去重 +
|
||||
-- 每实例 TTL,故本表无需 read-model NOTIFY 触发器。
|
||||
CREATE TABLE public.web_pages (
|
||||
url_hash bigint PRIMARY KEY,
|
||||
web_page_id bigint NOT NULL,
|
||||
state text NOT NULL,
|
||||
snapshot jsonb NOT NULL DEFAULT '{}'::jsonb,
|
||||
created_at bigint NOT NULL DEFAULT 0,
|
||||
refreshed_at bigint NOT NULL DEFAULT 0
|
||||
);
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
ALTER TABLE dispatch_outbox
|
||||
DROP COLUMN IF EXISTS exclude_auth_key_id;
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
DROP COLUMN IF EXISTS peer_settings;
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
DROP COLUMN IF EXISTS event_peers;
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
-- 0015_update_event_payloads_and_outbox_auth: keep setting-update payloads durable.
|
||||
--
|
||||
-- event_peers carries ordered dialog peers for updatePinnedDialogs.order.
|
||||
-- peer_settings carries updatePeerSettings flags.
|
||||
-- exclude_auth_key_id makes outbox exclusion precise for same session_id across auth keys.
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
ADD COLUMN IF NOT EXISTS event_peers JSONB NOT NULL DEFAULT '[]'::jsonb;
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
ADD COLUMN IF NOT EXISTS peer_settings JSONB NOT NULL DEFAULT '{}'::jsonb;
|
||||
|
||||
ALTER TABLE dispatch_outbox
|
||||
ADD COLUMN IF NOT EXISTS exclude_auth_key_id BIGINT NOT NULL DEFAULT 0;
|
||||
15
deploy/migrations/0015_user_update_events_web_page.down.sql
Normal file
15
deploy/migrations/0015_user_update_events_web_page.down.sql
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
-- 回退:从白名单移除 'web_page'(与 0003 形态一致)。
|
||||
ALTER TABLE public.user_update_events DROP CONSTRAINT IF EXISTS user_update_events_type_check;
|
||||
ALTER TABLE public.user_update_events ADD CONSTRAINT user_update_events_type_check CHECK (
|
||||
(event_type)::text = ANY (ARRAY[
|
||||
'new_message', 'read_history_inbox', 'read_history_outbox', 'read_message_contents',
|
||||
'edit_message', 'message_reactions', 'message_poll', 'draft_message', 'quick_replies',
|
||||
'new_quick_reply', 'delete_quick_reply', 'quick_reply_message', 'delete_quick_reply_messages',
|
||||
'contacts_reset', 'dialog_pinned', 'pinned_dialogs', 'pinned_messages', 'dialog_unread_mark',
|
||||
'peer_settings', 'peer_story_blocked', 'delete_messages', 'dialog_filter', 'dialog_filter_order',
|
||||
'dialog_filters', 'folder_peers', 'channel_available_messages', 'channel_view_forum_as_messages',
|
||||
'channel_state', 'saved_dialog_pinned', 'pinned_saved_dialogs', 'story', 'read_stories',
|
||||
'sent_story_reaction', 'new_story_reaction', 'noop',
|
||||
'read_channel_discussion_inbox', 'read_channel_discussion_outbox'
|
||||
]::text[])
|
||||
);
|
||||
18
deploy/migrations/0015_user_update_events_web_page.up.sql
Normal file
18
deploy/migrations/0015_user_update_events_web_page.up.sql
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
-- 链接预览异步回填(updateWebPage)新增 user_update_events 事件类型 'web_page':发送时挂的
|
||||
-- pending 链接预览占位被带外解析后,经 EditMessage 的 WebPageResolve 模式就地替换并 append
|
||||
-- 一条 web_page durable 事件(按 webPage id 关联,不标记「已编辑」)。0001/0003 的白名单未含它,
|
||||
-- 真 Postgres 上 append 会被 CHECK 约束拒绝(SQLSTATE 23514);memory store 无约束故单测未暴露。
|
||||
ALTER TABLE public.user_update_events DROP CONSTRAINT IF EXISTS user_update_events_type_check;
|
||||
ALTER TABLE public.user_update_events ADD CONSTRAINT user_update_events_type_check CHECK (
|
||||
(event_type)::text = ANY (ARRAY[
|
||||
'new_message', 'read_history_inbox', 'read_history_outbox', 'read_message_contents',
|
||||
'edit_message', 'web_page', 'message_reactions', 'message_poll', 'draft_message', 'quick_replies',
|
||||
'new_quick_reply', 'delete_quick_reply', 'quick_reply_message', 'delete_quick_reply_messages',
|
||||
'contacts_reset', 'dialog_pinned', 'pinned_dialogs', 'pinned_messages', 'dialog_unread_mark',
|
||||
'peer_settings', 'peer_story_blocked', 'delete_messages', 'dialog_filter', 'dialog_filter_order',
|
||||
'dialog_filters', 'folder_peers', 'channel_available_messages', 'channel_view_forum_as_messages',
|
||||
'channel_state', 'saved_dialog_pinned', 'pinned_saved_dialogs', 'story', 'read_stories',
|
||||
'sent_story_reaction', 'new_story_reaction', 'noop',
|
||||
'read_channel_discussion_inbox', 'read_channel_discussion_outbox'
|
||||
]::text[])
|
||||
);
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
-- 回退:从白名单移除 'channel_web_page'。
|
||||
ALTER TABLE public.channel_update_events DROP CONSTRAINT IF EXISTS channel_update_events_type_check;
|
||||
ALTER TABLE public.channel_update_events ADD CONSTRAINT channel_update_events_type_check CHECK (
|
||||
(event_type)::text = ANY (ARRAY[
|
||||
'new_channel_message', 'edit_channel_message', 'delete_channel_messages',
|
||||
'channel_participant', 'pinned_channel_messages', 'noop'
|
||||
]::text[])
|
||||
);
|
||||
12
deploy/migrations/0016_channel_update_events_web_page.up.sql
Normal file
12
deploy/migrations/0016_channel_update_events_web_page.up.sql
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
-- 频道链接预览异步回填(updateChannelWebPage)新增 channel_update_events 事件类型
|
||||
-- 'channel_web_page':频道消息的 pending 链接预览被带外解析后,经 EditChannelMessage 的
|
||||
-- WebPageResolve 模式就地替换并 append 一条 channel_web_page durable 事件。0001 的白名单未含它,
|
||||
-- 真 Postgres 上 append 会被 CHECK 约束拒绝(SQLSTATE 23514);memory store 无约束故单测未暴露
|
||||
-- (同 0015/0003 教训)。
|
||||
ALTER TABLE public.channel_update_events DROP CONSTRAINT IF EXISTS channel_update_events_type_check;
|
||||
ALTER TABLE public.channel_update_events ADD CONSTRAINT channel_update_events_type_check CHECK (
|
||||
(event_type)::text = ANY (ARRAY[
|
||||
'new_channel_message', 'edit_channel_message', 'channel_web_page', 'delete_channel_messages',
|
||||
'channel_participant', 'pinned_channel_messages', 'noop'
|
||||
]::text[])
|
||||
);
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
DROP INDEX IF EXISTS message_boxes_private_sender_live_idx;
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
DROP CONSTRAINT IF EXISTS user_update_events_type_check;
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
ADD CONSTRAINT user_update_events_type_check CHECK (
|
||||
event_type IN (
|
||||
'new_message',
|
||||
'read_history_inbox',
|
||||
'contacts_reset',
|
||||
'dialog_pinned',
|
||||
'pinned_dialogs',
|
||||
'dialog_unread_mark',
|
||||
'peer_settings',
|
||||
'noop'
|
||||
)
|
||||
);
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
DROP COLUMN IF EXISTS message_ids;
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
-- 0016_delete_message_updates: durable owner-view delete message updates.
|
||||
--
|
||||
-- message_ids carries updateDeleteMessages.messages for offline getDifference
|
||||
-- and reliable online outbox delivery.
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
ADD COLUMN IF NOT EXISTS message_ids JSONB NOT NULL DEFAULT '[]'::jsonb;
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
DROP CONSTRAINT IF EXISTS user_update_events_type_check;
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
ADD CONSTRAINT user_update_events_type_check CHECK (
|
||||
event_type IN (
|
||||
'new_message',
|
||||
'read_history_inbox',
|
||||
'contacts_reset',
|
||||
'dialog_pinned',
|
||||
'pinned_dialogs',
|
||||
'dialog_unread_mark',
|
||||
'peer_settings',
|
||||
'delete_messages',
|
||||
'noop'
|
||||
)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS message_boxes_private_sender_live_idx
|
||||
ON message_boxes (message_sender_id, private_message_id)
|
||||
WHERE NOT deleted;
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
-- 0017_dialog_folders rollback.
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
DROP CONSTRAINT IF EXISTS user_update_events_type_check;
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
ADD CONSTRAINT user_update_events_type_check CHECK (
|
||||
event_type IN (
|
||||
'new_message',
|
||||
'read_history_inbox',
|
||||
'contacts_reset',
|
||||
'dialog_pinned',
|
||||
'pinned_dialogs',
|
||||
'dialog_unread_mark',
|
||||
'peer_settings',
|
||||
'delete_messages',
|
||||
'noop'
|
||||
)
|
||||
);
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
DROP COLUMN IF EXISTS tags_enabled;
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
DROP COLUMN IF EXISTS filter_id;
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
DROP COLUMN IF EXISTS folder_peers;
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
DROP COLUMN IF EXISTS filter_order;
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
DROP COLUMN IF EXISTS dialog_filter;
|
||||
|
||||
DROP TABLE IF EXISTS dialog_filter_settings CASCADE;
|
||||
|
||||
DROP TABLE IF EXISTS dialog_filters CASCADE;
|
||||
|
||||
DROP INDEX IF EXISTS dialogs_user_folder_top_message_idx;
|
||||
|
||||
ALTER TABLE dialogs
|
||||
DROP CONSTRAINT IF EXISTS dialogs_folder_id_check;
|
||||
|
||||
ALTER TABLE dialogs
|
||||
DROP COLUMN IF EXISTS folder_id;
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
-- 0017_dialog_folders: archive folder, custom dialog filters, and durable folder updates.
|
||||
|
||||
ALTER TABLE dialogs
|
||||
ADD COLUMN IF NOT EXISTS folder_id INT NOT NULL DEFAULT 0;
|
||||
|
||||
ALTER TABLE dialogs
|
||||
DROP CONSTRAINT IF EXISTS dialogs_folder_id_check;
|
||||
|
||||
ALTER TABLE dialogs
|
||||
ADD CONSTRAINT dialogs_folder_id_check CHECK (folder_id >= 0);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS dialogs_user_folder_top_message_idx
|
||||
ON dialogs (user_id, folder_id, pinned DESC, top_message_date DESC, top_message_id DESC, peer_id DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS dialog_filters (
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
filter_id INT NOT NULL,
|
||||
is_chatlist BOOLEAN NOT NULL DEFAULT false,
|
||||
filter JSONB NOT NULL,
|
||||
order_value INT NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (user_id, filter_id),
|
||||
CONSTRAINT dialog_filters_id_check CHECK (filter_id >= 2),
|
||||
CONSTRAINT dialog_filters_filter_object_check CHECK (jsonb_typeof(filter) = 'object')
|
||||
) PARTITION BY HASH (user_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS dialog_filters_user_order_idx
|
||||
ON dialog_filters (user_id, order_value, filter_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS dialog_filter_settings (
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
tags_enabled BOOLEAN NOT NULL DEFAULT false,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (user_id)
|
||||
) PARTITION BY HASH (user_id);
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
i int;
|
||||
BEGIN
|
||||
FOR i IN 0..63 LOOP
|
||||
EXECUTE format(
|
||||
'CREATE TABLE IF NOT EXISTS dialog_filters_p%s PARTITION OF dialog_filters FOR VALUES WITH (MODULUS 64, REMAINDER %s)',
|
||||
lpad(i::text, 2, '0'),
|
||||
i
|
||||
);
|
||||
EXECUTE format(
|
||||
'CREATE TABLE IF NOT EXISTS dialog_filter_settings_p%s PARTITION OF dialog_filter_settings FOR VALUES WITH (MODULUS 64, REMAINDER %s)',
|
||||
lpad(i::text, 2, '0'),
|
||||
i
|
||||
);
|
||||
END LOOP;
|
||||
END $$;
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
ADD COLUMN IF NOT EXISTS dialog_filter JSONB NOT NULL DEFAULT '{}'::jsonb;
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
ADD COLUMN IF NOT EXISTS filter_order JSONB NOT NULL DEFAULT '[]'::jsonb;
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
ADD COLUMN IF NOT EXISTS folder_peers JSONB NOT NULL DEFAULT '[]'::jsonb;
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
ADD COLUMN IF NOT EXISTS filter_id INT NOT NULL DEFAULT 0;
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
ADD COLUMN IF NOT EXISTS tags_enabled BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
DROP CONSTRAINT IF EXISTS user_update_events_type_check;
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
ADD CONSTRAINT user_update_events_type_check CHECK (
|
||||
event_type IN (
|
||||
'new_message',
|
||||
'read_history_inbox',
|
||||
'contacts_reset',
|
||||
'dialog_pinned',
|
||||
'pinned_dialogs',
|
||||
'dialog_unread_mark',
|
||||
'peer_settings',
|
||||
'delete_messages',
|
||||
'dialog_filter',
|
||||
'dialog_filter_order',
|
||||
'dialog_filters',
|
||||
'folder_peers',
|
||||
'noop'
|
||||
)
|
||||
);
|
||||
1
deploy/migrations/0017_login_email.down.sql
Normal file
1
deploy/migrations/0017_login_email.down.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE public.account_passwords DROP COLUMN login_email;
|
||||
4
deploy/migrations/0017_login_email.up.sql
Normal file
4
deploy/migrations/0017_login_email.up.sql
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
-- 登录邮箱(login email):独立于 2FA 恢复邮箱的备用登录方式。新设备登录时验证码
|
||||
-- 改投递到该邮箱(auth.sentCodeTypeEmailCode)。仅存已确认的真实地址,下发时掩码。
|
||||
ALTER TABLE public.account_passwords
|
||||
ADD COLUMN login_email character varying(256) DEFAULT ''::character varying NOT NULL;
|
||||
1
deploy/migrations/0018_passkey_credentials.down.sql
Normal file
1
deploy/migrations/0018_passkey_credentials.down.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE public.passkey_credentials;
|
||||
18
deploy/migrations/0018_passkey_credentials.up.sql
Normal file
18
deploy/migrations/0018_passkey_credentials.up.sql
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
-- Passkey(WebAuthn/FIDO2)凭据。每条 = 一个已注册的公钥。sign_count 用 bigint
|
||||
-- 容纳 uint32;credential_id 为主键(原始字节,对外 base64url)。
|
||||
CREATE TABLE public.passkey_credentials (
|
||||
credential_id bytea NOT NULL,
|
||||
user_id bigint NOT NULL,
|
||||
public_key bytea NOT NULL,
|
||||
sign_count bigint DEFAULT 0 NOT NULL,
|
||||
aaguid bytea DEFAULT '\x'::bytea NOT NULL,
|
||||
name character varying(128) DEFAULT ''::character varying NOT NULL,
|
||||
transports text[] DEFAULT '{}'::text[] NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
last_used_at timestamp with time zone,
|
||||
CONSTRAINT passkey_credentials_pkey PRIMARY KEY (credential_id),
|
||||
CONSTRAINT passkey_credentials_user_id_fkey FOREIGN KEY (user_id)
|
||||
REFERENCES public.users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX passkey_credentials_user_id_idx ON public.passkey_credentials (user_id);
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
DROP INDEX IF EXISTS message_boxes_body_trgm_idx;
|
||||
DROP INDEX IF EXISTS contacts_user_saved_name_trgm_idx;
|
||||
DROP INDEX IF EXISTS users_name_lower_trgm_idx;
|
||||
DROP INDEX IF EXISTS users_username_lower_trgm_idx;
|
||||
DROP INDEX IF EXISTS users_phone_prefix_idx;
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
-- 0018_user_search_indexes: keep TDesktop global user search bounded as users grow.
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS users_phone_prefix_idx
|
||||
ON users (phone text_pattern_ops);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS users_username_lower_trgm_idx
|
||||
ON users USING gin (lower(username) gin_trgm_ops);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS users_name_lower_trgm_idx
|
||||
ON users USING gin (lower(trim(first_name || ' ' || last_name)) gin_trgm_ops);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS contacts_user_saved_name_trgm_idx
|
||||
ON contacts USING gin (lower(trim(contact_first_name || ' ' || contact_last_name)) gin_trgm_ops);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS message_boxes_body_trgm_idx
|
||||
ON message_boxes USING gin (body gin_trgm_ops)
|
||||
WHERE NOT deleted AND body <> '';
|
||||
2
deploy/migrations/0019_custom_themes.down.sql
Normal file
2
deploy/migrations/0019_custom_themes.down.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
DROP TABLE public.theme_user_installs;
|
||||
DROP TABLE public.themes;
|
||||
37
deploy/migrations/0019_custom_themes.up.sql
Normal file
37
deploy/migrations/0019_custom_themes.up.sql
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
-- 自定义云主题(account.createTheme/uploadTheme 等)。themes = 用户创建的主题目录,
|
||||
-- document_id 软引用 documents(无硬外键,避免与媒体表耦合;主题可能比 blob 长存,
|
||||
-- getTheme 时若 blob 缺失则退化为无 document)。settings = []domain.ThemeSettingsSpec 的
|
||||
-- JSONB(仅 accent 主题非空)。theme_user_installs = 每用户已安装/已存主题列表。
|
||||
CREATE TABLE public.themes (
|
||||
id bigint NOT NULL,
|
||||
access_hash bigint DEFAULT 0 NOT NULL,
|
||||
creator_user_id bigint NOT NULL,
|
||||
slug text NOT NULL,
|
||||
title text DEFAULT ''::text NOT NULL,
|
||||
emoticon text DEFAULT ''::text NOT NULL,
|
||||
for_chat boolean DEFAULT false NOT NULL,
|
||||
document_id bigint DEFAULT 0 NOT NULL,
|
||||
settings jsonb DEFAULT '[]'::jsonb NOT NULL,
|
||||
installs_count integer DEFAULT 0 NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT themes_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT themes_slug_key UNIQUE (slug),
|
||||
CONSTRAINT themes_creator_user_id_fkey FOREIGN KEY (creator_user_id)
|
||||
REFERENCES public.users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX themes_creator_user_id_idx ON public.themes (creator_user_id);
|
||||
|
||||
CREATE TABLE public.theme_user_installs (
|
||||
user_id bigint NOT NULL,
|
||||
theme_id bigint NOT NULL,
|
||||
dark boolean DEFAULT false NOT NULL,
|
||||
installed_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT theme_user_installs_pkey PRIMARY KEY (user_id, theme_id),
|
||||
CONSTRAINT theme_user_installs_user_id_fkey FOREIGN KEY (user_id)
|
||||
REFERENCES public.users(id) ON DELETE CASCADE,
|
||||
CONSTRAINT theme_user_installs_theme_id_fkey FOREIGN KEY (theme_id)
|
||||
REFERENCES public.themes(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX theme_user_installs_user_id_idx ON public.theme_user_installs (user_id);
|
||||
|
|
@ -1 +0,0 @@
|
|||
DROP INDEX IF EXISTS users_username_lower_unique_idx;
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
-- 0019_usernames: primary username lifecycle.
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS users_username_lower_unique_idx
|
||||
ON users (lower(username))
|
||||
WHERE username <> '';
|
||||
2
deploy/migrations/0020_channel_monoforum.down.sql
Normal file
2
deploy/migrations/0020_channel_monoforum.down.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE public.channels DROP COLUMN IF EXISTS linked_monoforum_id;
|
||||
ALTER TABLE public.channels DROP COLUMN IF EXISTS monoforum;
|
||||
6
deploy/migrations/0020_channel_monoforum.up.sql
Normal file
6
deploy/migrations/0020_channel_monoforum.up.sql
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
-- 频道私信(Direct Messages):母广播频道关联一个 monoforum 虚拟频道承载订阅者私信。
|
||||
-- monoforum=true 标记该频道是 monoforum;linked_monoforum_id 双向关联母频道 ↔ monoforum。
|
||||
ALTER TABLE public.channels
|
||||
ADD COLUMN monoforum boolean DEFAULT false NOT NULL;
|
||||
ALTER TABLE public.channels
|
||||
ADD COLUMN linked_monoforum_id bigint DEFAULT 0 NOT NULL;
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
ALTER TABLE user_update_events
|
||||
DROP CONSTRAINT IF EXISTS user_update_events_type_check;
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
ADD CONSTRAINT user_update_events_type_check CHECK (
|
||||
event_type IN (
|
||||
'new_message',
|
||||
'read_history_inbox',
|
||||
'contacts_reset',
|
||||
'dialog_pinned',
|
||||
'pinned_dialogs',
|
||||
'dialog_unread_mark',
|
||||
'peer_settings',
|
||||
'delete_messages',
|
||||
'dialog_filter',
|
||||
'dialog_filter_order',
|
||||
'dialog_filters',
|
||||
'folder_peers',
|
||||
'noop'
|
||||
)
|
||||
);
|
||||
|
||||
DROP INDEX IF EXISTS message_boxes_private_sender_owner_idx;
|
||||
DROP INDEX IF EXISTS user_update_events_read_outbox_idx;
|
||||
DROP INDEX IF EXISTS message_boxes_read_receipt_idx;
|
||||
|
||||
ALTER TABLE message_boxes
|
||||
DROP COLUMN IF EXISTS edit_date;
|
||||
|
||||
ALTER TABLE private_messages
|
||||
DROP COLUMN IF EXISTS edit_date;
|
||||
|
||||
ALTER TABLE users
|
||||
DROP COLUMN IF EXISTS about;
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
-- 0020_profile_message_state: profile about, message edits, and read outbox updates.
|
||||
|
||||
ALTER TABLE users
|
||||
ADD COLUMN IF NOT EXISTS about VARCHAR(255) NOT NULL DEFAULT '';
|
||||
|
||||
ALTER TABLE private_messages
|
||||
ADD COLUMN IF NOT EXISTS edit_date INT NOT NULL DEFAULT 0;
|
||||
|
||||
ALTER TABLE message_boxes
|
||||
ADD COLUMN IF NOT EXISTS edit_date INT NOT NULL DEFAULT 0;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS message_boxes_read_receipt_idx
|
||||
ON message_boxes (owner_user_id, peer_type, peer_id, box_id DESC)
|
||||
WHERE NOT deleted AND NOT outgoing;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS message_boxes_private_sender_owner_idx
|
||||
ON message_boxes (message_sender_id, private_message_id, owner_user_id)
|
||||
WHERE NOT deleted;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS user_update_events_read_outbox_idx
|
||||
ON user_update_events (user_id, peer_type, peer_id, max_id, date)
|
||||
WHERE event_type = 'read_history_outbox';
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
DROP CONSTRAINT IF EXISTS user_update_events_type_check;
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
ADD CONSTRAINT user_update_events_type_check CHECK (
|
||||
event_type IN (
|
||||
'new_message',
|
||||
'read_history_inbox',
|
||||
'read_history_outbox',
|
||||
'edit_message',
|
||||
'contacts_reset',
|
||||
'dialog_pinned',
|
||||
'pinned_dialogs',
|
||||
'dialog_unread_mark',
|
||||
'peer_settings',
|
||||
'delete_messages',
|
||||
'dialog_filter',
|
||||
'dialog_filter_order',
|
||||
'dialog_filters',
|
||||
'folder_peers',
|
||||
'noop'
|
||||
)
|
||||
);
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
DROP INDEX IF EXISTS channel_messages_monoforum_sublist_idx;
|
||||
ALTER TABLE public.channel_messages DROP COLUMN IF EXISTS saved_peer_id;
|
||||
ALTER TABLE public.channel_messages DROP COLUMN IF EXISTS saved_peer_type;
|
||||
9
deploy/migrations/0021_channel_message_saved_peer.up.sql
Normal file
9
deploy/migrations/0021_channel_message_saved_peer.up.sql
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
-- 频道私信(monoforum):私信消息存进 channel_messages(复用 channel pts/事件/difference),
|
||||
-- 用 saved_peer 维度按订阅者分子会话。普通频道消息 saved_peer 为空,部分索引仅覆盖 monoforum 行。
|
||||
ALTER TABLE public.channel_messages
|
||||
ADD COLUMN saved_peer_type character varying(16) DEFAULT ''::character varying NOT NULL;
|
||||
ALTER TABLE public.channel_messages
|
||||
ADD COLUMN saved_peer_id bigint DEFAULT 0 NOT NULL;
|
||||
CREATE INDEX channel_messages_monoforum_sublist_idx ON public.channel_messages
|
||||
USING btree (channel_id, saved_peer_id, id DESC)
|
||||
WHERE ((saved_peer_id <> 0) AND (NOT deleted));
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
DROP INDEX IF EXISTS message_boxes_reply_lookup_idx;
|
||||
|
||||
ALTER TABLE message_boxes
|
||||
DROP COLUMN IF EXISTS fwd_date,
|
||||
DROP COLUMN IF EXISTS fwd_from_name,
|
||||
DROP COLUMN IF EXISTS fwd_from_peer_id,
|
||||
DROP COLUMN IF EXISTS fwd_from_peer_type,
|
||||
DROP COLUMN IF EXISTS quote_offset,
|
||||
DROP COLUMN IF EXISTS quote_entities,
|
||||
DROP COLUMN IF EXISTS quote_text,
|
||||
DROP COLUMN IF EXISTS reply_to_top_id,
|
||||
DROP COLUMN IF EXISTS reply_to_peer_id,
|
||||
DROP COLUMN IF EXISTS reply_to_peer_type,
|
||||
DROP COLUMN IF EXISTS reply_to_msg_id,
|
||||
DROP COLUMN IF EXISTS noforwards,
|
||||
DROP COLUMN IF EXISTS silent;
|
||||
|
||||
ALTER TABLE private_messages
|
||||
DROP COLUMN IF EXISTS fwd_date,
|
||||
DROP COLUMN IF EXISTS fwd_from_name,
|
||||
DROP COLUMN IF EXISTS fwd_from_peer_id,
|
||||
DROP COLUMN IF EXISTS fwd_from_peer_type,
|
||||
DROP COLUMN IF EXISTS quote_offset,
|
||||
DROP COLUMN IF EXISTS quote_entities,
|
||||
DROP COLUMN IF EXISTS quote_text,
|
||||
DROP COLUMN IF EXISTS reply_to_top_id,
|
||||
DROP COLUMN IF EXISTS reply_to_peer_id,
|
||||
DROP COLUMN IF EXISTS reply_to_peer_type,
|
||||
DROP COLUMN IF EXISTS reply_to_msg_id,
|
||||
DROP COLUMN IF EXISTS noforwards,
|
||||
DROP COLUMN IF EXISTS silent;
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
-- 0021_message_reply_forward: private-message silent/reply/forward metadata.
|
||||
|
||||
ALTER TABLE private_messages
|
||||
ADD COLUMN IF NOT EXISTS silent BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN IF NOT EXISTS noforwards BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN IF NOT EXISTS reply_to_msg_id INT NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS reply_to_peer_type VARCHAR(16) NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS reply_to_peer_id BIGINT NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS reply_to_top_id INT NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS quote_text TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS quote_entities JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
ADD COLUMN IF NOT EXISTS quote_offset INT NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS fwd_from_peer_type VARCHAR(16) NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS fwd_from_peer_id BIGINT NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS fwd_from_name TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS fwd_date INT NOT NULL DEFAULT 0;
|
||||
|
||||
ALTER TABLE message_boxes
|
||||
ADD COLUMN IF NOT EXISTS silent BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN IF NOT EXISTS noforwards BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN IF NOT EXISTS reply_to_msg_id INT NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS reply_to_peer_type VARCHAR(16) NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS reply_to_peer_id BIGINT NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS reply_to_top_id INT NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS quote_text TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS quote_entities JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
ADD COLUMN IF NOT EXISTS quote_offset INT NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS fwd_from_peer_type VARCHAR(16) NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS fwd_from_peer_id BIGINT NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS fwd_from_name TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS fwd_date INT NOT NULL DEFAULT 0;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS message_boxes_reply_lookup_idx
|
||||
ON message_boxes (owner_user_id, peer_type, peer_id, box_id)
|
||||
WHERE NOT deleted;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
DROP INDEX IF EXISTS channel_messages_random_idx;
|
||||
CREATE UNIQUE INDEX channel_messages_random_idx ON public.channel_messages
|
||||
USING btree (channel_id, sender_user_id, random_id) WHERE (random_id <> 0);
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
-- 频道私信(monoforum):同一发件人(尤其频道管理员)可向不同订阅者子会话用相同 random_id 发消息,
|
||||
-- 幂等唯一性必须含 saved_peer,否则跨子会话复用 random_id 会被旧三元组唯一索引误拒/误判为重复。
|
||||
-- 普通频道消息 saved_peer_id=0 恒定,新四元组等价于原 (channel_id,sender_user_id,random_id),无行为变化。
|
||||
DROP INDEX IF EXISTS channel_messages_random_idx;
|
||||
CREATE UNIQUE INDEX channel_messages_random_idx ON public.channel_messages
|
||||
USING btree (channel_id, sender_user_id, saved_peer_id, random_id) WHERE (random_id <> 0);
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
DROP TABLE IF EXISTS channel_invites CASCADE;
|
||||
DROP TABLE IF EXISTS channel_dialogs CASCADE;
|
||||
DROP TABLE IF EXISTS channel_admin_log_events CASCADE;
|
||||
DROP TABLE IF EXISTS channel_update_events CASCADE;
|
||||
DROP TABLE IF EXISTS channel_messages CASCADE;
|
||||
DROP TABLE IF EXISTS channel_members CASCADE;
|
||||
DROP TABLE IF EXISTS channel_usernames CASCADE;
|
||||
DROP TABLE IF EXISTS channel_invite_hashes CASCADE;
|
||||
DROP TABLE IF EXISTS channels CASCADE;
|
||||
|
|
@ -1,398 +0,0 @@
|
|||
-- 0022_channels: supergroup/channel storage.
|
||||
--
|
||||
-- Channel messages are single-copy. Per-user dialog/read state is stored separately.
|
||||
-- Channel pts is scoped by channel_id and persisted in channel_update_events.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS channels (
|
||||
id BIGINT NOT NULL,
|
||||
access_hash BIGINT NOT NULL,
|
||||
creator_user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
|
||||
title TEXT NOT NULL,
|
||||
about TEXT NOT NULL DEFAULT '',
|
||||
username TEXT,
|
||||
broadcast BOOLEAN NOT NULL DEFAULT false,
|
||||
megagroup BOOLEAN NOT NULL DEFAULT false,
|
||||
forum BOOLEAN NOT NULL DEFAULT false,
|
||||
forum_tabs BOOLEAN NOT NULL DEFAULT false,
|
||||
noforwards BOOLEAN NOT NULL DEFAULT false,
|
||||
join_to_send BOOLEAN NOT NULL DEFAULT false,
|
||||
join_request BOOLEAN NOT NULL DEFAULT false,
|
||||
signatures BOOLEAN NOT NULL DEFAULT false,
|
||||
pre_history_hidden BOOLEAN NOT NULL DEFAULT false,
|
||||
participants_hidden BOOLEAN NOT NULL DEFAULT false,
|
||||
antispam BOOLEAN NOT NULL DEFAULT false,
|
||||
linked_chat_id BIGINT NOT NULL DEFAULT 0,
|
||||
slowmode_seconds INT NOT NULL DEFAULT 0,
|
||||
default_banned_rights JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
available_reactions JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
color_set BOOLEAN NOT NULL DEFAULT false,
|
||||
color INT NOT NULL DEFAULT 0,
|
||||
color_background_emoji_id BIGINT NOT NULL DEFAULT 0,
|
||||
profile_color_set BOOLEAN NOT NULL DEFAULT false,
|
||||
profile_color INT NOT NULL DEFAULT 0,
|
||||
profile_color_background_emoji_id BIGINT NOT NULL DEFAULT 0,
|
||||
emoji_status_document_id BIGINT NOT NULL DEFAULT 0,
|
||||
emoji_status_until INT NOT NULL DEFAULT 0,
|
||||
participants_count INT NOT NULL DEFAULT 0,
|
||||
admins_count INT NOT NULL DEFAULT 0,
|
||||
kicked_count INT NOT NULL DEFAULT 0,
|
||||
banned_count INT NOT NULL DEFAULT 0,
|
||||
top_message_id INT NOT NULL DEFAULT 0,
|
||||
pts INT NOT NULL DEFAULT 0,
|
||||
admin_log_seq BIGINT NOT NULL DEFAULT 0,
|
||||
ttl_period INT NOT NULL DEFAULT 0,
|
||||
date INT NOT NULL,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (id),
|
||||
CONSTRAINT channels_kind_check CHECK (
|
||||
((broadcast AND NOT megagroup AND NOT forum)
|
||||
OR (megagroup AND NOT broadcast))
|
||||
AND (NOT forum_tabs OR forum)
|
||||
),
|
||||
CONSTRAINT channels_title_nonempty_check CHECK (title <> '')
|
||||
) PARTITION BY HASH (id);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS channels_access_hash_idx
|
||||
ON channels (id, access_hash);
|
||||
CREATE INDEX IF NOT EXISTS channels_creator_idx
|
||||
ON channels (creator_user_id, id DESC)
|
||||
WHERE NOT deleted;
|
||||
CREATE INDEX IF NOT EXISTS channels_linked_chat_idx
|
||||
ON channels (linked_chat_id)
|
||||
WHERE linked_chat_id <> 0 AND NOT deleted;
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
i int;
|
||||
BEGIN
|
||||
FOR i IN 0..63 LOOP
|
||||
EXECUTE format(
|
||||
'CREATE TABLE IF NOT EXISTS channels_p%s PARTITION OF channels FOR VALUES WITH (MODULUS 64, REMAINDER %s)',
|
||||
lpad(i::text, 2, '0'),
|
||||
i
|
||||
);
|
||||
END LOOP;
|
||||
END $$;
|
||||
|
||||
-- PostgreSQL global unique indexes on partitioned tables must include the partition key.
|
||||
-- Keep username uniqueness in a compact lookup table instead of relying on channels(username).
|
||||
CREATE TABLE IF NOT EXISTS channel_usernames (
|
||||
username_lower TEXT PRIMARY KEY,
|
||||
channel_id BIGINT NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
CONSTRAINT channel_usernames_nonempty_check CHECK (username_lower <> '')
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS channel_members (
|
||||
channel_id BIGINT NOT NULL REFERENCES channels(id) ON DELETE CASCADE,
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
inviter_user_id BIGINT NOT NULL DEFAULT 0,
|
||||
role VARCHAR(16) NOT NULL DEFAULT 'member',
|
||||
status VARCHAR(16) NOT NULL DEFAULT 'active',
|
||||
joined_at INT NOT NULL DEFAULT 0,
|
||||
left_at INT NOT NULL DEFAULT 0,
|
||||
admin_rights JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
banned_rights JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
rank TEXT NOT NULL DEFAULT '',
|
||||
available_min_id INT NOT NULL DEFAULT 0,
|
||||
available_min_pts INT NOT NULL DEFAULT 0,
|
||||
read_inbox_max_id INT NOT NULL DEFAULT 0,
|
||||
read_inbox_date INT NOT NULL DEFAULT 0,
|
||||
read_outbox_max_id INT NOT NULL DEFAULT 0,
|
||||
unread_mark BOOLEAN NOT NULL DEFAULT false,
|
||||
slowmode_last_send_date INT NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (channel_id, user_id),
|
||||
CONSTRAINT channel_members_role_check CHECK (role IN ('creator', 'admin', 'member')),
|
||||
CONSTRAINT channel_members_status_check CHECK (status IN ('active', 'left', 'kicked', 'banned'))
|
||||
) PARTITION BY HASH (channel_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS channel_members_user_active_idx
|
||||
ON channel_members (user_id, channel_id)
|
||||
WHERE status = 'active';
|
||||
CREATE INDEX IF NOT EXISTS channel_members_user_left_idx
|
||||
ON channel_members (user_id, left_at DESC, channel_id DESC)
|
||||
WHERE status = 'left';
|
||||
CREATE INDEX IF NOT EXISTS channel_members_channel_role_idx
|
||||
ON channel_members (channel_id, role, user_id)
|
||||
WHERE status = 'active';
|
||||
CREATE INDEX IF NOT EXISTS channel_members_read_participants_idx
|
||||
ON channel_members (channel_id, read_inbox_max_id, user_id)
|
||||
WHERE status = 'active';
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
i int;
|
||||
BEGIN
|
||||
FOR i IN 0..63 LOOP
|
||||
EXECUTE format(
|
||||
'CREATE TABLE IF NOT EXISTS channel_members_p%s PARTITION OF channel_members FOR VALUES WITH (MODULUS 64, REMAINDER %s)',
|
||||
lpad(i::text, 2, '0'),
|
||||
i
|
||||
);
|
||||
END LOOP;
|
||||
END $$;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS channel_messages (
|
||||
channel_id BIGINT NOT NULL REFERENCES channels(id) ON DELETE CASCADE,
|
||||
id INT NOT NULL,
|
||||
random_id BIGINT NOT NULL DEFAULT 0,
|
||||
sender_user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
|
||||
from_peer_type VARCHAR(16) NOT NULL DEFAULT 'user',
|
||||
from_peer_id BIGINT NOT NULL,
|
||||
send_as_peer_type VARCHAR(16),
|
||||
send_as_peer_id BIGINT,
|
||||
message_date INT NOT NULL,
|
||||
edit_date INT NOT NULL DEFAULT 0,
|
||||
post BOOLEAN NOT NULL DEFAULT false,
|
||||
silent BOOLEAN NOT NULL DEFAULT false,
|
||||
noforwards BOOLEAN NOT NULL DEFAULT false,
|
||||
body TEXT NOT NULL DEFAULT '',
|
||||
entities JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
reply_to JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
reply_to_msg_id INT NOT NULL DEFAULT 0,
|
||||
reply_to_peer_type VARCHAR(16) NOT NULL DEFAULT '',
|
||||
reply_to_peer_id BIGINT NOT NULL DEFAULT 0,
|
||||
reply_to_top_id INT NOT NULL DEFAULT 0,
|
||||
fwd_from JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
discussion_channel_id BIGINT NOT NULL DEFAULT 0,
|
||||
discussion_message_id INT NOT NULL DEFAULT 0,
|
||||
action JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
pts INT NOT NULL,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (channel_id, id),
|
||||
CONSTRAINT channel_messages_peer_type_check CHECK (
|
||||
from_peer_type IN ('user', 'channel')
|
||||
AND (send_as_peer_type IS NULL OR send_as_peer_type IN ('user', 'channel'))
|
||||
AND (reply_to_peer_type = '' OR reply_to_peer_type IN ('user', 'channel'))
|
||||
),
|
||||
CONSTRAINT channel_messages_content_check CHECK (body <> '' OR action <> '{}'::jsonb)
|
||||
) PARTITION BY HASH (channel_id);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS channel_messages_random_idx
|
||||
ON channel_messages (channel_id, sender_user_id, random_id)
|
||||
WHERE random_id <> 0;
|
||||
CREATE INDEX IF NOT EXISTS channel_messages_history_idx
|
||||
ON channel_messages (channel_id, id DESC)
|
||||
WHERE NOT deleted;
|
||||
CREATE INDEX IF NOT EXISTS channel_messages_sender_history_idx
|
||||
ON channel_messages (channel_id, sender_user_id, id DESC)
|
||||
WHERE NOT deleted;
|
||||
CREATE INDEX IF NOT EXISTS channel_messages_date_idx
|
||||
ON channel_messages (channel_id, message_date DESC, id DESC)
|
||||
WHERE NOT deleted;
|
||||
CREATE INDEX IF NOT EXISTS channel_messages_reply_thread_idx
|
||||
ON channel_messages (channel_id, reply_to_top_id, id DESC)
|
||||
WHERE reply_to_top_id > 0 AND NOT deleted;
|
||||
CREATE INDEX IF NOT EXISTS channel_messages_discussion_ref_idx
|
||||
ON channel_messages (discussion_channel_id, discussion_message_id)
|
||||
WHERE discussion_channel_id <> 0 AND discussion_message_id <> 0 AND NOT deleted;
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
i int;
|
||||
BEGIN
|
||||
FOR i IN 0..63 LOOP
|
||||
EXECUTE format(
|
||||
'CREATE TABLE IF NOT EXISTS channel_messages_p%s PARTITION OF channel_messages FOR VALUES WITH (MODULUS 64, REMAINDER %s)',
|
||||
lpad(i::text, 2, '0'),
|
||||
i
|
||||
);
|
||||
END LOOP;
|
||||
END $$;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS channel_update_events (
|
||||
channel_id BIGINT NOT NULL REFERENCES channels(id) ON DELETE CASCADE,
|
||||
pts INT NOT NULL,
|
||||
pts_count INT NOT NULL DEFAULT 1,
|
||||
date INT NOT NULL,
|
||||
event_type VARCHAR(32) NOT NULL,
|
||||
message_id INT NOT NULL DEFAULT 0,
|
||||
message_ids JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
sender_user_id BIGINT NOT NULL DEFAULT 0,
|
||||
user_ids JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
payload JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (channel_id, pts),
|
||||
CONSTRAINT channel_update_events_pts_count_check CHECK (pts_count > 0),
|
||||
CONSTRAINT channel_update_events_type_check CHECK (
|
||||
event_type IN (
|
||||
'new_channel_message',
|
||||
'edit_channel_message',
|
||||
'delete_channel_messages',
|
||||
'channel_participant',
|
||||
'pinned_channel_messages',
|
||||
'noop'
|
||||
)
|
||||
)
|
||||
) PARTITION BY HASH (channel_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS channel_update_events_scan_idx
|
||||
ON channel_update_events (channel_id, pts ASC);
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
i int;
|
||||
BEGIN
|
||||
FOR i IN 0..63 LOOP
|
||||
EXECUTE format(
|
||||
'CREATE TABLE IF NOT EXISTS channel_update_events_p%s PARTITION OF channel_update_events FOR VALUES WITH (MODULUS 64, REMAINDER %s)',
|
||||
lpad(i::text, 2, '0'),
|
||||
i
|
||||
);
|
||||
END LOOP;
|
||||
END $$;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS channel_admin_log_events (
|
||||
channel_id BIGINT NOT NULL REFERENCES channels(id) ON DELETE CASCADE,
|
||||
id BIGINT NOT NULL,
|
||||
actor_user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
|
||||
event_date INT NOT NULL,
|
||||
event_type VARCHAR(48) NOT NULL,
|
||||
prev_string TEXT NOT NULL DEFAULT '',
|
||||
new_string TEXT NOT NULL DEFAULT '',
|
||||
prev_bool BOOLEAN NOT NULL DEFAULT false,
|
||||
new_bool BOOLEAN NOT NULL DEFAULT false,
|
||||
prev_int INT NOT NULL DEFAULT 0,
|
||||
new_int INT NOT NULL DEFAULT 0,
|
||||
prev_participant JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
new_participant JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
participant JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
message JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
prev_message JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
new_message JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
query TEXT NOT NULL DEFAULT '',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (channel_id, id),
|
||||
CONSTRAINT channel_admin_log_events_type_check CHECK (
|
||||
event_type IN (
|
||||
'change_title',
|
||||
'change_username',
|
||||
'change_linked_chat',
|
||||
'toggle_signatures',
|
||||
'toggle_pre_history_hidden',
|
||||
'toggle_forum',
|
||||
'toggle_anti_spam',
|
||||
'toggle_slow_mode',
|
||||
'participant_invite',
|
||||
'participant_join',
|
||||
'participant_leave',
|
||||
'participant_promote',
|
||||
'participant_demote',
|
||||
'participant_ban',
|
||||
'participant_unban',
|
||||
'participant_kick',
|
||||
'participant_unkick',
|
||||
'update_pinned',
|
||||
'send_message',
|
||||
'edit_message',
|
||||
'delete_message'
|
||||
)
|
||||
)
|
||||
) PARTITION BY HASH (channel_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS channel_admin_log_events_scan_idx
|
||||
ON channel_admin_log_events (channel_id, id DESC);
|
||||
CREATE INDEX IF NOT EXISTS channel_admin_log_events_actor_idx
|
||||
ON channel_admin_log_events (channel_id, actor_user_id, id DESC);
|
||||
CREATE INDEX IF NOT EXISTS channel_admin_log_events_type_idx
|
||||
ON channel_admin_log_events (channel_id, event_type, id DESC);
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
i int;
|
||||
BEGIN
|
||||
FOR i IN 0..63 LOOP
|
||||
EXECUTE format(
|
||||
'CREATE TABLE IF NOT EXISTS channel_admin_log_events_p%s PARTITION OF channel_admin_log_events FOR VALUES WITH (MODULUS 64, REMAINDER %s)',
|
||||
lpad(i::text, 2, '0'),
|
||||
i
|
||||
);
|
||||
END LOOP;
|
||||
END $$;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS channel_dialogs (
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
channel_id BIGINT NOT NULL REFERENCES channels(id) ON DELETE CASCADE,
|
||||
folder_id INT NOT NULL DEFAULT 0,
|
||||
top_message_id INT NOT NULL DEFAULT 0,
|
||||
top_message_date INT NOT NULL DEFAULT 0,
|
||||
read_inbox_max_id INT NOT NULL DEFAULT 0,
|
||||
read_outbox_max_id INT NOT NULL DEFAULT 0,
|
||||
unread_count INT NOT NULL DEFAULT 0,
|
||||
unread_mentions_count INT NOT NULL DEFAULT 0,
|
||||
unread_reactions_count INT NOT NULL DEFAULT 0,
|
||||
pinned BOOLEAN NOT NULL DEFAULT false,
|
||||
pinned_order INT NOT NULL DEFAULT 0,
|
||||
unread_mark BOOLEAN NOT NULL DEFAULT false,
|
||||
view_forum_as_messages BOOLEAN NOT NULL DEFAULT false,
|
||||
notify_settings JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (user_id, channel_id),
|
||||
CONSTRAINT channel_dialogs_folder_id_check CHECK (folder_id >= 0)
|
||||
) PARTITION BY HASH (user_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS channel_dialogs_user_top_idx
|
||||
ON channel_dialogs (user_id, folder_id, pinned DESC, pinned_order DESC, top_message_date DESC, top_message_id DESC, channel_id DESC);
|
||||
CREATE INDEX IF NOT EXISTS channel_dialogs_pinned_idx
|
||||
ON channel_dialogs (user_id, pinned)
|
||||
WHERE pinned;
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
i int;
|
||||
BEGIN
|
||||
FOR i IN 0..63 LOOP
|
||||
EXECUTE format(
|
||||
'CREATE TABLE IF NOT EXISTS channel_dialogs_p%s PARTITION OF channel_dialogs FOR VALUES WITH (MODULUS 64, REMAINDER %s)',
|
||||
lpad(i::text, 2, '0'),
|
||||
i
|
||||
);
|
||||
END LOOP;
|
||||
END $$;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS channel_invites (
|
||||
channel_id BIGINT NOT NULL REFERENCES channels(id) ON DELETE CASCADE,
|
||||
invite_id BIGINT NOT NULL,
|
||||
hash TEXT NOT NULL,
|
||||
admin_user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
|
||||
title TEXT NOT NULL DEFAULT '',
|
||||
permanent BOOLEAN NOT NULL DEFAULT false,
|
||||
revoked BOOLEAN NOT NULL DEFAULT false,
|
||||
request_needed BOOLEAN NOT NULL DEFAULT false,
|
||||
expire_date INT,
|
||||
usage_limit INT,
|
||||
usage_count INT NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (channel_id, invite_id)
|
||||
) PARTITION BY HASH (channel_id);
|
||||
|
||||
-- Keep invite hash uniqueness outside the partitioned table because PostgreSQL
|
||||
-- requires global unique indexes on partitions to include the partition key.
|
||||
CREATE TABLE IF NOT EXISTS channel_invite_hashes (
|
||||
hash TEXT PRIMARY KEY,
|
||||
channel_id BIGINT NOT NULL,
|
||||
invite_id BIGINT NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
CONSTRAINT channel_invite_hashes_nonempty_check CHECK (hash <> '')
|
||||
);
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
i int;
|
||||
BEGIN
|
||||
FOR i IN 0..63 LOOP
|
||||
EXECUTE format(
|
||||
'CREATE TABLE IF NOT EXISTS channel_invites_p%s PARTITION OF channel_invites FOR VALUES WITH (MODULUS 64, REMAINDER %s)',
|
||||
lpad(i::text, 2, '0'),
|
||||
i
|
||||
);
|
||||
END LOOP;
|
||||
END $$;
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
DROP INDEX IF EXISTS channel_invites_hash_lookup_idx;
|
||||
|
||||
ALTER TABLE channel_update_events
|
||||
DROP CONSTRAINT IF EXISTS channel_update_events_type_check;
|
||||
|
||||
ALTER TABLE channel_update_events
|
||||
ADD CONSTRAINT channel_update_events_type_check CHECK (
|
||||
event_type IN (
|
||||
'new_channel_message',
|
||||
'edit_channel_message',
|
||||
'delete_channel_messages',
|
||||
'channel_participant',
|
||||
'noop'
|
||||
)
|
||||
);
|
||||
|
||||
ALTER TABLE channels
|
||||
DROP COLUMN IF EXISTS pinned_message_id;
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
-- 0023_channel_admin_invites: metadata needed by channel admin/pin/invite RPCs.
|
||||
|
||||
ALTER TABLE channels
|
||||
ADD COLUMN IF NOT EXISTS pinned_message_id INT NOT NULL DEFAULT 0;
|
||||
|
||||
ALTER TABLE channel_update_events
|
||||
DROP CONSTRAINT IF EXISTS channel_update_events_type_check;
|
||||
|
||||
ALTER TABLE channel_update_events
|
||||
ADD CONSTRAINT channel_update_events_type_check CHECK (
|
||||
event_type IN (
|
||||
'new_channel_message',
|
||||
'edit_channel_message',
|
||||
'delete_channel_messages',
|
||||
'channel_participant',
|
||||
'pinned_channel_messages',
|
||||
'noop'
|
||||
)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS channel_invites_hash_lookup_idx
|
||||
ON channel_invite_hashes (hash, channel_id, invite_id);
|
||||
2
deploy/migrations/0023_message_effect.down.sql
Normal file
2
deploy/migrations/0023_message_effect.down.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE public.message_boxes DROP COLUMN effect;
|
||||
ALTER TABLE public.private_messages DROP COLUMN effect;
|
||||
6
deploy/migrations/0023_message_effect.up.sql
Normal file
6
deploy/migrations/0023_message_effect.up.sql
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
-- 消息特效(message effect):messages.sendMessage/sendMedia 的 effect:flags2.2?long,
|
||||
-- 私聊 1-1 专属动画特效(🎉/👍 等,发送方与接收方双向播放)。官方所有客户端仅在私聊
|
||||
-- 显示特效选择器,群/频道从不渲染,故只镜像私聊两张表(共享主体 + 每 owner 收件箱),
|
||||
-- channel_messages 不加列。镜像 via_bot_id / grouped_id 的标量列写法(bigint,0 表无特效)。
|
||||
ALTER TABLE public.private_messages ADD COLUMN effect bigint DEFAULT 0 NOT NULL;
|
||||
ALTER TABLE public.message_boxes ADD COLUMN effect bigint DEFAULT 0 NOT NULL;
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
-- No-op by design.
|
||||
--
|
||||
-- These columns/table are part of the fresh 0022 channel schema. This migration
|
||||
-- only backfills older developer databases that had already applied an earlier
|
||||
-- 0022 draft, so dropping the objects on a one-step rollback would corrupt fresh
|
||||
-- schemas where 0022 legitimately owns them.
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
-- 0024_channel_admin_log_backfill: bring existing developer DBs forward after
|
||||
-- channel settings/admin-log fields were added to the initial 0022 draft.
|
||||
--
|
||||
-- Fresh databases already get these objects from 0022; every statement here is
|
||||
-- idempotent so old local databases can migrate without reset.
|
||||
|
||||
ALTER TABLE channels
|
||||
ADD COLUMN IF NOT EXISTS pre_history_hidden BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN IF NOT EXISTS slowmode_seconds INT NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS admin_log_seq BIGINT NOT NULL DEFAULT 0;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS channel_admin_log_events (
|
||||
channel_id BIGINT NOT NULL REFERENCES channels(id) ON DELETE CASCADE,
|
||||
id BIGINT NOT NULL,
|
||||
actor_user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
|
||||
event_date INT NOT NULL,
|
||||
event_type VARCHAR(48) NOT NULL,
|
||||
prev_string TEXT NOT NULL DEFAULT '',
|
||||
new_string TEXT NOT NULL DEFAULT '',
|
||||
prev_bool BOOLEAN NOT NULL DEFAULT false,
|
||||
new_bool BOOLEAN NOT NULL DEFAULT false,
|
||||
prev_int INT NOT NULL DEFAULT 0,
|
||||
new_int INT NOT NULL DEFAULT 0,
|
||||
prev_participant JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
new_participant JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
participant JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
message JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
prev_message JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
new_message JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
query TEXT NOT NULL DEFAULT '',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (channel_id, id),
|
||||
CONSTRAINT channel_admin_log_events_type_check CHECK (
|
||||
event_type IN (
|
||||
'change_title',
|
||||
'change_username',
|
||||
'change_linked_chat',
|
||||
'toggle_signatures',
|
||||
'toggle_pre_history_hidden',
|
||||
'toggle_forum',
|
||||
'toggle_anti_spam',
|
||||
'toggle_slow_mode',
|
||||
'participant_invite',
|
||||
'participant_join',
|
||||
'participant_leave',
|
||||
'participant_promote',
|
||||
'participant_demote',
|
||||
'participant_ban',
|
||||
'participant_unban',
|
||||
'participant_kick',
|
||||
'participant_unkick',
|
||||
'update_pinned',
|
||||
'send_message',
|
||||
'edit_message',
|
||||
'delete_message'
|
||||
)
|
||||
)
|
||||
) PARTITION BY HASH (channel_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS channel_admin_log_events_scan_idx
|
||||
ON channel_admin_log_events (channel_id, id DESC);
|
||||
CREATE INDEX IF NOT EXISTS channel_admin_log_events_actor_idx
|
||||
ON channel_admin_log_events (channel_id, actor_user_id, id DESC);
|
||||
CREATE INDEX IF NOT EXISTS channel_admin_log_events_type_idx
|
||||
ON channel_admin_log_events (channel_id, event_type, id DESC);
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
i int;
|
||||
BEGIN
|
||||
FOR i IN 0..63 LOOP
|
||||
EXECUTE format(
|
||||
'CREATE TABLE IF NOT EXISTS channel_admin_log_events_p%s PARTITION OF channel_admin_log_events FOR VALUES WITH (MODULUS 64, REMAINDER %s)',
|
||||
lpad(i::text, 2, '0'),
|
||||
i
|
||||
);
|
||||
END LOOP;
|
||||
END $$;
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
ALTER TABLE public.users
|
||||
DROP COLUMN personal_channel_id,
|
||||
DROP COLUMN birthday_year,
|
||||
DROP COLUMN birthday_month,
|
||||
DROP COLUMN birthday_day;
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
-- account.updateBirthday / account.updatePersonalChannel:个人资料里的「生日」与「个人频道」。
|
||||
-- 生日存月/日/年(全 0 表示未设置;year=0 表示只填月日不含年份);个人频道存目标频道 id
|
||||
-- (0 表示未设置/已清除),资料投影时按 id 取频道对象与其最新一帖 id。
|
||||
ALTER TABLE public.users
|
||||
ADD COLUMN birthday_day integer DEFAULT 0 NOT NULL,
|
||||
ADD COLUMN birthday_month integer DEFAULT 0 NOT NULL,
|
||||
ADD COLUMN birthday_year integer DEFAULT 0 NOT NULL,
|
||||
ADD COLUMN personal_channel_id bigint DEFAULT 0 NOT NULL;
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
-- No-op by design.
|
||||
--
|
||||
-- Fresh databases own this column from 0022; this migration only repairs older
|
||||
-- developer databases that had already applied an earlier 0022 draft.
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
-- 0025_channel_member_slowmode_backfill: idempotent compatibility migration for
|
||||
-- developer databases that applied an earlier 0022 channel schema draft.
|
||||
|
||||
ALTER TABLE channel_members
|
||||
ADD COLUMN IF NOT EXISTS slowmode_last_send_date INT NOT NULL DEFAULT 0;
|
||||
20
deploy/migrations/0025_peer_usernames.down.sql
Normal file
20
deploy/migrations/0025_peer_usernames.down.sql
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
CREATE TABLE IF NOT EXISTS public.channel_usernames (
|
||||
username_lower text NOT NULL,
|
||||
channel_id bigint NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT channel_usernames_nonempty_check CHECK (username_lower <> '')
|
||||
);
|
||||
|
||||
ALTER TABLE ONLY public.channel_usernames
|
||||
ADD CONSTRAINT channel_usernames_pkey PRIMARY KEY (username_lower);
|
||||
|
||||
INSERT INTO public.channel_usernames (username_lower, channel_id)
|
||||
SELECT username_lower, peer_id
|
||||
FROM public.peer_usernames
|
||||
WHERE peer_type = 'channel';
|
||||
|
||||
DROP TRIGGER IF EXISTS users_delete_peer_username ON public.users;
|
||||
DROP TRIGGER IF EXISTS channels_delete_peer_username ON public.channels;
|
||||
DROP FUNCTION IF EXISTS public.delete_user_peer_username();
|
||||
DROP FUNCTION IF EXISTS public.delete_channel_peer_username();
|
||||
DROP TABLE IF EXISTS public.peer_usernames;
|
||||
51
deploy/migrations/0025_peer_usernames.up.sql
Normal file
51
deploy/migrations/0025_peer_usernames.up.sql
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
CREATE TABLE IF NOT EXISTS public.peer_usernames (
|
||||
username_lower text NOT NULL,
|
||||
peer_type text NOT NULL,
|
||||
peer_id bigint NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT peer_usernames_pkey PRIMARY KEY (username_lower),
|
||||
CONSTRAINT peer_usernames_nonempty_check CHECK (username_lower <> ''),
|
||||
CONSTRAINT peer_usernames_peer_id_check CHECK (peer_id <> 0),
|
||||
CONSTRAINT peer_usernames_peer_type_check CHECK (peer_type IN ('user', 'channel'))
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS peer_usernames_peer_unique_idx
|
||||
ON public.peer_usernames (peer_type, peer_id);
|
||||
|
||||
INSERT INTO public.peer_usernames (username_lower, peer_type, peer_id)
|
||||
SELECT lower(username), 'user', id
|
||||
FROM public.users
|
||||
WHERE username <> '';
|
||||
|
||||
INSERT INTO public.peer_usernames (username_lower, peer_type, peer_id)
|
||||
SELECT lower(username), 'channel', id
|
||||
FROM public.channels
|
||||
WHERE NOT deleted AND COALESCE(username, '') <> '';
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.delete_user_peer_username() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
DELETE FROM public.peer_usernames WHERE peer_type = 'user' AND peer_id = OLD.id;
|
||||
RETURN OLD;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.delete_channel_peer_username() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
DELETE FROM public.peer_usernames WHERE peer_type = 'channel' AND peer_id = OLD.id;
|
||||
RETURN OLD;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER users_delete_peer_username
|
||||
AFTER DELETE ON public.users
|
||||
FOR EACH ROW EXECUTE FUNCTION public.delete_user_peer_username();
|
||||
|
||||
CREATE TRIGGER channels_delete_peer_username
|
||||
AFTER DELETE ON public.channels
|
||||
FOR EACH ROW EXECUTE FUNCTION public.delete_channel_peer_username();
|
||||
|
||||
DROP TABLE IF EXISTS public.channel_usernames;
|
||||
|
|
@ -1 +0,0 @@
|
|||
-- No-op: read receipt dates and TDesktop app config keys are forward-compatible.
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue