Initial open source release
This commit is contained in:
commit
74992e893f
377 changed files with 118084 additions and 0 deletions
10
deploy/deploy.go
Normal file
10
deploy/deploy.go
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// Package deploy 提供部署期资源(迁移脚本)的嵌入访问。
|
||||
package deploy
|
||||
|
||||
import "embed"
|
||||
|
||||
// Migrations 是嵌入的 golang-migrate 迁移脚本(deploy/migrations/*.sql)。
|
||||
// 供 store/postgres 的迁移 runner 在启动时执行,避免运行时依赖外部文件路径。
|
||||
//
|
||||
//go:embed migrations/*.sql
|
||||
var Migrations embed.FS
|
||||
48
deploy/docker-compose.yml
Normal file
48
deploy/docker-compose.yml
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# telesrv 第一阶段三方依赖(开发用)。
|
||||
#
|
||||
# 启动: docker compose up -d
|
||||
# 状态: docker compose ps
|
||||
# 关停: docker compose down (保留数据卷)
|
||||
# 清空: docker compose down -v (连数据卷一起删,重置 schema/数据)
|
||||
#
|
||||
# server 端连接串见 internal/config/config.go 的默认值(localhost:5432 / localhost:6399)。
|
||||
name: telesrv
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:17-alpine
|
||||
container_name: telesrv-postgres
|
||||
environment:
|
||||
POSTGRES_DB: telesrv
|
||||
POSTGRES_USER: telesrv
|
||||
POSTGRES_PASSWORD: telesrv
|
||||
TZ: UTC
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U telesrv -d telesrv"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
restart: unless-stopped
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: telesrv-redis
|
||||
command: ["redis-server", "--appendonly", "yes"]
|
||||
ports:
|
||||
- "6399:6379" # 宿主 6379 常被其他项目占用,telesrv 对外用 6399(容器内仍 6379)
|
||||
volumes:
|
||||
- redisdata:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
redisdata:
|
||||
4
deploy/migrations/0001_init.down.sql
Normal file
4
deploy/migrations/0001_init.down.sql
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
-- 0001_init 回滚:按外键依赖逆序删表。
|
||||
DROP TABLE IF EXISTS authorizations;
|
||||
DROP TABLE IF EXISTS users;
|
||||
DROP TABLE IF EXISTS auth_keys;
|
||||
48
deploy/migrations/0001_init.up.sql
Normal file
48
deploy/migrations/0001_init.up.sql
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
-- 0001_init: telesrv 第一阶段持久化 schema(auth_key / user / authorization)。
|
||||
--
|
||||
-- 协议产物(auth_keys)与业务产物(authorizations)分表,遵循协议/业务隔离边界。
|
||||
|
||||
-- MTProto auth key:密钥交换产物。server 重启后据此解密客户端加密包,避免客户端重建密钥。
|
||||
CREATE TABLE IF NOT EXISTS auth_keys (
|
||||
auth_key_id BIGINT PRIMARY KEY, -- SHA1(auth_key) 低 64 位,小端解释为 int64
|
||||
body BYTEA NOT NULL, -- 256 字节 auth key 原文
|
||||
server_salt BIGINT NOT NULL, -- 密钥交换产出的初始 server salt
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- 用户:第一阶段仅登录链路必须字段。access_hash 为任何 InputUser 校验所必须,不可省。
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
-- 普通用户 ID 从 2026-06-01 00:00:00 Asia/Shanghai 的 Unix 秒级时间戳开始。
|
||||
-- 777000 等系统兼容账号显式插入,低于该区间。
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1780243200) PRIMARY KEY,
|
||||
access_hash BIGINT NOT NULL,
|
||||
phone VARCHAR(32) NOT NULL,
|
||||
first_name VARCHAR(64) NOT NULL DEFAULT '',
|
||||
last_name VARCHAR(64) NOT NULL DEFAULT '',
|
||||
username VARCHAR(64) NOT NULL DEFAULT '',
|
||||
country_code VARCHAR(8) NOT NULL DEFAULT '',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
CONSTRAINT users_phone_key UNIQUE (phone)
|
||||
);
|
||||
|
||||
-- 授权:auth_key ↔ user 绑定 + initConnection 带来的设备信息。
|
||||
-- 这是 auth.signIn 后绑定关系与 account.getAuthorizations 的权威来源;
|
||||
-- 也是 rpc.ClientInfo(device/app/layer)的持久化归宿。
|
||||
CREATE TABLE IF NOT EXISTS authorizations (
|
||||
auth_key_id BIGINT PRIMARY KEY REFERENCES auth_keys(auth_key_id) ON DELETE CASCADE,
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
hash BIGINT NOT NULL DEFAULT 0, -- getAuthorizations 列表项 hash
|
||||
layer INT NOT NULL DEFAULT 0,
|
||||
device_model VARCHAR(128) NOT NULL DEFAULT '',
|
||||
platform VARCHAR(64) NOT NULL DEFAULT '',
|
||||
system_version VARCHAR(64) NOT NULL DEFAULT '',
|
||||
api_id INT NOT NULL DEFAULT 0,
|
||||
app_version VARCHAR(64) NOT NULL DEFAULT '',
|
||||
ip VARCHAR(64) NOT NULL DEFAULT '',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
active_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- 按 user 反查授权(PushToUser / getAuthorizations)。
|
||||
CREATE INDEX IF NOT EXISTS authorizations_user_id_idx ON authorizations (user_id);
|
||||
5
deploy/migrations/0002_phase1_business.down.sql
Normal file
5
deploy/migrations/0002_phase1_business.down.sql
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
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;
|
||||
73
deploy/migrations/0002_phase1_business.up.sql
Normal file
73
deploy/migrations/0002_phase1_business.up.sql
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
-- 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);
|
||||
5
deploy/migrations/0003_startup_config_security.down.sql
Normal file
5
deploy/migrations/0003_startup_config_security.down.sql
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
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;
|
||||
64
deploy/migrations/0003_startup_config_security.up.sql
Normal file
64
deploy/migrations/0003_startup_config_security.up.sql
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
-- 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,2 @@
|
|||
ALTER TABLE temp_auth_key_bindings
|
||||
DROP COLUMN IF EXISTS temp_session_id;
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
-- 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;
|
||||
11
deploy/migrations/0005_system_login_messages.down.sql
Normal file
11
deploy/migrations/0005_system_login_messages.down.sql
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
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;
|
||||
44
deploy/migrations/0005_system_login_messages.up.sql
Normal file
44
deploy/migrations/0005_system_login_messages.up.sql
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
-- 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);
|
||||
2
deploy/migrations/0006_update_events.down.sql
Normal file
2
deploy/migrations/0006_update_events.down.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
DROP INDEX IF EXISTS update_events_auth_pts_idx;
|
||||
DROP TABLE IF EXISTS update_events;
|
||||
16
deploy/migrations/0006_update_events.up.sql
Normal file
16
deploy/migrations/0006_update_events.up.sql
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
-- 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);
|
||||
15
deploy/migrations/0007_read_history_events.down.sql
Normal file
15
deploy/migrations/0007_read_history_events.down.sql
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
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;
|
||||
23
deploy/migrations/0007_read_history_events.up.sql
Normal file
23
deploy/migrations/0007_read_history_events.up.sql
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
-- 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'));
|
||||
8
deploy/migrations/0008_user_id_sequence_base.down.sql
Normal file
8
deploy/migrations/0008_user_id_sequence_base.down.sql
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
-- 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
|
||||
);
|
||||
10
deploy/migrations/0008_user_id_sequence_base.up.sql
Normal file
10
deploy/migrations/0008_user_id_sequence_base.up.sql
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
-- 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
|
||||
);
|
||||
13
deploy/migrations/0009_private_message_pipeline.down.sql
Normal file
13
deploy/migrations/0009_private_message_pipeline.down.sql
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
-- 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);
|
||||
239
deploy/migrations/0009_private_message_pipeline.up.sql
Normal file
239
deploy/migrations/0009_private_message_pipeline.up.sql
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
-- 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;
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
DROP INDEX IF EXISTS dispatch_outbox_dispatching_stale_idx;
|
||||
DROP INDEX IF EXISTS message_boxes_dialog_date_seek_idx;
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
-- 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';
|
||||
6
deploy/migrations/0011_drop_dead_tables.down.sql
Normal file
6
deploy/migrations/0011_drop_dead_tables.down.sql
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
-- 0011 down (no-op): 本迁移清除的是已被取代、运行时零引用的死表
|
||||
-- (update_events / messages_legacy / dialogs_legacy)。
|
||||
--
|
||||
-- 全新项目明确不保留回退路径,故不在此重建这些表;历史结构可查 0002 / 0005 / 0006 / 0007 迁移脚本(保留未删)。
|
||||
-- golang-migrate 执行本文件即把版本回退到 0010,不恢复任何死表或其数据。
|
||||
SELECT 1;
|
||||
13
deploy/migrations/0011_drop_dead_tables.up.sql
Normal file
13
deploy/migrations/0011_drop_dead_tables.up.sql
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
-- 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;
|
||||
5
deploy/migrations/0012_outbox_delete_on_deliver.down.sql
Normal file
5
deploy/migrations/0012_outbox_delete_on_deliver.down.sql
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
-- 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'));
|
||||
12
deploy/migrations/0012_outbox_delete_on_deliver.up.sql
Normal file
12
deploy/migrations/0012_outbox_delete_on_deliver.up.sql
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
-- 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,17 @@
|
|||
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;
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
-- 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;
|
||||
10
deploy/migrations/0014_settings_update_events.down.sql
Normal file
10
deploy/migrations/0014_settings_update_events.down.sql
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
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;
|
||||
25
deploy/migrations/0014_settings_update_events.up.sql
Normal file
25
deploy/migrations/0014_settings_update_events.up.sql
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
-- 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'
|
||||
)
|
||||
);
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
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;
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
-- 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;
|
||||
21
deploy/migrations/0016_delete_message_updates.down.sql
Normal file
21
deploy/migrations/0016_delete_message_updates.down.sql
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
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;
|
||||
29
deploy/migrations/0016_delete_message_updates.up.sql
Normal file
29
deploy/migrations/0016_delete_message_updates.up.sql
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
-- 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;
|
||||
46
deploy/migrations/0017_dialog_folders.down.sql
Normal file
46
deploy/migrations/0017_dialog_folders.down.sql
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
-- 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;
|
||||
91
deploy/migrations/0017_dialog_folders.up.sql
Normal file
91
deploy/migrations/0017_dialog_folders.up.sql
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
-- 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'
|
||||
)
|
||||
);
|
||||
5
deploy/migrations/0018_user_search_indexes.down.sql
Normal file
5
deploy/migrations/0018_user_search_indexes.down.sql
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
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;
|
||||
19
deploy/migrations/0018_user_search_indexes.up.sql
Normal file
19
deploy/migrations/0018_user_search_indexes.up.sql
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
-- 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 <> '';
|
||||
1
deploy/migrations/0019_usernames.down.sql
Normal file
1
deploy/migrations/0019_usernames.down.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
DROP INDEX IF EXISTS users_username_lower_unique_idx;
|
||||
5
deploy/migrations/0019_usernames.up.sql
Normal file
5
deploy/migrations/0019_usernames.up.sql
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
-- 0019_usernames: primary username lifecycle.
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS users_username_lower_unique_idx
|
||||
ON users (lower(username))
|
||||
WHERE username <> '';
|
||||
34
deploy/migrations/0020_profile_message_state.down.sql
Normal file
34
deploy/migrations/0020_profile_message_state.down.sql
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
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;
|
||||
46
deploy/migrations/0020_profile_message_state.up.sql
Normal file
46
deploy/migrations/0020_profile_message_state.up.sql
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
-- 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'
|
||||
)
|
||||
);
|
||||
31
deploy/migrations/0021_message_reply_forward.down.sql
Normal file
31
deploy/migrations/0021_message_reply_forward.down.sql
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
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;
|
||||
35
deploy/migrations/0021_message_reply_forward.up.sql
Normal file
35
deploy/migrations/0021_message_reply_forward.up.sql
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
-- 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;
|
||||
9
deploy/migrations/0022_channels.down.sql
Normal file
9
deploy/migrations/0022_channels.down.sql
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
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;
|
||||
398
deploy/migrations/0022_channels.up.sql
Normal file
398
deploy/migrations/0022_channels.up.sql
Normal file
|
|
@ -0,0 +1,398 @@
|
|||
-- 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 $$;
|
||||
18
deploy/migrations/0023_channel_admin_invites.down.sql
Normal file
18
deploy/migrations/0023_channel_admin_invites.down.sql
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
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;
|
||||
22
deploy/migrations/0023_channel_admin_invites.up.sql
Normal file
22
deploy/migrations/0023_channel_admin_invites.up.sql
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
-- 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);
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
-- 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.
|
||||
78
deploy/migrations/0024_channel_admin_log_backfill.up.sql
Normal file
78
deploy/migrations/0024_channel_admin_log_backfill.up.sql
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
-- 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,4 @@
|
|||
-- 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.
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
-- 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;
|
||||
|
|
@ -0,0 +1 @@
|
|||
-- No-op: read receipt dates and TDesktop app config keys are forward-compatible.
|
||||
19
deploy/migrations/0026_channel_read_participants.up.sql
Normal file
19
deploy/migrations/0026_channel_read_participants.up.sql
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
-- 0026_channel_read_participants: store channel read receipt dates and expose TDesktop read mark config.
|
||||
|
||||
ALTER TABLE channel_members
|
||||
ADD COLUMN IF NOT EXISTS read_inbox_date INT NOT NULL DEFAULT 0;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS channel_members_read_participants_idx
|
||||
ON channel_members (channel_id, read_inbox_max_id, user_id)
|
||||
WHERE status = 'active';
|
||||
|
||||
UPDATE app_configs
|
||||
SET hash = 2,
|
||||
config_json = config_json
|
||||
|| jsonb_build_object(
|
||||
'chat_read_mark_size_threshold', 50,
|
||||
'chat_read_mark_expire_period', 604800,
|
||||
'pm_read_date_expire_period', 604800
|
||||
),
|
||||
updated_at = now()
|
||||
WHERE client = 'tdesktop';
|
||||
|
|
@ -0,0 +1 @@
|
|||
DROP INDEX IF EXISTS channel_messages_body_trgm_idx;
|
||||
10
deploy/migrations/0027_channel_message_search_indexes.up.sql
Normal file
10
deploy/migrations/0027_channel_message_search_indexes.up.sql
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
-- 0027_channel_message_search_indexes: bounded in-channel text search.
|
||||
--
|
||||
-- TDesktop uses messages.search with inputPeerChannel for in-chat search.
|
||||
-- Keep text lookup indexed on the single-copy channel_messages table.
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS channel_messages_body_trgm_idx
|
||||
ON channel_messages USING gin (body gin_trgm_ops)
|
||||
WHERE NOT deleted AND body <> '';
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
-- 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.
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
-- 0028_channel_noforwards_backfill: ensure older developer databases have
|
||||
-- the channel content-protection flag used by messages.toggleNoForwards.
|
||||
--
|
||||
-- Fresh databases already get this column from 0022.
|
||||
|
||||
ALTER TABLE channels
|
||||
ADD COLUMN IF NOT EXISTS noforwards BOOLEAN NOT NULL DEFAULT false;
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
-- 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.
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
-- 0029_channel_available_reactions: persisted reaction policy for
|
||||
-- messages.setChatAvailableReactions and channels.getFullChannel.
|
||||
--
|
||||
-- Fresh databases already get this column from 0022.
|
||||
|
||||
ALTER TABLE channels
|
||||
ADD COLUMN IF NOT EXISTS available_reactions JSONB NOT NULL DEFAULT '{}'::jsonb;
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
-- 0030_user_update_channel_peers rollback.
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
DROP CONSTRAINT IF EXISTS user_update_events_peer_type_check;
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
ADD CONSTRAINT user_update_events_peer_type_check
|
||||
CHECK (peer_type IS NULL OR peer_type IN ('user'));
|
||||
8
deploy/migrations/0030_user_update_channel_peers.up.sql
Normal file
8
deploy/migrations/0030_user_update_channel_peers.up.sql
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
-- 0030_user_update_channel_peers: allow account-level update events to reference channel peers.
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
DROP CONSTRAINT IF EXISTS user_update_events_peer_type_check;
|
||||
|
||||
ALTER TABLE user_update_events
|
||||
ADD CONSTRAINT user_update_events_peer_type_check
|
||||
CHECK (peer_type IS NULL OR peer_type IN ('user', 'channel'));
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE channel_members
|
||||
DROP COLUMN IF EXISTS available_min_pts;
|
||||
4
deploy/migrations/0031_channel_available_min_pts.up.sql
Normal file
4
deploy/migrations/0031_channel_available_min_pts.up.sql
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
-- 0031_channel_available_min_pts: channelDifference visibility floor per member.
|
||||
|
||||
ALTER TABLE channel_members
|
||||
ADD COLUMN IF NOT EXISTS available_min_pts INT NOT NULL DEFAULT 0;
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
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'
|
||||
)
|
||||
);
|
||||
26
deploy/migrations/0032_user_update_channel_available.up.sql
Normal file
26
deploy/migrations/0032_user_update_channel_available.up.sql
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
-- 0032_user_update_channel_available: durable account update for channel local clear.
|
||||
|
||||
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',
|
||||
'channel_available_messages',
|
||||
'noop'
|
||||
)
|
||||
);
|
||||
7
deploy/migrations/0033_app_config_quote_length.down.sql
Normal file
7
deploy/migrations/0033_app_config_quote_length.down.sql
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
-- 0033_app_config_quote_length rollback.
|
||||
|
||||
UPDATE app_configs
|
||||
SET hash = 2,
|
||||
config_json = config_json - 'quote_length_max',
|
||||
updated_at = now()
|
||||
WHERE client = 'tdesktop';
|
||||
7
deploy/migrations/0033_app_config_quote_length.up.sql
Normal file
7
deploy/migrations/0033_app_config_quote_length.up.sql
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
-- 0033_app_config_quote_length: expose TDesktop quote reply length limit explicitly.
|
||||
|
||||
UPDATE app_configs
|
||||
SET hash = 3,
|
||||
config_json = config_json || jsonb_build_object('quote_length_max', 1024),
|
||||
updated_at = now()
|
||||
WHERE client = 'tdesktop';
|
||||
6
deploy/migrations/0034_channel_invite_importers.down.sql
Normal file
6
deploy/migrations/0034_channel_invite_importers.down.sql
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
-- 0034_channel_invite_importers rollback.
|
||||
|
||||
DROP TABLE IF EXISTS channel_invite_importers;
|
||||
|
||||
ALTER TABLE channel_invites
|
||||
DROP COLUMN IF EXISTS requested_count;
|
||||
37
deploy/migrations/0034_channel_invite_importers.up.sql
Normal file
37
deploy/migrations/0034_channel_invite_importers.up.sql
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
-- 0034_channel_invite_importers: invite management read model.
|
||||
|
||||
ALTER TABLE channel_invites
|
||||
ADD COLUMN IF NOT EXISTS requested_count INT NOT NULL DEFAULT 0;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS channel_invite_importers (
|
||||
channel_id BIGINT NOT NULL REFERENCES channels(id) ON DELETE CASCADE,
|
||||
invite_id BIGINT NOT NULL,
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
date INT NOT NULL,
|
||||
requested BOOLEAN NOT NULL DEFAULT false,
|
||||
approved_by BIGINT NOT NULL DEFAULT 0,
|
||||
via_chatlist BOOLEAN NOT NULL DEFAULT false,
|
||||
about TEXT NOT NULL DEFAULT '',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (channel_id, user_id)
|
||||
) PARTITION BY HASH (channel_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS channel_invite_importers_link_idx
|
||||
ON channel_invite_importers (channel_id, invite_id, requested, date DESC, user_id DESC);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS channel_invite_importers_requested_idx
|
||||
ON channel_invite_importers (channel_id, requested, date DESC, user_id DESC);
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
i int;
|
||||
BEGIN
|
||||
FOR i IN 0..63 LOOP
|
||||
EXECUTE format(
|
||||
'CREATE TABLE IF NOT EXISTS channel_invite_importers_p%s PARTITION OF channel_invite_importers FOR VALUES WITH (MODULUS 64, REMAINDER %s)',
|
||||
lpad(i::text, 2, '0'),
|
||||
i
|
||||
);
|
||||
END LOOP;
|
||||
END $$;
|
||||
5
deploy/migrations/0035_channel_join_settings.down.sql
Normal file
5
deploy/migrations/0035_channel_join_settings.down.sql
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
-- 0035_channel_join_settings rollback.
|
||||
|
||||
ALTER TABLE channels
|
||||
DROP COLUMN IF EXISTS join_request,
|
||||
DROP COLUMN IF EXISTS join_to_send;
|
||||
5
deploy/migrations/0035_channel_join_settings.up.sql
Normal file
5
deploy/migrations/0035_channel_join_settings.up.sql
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
-- 0035_channel_join_settings: public join/send gating flags.
|
||||
|
||||
ALTER TABLE channels
|
||||
ADD COLUMN IF NOT EXISTS join_to_send BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN IF NOT EXISTS join_request BOOLEAN NOT NULL DEFAULT false;
|
||||
4
deploy/migrations/0036_channel_discussion_links.down.sql
Normal file
4
deploy/migrations/0036_channel_discussion_links.down.sql
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
DROP INDEX IF EXISTS channels_linked_chat_idx;
|
||||
|
||||
ALTER TABLE channels
|
||||
DROP COLUMN IF EXISTS linked_chat_id;
|
||||
8
deploy/migrations/0036_channel_discussion_links.up.sql
Normal file
8
deploy/migrations/0036_channel_discussion_links.up.sql
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
-- 0036_channel_discussion_links: persist bidirectional broadcast <-> discussion group links.
|
||||
|
||||
ALTER TABLE channels
|
||||
ADD COLUMN IF NOT EXISTS linked_chat_id BIGINT NOT NULL DEFAULT 0;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS channels_linked_chat_idx
|
||||
ON channels (linked_chat_id)
|
||||
WHERE linked_chat_id <> 0 AND NOT deleted;
|
||||
10
deploy/migrations/0037_channel_message_threads.down.sql
Normal file
10
deploy/migrations/0037_channel_message_threads.down.sql
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
DROP INDEX IF EXISTS channel_messages_discussion_ref_idx;
|
||||
DROP INDEX IF EXISTS channel_messages_reply_thread_idx;
|
||||
|
||||
ALTER TABLE channel_messages
|
||||
DROP COLUMN IF EXISTS discussion_message_id,
|
||||
DROP COLUMN IF EXISTS discussion_channel_id,
|
||||
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;
|
||||
36
deploy/migrations/0037_channel_message_threads.up.sql
Normal file
36
deploy/migrations/0037_channel_message_threads.up.sql
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
ALTER TABLE channel_messages
|
||||
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 discussion_channel_id BIGINT NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS discussion_message_id INT NOT NULL DEFAULT 0;
|
||||
|
||||
UPDATE channel_messages
|
||||
SET
|
||||
reply_to_msg_id = CASE
|
||||
WHEN (reply_to ->> 'MessageID') ~ '^[0-9]+$' THEN (reply_to ->> 'MessageID')::INT
|
||||
ELSE reply_to_msg_id
|
||||
END,
|
||||
reply_to_peer_type = COALESCE(NULLIF(reply_to #>> '{Peer,Type}', ''), reply_to_peer_type),
|
||||
reply_to_peer_id = CASE
|
||||
WHEN (reply_to #>> '{Peer,ID}') ~ '^[0-9]+$' THEN (reply_to #>> '{Peer,ID}')::BIGINT
|
||||
ELSE reply_to_peer_id
|
||||
END,
|
||||
reply_to_top_id = CASE
|
||||
WHEN (reply_to ->> 'TopMessageID') ~ '^[0-9]+$' AND (reply_to ->> 'TopMessageID')::INT > 0
|
||||
THEN (reply_to ->> 'TopMessageID')::INT
|
||||
WHEN (reply_to ->> 'MessageID') ~ '^[0-9]+$'
|
||||
THEN (reply_to ->> 'MessageID')::INT
|
||||
ELSE reply_to_top_id
|
||||
END
|
||||
WHERE reply_to <> '{}'::jsonb
|
||||
AND reply_to_msg_id = 0;
|
||||
|
||||
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;
|
||||
6
deploy/migrations/0038_channel_default_send_as.down.sql
Normal file
6
deploy/migrations/0038_channel_default_send_as.down.sql
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
ALTER TABLE channel_dialogs
|
||||
DROP CONSTRAINT IF EXISTS channel_dialogs_default_send_as_peer_type_check;
|
||||
|
||||
ALTER TABLE channel_dialogs
|
||||
DROP COLUMN IF EXISTS default_send_as_peer_id,
|
||||
DROP COLUMN IF EXISTS default_send_as_peer_type;
|
||||
13
deploy/migrations/0038_channel_default_send_as.up.sql
Normal file
13
deploy/migrations/0038_channel_default_send_as.up.sql
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
ALTER TABLE channel_dialogs
|
||||
ADD COLUMN IF NOT EXISTS default_send_as_peer_type VARCHAR(16),
|
||||
ADD COLUMN IF NOT EXISTS default_send_as_peer_id BIGINT;
|
||||
|
||||
ALTER TABLE channel_dialogs
|
||||
DROP CONSTRAINT IF EXISTS channel_dialogs_default_send_as_peer_type_check;
|
||||
|
||||
ALTER TABLE channel_dialogs
|
||||
ADD CONSTRAINT channel_dialogs_default_send_as_peer_type_check
|
||||
CHECK (
|
||||
default_send_as_peer_type IS NULL
|
||||
OR default_send_as_peer_type IN ('user', 'channel')
|
||||
);
|
||||
4
deploy/migrations/0039_channel_message_views.down.sql
Normal file
4
deploy/migrations/0039_channel_message_views.down.sql
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
DROP TABLE IF EXISTS channel_message_viewers CASCADE;
|
||||
|
||||
ALTER TABLE channel_messages
|
||||
DROP COLUMN IF EXISTS views_count;
|
||||
27
deploy/migrations/0039_channel_message_views.up.sql
Normal file
27
deploy/migrations/0039_channel_message_views.up.sql
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
ALTER TABLE channel_messages
|
||||
ADD COLUMN IF NOT EXISTS views_count INT NOT NULL DEFAULT 0;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS channel_message_viewers (
|
||||
channel_id BIGINT NOT NULL,
|
||||
message_id INT NOT NULL,
|
||||
viewer_user_id BIGINT NOT NULL,
|
||||
viewed_at INT NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (channel_id, message_id, viewer_user_id),
|
||||
CONSTRAINT channel_message_viewers_positive_check CHECK (
|
||||
channel_id > 0 AND message_id > 0 AND viewer_user_id > 0
|
||||
)
|
||||
) PARTITION BY HASH (channel_id);
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
i int;
|
||||
BEGIN
|
||||
FOR i IN 0..63 LOOP
|
||||
EXECUTE format(
|
||||
'CREATE TABLE IF NOT EXISTS channel_message_viewers_p%s PARTITION OF channel_message_viewers FOR VALUES WITH (MODULUS 64, REMAINDER %s)',
|
||||
lpad(i::text, 2, '0'),
|
||||
i
|
||||
);
|
||||
END LOOP;
|
||||
END $$;
|
||||
1
deploy/migrations/0040_channel_unread_mentions.down.sql
Normal file
1
deploy/migrations/0040_channel_unread_mentions.down.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS channel_unread_mentions CASCADE;
|
||||
25
deploy/migrations/0040_channel_unread_mentions.up.sql
Normal file
25
deploy/migrations/0040_channel_unread_mentions.up.sql
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
CREATE TABLE IF NOT EXISTS channel_unread_mentions (
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
channel_id BIGINT NOT NULL REFERENCES channels(id) ON DELETE CASCADE,
|
||||
message_id INT NOT NULL,
|
||||
top_message_id INT NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (user_id, channel_id, message_id),
|
||||
FOREIGN KEY (channel_id, message_id) REFERENCES channel_messages(channel_id, id) ON DELETE CASCADE
|
||||
) PARTITION BY HASH (user_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS channel_unread_mentions_peer_idx
|
||||
ON channel_unread_mentions (user_id, channel_id, top_message_id, message_id DESC);
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
i int;
|
||||
BEGIN
|
||||
FOR i IN 0..63 LOOP
|
||||
EXECUTE format(
|
||||
'CREATE TABLE IF NOT EXISTS channel_unread_mentions_p%s PARTITION OF channel_unread_mentions FOR VALUES WITH (MODULUS 64, REMAINDER %s)',
|
||||
lpad(i::text, 2, '0'),
|
||||
i
|
||||
);
|
||||
END LOOP;
|
||||
END $$;
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE channels
|
||||
DROP COLUMN IF EXISTS participants_hidden;
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE channels
|
||||
ADD COLUMN IF NOT EXISTS participants_hidden BOOLEAN NOT NULL DEFAULT false;
|
||||
9
deploy/migrations/0042_channel_colors.down.sql
Normal file
9
deploy/migrations/0042_channel_colors.down.sql
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
ALTER TABLE channels
|
||||
DROP COLUMN IF EXISTS emoji_status_until,
|
||||
DROP COLUMN IF EXISTS emoji_status_document_id,
|
||||
DROP COLUMN IF EXISTS profile_color_background_emoji_id,
|
||||
DROP COLUMN IF EXISTS profile_color,
|
||||
DROP COLUMN IF EXISTS profile_color_set,
|
||||
DROP COLUMN IF EXISTS color_background_emoji_id,
|
||||
DROP COLUMN IF EXISTS color,
|
||||
DROP COLUMN IF EXISTS color_set;
|
||||
9
deploy/migrations/0042_channel_colors.up.sql
Normal file
9
deploy/migrations/0042_channel_colors.up.sql
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
ALTER TABLE channels
|
||||
ADD COLUMN IF NOT EXISTS color_set BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN IF NOT EXISTS color INT NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS color_background_emoji_id BIGINT NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS profile_color_set BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN IF NOT EXISTS profile_color INT NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS profile_color_background_emoji_id BIGINT NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS emoji_status_document_id BIGINT NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS emoji_status_until INT NOT NULL DEFAULT 0;
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE channel_dialogs
|
||||
DROP COLUMN IF EXISTS view_forum_as_messages;
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE channel_dialogs
|
||||
ADD COLUMN IF NOT EXISTS view_forum_as_messages BOOLEAN NOT NULL DEFAULT false;
|
||||
40
deploy/migrations/0044_channel_antispam.down.sql
Normal file
40
deploy/migrations/0044_channel_antispam.down.sql
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
-- 0044_channel_antispam rollback.
|
||||
|
||||
UPDATE app_configs
|
||||
SET hash = 3,
|
||||
config_json = config_json
|
||||
- 'telegram_antispam_group_size_min'
|
||||
- 'telegram_antispam_user_id',
|
||||
updated_at = now()
|
||||
WHERE client = 'tdesktop' AND hash = 4;
|
||||
|
||||
ALTER TABLE channel_admin_log_events
|
||||
DROP CONSTRAINT IF EXISTS channel_admin_log_events_type_check;
|
||||
|
||||
ALTER TABLE channel_admin_log_events
|
||||
ADD 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_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'
|
||||
)
|
||||
);
|
||||
|
||||
ALTER TABLE channels
|
||||
DROP COLUMN IF EXISTS antispam;
|
||||
44
deploy/migrations/0044_channel_antispam.up.sql
Normal file
44
deploy/migrations/0044_channel_antispam.up.sql
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
-- 0044_channel_antispam: persist native antispam state and expose TDesktop config.
|
||||
|
||||
ALTER TABLE channels
|
||||
ADD COLUMN IF NOT EXISTS antispam BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
ALTER TABLE channel_admin_log_events
|
||||
DROP CONSTRAINT IF EXISTS channel_admin_log_events_type_check;
|
||||
|
||||
ALTER TABLE channel_admin_log_events
|
||||
ADD 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'
|
||||
)
|
||||
);
|
||||
|
||||
UPDATE app_configs
|
||||
SET hash = 4,
|
||||
config_json = config_json
|
||||
|| jsonb_build_object(
|
||||
'telegram_antispam_group_size_min', 200,
|
||||
'telegram_antispam_user_id', '5434988373'
|
||||
),
|
||||
updated_at = now()
|
||||
WHERE client = 'tdesktop';
|
||||
45
deploy/migrations/0045_channel_forum_tabs.down.sql
Normal file
45
deploy/migrations/0045_channel_forum_tabs.down.sql
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
-- 0045_channel_forum_tabs rollback.
|
||||
|
||||
ALTER TABLE channel_admin_log_events
|
||||
DROP CONSTRAINT IF EXISTS channel_admin_log_events_type_check;
|
||||
|
||||
DELETE FROM channel_admin_log_events
|
||||
WHERE event_type = 'toggle_forum';
|
||||
|
||||
ALTER TABLE channel_admin_log_events
|
||||
ADD 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_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'
|
||||
)
|
||||
);
|
||||
|
||||
ALTER TABLE channels
|
||||
DROP CONSTRAINT IF EXISTS channels_kind_check;
|
||||
|
||||
ALTER TABLE channels
|
||||
ADD CONSTRAINT channels_kind_check CHECK (
|
||||
(broadcast AND NOT megagroup AND NOT forum)
|
||||
OR (megagroup AND NOT broadcast)
|
||||
);
|
||||
|
||||
ALTER TABLE channels
|
||||
DROP COLUMN IF EXISTS forum_tabs;
|
||||
48
deploy/migrations/0045_channel_forum_tabs.up.sql
Normal file
48
deploy/migrations/0045_channel_forum_tabs.up.sql
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
-- 0045_channel_forum_tabs: persist Layer 225 forum layout and admin-log action.
|
||||
|
||||
ALTER TABLE channels
|
||||
ADD COLUMN IF NOT EXISTS forum_tabs BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
UPDATE channels
|
||||
SET forum_tabs = false
|
||||
WHERE NOT forum AND forum_tabs;
|
||||
|
||||
ALTER TABLE channels
|
||||
DROP CONSTRAINT IF EXISTS channels_kind_check;
|
||||
|
||||
ALTER TABLE channels
|
||||
ADD CONSTRAINT channels_kind_check CHECK (
|
||||
((broadcast AND NOT megagroup AND NOT forum)
|
||||
OR (megagroup AND NOT broadcast))
|
||||
AND (NOT forum_tabs OR forum)
|
||||
);
|
||||
|
||||
ALTER TABLE channel_admin_log_events
|
||||
DROP CONSTRAINT IF EXISTS channel_admin_log_events_type_check;
|
||||
|
||||
ALTER TABLE channel_admin_log_events
|
||||
ADD 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'
|
||||
)
|
||||
);
|
||||
3
deploy/migrations/0046_channel_forum_topics.down.sql
Normal file
3
deploy/migrations/0046_channel_forum_topics.down.sql
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
-- 0046_channel_forum_topics rollback.
|
||||
|
||||
DROP TABLE IF EXISTS channel_forum_topics CASCADE;
|
||||
55
deploy/migrations/0046_channel_forum_topics.up.sql
Normal file
55
deploy/migrations/0046_channel_forum_topics.up.sql
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
-- 0046_channel_forum_topics: forum topic read model.
|
||||
--
|
||||
-- Topic ids are the channel service message ids produced by
|
||||
-- messages.createForumTopic. Messages stay single-copy in channel_messages;
|
||||
-- this table stores the bounded topic index/read counters TDesktop needs for
|
||||
-- messages.getForumTopics/getForumTopicsByID.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS channel_forum_topics (
|
||||
channel_id BIGINT NOT NULL REFERENCES channels(id) ON DELETE CASCADE,
|
||||
topic_id INT NOT NULL,
|
||||
creator_user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
|
||||
title TEXT NOT NULL DEFAULT '',
|
||||
icon_color INT NOT NULL DEFAULT 0,
|
||||
icon_emoji_id BIGINT NOT NULL DEFAULT 0,
|
||||
title_missing BOOLEAN NOT NULL DEFAULT false,
|
||||
closed BOOLEAN NOT NULL DEFAULT false,
|
||||
hidden BOOLEAN NOT NULL DEFAULT false,
|
||||
pinned BOOLEAN NOT NULL DEFAULT false,
|
||||
pinned_order INT NOT NULL DEFAULT 0,
|
||||
date INT 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,
|
||||
unread_poll_votes_count INT NOT NULL DEFAULT 0,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (channel_id, topic_id),
|
||||
CONSTRAINT channel_forum_topics_title_check CHECK (title <> '' OR title_missing),
|
||||
CONSTRAINT channel_forum_topics_ids_check CHECK (topic_id > 0 AND top_message_id >= 0)
|
||||
) PARTITION BY HASH (channel_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS channel_forum_topics_page_idx
|
||||
ON channel_forum_topics (channel_id, pinned DESC, pinned_order DESC, date DESC, topic_id DESC)
|
||||
WHERE NOT deleted;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS channel_forum_topics_title_idx
|
||||
ON channel_forum_topics (channel_id, lower(title), date DESC, topic_id DESC)
|
||||
WHERE NOT deleted;
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
i int;
|
||||
BEGIN
|
||||
FOR i IN 0..63 LOOP
|
||||
EXECUTE format(
|
||||
'CREATE TABLE IF NOT EXISTS channel_forum_topics_p%s PARTITION OF channel_forum_topics FOR VALUES WITH (MODULUS 64, REMAINDER %s)',
|
||||
lpad(i::text, 2, '0'),
|
||||
i
|
||||
);
|
||||
END LOOP;
|
||||
END $$;
|
||||
1
deploy/migrations/0047_dialog_drafts.down.sql
Normal file
1
deploy/migrations/0047_dialog_drafts.down.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS dialog_drafts CASCADE;
|
||||
31
deploy/migrations/0047_dialog_drafts.up.sql
Normal file
31
deploy/migrations/0047_dialog_drafts.up.sql
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
-- 0047_dialog_drafts: persistent cloud drafts for private dialogs and channels.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS dialog_drafts (
|
||||
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,
|
||||
date INT NOT NULL DEFAULT 0,
|
||||
draft JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (user_id, peer_type, peer_id, top_message_id),
|
||||
CONSTRAINT dialog_drafts_peer_type_check CHECK (peer_type IN ('user', 'channel')),
|
||||
CONSTRAINT dialog_drafts_top_message_id_check CHECK (top_message_id >= 0)
|
||||
) PARTITION BY HASH (user_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS dialog_drafts_user_date_idx
|
||||
ON dialog_drafts (user_id, date DESC, peer_type, peer_id, top_message_id);
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
i int;
|
||||
BEGIN
|
||||
FOR i IN 0..63 LOOP
|
||||
EXECUTE format(
|
||||
'CREATE TABLE IF NOT EXISTS dialog_drafts_p%s PARTITION OF dialog_drafts FOR VALUES WITH (MODULUS 64, REMAINDER %s)',
|
||||
lpad(i::text, 2, '0'),
|
||||
i
|
||||
);
|
||||
END LOOP;
|
||||
END $$;
|
||||
|
|
@ -0,0 +1 @@
|
|||
DROP INDEX IF EXISTS channels_public_broadcast_recommendations_idx;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
CREATE INDEX IF NOT EXISTS channels_public_broadcast_recommendations_idx
|
||||
ON channels (participants_count DESC, date DESC, id DESC)
|
||||
WHERE broadcast AND NOT megagroup AND NOT deleted AND COALESCE(username, '') <> '';
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
DROP INDEX IF EXISTS channels_public_title_trgm_idx;
|
||||
DROP INDEX IF EXISTS channels_public_username_trgm_idx;
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
-- 0049_channel_public_peer_search_indexes: indexed public channel peer lookup for contacts.search.
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS channels_public_username_trgm_idx
|
||||
ON channels USING gin (lower(username) gin_trgm_ops)
|
||||
WHERE NOT deleted AND COALESCE(username, '') <> '';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS channels_public_title_trgm_idx
|
||||
ON channels USING gin (lower(title) gin_trgm_ops)
|
||||
WHERE NOT deleted AND COALESCE(username, '') <> '';
|
||||
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