diff --git a/simplex-chat.cabal b/simplex-chat.cabal index e8f43f5f07..6557411ee7 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -120,6 +120,7 @@ library Simplex.Chat.Store.Postgres.Migrations.M20250919_group_summary Simplex.Chat.Store.Postgres.Migrations.M20250922_remove_unused_connections Simplex.Chat.Store.Postgres.Migrations.M20251007_connections_sync + Simplex.Chat.Store.Postgres.Migrations.M20251017_chat_tags_cascade else exposed-modules: Simplex.Chat.Archive @@ -264,6 +265,7 @@ library Simplex.Chat.Store.SQLite.Migrations.M20250919_group_summary Simplex.Chat.Store.SQLite.Migrations.M20250922_remove_unused_connections Simplex.Chat.Store.SQLite.Migrations.M20251007_connections_sync + Simplex.Chat.Store.SQLite.Migrations.M20251017_chat_tags_cascade other-modules: Paths_simplex_chat hs-source-dirs: diff --git a/src/Simplex/Chat/Store/Postgres/Migrations.hs b/src/Simplex/Chat/Store/Postgres/Migrations.hs index c6c04b465b..89f8f6070b 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations.hs +++ b/src/Simplex/Chat/Store/Postgres/Migrations.hs @@ -20,6 +20,7 @@ import Simplex.Chat.Store.Postgres.Migrations.M20250813_delivery_tasks import Simplex.Chat.Store.Postgres.Migrations.M20250919_group_summary import Simplex.Chat.Store.Postgres.Migrations.M20250922_remove_unused_connections import Simplex.Chat.Store.Postgres.Migrations.M20251007_connections_sync +import Simplex.Chat.Store.Postgres.Migrations.M20251017_chat_tags_cascade import Simplex.Messaging.Agent.Store.Shared (Migration (..)) schemaMigrations :: [(String, Text, Maybe Text)] @@ -39,7 +40,8 @@ schemaMigrations = ("20250813_delivery_tasks", m20250813_delivery_tasks, Just down_m20250813_delivery_tasks), ("20250919_group_summary", m20250919_group_summary, Just down_m20250919_group_summary), ("20250922_remove_unused_connections", m20250922_remove_unused_connections, Just down_m20250922_remove_unused_connections), - ("20251007_connections_sync", m20251007_connections_sync, Just down_m20251007_connections_sync) + ("20251007_connections_sync", m20251007_connections_sync, Just down_m20251007_connections_sync), + ("20251017_chat_tags_cascade", m20251017_chat_tags_cascade, Just down_m20251017_chat_tags_cascade) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/M20251017_chat_tags_cascade.hs b/src/Simplex/Chat/Store/Postgres/Migrations/M20251017_chat_tags_cascade.hs new file mode 100644 index 0000000000..f5a227a3c5 --- /dev/null +++ b/src/Simplex/Chat/Store/Postgres/Migrations/M20251017_chat_tags_cascade.hs @@ -0,0 +1,32 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Store.Postgres.Migrations.M20251017_chat_tags_cascade where + +import Data.Text (Text) +import qualified Data.Text as T +import Text.RawString.QQ (r) + +m20251017_chat_tags_cascade :: Text +m20251017_chat_tags_cascade = + T.pack + [r| +ALTER TABLE chat_tags DROP CONSTRAINT chat_tags_user_id_fkey; + +ALTER TABLE chat_tags + ADD CONSTRAINT chat_tags_user_id_fkey + FOREIGN KEY (user_id) + REFERENCES users(user_id) + ON DELETE CASCADE; +|] + +down_m20251017_chat_tags_cascade :: Text +down_m20251017_chat_tags_cascade = + T.pack + [r| +ALTER TABLE chat_tags DROP CONSTRAINT chat_tags_user_id_fkey; + +ALTER TABLE chat_tags + ADD CONSTRAINT chat_tags_user_id_fkey + FOREIGN KEY (user_id) + REFERENCES users(user_id); +|] diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql b/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql index 6b54b6d9ee..601dd97a6e 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql @@ -2479,7 +2479,7 @@ ALTER TABLE ONLY test_chat_schema.chat_tags_chats ALTER TABLE ONLY test_chat_schema.chat_tags - ADD CONSTRAINT chat_tags_user_id_fkey FOREIGN KEY (user_id) REFERENCES test_chat_schema.users(user_id); + ADD CONSTRAINT chat_tags_user_id_fkey FOREIGN KEY (user_id) REFERENCES test_chat_schema.users(user_id) ON DELETE CASCADE; diff --git a/src/Simplex/Chat/Store/SQLite/Migrations.hs b/src/Simplex/Chat/Store/SQLite/Migrations.hs index e568e2a663..1c819e6537 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations.hs @@ -143,6 +143,7 @@ import Simplex.Chat.Store.SQLite.Migrations.M20250813_delivery_tasks import Simplex.Chat.Store.SQLite.Migrations.M20250919_group_summary import Simplex.Chat.Store.SQLite.Migrations.M20250922_remove_unused_connections import Simplex.Chat.Store.SQLite.Migrations.M20251007_connections_sync +import Simplex.Chat.Store.SQLite.Migrations.M20251017_chat_tags_cascade import Simplex.Messaging.Agent.Store.Shared (Migration (..)) schemaMigrations :: [(String, Query, Maybe Query)] @@ -285,7 +286,8 @@ schemaMigrations = ("20250813_delivery_tasks", m20250813_delivery_tasks, Just down_m20250813_delivery_tasks), ("20250919_group_summary", m20250919_group_summary, Just down_m20250919_group_summary), ("20250922_remove_unused_connections", m20250922_remove_unused_connections, Just down_m20250922_remove_unused_connections), - ("20251007_connections_sync", m20251007_connections_sync, Just down_m20251007_connections_sync) + ("20251007_connections_sync", m20251007_connections_sync, Just down_m20251007_connections_sync), + ("20251017_chat_tags_cascade", m20251017_chat_tags_cascade, Just down_m20251017_chat_tags_cascade) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/M20251017_chat_tags_cascade.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20251017_chat_tags_cascade.hs new file mode 100644 index 0000000000..1f82831404 --- /dev/null +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20251017_chat_tags_cascade.hs @@ -0,0 +1,30 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Store.SQLite.Migrations.M20251017_chat_tags_cascade where + +import Database.SQLite.Simple (Query) +import Database.SQLite.Simple.QQ (sql) + +m20251017_chat_tags_cascade :: Query +m20251017_chat_tags_cascade = + [sql| +PRAGMA writable_schema=1; + +UPDATE sqlite_master +SET sql = replace(sql, 'user_id INTEGER REFERENCES users', 'user_id INTEGER REFERENCES users ON DELETE CASCADE') +WHERE name = 'chat_tags' AND type = 'table'; + +PRAGMA writable_schema=0; +|] + +down_m20251017_chat_tags_cascade :: Query +down_m20251017_chat_tags_cascade = + [sql| +PRAGMA writable_schema=1; + +UPDATE sqlite_master +SET sql = replace(sql, 'user_id INTEGER REFERENCES users ON DELETE CASCADE', 'user_id INTEGER REFERENCES users') +WHERE name = 'chat_tags' AND type = 'table'; + +PRAGMA writable_schema=0; +|] diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt index 91db234fd1..dbb4823024 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt @@ -3329,6 +3329,16 @@ Query: Plan: SEARCH chat_item_versions USING INDEX idx_chat_item_versions_chat_item_id (chat_item_id=?) +Query: + SELECT chat_tag_id, chat_tag_emoji, chat_tag_text + FROM chat_tags + WHERE user_id = ? + ORDER BY tag_order + +Plan: +SEARCH chat_tags USING INDEX idx_chat_tags_user_id (user_id=?) +USE TEMP B-TREE FOR ORDER BY + Query: SELECT command_id, connection_id, command_function, command_status FROM commands @@ -4333,6 +4343,20 @@ Query: Plan: +Query: + INSERT INTO chat_tags (user_id, chat_tag_emoji, chat_tag_text, tag_order) + VALUES (?,?,?, COALESCE((SELECT MAX(tag_order) + 1 FROM chat_tags WHERE user_id = ?), 1)) + +Plan: +SCALAR SUBQUERY 1 +SEARCH chat_tags USING INDEX idx_chat_tags_user_id (user_id=?) + +Query: + INSERT INTO chat_tags_chats (contact_id, chat_tag_id) + VALUES (?,?) + +Plan: + Query: INSERT INTO commands (connection_id, command_function, command_status, user_id, created_at, updated_at) VALUES (?,?,?,?,?,?) diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql index 7d8f9d0dcd..4b12246a3f 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql @@ -663,7 +663,7 @@ CREATE TABLE operator_usage_conditions( ); CREATE TABLE chat_tags( chat_tag_id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id INTEGER REFERENCES users, + user_id INTEGER REFERENCES users ON DELETE CASCADE, chat_tag_text TEXT NOT NULL, chat_tag_emoji TEXT, tag_order INTEGER NOT NULL diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index b4bce68535..1b93013258 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -134,6 +134,7 @@ chatDirectTests = do it "both users have contact link" testMultipleUserAddresses it "create user with same servers" testCreateUserSameServers it "delete user" testDeleteUser + it "delete user with chat tags" testDeleteUserChatTags it "users have different chat item TTL configuration, chat items expire" testUsersDifferentCIExpirationTTL it "chat items expire after restart for all users according to per user configuration" testUsersRestartCIExpiration it "chat items only expire for users who configured expiration" testEnableCIExpirationOnlyForOneUser @@ -2110,6 +2111,26 @@ testDeleteUser = alice ##> "/users" alice <## "no users" +testDeleteUserChatTags :: HasCallStack => TestParams -> IO () +testDeleteUserChatTags = + testChat2 aliceProfile bobProfile $ + \alice bob -> do + connectUsers alice bob + + alice ##> "/_create tag {\"text\":\"my tag\"}" + alice <## "[{\"chatTagId\":1,\"chatTagText\":\"my tag\"}]" + alice ##> "/_tags @2 1" + alice <## "chat tags updated" + + alice ##> "/create user alisa" + showActiveUser alice "alisa" + + alice ##> "/_delete user 1 del_smp=off" + alice <## "ok" + + alice ##> "/users" + alice <## "alisa (active)" + testUsersDifferentCIExpirationTTL :: HasCallStack => TestParams -> IO () testUsersDifferentCIExpirationTTL ps = do withNewTestChat ps "bob" bobProfile $ \bob -> do