From 765505311d50643f3edf8f3212aa5064dbc0168d Mon Sep 17 00:00:00 2001 From: "Evgeny @ SimpleX Chat" <259188159+evgeny-simplex@users.noreply.github.com> Date: Fri, 11 Sep 2026 11:30:54 +0000 Subject: [PATCH] postgres migration --- simplex-chat.cabal | 1 + src/Simplex/Chat/Store/Postgres/Migrations.hs | 4 +- .../Postgres/Migrations/M20260905_feeds.hs | 92 +++++++++++++++++++ .../SQLite/Migrations/M20260905_feeds.hs | 4 +- .../Store/SQLite/Migrations/chat_schema.sql | 5 +- tests/ChatTests/Direct.hs | 6 +- tests/ChatTests/Profiles.hs | 6 +- 7 files changed, 107 insertions(+), 11 deletions(-) create mode 100644 src/Simplex/Chat/Store/Postgres/Migrations/M20260905_feeds.hs diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 3dcb99f6d0..1eca273d96 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -157,6 +157,7 @@ library Simplex.Chat.Store.Postgres.Migrations.M20260813_auto_accept_group_invitations Simplex.Chat.Store.Postgres.Migrations.M20260822_forward_link Simplex.Chat.Store.Postgres.Migrations.M20260828_file_expiry + Simplex.Chat.Store.Postgres.Migrations.M20260905_feeds else exposed-modules: Simplex.Chat.Archive diff --git a/src/Simplex/Chat/Store/Postgres/Migrations.hs b/src/Simplex/Chat/Store/Postgres/Migrations.hs index 91f2b13fb5..5572b2e23d 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations.hs +++ b/src/Simplex/Chat/Store/Postgres/Migrations.hs @@ -49,6 +49,7 @@ import Simplex.Chat.Store.Postgres.Migrations.M20260723_contact_request_rejectio import Simplex.Chat.Store.Postgres.Migrations.M20260813_auto_accept_group_invitations import Simplex.Chat.Store.Postgres.Migrations.M20260822_forward_link import Simplex.Chat.Store.Postgres.Migrations.M20260828_file_expiry +import Simplex.Chat.Store.Postgres.Migrations.M20260905_feeds import Simplex.Messaging.Agent.Store.Shared (Migration (..)) schemaMigrations :: [(String, Text, Maybe Text)] @@ -97,7 +98,8 @@ schemaMigrations = ("20260723_contact_request_rejection", m20260723_contact_request_rejection, Just down_m20260723_contact_request_rejection), ("20260813_auto_accept_group_invitations", m20260813_auto_accept_group_invitations, Just down_m20260813_auto_accept_group_invitations), ("20260822_forward_link", m20260822_forward_link, Just down_m20260822_forward_link), - ("20260828_file_expiry", m20260828_file_expiry, Just down_m20260828_file_expiry) + ("20260828_file_expiry", m20260828_file_expiry, Just down_m20260828_file_expiry), + ("20260905_feeds", m20260905_feeds, Just down_m20260905_feeds) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/M20260905_feeds.hs b/src/Simplex/Chat/Store/Postgres/Migrations/M20260905_feeds.hs new file mode 100644 index 0000000000..2c262ef7ef --- /dev/null +++ b/src/Simplex/Chat/Store/Postgres/Migrations/M20260905_feeds.hs @@ -0,0 +1,92 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Store.Postgres.Migrations.M20260905_feeds where + +import Data.Text (Text) +import qualified Data.Text as T +import Text.RawString.QQ (r) + +m20260905_feeds :: Text +m20260905_feeds = + T.pack + [r| +CREATE TABLE feeds ( + feed_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + user_id BIGINT NOT NULL REFERENCES users ON DELETE CASCADE, + created_at TIMESTAMPTZ NOT NULL DEFAULT (now()), + updated_at TIMESTAMPTZ NOT NULL DEFAULT (now()), + chat_ts TIMESTAMPTZ NOT NULL DEFAULT (now()), + favorite SMALLINT NOT NULL DEFAULT 0, + unread_chat SMALLINT NOT NULL DEFAULT 0 +); +CREATE INDEX idx_feeds_user_id ON feeds(user_id); +INSERT INTO feeds (user_id) SELECT user_id FROM users; + +ALTER TABLE chat_items ADD COLUMN feed_id BIGINT REFERENCES feeds ON DELETE CASCADE; +ALTER TABLE chat_items ADD COLUMN feed_item_id BIGINT REFERENCES chat_items ON DELETE SET NULL; +ALTER TABLE chat_items ADD COLUMN item_feed SMALLINT NOT NULL DEFAULT 0; +CREATE INDEX idx_chat_items_feed_id ON chat_items(feed_id); +CREATE INDEX idx_chat_items_feeds_created_at ON chat_items(user_id, feed_id, created_at); +CREATE INDEX idx_chat_items_feed_item_contact ON chat_items(feed_item_id, contact_id); +CREATE INDEX idx_chat_items_feed_item_group ON chat_items(feed_item_id, group_id); + +ALTER TABLE messages ADD COLUMN feed_id BIGINT REFERENCES feeds ON DELETE CASCADE; +CREATE INDEX idx_messages_feed_id ON messages(feed_id); + +ALTER TABLE files ADD COLUMN feed_id BIGINT REFERENCES feeds ON DELETE CASCADE; +CREATE INDEX idx_files_feed_id ON files(feed_id); + +ALTER TABLE contacts ADD COLUMN drop_feed SMALLINT NOT NULL DEFAULT 0; +ALTER TABLE groups ADD COLUMN drop_feed SMALLINT NOT NULL DEFAULT 0; +CREATE INDEX idx_contacts_user_id ON contacts(user_id, contact_id); +CREATE INDEX idx_groups_user_id_business_chat ON groups(user_id, business_chat, group_id); + +ALTER TABLE delivery_jobs ALTER COLUMN group_id DROP NOT NULL; + +ALTER TABLE delivery_jobs ADD COLUMN feed_id BIGINT REFERENCES feeds ON DELETE CASCADE; +ALTER TABLE delivery_jobs ADD COLUMN chat_item_id BIGINT REFERENCES chat_items ON DELETE CASCADE; +ALTER TABLE delivery_jobs ADD COLUMN message_ids TEXT; +ALTER TABLE delivery_jobs ADD COLUMN feed_cursor_id BIGINT; +CREATE INDEX idx_delivery_jobs_feed_next ON delivery_jobs(feed_id, worker_scope, failed, job_status); +CREATE INDEX idx_delivery_jobs_chat_item_id ON delivery_jobs(chat_item_id); +|] + +down_m20260905_feeds :: Text +down_m20260905_feeds = + T.pack + [r| +DROP INDEX idx_delivery_jobs_chat_item_id; +DROP INDEX idx_delivery_jobs_feed_next; +DELETE FROM delivery_jobs WHERE group_id IS NULL; +ALTER TABLE delivery_jobs DROP COLUMN feed_cursor_id; +ALTER TABLE delivery_jobs DROP COLUMN message_ids; +ALTER TABLE delivery_jobs DROP COLUMN chat_item_id; +ALTER TABLE delivery_jobs DROP COLUMN feed_id; + +ALTER TABLE delivery_jobs ALTER COLUMN group_id SET NOT NULL; + +DROP INDEX idx_groups_user_id_business_chat; +DROP INDEX idx_contacts_user_id; +ALTER TABLE groups DROP COLUMN drop_feed; +ALTER TABLE contacts DROP COLUMN drop_feed; + +DELETE FROM files WHERE feed_id IS NOT NULL; +DROP INDEX idx_files_feed_id; +ALTER TABLE files DROP COLUMN feed_id; + +DELETE FROM messages WHERE feed_id IS NOT NULL; +DROP INDEX idx_messages_feed_id; +ALTER TABLE messages DROP COLUMN feed_id; + +DELETE FROM chat_items WHERE feed_id IS NOT NULL; +DROP INDEX idx_chat_items_feed_item_group; +DROP INDEX idx_chat_items_feed_item_contact; +DROP INDEX idx_chat_items_feeds_created_at; +DROP INDEX idx_chat_items_feed_id; +ALTER TABLE chat_items DROP COLUMN item_feed; +ALTER TABLE chat_items DROP COLUMN feed_item_id; +ALTER TABLE chat_items DROP COLUMN feed_id; + +DROP INDEX idx_feeds_user_id; +DROP TABLE feeds; +|] diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/M20260905_feeds.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20260905_feeds.hs index ea484cc7d0..689d3521f0 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/M20260905_feeds.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20260905_feeds.hs @@ -36,8 +36,8 @@ CREATE INDEX idx_files_feed_id ON files(feed_id); ALTER TABLE contacts ADD COLUMN drop_feed INTEGER NOT NULL DEFAULT 0; ALTER TABLE groups ADD COLUMN drop_feed INTEGER NOT NULL DEFAULT 0; -CREATE INDEX idx_contacts_user_id ON contacts(user_id); -CREATE INDEX idx_groups_user_id_business_chat ON groups(user_id, business_chat); +CREATE INDEX idx_contacts_user_id ON contacts(user_id, contact_id); +CREATE INDEX idx_groups_user_id_business_chat ON groups(user_id, business_chat, group_id); PRAGMA writable_schema=1; diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql index c352e85956..6234fd6c1d 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql @@ -1423,10 +1423,11 @@ CREATE INDEX idx_chat_items_feed_item_group ON chat_items( ); CREATE INDEX idx_messages_feed_id ON messages(feed_id); CREATE INDEX idx_files_feed_id ON files(feed_id); -CREATE INDEX idx_contacts_user_id ON contacts(user_id); +CREATE INDEX idx_contacts_user_id ON contacts(user_id, contact_id); CREATE INDEX idx_groups_user_id_business_chat ON groups( user_id, - business_chat + business_chat, + group_id ); CREATE INDEX idx_delivery_jobs_feed_next ON delivery_jobs( feed_id, diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index 22981c76c3..850e1eccb4 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -1998,14 +1998,14 @@ testMultipleUserAddresses = cLinkAlisa <- getContactLink alice True bob ##> ("/c " <> cLinkAlisa) alice <#? bob - alice #$> ("/_get chats 2 pcc=on", chats, [("@bob", "Audio/video calls: enabled"), ("@Ask SimpleX Team", ""), ("*", "")]) + alice #$> ("/_get chats 2 pcc=on", chats, [("@bob", "Audio/video calls: enabled"), ("@Ask SimpleX Team", ""), ("*", ""), ("%", "")]) alice ##> "/ac bob" alice <## "bob (Bob): accepting contact request, you can send messages to contact" concurrently_ (bob <## "alisa: contact is connected") (alice <## "bob (Bob): contact is connected") threadDelay 100000 - alice #$> ("/_get chats 2 pcc=on", chats, [("@bob", lastChatFeature), ("@Ask SimpleX Team", ""), ("*", "")]) + alice #$> ("/_get chats 2 pcc=on", chats, [("@bob", lastChatFeature), ("@Ask SimpleX Team", ""), ("*", ""), ("%", "")]) alice <##> bob bob #> "@alice hey alice" @@ -2036,7 +2036,7 @@ testMultipleUserAddresses = (cath <## "alisa: contact is connected") (alice <## "cath (Catherine): contact is connected") threadDelay 100000 - alice #$> ("/_get chats 2 pcc=on", chats, [("@cath", lastChatFeature), ("@bob", "hey"), ("@Ask SimpleX Team", ""), ("*", "")]) + alice #$> ("/_get chats 2 pcc=on", chats, [("@cath", lastChatFeature), ("@bob", "hey"), ("@Ask SimpleX Team", ""), ("*", ""), ("%", "")]) alice <##> cath -- first user doesn't have cath as contact diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs index efe2097504..65bb4e120c 100644 --- a/tests/ChatTests/Profiles.hs +++ b/tests/ChatTests/Profiles.hs @@ -4043,7 +4043,7 @@ testShortLinkChangePreparedContactUser = testChat2 aliceProfile bobProfile test alice @@@ [("@robert", "hey")] alice `hasContactProfiles` ["alice", "robert"] - bob #$> ("/_get chats 2 pcc=on", chats, [("@alice", "hey"), ("@Ask SimpleX Team", ""), ("*", "")]) + bob #$> ("/_get chats 2 pcc=on", chats, [("@alice", "hey"), ("@Ask SimpleX Team", ""), ("*", ""), ("%", "")]) bob `hasContactProfiles` ["robert", "alice", "Ask SimpleX Team"] bob ##> "/user bob" showActiveUser bob "bob (Bob)" @@ -4100,7 +4100,7 @@ testShortLinkChangePreparedContactUserDuplicate = testChat2 aliceProfile bobProf alice @@@ [("@robert", "hey"), ("@robert_1", "hey")] alice `hasContactProfiles` ["alice", "robert", "robert"] - bob #$> ("/_get chats 2 pcc=on", chats, [("@alice", "hey"), ("@alice_1", "hey"), ("@Ask SimpleX Team", ""), ("*", "")]) + bob #$> ("/_get chats 2 pcc=on", chats, [("@alice", "hey"), ("@alice_1", "hey"), ("@Ask SimpleX Team", ""), ("*", ""), ("%", "")]) bob `hasContactProfiles` ["robert", "alice", "alice", "Ask SimpleX Team"] bob ##> "/user bob" showActiveUser bob "bob (Bob)" @@ -4194,7 +4194,7 @@ testShortLinkChangePreparedGroupUser = testChat3 aliceProfile bobProfile cathPro alice @@@ [("#team", "3"), ("@cath","sent invitation to join group team as admin")] alice `hasContactProfiles` ["alice", "cath", "robert"] - bob #$> ("/_get chats 2 pcc=on", chats, [("#team", "3"), ("@Ask SimpleX Team", ""), ("*", "")]) + bob #$> ("/_get chats 2 pcc=on", chats, [("#team", "3"), ("@Ask SimpleX Team", ""), ("*", ""), ("%", "")]) bob `hasContactProfiles` ["robert", "alice", "cath", "Ask SimpleX Team"] cath @@@ [("#team", "3"), ("@alice","received invitation to join group team as admin")] cath `hasContactProfiles` ["cath", "alice", "robert"]