owpengram-server/internal/store/postgres/queries/dialog.sql

1043 lines
34 KiB
SQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- name: ListDialogsByUser :many
WITH base AS (
SELECT
d.user_id,
d.peer_type,
d.peer_id,
d.folder_id,
d.top_message_id,
d.top_message_date,
d.read_inbox_max_id,
d.read_outbox_max_id,
d.unread_count,
d.unread_mentions_count,
d.unread_reactions_count,
d.ttl_period,
d.theme_emoticon,
d.has_scheduled,
d.pinned,
d.pinned_order,
d.unread_mark,
d.hidden_peer_settings_bar,
COALESCE(u.id, 0)::bigint AS peer_user_id,
COALESCE(u.access_hash, 0)::bigint AS peer_access_hash,
COALESCE(NULLIF(c.contact_phone, ''), u.phone, '')::text AS peer_phone,
COALESCE(NULLIF(c.contact_first_name, ''), u.first_name, '')::text AS peer_first_name,
COALESCE(c.contact_last_name, u.last_name, '')::text AS peer_last_name,
COALESCE(u.username, '')::text AS peer_username,
COALESCE(u.country_code, '')::text AS peer_country_code,
COALESCE(u.verified, false)::boolean AS peer_verified,
COALESCE(u.support, false)::boolean AS peer_support,
COALESCE(u.is_bot, false)::boolean AS peer_is_bot,
COALESCE(u.bot_info_version, 0)::int AS peer_bot_info_version,
COALESCE(EXTRACT(EPOCH FROM u.premium_expires_at), 0)::bigint AS peer_premium_until,
COALESCE(u.emoji_status_document_id, 0)::bigint AS peer_emoji_status_document_id,
COALESCE(u.emoji_status_until, 0)::bigint AS peer_emoji_status_until,
COALESCE(u.last_seen_at, 0)::bigint AS peer_last_seen_at,
(c.contact_user_id IS NOT NULL)::boolean AS peer_contact,
COALESCE(c.mutual, false)::boolean AS peer_mutual,
COALESCE(m.box_id, 0)::int AS message_id,
COALESCE(m.private_message_id, 0)::bigint AS message_private_message_id,
COALESCE(m.from_user_id, 0)::bigint AS message_from_user_id,
COALESCE(m.message_date, 0)::int AS message_date,
COALESCE(m.outgoing, false)::boolean AS message_outgoing,
COALESCE(m.body, '')::text AS message_body,
COALESCE(m.entities::text, '[]')::text AS message_entities_json,
COALESCE(m.media::text, '{}')::text AS message_media_json,
COALESCE(m.ttl_period, 0)::int AS message_ttl_period,
COALESCE(m.expires_at, 0)::int AS message_expires_at,
COALESCE(m.edit_date, 0)::int AS message_edit_date,
COALESCE(m.hide_edited, false)::boolean AS message_hide_edited,
COALESCE(m.silent, false)::boolean AS message_silent,
COALESCE(m.noforwards, false)::boolean AS message_noforwards,
COALESCE(m.reply_to_msg_id, 0)::int AS message_reply_to_msg_id,
COALESCE(m.reply_to_peer_type, '')::text AS message_reply_to_peer_type,
COALESCE(m.reply_to_peer_id, 0)::bigint AS message_reply_to_peer_id,
COALESCE(m.reply_to_top_id, 0)::int AS message_reply_to_top_id,
COALESCE(m.reply_to_story_id, 0)::int AS message_reply_to_story_id,
COALESCE(m.quote_text, '')::text AS message_quote_text,
COALESCE(m.quote_entities::text, '[]')::text AS message_quote_entities_json,
COALESCE(m.quote_offset, 0)::int AS message_quote_offset,
COALESCE(m.fwd_from_peer_type, '')::text AS message_fwd_from_peer_type,
COALESCE(m.fwd_from_peer_id, 0)::bigint AS message_fwd_from_peer_id,
COALESCE(m.fwd_from_name, '')::text AS message_fwd_from_name,
COALESCE(m.fwd_date, 0)::int AS message_fwd_date,
COALESCE(m.fwd_saved_from_peer_type, '')::text AS message_fwd_saved_from_peer_type,
COALESCE(m.fwd_saved_from_peer_id, 0)::bigint AS message_fwd_saved_from_peer_id,
COALESCE(m.fwd_saved_from_msg_id, 0)::int AS message_fwd_saved_from_msg_id,
COALESCE(m.saved_peer_type, '')::text AS message_saved_peer_type,
COALESCE(m.saved_peer_id, 0)::bigint AS message_saved_peer_id,
COALESCE(m.media_unread, false)::boolean AS message_media_unread,
COALESCE(m.reaction_unread, false)::boolean AS message_reaction_unread,
COALESCE(m.via_bot_id, 0)::bigint AS message_via_bot_id,
COALESCE(m.grouped_id, 0)::bigint AS message_grouped_id,
COALESCE(m.effect, 0)::bigint AS message_effect,
COALESCE(m.reply_markup::text, '{}')::text AS message_reply_markup_json,
COALESCE(m.rich_message::text, '{}')::text AS message_rich_message_json,
COALESCE(m.pinned, false)::boolean AS message_pinned
FROM dialogs d
LEFT JOIN users u ON d.peer_type = 'user' AND u.id = d.peer_id
LEFT JOIN contacts c ON d.peer_type = 'user' AND c.user_id = d.user_id AND c.contact_user_id = d.peer_id
LEFT JOIN message_boxes m ON m.owner_user_id = d.user_id AND m.box_id = d.top_message_id AND NOT m.deleted
WHERE d.user_id = $1
AND (
-- 不带 folder_id flag 视为主列表folder 0官方语义下归档对话只以
-- dialogFolder 聚合条目出现在主列表DrKLO Android 主列表请求不设 flag。
(NOT sqlc.arg(has_folder_id)::boolean AND d.folder_id = 0)
OR (
sqlc.arg(has_folder_id)::boolean
AND sqlc.arg(folder_id)::int < 2
AND d.folder_id = sqlc.arg(folder_id)::int
)
OR (
sqlc.arg(folder_id)::int >= 2
AND NOT (sqlc.arg(folder_exclude_archived)::boolean AND d.folder_id = 1)
AND NOT (sqlc.arg(folder_exclude_read)::boolean AND d.unread_count = 0 AND NOT d.unread_mark)
AND NOT EXISTS (
SELECT 1
FROM (
SELECT fpt.peer_type, fpi.peer_id
FROM unnest(sqlc.arg(folder_exclude_peer_types)::text[]) WITH ORDINALITY AS fpt(peer_type, ord)
JOIN unnest(sqlc.arg(folder_exclude_peer_ids)::bigint[]) WITH ORDINALITY AS fpi(peer_id, ord) USING (ord)
) fp
WHERE fp.peer_type = d.peer_type AND fp.peer_id = d.peer_id
)
AND (
EXISTS (
SELECT 1
FROM (
SELECT fpt.peer_type, fpi.peer_id
FROM unnest(sqlc.arg(folder_include_peer_types)::text[]) WITH ORDINALITY AS fpt(peer_type, ord)
JOIN unnest(sqlc.arg(folder_include_peer_ids)::bigint[]) WITH ORDINALITY AS fpi(peer_id, ord) USING (ord)
) fp
WHERE fp.peer_type = d.peer_type AND fp.peer_id = d.peer_id
)
OR EXISTS (
SELECT 1
FROM (
SELECT fpt.peer_type, fpi.peer_id
FROM unnest(sqlc.arg(folder_pinned_peer_types)::text[]) WITH ORDINALITY AS fpt(peer_type, ord)
JOIN unnest(sqlc.arg(folder_pinned_peer_ids)::bigint[]) WITH ORDINALITY AS fpi(peer_id, ord) USING (ord)
) fp
WHERE fp.peer_type = d.peer_type AND fp.peer_id = d.peer_id
)
OR (sqlc.arg(folder_contacts)::boolean AND c.contact_user_id IS NOT NULL)
OR (sqlc.arg(folder_non_contacts)::boolean AND c.contact_user_id IS NULL)
)
)
)
AND (NOT sqlc.arg(pinned_only)::boolean OR d.pinned)
AND (NOT sqlc.arg(exclude_pinned)::boolean OR NOT d.pinned)
),
paged AS (
SELECT *
FROM base
WHERE (
(sqlc.arg(offset_date)::int <= 0 AND sqlc.arg(offset_id)::int <= 0)
OR (
sqlc.arg(offset_date)::int > 0
AND (
top_message_date < sqlc.arg(offset_date)::int
OR (
top_message_date = sqlc.arg(offset_date)::int
AND (
sqlc.arg(offset_id)::int <= 0
OR top_message_id < sqlc.arg(offset_id)::int
OR (
top_message_id = sqlc.arg(offset_id)::int
AND sqlc.arg(has_offset_peer)::boolean
AND peer_id < sqlc.arg(offset_peer_id)::bigint
)
)
)
)
)
OR (
sqlc.arg(offset_date)::int <= 0
AND sqlc.arg(offset_id)::int > 0
AND (
top_message_id < sqlc.arg(offset_id)::int
OR (
top_message_id = sqlc.arg(offset_id)::int
AND sqlc.arg(has_offset_peer)::boolean
AND peer_id < sqlc.arg(offset_peer_id)::bigint
)
)
)
)
)
SELECT
user_id,
peer_type::text AS peer_type,
peer_id::bigint AS peer_id,
folder_id,
top_message_id,
top_message_date,
read_inbox_max_id,
read_outbox_max_id,
unread_count,
unread_mentions_count,
unread_reactions_count,
ttl_period,
theme_emoticon,
has_scheduled,
pinned,
pinned_order,
unread_mark,
hidden_peer_settings_bar,
peer_user_id,
peer_access_hash,
peer_phone,
peer_first_name,
peer_last_name,
peer_username,
peer_country_code,
peer_verified,
peer_support,
peer_is_bot,
peer_bot_info_version,
peer_premium_until,
peer_emoji_status_document_id,
peer_emoji_status_until,
peer_last_seen_at,
peer_contact,
peer_mutual,
message_id,
message_private_message_id,
message_from_user_id,
message_date,
message_outgoing,
message_body,
message_entities_json,
message_media_json,
message_ttl_period,
message_expires_at,
message_edit_date,
message_hide_edited,
message_silent,
message_noforwards,
message_reply_to_msg_id,
message_reply_to_peer_type,
message_reply_to_peer_id,
message_reply_to_top_id,
message_reply_to_story_id,
message_quote_text,
message_quote_entities_json,
message_quote_offset,
message_fwd_from_peer_type,
message_fwd_from_peer_id,
message_fwd_from_name,
message_fwd_date,
message_fwd_saved_from_peer_type,
message_fwd_saved_from_peer_id,
message_fwd_saved_from_msg_id,
message_saved_peer_type,
message_saved_peer_id,
message_media_unread,
message_reaction_unread,
message_via_bot_id,
message_grouped_id,
message_effect,
message_reply_markup_json,
message_rich_message_json,
message_pinned
FROM paged
ORDER BY
pinned DESC,
CASE WHEN pinned THEN COALESCE(pinned_order, 0) ELSE 0 END DESC,
top_message_date DESC,
top_message_id DESC,
peer_id DESC
LIMIT sqlc.arg(limit_count);
-- name: ListDialogSummaryByUser :many
SELECT
d.peer_type,
d.peer_id,
d.folder_id,
d.top_message_id,
d.top_message_date,
d.read_inbox_max_id,
d.read_outbox_max_id,
d.unread_count,
d.unread_mentions_count,
d.unread_reactions_count,
d.ttl_period,
d.theme_emoticon,
d.has_scheduled,
d.pinned,
d.pinned_order,
d.unread_mark,
d.hidden_peer_settings_bar
FROM dialogs d
LEFT JOIN contacts c ON d.peer_type = 'user' AND c.user_id = d.user_id AND c.contact_user_id = d.peer_id
WHERE d.user_id = $1
AND (
(NOT sqlc.arg(has_folder_id)::boolean AND d.folder_id = 0)
OR (
sqlc.arg(has_folder_id)::boolean
AND sqlc.arg(folder_id)::int < 2
AND d.folder_id = sqlc.arg(folder_id)::int
)
OR (
sqlc.arg(folder_id)::int >= 2
AND NOT (sqlc.arg(folder_exclude_archived)::boolean AND d.folder_id = 1)
AND NOT (sqlc.arg(folder_exclude_read)::boolean AND d.unread_count = 0 AND NOT d.unread_mark)
AND NOT EXISTS (
SELECT 1
FROM (
SELECT fpt.peer_type, fpi.peer_id
FROM unnest(sqlc.arg(folder_exclude_peer_types)::text[]) WITH ORDINALITY AS fpt(peer_type, ord)
JOIN unnest(sqlc.arg(folder_exclude_peer_ids)::bigint[]) WITH ORDINALITY AS fpi(peer_id, ord) USING (ord)
) fp
WHERE fp.peer_type = d.peer_type AND fp.peer_id = d.peer_id
)
AND (
EXISTS (
SELECT 1
FROM (
SELECT fpt.peer_type, fpi.peer_id
FROM unnest(sqlc.arg(folder_include_peer_types)::text[]) WITH ORDINALITY AS fpt(peer_type, ord)
JOIN unnest(sqlc.arg(folder_include_peer_ids)::bigint[]) WITH ORDINALITY AS fpi(peer_id, ord) USING (ord)
) fp
WHERE fp.peer_type = d.peer_type AND fp.peer_id = d.peer_id
)
OR EXISTS (
SELECT 1
FROM (
SELECT fpt.peer_type, fpi.peer_id
FROM unnest(sqlc.arg(folder_pinned_peer_types)::text[]) WITH ORDINALITY AS fpt(peer_type, ord)
JOIN unnest(sqlc.arg(folder_pinned_peer_ids)::bigint[]) WITH ORDINALITY AS fpi(peer_id, ord) USING (ord)
) fp
WHERE fp.peer_type = d.peer_type AND fp.peer_id = d.peer_id
)
OR (sqlc.arg(folder_contacts)::boolean AND c.contact_user_id IS NOT NULL)
OR (sqlc.arg(folder_non_contacts)::boolean AND c.contact_user_id IS NULL)
)
)
)
AND (NOT sqlc.arg(pinned_only)::boolean OR d.pinned)
AND (NOT sqlc.arg(exclude_pinned)::boolean OR NOT d.pinned)
ORDER BY
d.pinned DESC,
CASE WHEN d.pinned THEN COALESCE(d.pinned_order, 0) ELSE 0 END DESC,
d.top_message_date DESC,
d.top_message_id DESC,
d.peer_id DESC;
-- name: ListDialogsByPeers :many
WITH requested AS (
SELECT
(sqlc.arg(peer_types)::text[])[i] AS peer_type,
(sqlc.arg(peer_ids)::bigint[])[i] AS peer_id,
i::int AS ord
FROM generate_subscripts(sqlc.arg(peer_ids)::bigint[], 1) AS g(i)
WHERE i <= cardinality(sqlc.arg(peer_types)::text[])
),
deduped AS (
SELECT DISTINCT ON (peer_type, peer_id)
peer_type,
peer_id,
ord
FROM requested
ORDER BY peer_type, peer_id, ord
),
base AS (
SELECT
sqlc.arg(user_id)::bigint AS user_id,
r.peer_type,
r.peer_id,
COALESCE(d.folder_id, 0)::int AS folder_id,
COALESCE(d.top_message_id, 0)::int AS top_message_id,
COALESCE(d.top_message_date, 0)::int AS top_message_date,
COALESCE(d.read_inbox_max_id, 0)::int AS read_inbox_max_id,
COALESCE(d.read_outbox_max_id, 0)::int AS read_outbox_max_id,
COALESCE(d.unread_count, 0)::int AS unread_count,
COALESCE(d.unread_mentions_count, 0)::int AS unread_mentions_count,
COALESCE(d.unread_reactions_count, 0)::int AS unread_reactions_count,
COALESCE(d.ttl_period, 0)::int AS ttl_period,
COALESCE(d.theme_emoticon, '')::text AS theme_emoticon,
COALESCE(d.has_scheduled, false)::boolean AS has_scheduled,
COALESCE(d.pinned, false)::boolean AS pinned,
COALESCE(d.pinned_order, 0)::int AS pinned_order,
COALESCE(d.unread_mark, false)::boolean AS unread_mark,
COALESCE(d.hidden_peer_settings_bar, false)::boolean AS hidden_peer_settings_bar,
COALESCE(u.id, 0)::bigint AS peer_user_id,
COALESCE(u.access_hash, 0)::bigint AS peer_access_hash,
COALESCE(NULLIF(c.contact_phone, ''), u.phone, '')::text AS peer_phone,
COALESCE(NULLIF(c.contact_first_name, ''), u.first_name, '')::text AS peer_first_name,
COALESCE(c.contact_last_name, u.last_name, '')::text AS peer_last_name,
COALESCE(u.username, '')::text AS peer_username,
COALESCE(u.country_code, '')::text AS peer_country_code,
COALESCE(u.verified, false)::boolean AS peer_verified,
COALESCE(u.support, false)::boolean AS peer_support,
COALESCE(u.is_bot, false)::boolean AS peer_is_bot,
COALESCE(u.bot_info_version, 0)::int AS peer_bot_info_version,
COALESCE(EXTRACT(EPOCH FROM u.premium_expires_at), 0)::bigint AS peer_premium_until,
COALESCE(u.emoji_status_document_id, 0)::bigint AS peer_emoji_status_document_id,
COALESCE(u.emoji_status_until, 0)::bigint AS peer_emoji_status_until,
COALESCE(u.last_seen_at, 0)::bigint AS peer_last_seen_at,
(c.contact_user_id IS NOT NULL)::boolean AS peer_contact,
COALESCE(c.mutual, false)::boolean AS peer_mutual,
COALESCE(m.box_id, 0)::int AS message_id,
COALESCE(m.private_message_id, 0)::bigint AS message_private_message_id,
COALESCE(m.from_user_id, 0)::bigint AS message_from_user_id,
COALESCE(m.message_date, 0)::int AS message_date,
COALESCE(m.outgoing, false)::boolean AS message_outgoing,
COALESCE(m.body, '')::text AS message_body,
COALESCE(m.entities::text, '[]')::text AS message_entities_json,
COALESCE(m.media::text, '{}')::text AS message_media_json,
COALESCE(m.ttl_period, 0)::int AS message_ttl_period,
COALESCE(m.expires_at, 0)::int AS message_expires_at,
COALESCE(m.edit_date, 0)::int AS message_edit_date,
COALESCE(m.hide_edited, false)::boolean AS message_hide_edited,
COALESCE(m.silent, false)::boolean AS message_silent,
COALESCE(m.noforwards, false)::boolean AS message_noforwards,
COALESCE(m.reply_to_msg_id, 0)::int AS message_reply_to_msg_id,
COALESCE(m.reply_to_peer_type, '')::text AS message_reply_to_peer_type,
COALESCE(m.reply_to_peer_id, 0)::bigint AS message_reply_to_peer_id,
COALESCE(m.reply_to_top_id, 0)::int AS message_reply_to_top_id,
COALESCE(m.reply_to_story_id, 0)::int AS message_reply_to_story_id,
COALESCE(m.quote_text, '')::text AS message_quote_text,
COALESCE(m.quote_entities::text, '[]')::text AS message_quote_entities_json,
COALESCE(m.quote_offset, 0)::int AS message_quote_offset,
COALESCE(m.fwd_from_peer_type, '')::text AS message_fwd_from_peer_type,
COALESCE(m.fwd_from_peer_id, 0)::bigint AS message_fwd_from_peer_id,
COALESCE(m.fwd_from_name, '')::text AS message_fwd_from_name,
COALESCE(m.fwd_date, 0)::int AS message_fwd_date,
COALESCE(m.fwd_saved_from_peer_type, '')::text AS message_fwd_saved_from_peer_type,
COALESCE(m.fwd_saved_from_peer_id, 0)::bigint AS message_fwd_saved_from_peer_id,
COALESCE(m.fwd_saved_from_msg_id, 0)::int AS message_fwd_saved_from_msg_id,
COALESCE(m.saved_peer_type, '')::text AS message_saved_peer_type,
COALESCE(m.saved_peer_id, 0)::bigint AS message_saved_peer_id,
COALESCE(m.media_unread, false)::boolean AS message_media_unread,
COALESCE(m.reaction_unread, false)::boolean AS message_reaction_unread,
COALESCE(m.via_bot_id, 0)::bigint AS message_via_bot_id,
COALESCE(m.grouped_id, 0)::bigint AS message_grouped_id,
COALESCE(m.effect, 0)::bigint AS message_effect,
COALESCE(m.reply_markup::text, '{}')::text AS message_reply_markup_json,
COALESCE(m.rich_message::text, '{}')::text AS message_rich_message_json,
COALESCE(m.pinned, false)::boolean AS message_pinned,
r.ord
FROM deduped r
LEFT JOIN dialogs d
ON d.user_id = sqlc.arg(user_id)::bigint
AND d.peer_type = r.peer_type
AND d.peer_id = r.peer_id
LEFT JOIN users u ON r.peer_type = 'user' AND u.id = r.peer_id
LEFT JOIN contacts c ON r.peer_type = 'user' AND c.user_id = sqlc.arg(user_id)::bigint AND c.contact_user_id = r.peer_id
LEFT JOIN message_boxes m ON m.owner_user_id = sqlc.arg(user_id)::bigint AND m.box_id = d.top_message_id AND NOT m.deleted
)
SELECT
user_id,
peer_type::text AS peer_type,
peer_id::bigint AS peer_id,
folder_id,
top_message_id,
top_message_date,
read_inbox_max_id,
read_outbox_max_id,
unread_count,
unread_mentions_count,
unread_reactions_count,
ttl_period,
theme_emoticon,
has_scheduled,
pinned,
pinned_order,
unread_mark,
hidden_peer_settings_bar,
peer_user_id,
peer_access_hash,
peer_phone,
peer_first_name,
peer_last_name,
peer_username,
peer_country_code,
peer_verified,
peer_support,
peer_is_bot,
peer_bot_info_version,
peer_premium_until,
peer_emoji_status_document_id,
peer_emoji_status_until,
peer_last_seen_at,
peer_contact,
peer_mutual,
message_id,
message_private_message_id,
message_from_user_id,
message_date,
message_outgoing,
message_body,
message_entities_json,
message_media_json,
message_ttl_period,
message_expires_at,
message_edit_date,
message_hide_edited,
message_silent,
message_noforwards,
message_reply_to_msg_id,
message_reply_to_peer_type,
message_reply_to_peer_id,
message_reply_to_top_id,
message_reply_to_story_id,
message_quote_text,
message_quote_entities_json,
message_quote_offset,
message_fwd_from_peer_type,
message_fwd_from_peer_id,
message_fwd_from_name,
message_fwd_date,
message_fwd_saved_from_peer_type,
message_fwd_saved_from_peer_id,
message_fwd_saved_from_msg_id,
message_saved_peer_type,
message_saved_peer_id,
message_media_unread,
message_reaction_unread,
message_via_bot_id,
message_grouped_id,
message_effect,
message_reply_markup_json,
message_rich_message_json,
message_pinned
FROM base
ORDER BY ord;
-- name: UpsertDialog :exec
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,
unread_mark
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12
)
ON CONFLICT (user_id, peer_type, peer_id) DO UPDATE SET
top_message_id = EXCLUDED.top_message_id,
top_message_date = EXCLUDED.top_message_date,
read_inbox_max_id = EXCLUDED.read_inbox_max_id,
read_outbox_max_id = EXCLUDED.read_outbox_max_id,
unread_count = EXCLUDED.unread_count,
unread_mentions_count = EXCLUDED.unread_mentions_count,
unread_reactions_count = EXCLUDED.unread_reactions_count,
pinned = EXCLUDED.pinned,
unread_mark = EXCLUDED.unread_mark,
updated_at = now();
-- name: UpsertOutboxDialog :exec
-- 发送方向该会话发出消息即视为已知晓内容:清除手动未读标记,
-- 与 channel 发送路径、readHistory 清除语义对齐。
INSERT INTO dialogs (
user_id,
peer_type,
peer_id,
top_message_id,
top_message_date,
unread_count
) VALUES (
$1, $2, $3, $4, $5, 0
)
ON CONFLICT (user_id, peer_type, peer_id) DO UPDATE SET
top_message_id = EXCLUDED.top_message_id,
top_message_date = EXCLUDED.top_message_date,
unread_mark = false,
updated_at = now();
-- name: UpsertInboxDialog :exec
INSERT INTO dialogs (
user_id,
peer_type,
peer_id,
top_message_id,
top_message_date,
unread_count
) VALUES (
$1, $2, $3, $4, $5, 1
)
ON CONFLICT (user_id, peer_type, peer_id) DO UPDATE SET
top_message_id = GREATEST(dialogs.top_message_id, EXCLUDED.top_message_id),
top_message_date = CASE
WHEN EXCLUDED.top_message_id >= dialogs.top_message_id THEN EXCLUDED.top_message_date
ELSE dialogs.top_message_date
END,
unread_count = (
SELECT COUNT(*)::int
FROM message_boxes m
WHERE m.owner_user_id = dialogs.user_id
AND m.peer_type = dialogs.peer_type
AND m.peer_id = dialogs.peer_id
AND NOT m.deleted
AND NOT m.outgoing
AND m.box_id > dialogs.read_inbox_max_id
AND m.box_id <= GREATEST(dialogs.top_message_id, EXCLUDED.top_message_id)
),
updated_at = now();
-- name: MarkDialogRead :one
WITH target AS (
SELECT
d.user_id,
d.peer_type,
d.peer_id,
d.top_message_id,
d.read_inbox_max_id,
d.unread_count,
LEAST(
d.top_message_id,
CASE WHEN sqlc.arg(max_id)::int > 0 THEN sqlc.arg(max_id)::int ELSE d.top_message_id END
)::int AS requested_read_max_id
FROM dialogs d
WHERE d.user_id = $1
AND d.peer_type = $2
AND d.peer_id = $3
),
updated AS (
UPDATE dialogs d
SET
read_inbox_max_id = GREATEST(d.read_inbox_max_id, target.requested_read_max_id),
unread_count = (
SELECT count(*)::int
FROM message_boxes m
WHERE m.owner_user_id = d.user_id
AND m.peer_type = d.peer_type
AND m.peer_id = d.peer_id
AND NOT m.deleted
AND NOT m.outgoing
AND m.box_id > GREATEST(d.read_inbox_max_id, target.requested_read_max_id)
),
unread_mark = false,
unread_mentions_count = 0,
updated_at = now()
FROM target
WHERE d.user_id = target.user_id
AND d.peer_type = target.peer_type
AND d.peer_id = target.peer_id
RETURNING
d.user_id,
d.peer_type,
d.peer_id,
d.read_inbox_max_id,
d.unread_count,
(
target.unread_count > 0
OR target.requested_read_max_id > target.read_inbox_max_id
)::boolean AS changed
)
SELECT
user_id,
peer_type,
peer_id,
read_inbox_max_id,
unread_count,
changed
FROM updated;
-- name: SetDialogPinned :one
-- 置顶顺序在 dialog 当前 folder0 主列表/1 归档)内独立分配,
-- 返回 folder_id 供 updateDialogPinned.folder_id 使用。
WITH target AS (
SELECT d.folder_id
FROM dialogs d
WHERE d.user_id = sqlc.arg(user_id)::bigint
AND d.peer_type = sqlc.arg(peer_type)::text
AND d.peer_id = sqlc.arg(peer_id)::bigint
),
next_order AS (
-- order 空间跨 dialogs/channel_dialogs 两表统一(合并层按 pinned_order
-- 全局排序),但仅在目标会话所在 folder 内取最大值。
SELECT GREATEST(
COALESCE((
SELECT MAX(d.pinned_order)
FROM dialogs d, target t
WHERE d.user_id = sqlc.arg(user_id)::bigint
AND d.pinned
AND d.folder_id = t.folder_id
), 0),
COALESCE((
SELECT MAX(cd.pinned_order)
FROM channel_dialogs cd, target t
WHERE cd.user_id = sqlc.arg(user_id)::bigint
AND cd.pinned
AND COALESCE(cd.folder_id, 0) = t.folder_id
), 0)
)::int + 1 AS value
),
updated AS (
UPDATE dialogs d
SET pinned = sqlc.arg(pinned)::boolean,
pinned_order = CASE
WHEN sqlc.arg(pinned)::boolean THEN
CASE WHEN d.pinned_order > 0 THEN d.pinned_order ELSE next_order.value END
ELSE 0
END,
updated_at = now()
FROM next_order
WHERE d.user_id = sqlc.arg(user_id)::bigint
AND d.peer_type = sqlc.arg(peer_type)::text
AND d.peer_id = sqlc.arg(peer_id)::bigint
RETURNING d.folder_id
)
SELECT
EXISTS (SELECT 1 FROM updated)::boolean AS changed,
COALESCE((SELECT folder_id FROM updated), 0)::int AS folder_id;
-- name: SetDialogUnreadMark :one
-- 值守卫 IS DISTINCT FROM重复标记同一值不应算 changed否则上层会记一条
-- 幽灵 durable 未读标事件并多推一次 update对齐频道侧 SetChannelDialogUnreadMark
WITH updated AS (
UPDATE dialogs d
SET unread_mark = sqlc.arg(unread)::boolean,
updated_at = now()
WHERE d.user_id = sqlc.arg(user_id)::bigint
AND d.peer_type = sqlc.arg(peer_type)::text
AND d.peer_id = sqlc.arg(peer_id)::bigint
AND d.unread_mark IS DISTINCT FROM sqlc.arg(unread)::boolean
RETURNING d.user_id
)
SELECT EXISTS (SELECT 1 FROM updated)::boolean AS changed;
-- name: ListDialogUnreadMarks :many
SELECT
peer_type,
peer_id
FROM dialogs
WHERE user_id = $1
AND unread_mark
ORDER BY top_message_date DESC, top_message_id DESC, peer_id DESC;
-- name: SetPeerSettingsBarHidden :one
WITH updated AS (
UPDATE dialogs d
SET hidden_peer_settings_bar = true,
updated_at = now()
WHERE d.user_id = sqlc.arg(user_id)::bigint
AND d.peer_type = sqlc.arg(peer_type)::text
AND d.peer_id = sqlc.arg(peer_id)::bigint
RETURNING d.user_id
)
SELECT EXISTS (SELECT 1 FROM updated)::boolean AS changed;
-- name: GetPeerSettingsBarHidden :one
SELECT hidden_peer_settings_bar
FROM dialogs
WHERE user_id = $1
AND peer_type = $2
AND peer_id = $3;
-- name: ReorderPinnedDialogs :exec
WITH requested AS (
SELECT
(sqlc.arg(peer_types)::text[])[i] AS peer_type,
(sqlc.arg(peer_ids)::bigint[])[i] AS peer_id,
i::int AS pos
FROM generate_subscripts(sqlc.arg(peer_ids)::bigint[], 1) AS g(i)
WHERE i <= cardinality(sqlc.arg(peer_types)::text[])
),
deduped AS (
-- 请求数组首位是置顶区最顶部;统一编码为"值越大越靠前",与频道
-- reorder、toggle 的 MAX+1 分配以及合并层排序方向一致。
SELECT DISTINCT ON (peer_type, peer_id)
peer_type,
peer_id,
(cardinality(sqlc.arg(peer_ids)::bigint[]) - pos + 1)::int AS ord
FROM requested
ORDER BY peer_type, peer_id, pos
)
UPDATE dialogs d
SET pinned = true,
pinned_order = deduped.ord,
updated_at = now()
FROM deduped
WHERE d.user_id = sqlc.arg(user_id)::bigint
AND d.peer_type = deduped.peer_type
AND d.peer_id = deduped.peer_id
AND d.folder_id = sqlc.arg(folder_id)::int;
-- name: EditDialogPeerFolders :exec
WITH requested AS (
SELECT
(sqlc.arg(peer_types)::text[])[i] AS peer_type,
(sqlc.arg(peer_ids)::bigint[])[i] AS peer_id,
(sqlc.arg(folder_ids)::int[])[i] AS folder_id
FROM generate_subscripts(sqlc.arg(peer_ids)::bigint[], 1) AS g(i)
WHERE i <= cardinality(sqlc.arg(peer_types)::text[])
AND i <= cardinality(sqlc.arg(folder_ids)::int[])
),
deduped AS (
SELECT DISTINCT ON (peer_type, peer_id)
peer_type,
peer_id,
folder_id
FROM requested
WHERE folder_id IN (0, 1)
ORDER BY peer_type, peer_id
)
UPDATE dialogs d
-- 换 folder 时清 pinnedTDesktop History::setFolderPointer 在归档/还原时
-- 本地无条件 unpin服务端保留旧 pin 会在下次 getDialogs 时把状态漂移回来。
SET folder_id = deduped.folder_id,
pinned = CASE WHEN d.folder_id <> deduped.folder_id THEN false ELSE d.pinned END,
pinned_order = CASE WHEN d.folder_id <> deduped.folder_id THEN 0 ELSE d.pinned_order END,
updated_at = now()
FROM deduped
WHERE d.user_id = sqlc.arg(user_id)::bigint
AND d.peer_type = deduped.peer_type
AND d.peer_id = deduped.peer_id;
-- name: SetDialogArchivePinned :one
-- archive folder 行本身的置顶状态toggleDialogPin(inputDialogPeerFolder))。
-- 无行时官方默认视为 pinned=true所以这里总是落一行显式值。
WITH current AS (
SELECT archive_pinned
FROM dialog_filter_settings
WHERE user_id = sqlc.arg(user_id)::bigint
),
upserted AS (
INSERT INTO dialog_filter_settings (user_id, archive_pinned)
VALUES (sqlc.arg(user_id)::bigint, sqlc.arg(pinned)::boolean)
ON CONFLICT (user_id) DO UPDATE SET
archive_pinned = EXCLUDED.archive_pinned,
updated_at = now()
RETURNING archive_pinned
)
SELECT (COALESCE((SELECT archive_pinned FROM current), true) IS DISTINCT FROM sqlc.arg(pinned)::boolean)::boolean AS changed
FROM upserted;
-- name: GetDialogArchivePinned :one
SELECT archive_pinned
FROM dialog_filter_settings
WHERE user_id = $1;
-- name: CountArchiveUnreadDialogs :one
-- 归档folder_id=1未读聚合有未读或手动标记未读的会话数 + 未读消息总数。
SELECT
COUNT(*) FILTER (WHERE unread_count > 0 OR unread_mark)::int AS unread_peers,
COALESCE(SUM(unread_count), 0)::int AS unread_messages
FROM dialogs
WHERE user_id = $1
AND folder_id = 1;
-- name: ClearPinnedDialogsNotInOrder :exec
WITH requested AS (
SELECT
(sqlc.arg(peer_types)::text[])[i] AS peer_type,
(sqlc.arg(peer_ids)::bigint[])[i] AS peer_id
FROM generate_subscripts(sqlc.arg(peer_ids)::bigint[], 1) AS g(i)
WHERE i <= cardinality(sqlc.arg(peer_types)::text[])
)
UPDATE dialogs d
SET pinned = false,
pinned_order = 0,
updated_at = now()
WHERE d.user_id = sqlc.arg(user_id)::bigint
AND d.pinned
AND d.folder_id = sqlc.arg(folder_id)::int
AND NOT EXISTS (
SELECT 1
FROM requested r
WHERE r.peer_type = d.peer_type
AND r.peer_id = d.peer_id
);
-- name: RefreshDialogAfterMessageDelete :exec
UPDATE dialogs d
SET
top_message_id = sqlc.arg(top_message_id)::int,
top_message_date = sqlc.arg(top_message_date)::int,
unread_count = (
SELECT COUNT(*)::int
FROM message_boxes m
WHERE m.owner_user_id = d.user_id
AND m.peer_type = d.peer_type
AND m.peer_id = d.peer_id
AND NOT m.deleted
AND NOT m.outgoing
AND m.box_id > d.read_inbox_max_id
),
unread_mentions_count = 0,
unread_reactions_count = (
SELECT COUNT(*)::int
FROM message_boxes m2
WHERE m2.owner_user_id = d.user_id
AND m2.peer_type = d.peer_type
AND m2.peer_id = d.peer_id
AND NOT m2.deleted
AND m2.reaction_unread
),
updated_at = now()
WHERE d.user_id = sqlc.arg(user_id)::bigint
AND d.peer_type = sqlc.arg(peer_type)::text
AND d.peer_id = sqlc.arg(peer_id)::bigint;
-- name: DeleteDialogByPeer :exec
WITH dropped_drafts AS (
-- 删除会话同时丢弃该 peer 的云草稿,避免对端重建会话后旧草稿复活。
DELETE FROM dialog_drafts dd
WHERE dd.user_id = sqlc.arg(user_id)::bigint
AND dd.peer_type = sqlc.arg(peer_type)::text
AND dd.peer_id = sqlc.arg(peer_id)::bigint
)
DELETE FROM dialogs d
WHERE d.user_id = sqlc.arg(user_id)::bigint
AND d.peer_type = sqlc.arg(peer_type)::text
AND d.peer_id = sqlc.arg(peer_id)::bigint;
-- name: ListDialogFolders :many
SELECT
filter_id,
is_chatlist,
filter::text AS filter_json
FROM dialog_filters
WHERE user_id = $1
ORDER BY order_value ASC, filter_id ASC;
-- name: GetDialogFolder :one
SELECT
filter_id,
is_chatlist,
filter::text AS filter_json
FROM dialog_filters
WHERE user_id = $1
AND filter_id = $2;
-- name: UpsertDialogFolder :exec
INSERT INTO dialog_filters (
user_id,
filter_id,
is_chatlist,
filter,
order_value
) VALUES (
$1,
$2,
$3,
sqlc.arg(filter_json)::jsonb,
COALESCE(
(SELECT order_value FROM dialog_filters WHERE user_id = $1 AND filter_id = $2),
(SELECT COALESCE(MAX(order_value), 0) + 1 FROM dialog_filters WHERE user_id = $1)
)
)
ON CONFLICT (user_id, filter_id) DO UPDATE SET
is_chatlist = EXCLUDED.is_chatlist,
filter = EXCLUDED.filter,
updated_at = now();
-- name: DeleteDialogFolder :exec
DELETE FROM dialog_filters
WHERE user_id = $1
AND filter_id = $2;
-- name: ReorderDialogFolders :exec
WITH requested AS (
SELECT filter_id, ord::int AS order_value
FROM unnest(sqlc.arg(filter_ids)::int[]) WITH ORDINALITY AS t(filter_id, ord)
),
deduped AS (
SELECT DISTINCT ON (filter_id)
filter_id,
order_value
FROM requested
WHERE filter_id >= 2
ORDER BY filter_id, order_value
)
UPDATE dialog_filters f
SET order_value = deduped.order_value,
updated_at = now()
FROM deduped
WHERE f.user_id = sqlc.arg(user_id)::bigint
AND f.filter_id = deduped.filter_id;
-- name: GetDialogFolderTags :one
SELECT tags_enabled
FROM dialog_filter_settings
WHERE user_id = $1;
-- name: SetDialogFolderTags :exec
INSERT INTO dialog_filter_settings (
user_id,
tags_enabled
) VALUES (
$1,
$2
)
ON CONFLICT (user_id) DO UPDATE SET
tags_enabled = EXCLUDED.tags_enabled,
updated_at = now();
-- name: UpsertDialogDraft :exec
INSERT INTO dialog_drafts (
user_id,
peer_type,
peer_id,
top_message_id,
date,
draft
) VALUES (
$1,
$2,
$3,
$4,
$5,
sqlc.arg(draft_json)::jsonb
)
ON CONFLICT (user_id, peer_type, peer_id, top_message_id) DO UPDATE SET
date = EXCLUDED.date,
draft = EXCLUDED.draft,
updated_at = now();
-- name: DeleteDialogDraft :one
WITH deleted AS (
DELETE FROM dialog_drafts
WHERE user_id = $1
AND peer_type = $2
AND peer_id = $3
AND top_message_id = $4
RETURNING user_id
)
SELECT EXISTS (SELECT 1 FROM deleted)::boolean AS changed;
-- name: ListDialogDrafts :many
SELECT draft::text AS draft_json
FROM dialog_drafts
WHERE user_id = $1
ORDER BY date DESC, peer_type ASC, peer_id DESC, top_message_id DESC
LIMIT sqlc.arg(limit_count);
-- name: ClearDialogDrafts :many
WITH doomed AS (
SELECT d.user_id, d.peer_type, d.peer_id, d.top_message_id
FROM dialog_drafts d
WHERE d.user_id = $1
ORDER BY d.date DESC, d.peer_type ASC, d.peer_id DESC, d.top_message_id DESC
LIMIT sqlc.arg(limit_count)
),
deleted AS (
DELETE FROM dialog_drafts d
USING doomed
WHERE d.user_id = doomed.user_id
AND d.peer_type = doomed.peer_type
AND d.peer_id = doomed.peer_id
AND d.top_message_id = doomed.top_message_id
RETURNING d.draft::text AS draft_json
)
SELECT draft_json
FROM deleted;
-- name: AdvanceDialogReadInboxFloor :exec
UPDATE dialogs
SET read_inbox_max_id = GREATEST(read_inbox_max_id, @read_inbox_max_id::int),
updated_at = now()
WHERE user_id = @user_id
AND peer_type = @peer_type
AND peer_id = @peer_id;