// Code generated by sqlc. DO NOT EDIT. // versions: // sqlc v1.31.1 // source: dialog.sql package sqlcgen import ( "context" ) const advanceDialogReadInboxFloor = `-- name: AdvanceDialogReadInboxFloor :exec UPDATE dialogs SET read_inbox_max_id = GREATEST(read_inbox_max_id, $1::int), updated_at = now() WHERE user_id = $2 AND peer_type = $3 AND peer_id = $4 ` type AdvanceDialogReadInboxFloorParams struct { ReadInboxMaxID int32 UserID int64 PeerType string PeerID int64 } func (q *Queries) AdvanceDialogReadInboxFloor(ctx context.Context, arg AdvanceDialogReadInboxFloorParams) error { _, err := q.db.Exec(ctx, advanceDialogReadInboxFloor, arg.ReadInboxMaxID, arg.UserID, arg.PeerType, arg.PeerID, ) return err } const clearDialogDrafts = `-- 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 $2 ), 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 ` type ClearDialogDraftsParams struct { UserID int64 LimitCount int32 } func (q *Queries) ClearDialogDrafts(ctx context.Context, arg ClearDialogDraftsParams) ([]string, error) { rows, err := q.db.Query(ctx, clearDialogDrafts, arg.UserID, arg.LimitCount) if err != nil { return nil, err } defer rows.Close() var items []string for rows.Next() { var draft_json string if err := rows.Scan(&draft_json); err != nil { return nil, err } items = append(items, draft_json) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const clearPinnedDialogsNotInOrder = `-- name: ClearPinnedDialogsNotInOrder :exec WITH requested AS ( SELECT ($3::text[])[i] AS peer_type, ($4::bigint[])[i] AS peer_id FROM generate_subscripts($4::bigint[], 1) AS g(i) WHERE i <= cardinality($3::text[]) ) UPDATE dialogs d SET pinned = false, pinned_order = 0, updated_at = now() WHERE d.user_id = $1::bigint AND d.pinned AND d.folder_id = $2::int AND NOT EXISTS ( SELECT 1 FROM requested r WHERE r.peer_type = d.peer_type AND r.peer_id = d.peer_id ) ` type ClearPinnedDialogsNotInOrderParams struct { UserID int64 FolderID int32 PeerTypes []string PeerIds []int64 } func (q *Queries) ClearPinnedDialogsNotInOrder(ctx context.Context, arg ClearPinnedDialogsNotInOrderParams) error { _, err := q.db.Exec(ctx, clearPinnedDialogsNotInOrder, arg.UserID, arg.FolderID, arg.PeerTypes, arg.PeerIds, ) return err } const countArchiveUnreadDialogs = `-- name: CountArchiveUnreadDialogs :one 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 ` type CountArchiveUnreadDialogsRow struct { UnreadPeers int32 UnreadMessages int32 } // 归档(folder_id=1)未读聚合:有未读或手动标记未读的会话数 + 未读消息总数。 func (q *Queries) CountArchiveUnreadDialogs(ctx context.Context, userID int64) (CountArchiveUnreadDialogsRow, error) { row := q.db.QueryRow(ctx, countArchiveUnreadDialogs, userID) var i CountArchiveUnreadDialogsRow err := row.Scan(&i.UnreadPeers, &i.UnreadMessages) return i, err } const deleteDialogByPeer = `-- name: DeleteDialogByPeer :exec WITH dropped_drafts AS ( -- 删除会话同时丢弃该 peer 的云草稿,避免对端重建会话后旧草稿复活。 DELETE FROM dialog_drafts dd WHERE dd.user_id = $1::bigint AND dd.peer_type = $2::text AND dd.peer_id = $3::bigint ) DELETE FROM dialogs d WHERE d.user_id = $1::bigint AND d.peer_type = $2::text AND d.peer_id = $3::bigint ` type DeleteDialogByPeerParams struct { UserID int64 PeerType string PeerID int64 } func (q *Queries) DeleteDialogByPeer(ctx context.Context, arg DeleteDialogByPeerParams) error { _, err := q.db.Exec(ctx, deleteDialogByPeer, arg.UserID, arg.PeerType, arg.PeerID) return err } const deleteDialogDraft = `-- 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 ` type DeleteDialogDraftParams struct { UserID int64 PeerType string PeerID int64 TopMessageID int32 } func (q *Queries) DeleteDialogDraft(ctx context.Context, arg DeleteDialogDraftParams) (bool, error) { row := q.db.QueryRow(ctx, deleteDialogDraft, arg.UserID, arg.PeerType, arg.PeerID, arg.TopMessageID, ) var changed bool err := row.Scan(&changed) return changed, err } const deleteDialogFolder = `-- name: DeleteDialogFolder :exec DELETE FROM dialog_filters WHERE user_id = $1 AND filter_id = $2 ` type DeleteDialogFolderParams struct { UserID int64 FilterID int32 } func (q *Queries) DeleteDialogFolder(ctx context.Context, arg DeleteDialogFolderParams) error { _, err := q.db.Exec(ctx, deleteDialogFolder, arg.UserID, arg.FilterID) return err } const editDialogPeerFolders = `-- name: EditDialogPeerFolders :exec WITH requested AS ( SELECT ($2::text[])[i] AS peer_type, ($3::bigint[])[i] AS peer_id, ($4::int[])[i] AS folder_id FROM generate_subscripts($3::bigint[], 1) AS g(i) WHERE i <= cardinality($2::text[]) AND i <= cardinality($4::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 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 = $1::bigint AND d.peer_type = deduped.peer_type AND d.peer_id = deduped.peer_id ` type EditDialogPeerFoldersParams struct { UserID int64 PeerTypes []string PeerIds []int64 FolderIds []int32 } // 换 folder 时清 pinned:TDesktop History::setFolderPointer 在归档/还原时 // 本地无条件 unpin,服务端保留旧 pin 会在下次 getDialogs 时把状态漂移回来。 func (q *Queries) EditDialogPeerFolders(ctx context.Context, arg EditDialogPeerFoldersParams) error { _, err := q.db.Exec(ctx, editDialogPeerFolders, arg.UserID, arg.PeerTypes, arg.PeerIds, arg.FolderIds, ) return err } const getDialogArchivePinned = `-- name: GetDialogArchivePinned :one SELECT archive_pinned FROM dialog_filter_settings WHERE user_id = $1 ` func (q *Queries) GetDialogArchivePinned(ctx context.Context, userID int64) (bool, error) { row := q.db.QueryRow(ctx, getDialogArchivePinned, userID) var archive_pinned bool err := row.Scan(&archive_pinned) return archive_pinned, err } const getDialogFolder = `-- name: GetDialogFolder :one SELECT filter_id, is_chatlist, filter::text AS filter_json FROM dialog_filters WHERE user_id = $1 AND filter_id = $2 ` type GetDialogFolderParams struct { UserID int64 FilterID int32 } type GetDialogFolderRow struct { FilterID int32 IsChatlist bool FilterJson string } func (q *Queries) GetDialogFolder(ctx context.Context, arg GetDialogFolderParams) (GetDialogFolderRow, error) { row := q.db.QueryRow(ctx, getDialogFolder, arg.UserID, arg.FilterID) var i GetDialogFolderRow err := row.Scan(&i.FilterID, &i.IsChatlist, &i.FilterJson) return i, err } const getDialogFolderTags = `-- name: GetDialogFolderTags :one SELECT tags_enabled FROM dialog_filter_settings WHERE user_id = $1 ` func (q *Queries) GetDialogFolderTags(ctx context.Context, userID int64) (bool, error) { row := q.db.QueryRow(ctx, getDialogFolderTags, userID) var tags_enabled bool err := row.Scan(&tags_enabled) return tags_enabled, err } const getPeerSettingsBarHidden = `-- name: GetPeerSettingsBarHidden :one SELECT hidden_peer_settings_bar FROM dialogs WHERE user_id = $1 AND peer_type = $2 AND peer_id = $3 ` type GetPeerSettingsBarHiddenParams struct { UserID int64 PeerType string PeerID int64 } func (q *Queries) GetPeerSettingsBarHidden(ctx context.Context, arg GetPeerSettingsBarHiddenParams) (bool, error) { row := q.db.QueryRow(ctx, getPeerSettingsBarHidden, arg.UserID, arg.PeerType, arg.PeerID) var hidden_peer_settings_bar bool err := row.Scan(&hidden_peer_settings_bar) return hidden_peer_settings_bar, err } const listDialogDrafts = `-- 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 $2 ` type ListDialogDraftsParams struct { UserID int64 LimitCount int32 } func (q *Queries) ListDialogDrafts(ctx context.Context, arg ListDialogDraftsParams) ([]string, error) { rows, err := q.db.Query(ctx, listDialogDrafts, arg.UserID, arg.LimitCount) if err != nil { return nil, err } defer rows.Close() var items []string for rows.Next() { var draft_json string if err := rows.Scan(&draft_json); err != nil { return nil, err } items = append(items, draft_json) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listDialogDraftsByPeers = `-- name: ListDialogDraftsByPeers :many SELECT d.draft::text AS draft_json FROM dialog_drafts d WHERE d.user_id = $1 AND d.top_message_id = 0 AND EXISTS ( SELECT 1 FROM unnest($2::text[]) WITH ORDINALITY AS requested_type(peer_type, ord) JOIN unnest($3::bigint[]) WITH ORDINALITY AS requested_id(peer_id, ord) USING (ord) WHERE requested_type.peer_type = d.peer_type AND requested_id.peer_id = d.peer_id ) ` type ListDialogDraftsByPeersParams struct { UserID int64 PeerTypes []string PeerIds []int64 } func (q *Queries) ListDialogDraftsByPeers(ctx context.Context, arg ListDialogDraftsByPeersParams) ([]string, error) { rows, err := q.db.Query(ctx, listDialogDraftsByPeers, arg.UserID, arg.PeerTypes, arg.PeerIds) if err != nil { return nil, err } defer rows.Close() var items []string for rows.Next() { var draft_json string if err := rows.Scan(&draft_json); err != nil { return nil, err } items = append(items, draft_json) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listDialogFolders = `-- 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 ` type ListDialogFoldersRow struct { FilterID int32 IsChatlist bool FilterJson string } func (q *Queries) ListDialogFolders(ctx context.Context, userID int64) ([]ListDialogFoldersRow, error) { rows, err := q.db.Query(ctx, listDialogFolders, userID) if err != nil { return nil, err } defer rows.Close() var items []ListDialogFoldersRow for rows.Next() { var i ListDialogFoldersRow if err := rows.Scan(&i.FilterID, &i.IsChatlist, &i.FilterJson); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listDialogSummaryByUser = `-- 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 $2::boolean AND d.folder_id = 0) OR ( $2::boolean AND $3::int < 2 AND d.folder_id = $3::int ) OR ( $3::int >= 2 AND NOT ($4::boolean AND d.folder_id = 1) AND NOT ($5::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($6::text[]) WITH ORDINALITY AS fpt(peer_type, ord) JOIN unnest($7::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($8::text[]) WITH ORDINALITY AS fpt(peer_type, ord) JOIN unnest($9::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($10::text[]) WITH ORDINALITY AS fpt(peer_type, ord) JOIN unnest($11::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 ($12::boolean AND c.contact_user_id IS NOT NULL) OR ($13::boolean AND c.contact_user_id IS NULL) ) ) ) AND (NOT $14::boolean OR d.pinned) AND (NOT $15::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 ` type ListDialogSummaryByUserParams struct { UserID int64 HasFolderID bool FolderID int32 FolderExcludeArchived bool FolderExcludeRead bool FolderExcludePeerTypes []string FolderExcludePeerIds []int64 FolderIncludePeerTypes []string FolderIncludePeerIds []int64 FolderPinnedPeerTypes []string FolderPinnedPeerIds []int64 FolderContacts bool FolderNonContacts bool PinnedOnly bool ExcludePinned bool } type ListDialogSummaryByUserRow struct { PeerType string PeerID int64 FolderID int32 TopMessageID int32 TopMessageDate int32 ReadInboxMaxID int32 ReadOutboxMaxID int32 UnreadCount int32 UnreadMentionsCount int32 UnreadReactionsCount int32 TtlPeriod int32 ThemeEmoticon string HasScheduled bool Pinned bool PinnedOrder int32 UnreadMark bool HiddenPeerSettingsBar bool } func (q *Queries) ListDialogSummaryByUser(ctx context.Context, arg ListDialogSummaryByUserParams) ([]ListDialogSummaryByUserRow, error) { rows, err := q.db.Query(ctx, listDialogSummaryByUser, arg.UserID, arg.HasFolderID, arg.FolderID, arg.FolderExcludeArchived, arg.FolderExcludeRead, arg.FolderExcludePeerTypes, arg.FolderExcludePeerIds, arg.FolderIncludePeerTypes, arg.FolderIncludePeerIds, arg.FolderPinnedPeerTypes, arg.FolderPinnedPeerIds, arg.FolderContacts, arg.FolderNonContacts, arg.PinnedOnly, arg.ExcludePinned, ) if err != nil { return nil, err } defer rows.Close() var items []ListDialogSummaryByUserRow for rows.Next() { var i ListDialogSummaryByUserRow if err := rows.Scan( &i.PeerType, &i.PeerID, &i.FolderID, &i.TopMessageID, &i.TopMessageDate, &i.ReadInboxMaxID, &i.ReadOutboxMaxID, &i.UnreadCount, &i.UnreadMentionsCount, &i.UnreadReactionsCount, &i.TtlPeriod, &i.ThemeEmoticon, &i.HasScheduled, &i.Pinned, &i.PinnedOrder, &i.UnreadMark, &i.HiddenPeerSettingsBar, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listDialogUnreadMarks = `-- 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 ` type ListDialogUnreadMarksRow struct { PeerType string PeerID int64 } func (q *Queries) ListDialogUnreadMarks(ctx context.Context, userID int64) ([]ListDialogUnreadMarksRow, error) { rows, err := q.db.Query(ctx, listDialogUnreadMarks, userID) if err != nil { return nil, err } defer rows.Close() var items []ListDialogUnreadMarksRow for rows.Next() { var i ListDialogUnreadMarksRow if err := rows.Scan(&i.PeerType, &i.PeerID); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listDialogsByPeers = `-- name: ListDialogsByPeers :many WITH requested AS ( SELECT ($1::text[])[i] AS peer_type, ($2::bigint[])[i] AS peer_id, i::int AS ord FROM generate_subscripts($2::bigint[], 1) AS g(i) WHERE i <= cardinality($1::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 $3::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 = $3::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 = $3::bigint AND c.contact_user_id = r.peer_id LEFT JOIN message_boxes m ON m.owner_user_id = $3::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 ` type ListDialogsByPeersParams struct { PeerTypes []string PeerIds []int64 UserID int64 } type ListDialogsByPeersRow struct { UserID int64 PeerType string PeerID int64 FolderID int32 TopMessageID int32 TopMessageDate int32 ReadInboxMaxID int32 ReadOutboxMaxID int32 UnreadCount int32 UnreadMentionsCount int32 UnreadReactionsCount int32 TtlPeriod int32 ThemeEmoticon string HasScheduled bool Pinned bool PinnedOrder int32 UnreadMark bool HiddenPeerSettingsBar bool PeerUserID int64 PeerAccessHash int64 PeerPhone string PeerFirstName string PeerLastName string PeerUsername string PeerCountryCode string PeerVerified bool PeerSupport bool PeerIsBot bool PeerBotInfoVersion int32 PeerPremiumUntil int64 PeerEmojiStatusDocumentID int64 PeerEmojiStatusUntil int64 PeerLastSeenAt int64 PeerContact bool PeerMutual bool MessageID int32 MessagePrivateMessageID int64 MessageFromUserID int64 MessageDate int32 MessageOutgoing bool MessageBody string MessageEntitiesJson string MessageMediaJson string MessageTtlPeriod int32 MessageExpiresAt int32 MessageEditDate int32 MessageHideEdited bool MessageSilent bool MessageNoforwards bool MessageReplyToMsgID int32 MessageReplyToPeerType string MessageReplyToPeerID int64 MessageReplyToTopID int32 MessageReplyToStoryID int32 MessageQuoteText string MessageQuoteEntitiesJson string MessageQuoteOffset int32 MessageFwdFromPeerType string MessageFwdFromPeerID int64 MessageFwdFromName string MessageFwdDate int32 MessageFwdSavedFromPeerType string MessageFwdSavedFromPeerID int64 MessageFwdSavedFromMsgID int32 MessageSavedPeerType string MessageSavedPeerID int64 MessageMediaUnread bool MessageReactionUnread bool MessageViaBotID int64 MessageGroupedID int64 MessageEffect int64 MessageReplyMarkupJson string MessageRichMessageJson string MessagePinned bool } func (q *Queries) ListDialogsByPeers(ctx context.Context, arg ListDialogsByPeersParams) ([]ListDialogsByPeersRow, error) { rows, err := q.db.Query(ctx, listDialogsByPeers, arg.PeerTypes, arg.PeerIds, arg.UserID) if err != nil { return nil, err } defer rows.Close() var items []ListDialogsByPeersRow for rows.Next() { var i ListDialogsByPeersRow if err := rows.Scan( &i.UserID, &i.PeerType, &i.PeerID, &i.FolderID, &i.TopMessageID, &i.TopMessageDate, &i.ReadInboxMaxID, &i.ReadOutboxMaxID, &i.UnreadCount, &i.UnreadMentionsCount, &i.UnreadReactionsCount, &i.TtlPeriod, &i.ThemeEmoticon, &i.HasScheduled, &i.Pinned, &i.PinnedOrder, &i.UnreadMark, &i.HiddenPeerSettingsBar, &i.PeerUserID, &i.PeerAccessHash, &i.PeerPhone, &i.PeerFirstName, &i.PeerLastName, &i.PeerUsername, &i.PeerCountryCode, &i.PeerVerified, &i.PeerSupport, &i.PeerIsBot, &i.PeerBotInfoVersion, &i.PeerPremiumUntil, &i.PeerEmojiStatusDocumentID, &i.PeerEmojiStatusUntil, &i.PeerLastSeenAt, &i.PeerContact, &i.PeerMutual, &i.MessageID, &i.MessagePrivateMessageID, &i.MessageFromUserID, &i.MessageDate, &i.MessageOutgoing, &i.MessageBody, &i.MessageEntitiesJson, &i.MessageMediaJson, &i.MessageTtlPeriod, &i.MessageExpiresAt, &i.MessageEditDate, &i.MessageHideEdited, &i.MessageSilent, &i.MessageNoforwards, &i.MessageReplyToMsgID, &i.MessageReplyToPeerType, &i.MessageReplyToPeerID, &i.MessageReplyToTopID, &i.MessageReplyToStoryID, &i.MessageQuoteText, &i.MessageQuoteEntitiesJson, &i.MessageQuoteOffset, &i.MessageFwdFromPeerType, &i.MessageFwdFromPeerID, &i.MessageFwdFromName, &i.MessageFwdDate, &i.MessageFwdSavedFromPeerType, &i.MessageFwdSavedFromPeerID, &i.MessageFwdSavedFromMsgID, &i.MessageSavedPeerType, &i.MessageSavedPeerID, &i.MessageMediaUnread, &i.MessageReactionUnread, &i.MessageViaBotID, &i.MessageGroupedID, &i.MessageEffect, &i.MessageReplyMarkupJson, &i.MessageRichMessageJson, &i.MessagePinned, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listDialogsByUser = `-- 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 $3::boolean AND d.folder_id = 0) OR ( $3::boolean AND $4::int < 2 AND d.folder_id = $4::int ) OR ( $4::int >= 2 AND NOT ($5::boolean AND d.folder_id = 1) AND NOT ($6::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($7::text[]) WITH ORDINALITY AS fpt(peer_type, ord) JOIN unnest($8::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($9::text[]) WITH ORDINALITY AS fpt(peer_type, ord) JOIN unnest($10::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($11::text[]) WITH ORDINALITY AS fpt(peer_type, ord) JOIN unnest($12::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 ($13::boolean AND c.contact_user_id IS NOT NULL) OR ($14::boolean AND c.contact_user_id IS NULL) ) ) ) AND (NOT $15::boolean OR d.pinned) AND (NOT $16::boolean OR NOT d.pinned) ), paged AS ( SELECT user_id, peer_type, 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 WHERE ( ($17::int <= 0 AND $18::int <= 0) OR ( $17::int > 0 AND ( top_message_date < $17::int OR ( top_message_date = $17::int AND ( $18::int <= 0 OR top_message_id < $18::int OR ( top_message_id = $18::int AND $19::boolean AND peer_id < $20::bigint ) ) ) ) ) OR ( $17::int <= 0 AND $18::int > 0 AND ( top_message_id < $18::int OR ( top_message_id = $18::int AND $19::boolean AND peer_id < $20::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 $2 ` type ListDialogsByUserParams struct { UserID int64 LimitCount int32 HasFolderID bool FolderID int32 FolderExcludeArchived bool FolderExcludeRead bool FolderExcludePeerTypes []string FolderExcludePeerIds []int64 FolderIncludePeerTypes []string FolderIncludePeerIds []int64 FolderPinnedPeerTypes []string FolderPinnedPeerIds []int64 FolderContacts bool FolderNonContacts bool PinnedOnly bool ExcludePinned bool OffsetDate int32 OffsetID int32 HasOffsetPeer bool OffsetPeerID int64 } type ListDialogsByUserRow struct { UserID int64 PeerType string PeerID int64 FolderID int32 TopMessageID int32 TopMessageDate int32 ReadInboxMaxID int32 ReadOutboxMaxID int32 UnreadCount int32 UnreadMentionsCount int32 UnreadReactionsCount int32 TtlPeriod int32 ThemeEmoticon string HasScheduled bool Pinned bool PinnedOrder int32 UnreadMark bool HiddenPeerSettingsBar bool PeerUserID int64 PeerAccessHash int64 PeerPhone string PeerFirstName string PeerLastName string PeerUsername string PeerCountryCode string PeerVerified bool PeerSupport bool PeerIsBot bool PeerBotInfoVersion int32 PeerPremiumUntil int64 PeerEmojiStatusDocumentID int64 PeerEmojiStatusUntil int64 PeerLastSeenAt int64 PeerContact bool PeerMutual bool MessageID int32 MessagePrivateMessageID int64 MessageFromUserID int64 MessageDate int32 MessageOutgoing bool MessageBody string MessageEntitiesJson string MessageMediaJson string MessageTtlPeriod int32 MessageExpiresAt int32 MessageEditDate int32 MessageHideEdited bool MessageSilent bool MessageNoforwards bool MessageReplyToMsgID int32 MessageReplyToPeerType string MessageReplyToPeerID int64 MessageReplyToTopID int32 MessageReplyToStoryID int32 MessageQuoteText string MessageQuoteEntitiesJson string MessageQuoteOffset int32 MessageFwdFromPeerType string MessageFwdFromPeerID int64 MessageFwdFromName string MessageFwdDate int32 MessageFwdSavedFromPeerType string MessageFwdSavedFromPeerID int64 MessageFwdSavedFromMsgID int32 MessageSavedPeerType string MessageSavedPeerID int64 MessageMediaUnread bool MessageReactionUnread bool MessageViaBotID int64 MessageGroupedID int64 MessageEffect int64 MessageReplyMarkupJson string MessageRichMessageJson string MessagePinned bool } func (q *Queries) ListDialogsByUser(ctx context.Context, arg ListDialogsByUserParams) ([]ListDialogsByUserRow, error) { rows, err := q.db.Query(ctx, listDialogsByUser, arg.UserID, arg.LimitCount, arg.HasFolderID, arg.FolderID, arg.FolderExcludeArchived, arg.FolderExcludeRead, arg.FolderExcludePeerTypes, arg.FolderExcludePeerIds, arg.FolderIncludePeerTypes, arg.FolderIncludePeerIds, arg.FolderPinnedPeerTypes, arg.FolderPinnedPeerIds, arg.FolderContacts, arg.FolderNonContacts, arg.PinnedOnly, arg.ExcludePinned, arg.OffsetDate, arg.OffsetID, arg.HasOffsetPeer, arg.OffsetPeerID, ) if err != nil { return nil, err } defer rows.Close() var items []ListDialogsByUserRow for rows.Next() { var i ListDialogsByUserRow if err := rows.Scan( &i.UserID, &i.PeerType, &i.PeerID, &i.FolderID, &i.TopMessageID, &i.TopMessageDate, &i.ReadInboxMaxID, &i.ReadOutboxMaxID, &i.UnreadCount, &i.UnreadMentionsCount, &i.UnreadReactionsCount, &i.TtlPeriod, &i.ThemeEmoticon, &i.HasScheduled, &i.Pinned, &i.PinnedOrder, &i.UnreadMark, &i.HiddenPeerSettingsBar, &i.PeerUserID, &i.PeerAccessHash, &i.PeerPhone, &i.PeerFirstName, &i.PeerLastName, &i.PeerUsername, &i.PeerCountryCode, &i.PeerVerified, &i.PeerSupport, &i.PeerIsBot, &i.PeerBotInfoVersion, &i.PeerPremiumUntil, &i.PeerEmojiStatusDocumentID, &i.PeerEmojiStatusUntil, &i.PeerLastSeenAt, &i.PeerContact, &i.PeerMutual, &i.MessageID, &i.MessagePrivateMessageID, &i.MessageFromUserID, &i.MessageDate, &i.MessageOutgoing, &i.MessageBody, &i.MessageEntitiesJson, &i.MessageMediaJson, &i.MessageTtlPeriod, &i.MessageExpiresAt, &i.MessageEditDate, &i.MessageHideEdited, &i.MessageSilent, &i.MessageNoforwards, &i.MessageReplyToMsgID, &i.MessageReplyToPeerType, &i.MessageReplyToPeerID, &i.MessageReplyToTopID, &i.MessageReplyToStoryID, &i.MessageQuoteText, &i.MessageQuoteEntitiesJson, &i.MessageQuoteOffset, &i.MessageFwdFromPeerType, &i.MessageFwdFromPeerID, &i.MessageFwdFromName, &i.MessageFwdDate, &i.MessageFwdSavedFromPeerType, &i.MessageFwdSavedFromPeerID, &i.MessageFwdSavedFromMsgID, &i.MessageSavedPeerType, &i.MessageSavedPeerID, &i.MessageMediaUnread, &i.MessageReactionUnread, &i.MessageViaBotID, &i.MessageGroupedID, &i.MessageEffect, &i.MessageReplyMarkupJson, &i.MessageRichMessageJson, &i.MessagePinned, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const markDialogRead = `-- 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 $4::int > 0 THEN $4::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 ` type MarkDialogReadParams struct { UserID int64 PeerType string PeerID int64 MaxID int32 } type MarkDialogReadRow struct { UserID int64 PeerType string PeerID int64 ReadInboxMaxID int32 UnreadCount int32 Changed bool } func (q *Queries) MarkDialogRead(ctx context.Context, arg MarkDialogReadParams) (MarkDialogReadRow, error) { row := q.db.QueryRow(ctx, markDialogRead, arg.UserID, arg.PeerType, arg.PeerID, arg.MaxID, ) var i MarkDialogReadRow err := row.Scan( &i.UserID, &i.PeerType, &i.PeerID, &i.ReadInboxMaxID, &i.UnreadCount, &i.Changed, ) return i, err } const refreshDialogAfterMessageDelete = `-- name: RefreshDialogAfterMessageDelete :exec UPDATE dialogs d SET top_message_id = $1::int, top_message_date = $2::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 = $3::bigint AND d.peer_type = $4::text AND d.peer_id = $5::bigint ` type RefreshDialogAfterMessageDeleteParams struct { TopMessageID int32 TopMessageDate int32 UserID int64 PeerType string PeerID int64 } func (q *Queries) RefreshDialogAfterMessageDelete(ctx context.Context, arg RefreshDialogAfterMessageDeleteParams) error { _, err := q.db.Exec(ctx, refreshDialogAfterMessageDelete, arg.TopMessageID, arg.TopMessageDate, arg.UserID, arg.PeerType, arg.PeerID, ) return err } const reorderDialogFolders = `-- name: ReorderDialogFolders :exec WITH requested AS ( SELECT filter_id, ord::int AS order_value FROM unnest($2::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 = $1::bigint AND f.filter_id = deduped.filter_id ` type ReorderDialogFoldersParams struct { UserID int64 FilterIds []int32 } func (q *Queries) ReorderDialogFolders(ctx context.Context, arg ReorderDialogFoldersParams) error { _, err := q.db.Exec(ctx, reorderDialogFolders, arg.UserID, arg.FilterIds) return err } const reorderPinnedDialogs = `-- name: ReorderPinnedDialogs :exec WITH requested AS ( SELECT ($3::text[])[i] AS peer_type, ($4::bigint[])[i] AS peer_id, i::int AS pos FROM generate_subscripts($4::bigint[], 1) AS g(i) WHERE i <= cardinality($3::text[]) ), deduped AS ( -- 请求数组首位是置顶区最顶部;统一编码为"值越大越靠前",与频道 -- reorder、toggle 的 MAX+1 分配以及合并层排序方向一致。 SELECT DISTINCT ON (peer_type, peer_id) peer_type, peer_id, (cardinality($4::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 = $1::bigint AND d.peer_type = deduped.peer_type AND d.peer_id = deduped.peer_id AND d.folder_id = $2::int ` type ReorderPinnedDialogsParams struct { UserID int64 FolderID int32 PeerTypes []string PeerIds []int64 } func (q *Queries) ReorderPinnedDialogs(ctx context.Context, arg ReorderPinnedDialogsParams) error { _, err := q.db.Exec(ctx, reorderPinnedDialogs, arg.UserID, arg.FolderID, arg.PeerTypes, arg.PeerIds, ) return err } const setDialogArchivePinned = `-- name: SetDialogArchivePinned :one WITH current AS ( SELECT archive_pinned FROM dialog_filter_settings WHERE user_id = $2::bigint ), upserted AS ( INSERT INTO dialog_filter_settings (user_id, archive_pinned) VALUES ($2::bigint, $1::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 $1::boolean)::boolean AS changed FROM upserted ` type SetDialogArchivePinnedParams struct { Pinned bool UserID int64 } // archive folder 行本身的置顶状态(toggleDialogPin(inputDialogPeerFolder))。 // 无行时官方默认视为 pinned=true,所以这里总是落一行显式值。 func (q *Queries) SetDialogArchivePinned(ctx context.Context, arg SetDialogArchivePinnedParams) (bool, error) { row := q.db.QueryRow(ctx, setDialogArchivePinned, arg.Pinned, arg.UserID) var changed bool err := row.Scan(&changed) return changed, err } const setDialogFolderTags = `-- 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() ` type SetDialogFolderTagsParams struct { UserID int64 TagsEnabled bool } func (q *Queries) SetDialogFolderTags(ctx context.Context, arg SetDialogFolderTagsParams) error { _, err := q.db.Exec(ctx, setDialogFolderTags, arg.UserID, arg.TagsEnabled) return err } const setDialogPinned = `-- name: SetDialogPinned :one WITH target AS ( SELECT d.folder_id FROM dialogs d WHERE d.user_id = $1::bigint AND d.peer_type = $2::text AND d.peer_id = $3::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 = $1::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 = $1::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 = $4::boolean, pinned_order = CASE WHEN $4::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 = $1::bigint AND d.peer_type = $2::text AND d.peer_id = $3::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 ` type SetDialogPinnedParams struct { UserID int64 PeerType string PeerID int64 Pinned bool } type SetDialogPinnedRow struct { Changed bool FolderID int32 } // 置顶顺序在 dialog 当前 folder(0 主列表/1 归档)内独立分配, // 返回 folder_id 供 updateDialogPinned.folder_id 使用。 func (q *Queries) SetDialogPinned(ctx context.Context, arg SetDialogPinnedParams) (SetDialogPinnedRow, error) { row := q.db.QueryRow(ctx, setDialogPinned, arg.UserID, arg.PeerType, arg.PeerID, arg.Pinned, ) var i SetDialogPinnedRow err := row.Scan(&i.Changed, &i.FolderID) return i, err } const setDialogUnreadMark = `-- name: SetDialogUnreadMark :one WITH updated AS ( UPDATE dialogs d SET unread_mark = $1::boolean, updated_at = now() WHERE d.user_id = $2::bigint AND d.peer_type = $3::text AND d.peer_id = $4::bigint AND d.unread_mark IS DISTINCT FROM $1::boolean RETURNING d.user_id ) SELECT EXISTS (SELECT 1 FROM updated)::boolean AS changed ` type SetDialogUnreadMarkParams struct { Unread bool UserID int64 PeerType string PeerID int64 } // 值守卫 IS DISTINCT FROM:重复标记同一值不应算 changed,否则上层会记一条 // 幽灵 durable 未读标事件并多推一次 update(对齐频道侧 SetChannelDialogUnreadMark)。 func (q *Queries) SetDialogUnreadMark(ctx context.Context, arg SetDialogUnreadMarkParams) (bool, error) { row := q.db.QueryRow(ctx, setDialogUnreadMark, arg.Unread, arg.UserID, arg.PeerType, arg.PeerID, ) var changed bool err := row.Scan(&changed) return changed, err } const setPeerSettingsBarHidden = `-- name: SetPeerSettingsBarHidden :one WITH updated AS ( UPDATE dialogs d SET hidden_peer_settings_bar = true, updated_at = now() WHERE d.user_id = $1::bigint AND d.peer_type = $2::text AND d.peer_id = $3::bigint RETURNING d.user_id ) SELECT EXISTS (SELECT 1 FROM updated)::boolean AS changed ` type SetPeerSettingsBarHiddenParams struct { UserID int64 PeerType string PeerID int64 } func (q *Queries) SetPeerSettingsBarHidden(ctx context.Context, arg SetPeerSettingsBarHiddenParams) (bool, error) { row := q.db.QueryRow(ctx, setPeerSettingsBarHidden, arg.UserID, arg.PeerType, arg.PeerID) var changed bool err := row.Scan(&changed) return changed, err } const upsertDialog = `-- 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() ` type UpsertDialogParams struct { UserID int64 PeerType string PeerID int64 TopMessageID int32 TopMessageDate int32 ReadInboxMaxID int32 ReadOutboxMaxID int32 UnreadCount int32 UnreadMentionsCount int32 UnreadReactionsCount int32 Pinned bool UnreadMark bool } func (q *Queries) UpsertDialog(ctx context.Context, arg UpsertDialogParams) error { _, err := q.db.Exec(ctx, upsertDialog, arg.UserID, arg.PeerType, arg.PeerID, arg.TopMessageID, arg.TopMessageDate, arg.ReadInboxMaxID, arg.ReadOutboxMaxID, arg.UnreadCount, arg.UnreadMentionsCount, arg.UnreadReactionsCount, arg.Pinned, arg.UnreadMark, ) return err } const upsertDialogDraft = `-- name: UpsertDialogDraft :exec INSERT INTO dialog_drafts ( user_id, peer_type, peer_id, top_message_id, date, draft ) VALUES ( $1, $2, $3, $4, $5, $6::jsonb ) ON CONFLICT (user_id, peer_type, peer_id, top_message_id) DO UPDATE SET date = EXCLUDED.date, draft = EXCLUDED.draft, updated_at = now() ` type UpsertDialogDraftParams struct { UserID int64 PeerType string PeerID int64 TopMessageID int32 Date int32 DraftJson []byte } func (q *Queries) UpsertDialogDraft(ctx context.Context, arg UpsertDialogDraftParams) error { _, err := q.db.Exec(ctx, upsertDialogDraft, arg.UserID, arg.PeerType, arg.PeerID, arg.TopMessageID, arg.Date, arg.DraftJson, ) return err } const upsertDialogFolder = `-- name: UpsertDialogFolder :exec INSERT INTO dialog_filters ( user_id, filter_id, is_chatlist, filter, order_value ) VALUES ( $1, $2, $3, $4::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() ` type UpsertDialogFolderParams struct { UserID int64 FilterID int32 IsChatlist bool FilterJson []byte } func (q *Queries) UpsertDialogFolder(ctx context.Context, arg UpsertDialogFolderParams) error { _, err := q.db.Exec(ctx, upsertDialogFolder, arg.UserID, arg.FilterID, arg.IsChatlist, arg.FilterJson, ) return err } const upsertInboxDialog = `-- 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() ` type UpsertInboxDialogParams struct { UserID int64 PeerType string PeerID int64 TopMessageID int32 TopMessageDate int32 } func (q *Queries) UpsertInboxDialog(ctx context.Context, arg UpsertInboxDialogParams) error { _, err := q.db.Exec(ctx, upsertInboxDialog, arg.UserID, arg.PeerType, arg.PeerID, arg.TopMessageID, arg.TopMessageDate, ) return err } const upsertOutboxDialog = `-- name: UpsertOutboxDialog :exec 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() ` type UpsertOutboxDialogParams struct { UserID int64 PeerType string PeerID int64 TopMessageID int32 TopMessageDate int32 } // 发送方向该会话发出消息即视为已知晓内容:清除手动未读标记, // 与 channel 发送路径、readHistory 清除语义对齐。 func (q *Queries) UpsertOutboxDialog(ctx context.Context, arg UpsertOutboxDialogParams) error { _, err := q.db.Exec(ctx, upsertOutboxDialog, arg.UserID, arg.PeerType, arg.PeerID, arg.TopMessageID, arg.TopMessageDate, ) return err }