// Code generated by sqlc. DO NOT EDIT. // versions: // sqlc v1.31.1 // source: user_update_event.sql package sqlcgen import ( "context" ) const appendUserUpdateEvent = `-- name: AppendUserUpdateEvent :exec INSERT INTO user_update_events ( user_id, pts, pts_count, date, event_type, event_bool, event_peers, peer_settings, message_ids, dialog_filter, filter_order, folder_peers, story_payload, reaction_payload, message_box_id, peer_type, peer_id, filter_id, max_id, still_unread_count, channel_pts, tags_enabled, folder_id ) VALUES ( $1, $2, $3, $4, $5, $6::boolean, $7::jsonb, $8::jsonb, $9::jsonb, $10::jsonb, $11::jsonb, $12::jsonb, $13::jsonb, $14::jsonb, $15, $16::text, $17::bigint, $18::int, $19::int, $20::int, $21::int, $22::boolean, $23::int ) ` type AppendUserUpdateEventParams struct { UserID int64 Pts int32 PtsCount int32 Date int32 EventType string EventBool bool EventPeers []byte PeerSettings []byte MessageIds []byte DialogFilter []byte FilterOrder []byte FolderPeers []byte StoryPayload []byte ReactionPayload []byte MessageBoxID *int32 PeerType *string PeerID *int64 FilterID int32 MaxID int32 StillUnreadCount int32 ChannelPts int32 TagsEnabled bool FolderID int32 } func (q *Queries) AppendUserUpdateEvent(ctx context.Context, arg AppendUserUpdateEventParams) error { _, err := q.db.Exec(ctx, appendUserUpdateEvent, arg.UserID, arg.Pts, arg.PtsCount, arg.Date, arg.EventType, arg.EventBool, arg.EventPeers, arg.PeerSettings, arg.MessageIds, arg.DialogFilter, arg.FilterOrder, arg.FolderPeers, arg.StoryPayload, arg.ReactionPayload, arg.MessageBoxID, arg.PeerType, arg.PeerID, arg.FilterID, arg.MaxID, arg.StillUnreadCount, arg.ChannelPts, arg.TagsEnabled, arg.FolderID, ) return err } const batchListDispatchEvents = `-- name: BatchListDispatchEvents :many SELECT e.user_id, e.pts, e.pts_count, e.date, e.event_type, e.event_bool, COALESCE(e.event_peers::text, '[]')::text AS event_peers_json, COALESCE(e.peer_settings::text, '{}')::text AS peer_settings_json, COALESCE(e.message_ids::text, '[]')::text AS message_ids_json, COALESCE(e.dialog_filter::text, '{}')::text AS dialog_filter_json, COALESCE(e.filter_order::text, '[]')::text AS filter_order_json, COALESCE(e.folder_peers::text, '[]')::text AS folder_peers_json, COALESCE(e.story_payload::text, '{}')::text AS story_payload_json, COALESCE(e.reaction_payload::text, '{}')::text AS reaction_payload_json, COALESCE(e.peer_type, '')::text AS event_peer_type, COALESCE(e.peer_id, 0)::bigint AS event_peer_id, e.filter_id, e.max_id, e.still_unread_count, e.channel_pts, e.tags_enabled, e.folder_id, COALESCE(m.box_id, 0)::int AS message_id, COALESCE(m.private_message_id, 0)::bigint AS private_message_id, COALESCE(m.owner_user_id, 0)::bigint AS owner_user_id, COALESCE(m.peer_type, '')::text AS peer_type, COALESCE(m.peer_id, 0)::bigint AS peer_id, COALESCE(m.from_user_id, 0)::bigint AS from_user_id, COALESCE(m.message_date, 0)::int AS message_date, COALESCE(m.ttl_period, 0)::int AS ttl_period, COALESCE(m.expires_at, 0)::int AS expires_at, COALESCE(m.edit_date, 0)::int AS edit_date, COALESCE(m.hide_edited, false)::boolean AS hide_edited, COALESCE(m.outgoing, false)::boolean AS outgoing, COALESCE(m.body, '')::text AS body, COALESCE(m.entities::text, '[]')::text AS message_entities_json, COALESCE(m.silent, false)::boolean AS silent, COALESCE(m.noforwards, false)::boolean AS noforwards, COALESCE(m.reply_to_msg_id, 0)::int AS reply_to_msg_id, COALESCE(m.reply_to_peer_type, '')::text AS reply_to_peer_type, COALESCE(m.reply_to_peer_id, 0)::bigint AS reply_to_peer_id, COALESCE(m.reply_to_top_id, 0)::int AS reply_to_top_id, COALESCE(m.reply_to_story_id, 0)::int AS reply_to_story_id, COALESCE(m.quote_text, '')::text AS quote_text, COALESCE(m.quote_entities::text, '[]')::text AS quote_entities_json, COALESCE(m.quote_offset, 0)::int AS quote_offset, COALESCE(m.fwd_from_peer_type, '')::text AS fwd_from_peer_type, COALESCE(m.fwd_from_peer_id, 0)::bigint AS fwd_from_peer_id, COALESCE(m.fwd_from_name, '')::text AS fwd_from_name, COALESCE(m.fwd_date, 0)::int AS fwd_date, COALESCE(m.fwd_saved_from_peer_type, '')::text AS fwd_saved_from_peer_type, COALESCE(m.fwd_saved_from_peer_id, 0)::bigint AS fwd_saved_from_peer_id, COALESCE(m.fwd_saved_from_msg_id, 0)::int AS fwd_saved_from_msg_id, COALESCE(m.saved_peer_type, '')::text AS saved_peer_type, COALESCE(m.saved_peer_id, 0)::bigint AS saved_peer_id, COALESCE(m.media::text, '{}')::text AS media_json, COALESCE(m.media_unread, false)::boolean AS media_unread, COALESCE(m.reaction_unread, false)::boolean AS reaction_unread, COALESCE(m.pinned, false)::boolean AS pinned, COALESCE(m.via_bot_id, 0)::bigint AS via_bot_id, COALESCE(m.grouped_id, 0)::bigint AS grouped_id, COALESCE(m.effect, 0)::bigint AS effect, COALESCE(m.reply_markup::text, '{}')::text AS reply_markup_json, COALESCE(m.rich_message::text, '{}')::text AS rich_message_json, COALESCE(peer_u.id, 0)::bigint AS peer_user_id, COALESCE(peer_u.access_hash, 0)::bigint AS peer_access_hash, COALESCE(peer_u.phone, '')::text AS peer_phone, COALESCE(peer_u.first_name, '')::text AS peer_first_name, COALESCE(peer_u.last_name, '')::text AS peer_last_name, COALESCE(peer_u.username, '')::text AS peer_username, COALESCE(peer_u.country_code, '')::text AS peer_country_code, COALESCE(peer_u.verified, false)::boolean AS peer_verified, COALESCE(peer_u.support, false)::boolean AS peer_support, COALESCE(peer_u.is_bot, false)::boolean AS peer_is_bot, COALESCE(peer_u.bot_info_version, 0)::int AS peer_bot_info_version, COALESCE(EXTRACT(EPOCH FROM peer_u.premium_expires_at), 0)::bigint AS peer_premium_until, COALESCE(peer_u.emoji_status_document_id, 0)::bigint AS peer_emoji_status_document_id, COALESCE(peer_u.emoji_status_until, 0)::bigint AS peer_emoji_status_until, COALESCE(from_u.id, 0)::bigint AS from_user_user_id, COALESCE(from_u.access_hash, 0)::bigint AS from_user_access_hash, COALESCE(from_u.phone, '')::text AS from_user_phone, COALESCE(from_u.first_name, '')::text AS from_user_first_name, COALESCE(from_u.last_name, '')::text AS from_user_last_name, COALESCE(from_u.username, '')::text AS from_user_username, COALESCE(from_u.country_code, '')::text AS from_user_country_code, COALESCE(from_u.verified, false)::boolean AS from_user_verified, COALESCE(from_u.support, false)::boolean AS from_user_support, COALESCE(from_u.is_bot, false)::boolean AS from_user_is_bot, COALESCE(from_u.bot_info_version, 0)::int AS from_user_bot_info_version, COALESCE(EXTRACT(EPOCH FROM from_u.premium_expires_at), 0)::bigint AS from_user_premium_until, COALESCE(from_u.emoji_status_document_id, 0)::bigint AS from_user_emoji_status_document_id, COALESCE(from_u.emoji_status_until, 0)::bigint AS from_user_emoji_status_until, COALESCE(fwd_u.id, 0)::bigint AS fwd_user_id, COALESCE(fwd_u.access_hash, 0)::bigint AS fwd_user_access_hash, COALESCE(fwd_u.phone, '')::text AS fwd_user_phone, COALESCE(fwd_u.first_name, '')::text AS fwd_user_first_name, COALESCE(fwd_u.last_name, '')::text AS fwd_user_last_name, COALESCE(fwd_u.username, '')::text AS fwd_user_username, COALESCE(fwd_u.country_code, '')::text AS fwd_user_country_code, COALESCE(fwd_u.verified, false)::boolean AS fwd_user_verified, COALESCE(fwd_u.support, false)::boolean AS fwd_user_support, COALESCE(fwd_u.is_bot, false)::boolean AS fwd_user_is_bot, COALESCE(fwd_u.bot_info_version, 0)::int AS fwd_user_bot_info_version, COALESCE(EXTRACT(EPOCH FROM fwd_u.premium_expires_at), 0)::bigint AS fwd_user_premium_until, COALESCE(fwd_u.emoji_status_document_id, 0)::bigint AS fwd_user_emoji_status_document_id, COALESCE(fwd_u.emoji_status_until, 0)::bigint AS fwd_user_emoji_status_until, COALESCE(reply_u.id, 0)::bigint AS reply_user_id, COALESCE(reply_u.access_hash, 0)::bigint AS reply_user_access_hash, COALESCE(reply_u.phone, '')::text AS reply_user_phone, COALESCE(reply_u.first_name, '')::text AS reply_user_first_name, COALESCE(reply_u.last_name, '')::text AS reply_user_last_name, COALESCE(reply_u.username, '')::text AS reply_user_username, COALESCE(reply_u.country_code, '')::text AS reply_user_country_code, COALESCE(reply_u.verified, false)::boolean AS reply_user_verified, COALESCE(reply_u.support, false)::boolean AS reply_user_support, COALESCE(reply_u.is_bot, false)::boolean AS reply_user_is_bot, COALESCE(reply_u.bot_info_version, 0)::int AS reply_user_bot_info_version, COALESCE(EXTRACT(EPOCH FROM reply_u.premium_expires_at), 0)::bigint AS reply_user_premium_until, COALESCE(reply_u.emoji_status_document_id, 0)::bigint AS reply_user_emoji_status_document_id, COALESCE(reply_u.emoji_status_until, 0)::bigint AS reply_user_emoji_status_until FROM unnest($1::bigint[]) WITH ORDINALITY AS u(user_id, ord) JOIN unnest($2::int[]) WITH ORDINALITY AS p(pts, ord) USING (ord) JOIN user_update_events e ON e.user_id = u.user_id AND e.pts = p.pts LEFT JOIN message_boxes m ON m.owner_user_id = e.user_id AND m.box_id = e.message_box_id LEFT JOIN users peer_u ON m.peer_type = 'user' AND peer_u.id = m.peer_id LEFT JOIN users from_u ON from_u.id = m.from_user_id LEFT JOIN users fwd_u ON m.fwd_from_peer_type = 'user' AND fwd_u.id = m.fwd_from_peer_id LEFT JOIN users reply_u ON m.reply_to_peer_type = 'user' AND reply_u.id = m.reply_to_peer_id ` type BatchListDispatchEventsParams struct { UserIds []int64 PtsList []int32 } type BatchListDispatchEventsRow struct { UserID int64 Pts int32 PtsCount int32 Date int32 EventType string EventBool bool EventPeersJson string PeerSettingsJson string MessageIdsJson string DialogFilterJson string FilterOrderJson string FolderPeersJson string StoryPayloadJson string ReactionPayloadJson string EventPeerType string EventPeerID int64 FilterID int32 MaxID int32 StillUnreadCount int32 ChannelPts int32 TagsEnabled bool FolderID int32 MessageID int32 PrivateMessageID int64 OwnerUserID int64 PeerType string PeerID int64 FromUserID int64 MessageDate int32 TtlPeriod int32 ExpiresAt int32 EditDate int32 HideEdited bool Outgoing bool Body string MessageEntitiesJson string Silent bool Noforwards bool ReplyToMsgID int32 ReplyToPeerType string ReplyToPeerID int64 ReplyToTopID int32 ReplyToStoryID int32 QuoteText string QuoteEntitiesJson string QuoteOffset int32 FwdFromPeerType string FwdFromPeerID int64 FwdFromName string FwdDate int32 FwdSavedFromPeerType string FwdSavedFromPeerID int64 FwdSavedFromMsgID int32 SavedPeerType string SavedPeerID int64 MediaJson string MediaUnread bool ReactionUnread bool Pinned bool ViaBotID int64 GroupedID int64 Effect int64 ReplyMarkupJson string RichMessageJson string 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 FromUserUserID int64 FromUserAccessHash int64 FromUserPhone string FromUserFirstName string FromUserLastName string FromUserUsername string FromUserCountryCode string FromUserVerified bool FromUserSupport bool FromUserIsBot bool FromUserBotInfoVersion int32 FromUserPremiumUntil int64 FromUserEmojiStatusDocumentID int64 FromUserEmojiStatusUntil int64 FwdUserID int64 FwdUserAccessHash int64 FwdUserPhone string FwdUserFirstName string FwdUserLastName string FwdUserUsername string FwdUserCountryCode string FwdUserVerified bool FwdUserSupport bool FwdUserIsBot bool FwdUserBotInfoVersion int32 FwdUserPremiumUntil int64 FwdUserEmojiStatusDocumentID int64 FwdUserEmojiStatusUntil int64 ReplyUserID int64 ReplyUserAccessHash int64 ReplyUserPhone string ReplyUserFirstName string ReplyUserLastName string ReplyUserUsername string ReplyUserCountryCode string ReplyUserVerified bool ReplyUserSupport bool ReplyUserIsBot bool ReplyUserBotInfoVersion int32 ReplyUserPremiumUntil int64 ReplyUserEmojiStatusDocumentID int64 ReplyUserEmojiStatusUntil int64 } // 按 (user_id, pts) 精确批量取账号事件,供 outbox worker 一次性加载一批 claim 的事件详情, // 取代逐条 ListUserUpdateEventsAfter。列与 ListUserUpdateEventsAfter 完全一致以复用转换逻辑。 func (q *Queries) BatchListDispatchEvents(ctx context.Context, arg BatchListDispatchEventsParams) ([]BatchListDispatchEventsRow, error) { rows, err := q.db.Query(ctx, batchListDispatchEvents, arg.UserIds, arg.PtsList) if err != nil { return nil, err } defer rows.Close() var items []BatchListDispatchEventsRow for rows.Next() { var i BatchListDispatchEventsRow if err := rows.Scan( &i.UserID, &i.Pts, &i.PtsCount, &i.Date, &i.EventType, &i.EventBool, &i.EventPeersJson, &i.PeerSettingsJson, &i.MessageIdsJson, &i.DialogFilterJson, &i.FilterOrderJson, &i.FolderPeersJson, &i.StoryPayloadJson, &i.ReactionPayloadJson, &i.EventPeerType, &i.EventPeerID, &i.FilterID, &i.MaxID, &i.StillUnreadCount, &i.ChannelPts, &i.TagsEnabled, &i.FolderID, &i.MessageID, &i.PrivateMessageID, &i.OwnerUserID, &i.PeerType, &i.PeerID, &i.FromUserID, &i.MessageDate, &i.TtlPeriod, &i.ExpiresAt, &i.EditDate, &i.HideEdited, &i.Outgoing, &i.Body, &i.MessageEntitiesJson, &i.Silent, &i.Noforwards, &i.ReplyToMsgID, &i.ReplyToPeerType, &i.ReplyToPeerID, &i.ReplyToTopID, &i.ReplyToStoryID, &i.QuoteText, &i.QuoteEntitiesJson, &i.QuoteOffset, &i.FwdFromPeerType, &i.FwdFromPeerID, &i.FwdFromName, &i.FwdDate, &i.FwdSavedFromPeerType, &i.FwdSavedFromPeerID, &i.FwdSavedFromMsgID, &i.SavedPeerType, &i.SavedPeerID, &i.MediaJson, &i.MediaUnread, &i.ReactionUnread, &i.Pinned, &i.ViaBotID, &i.GroupedID, &i.Effect, &i.ReplyMarkupJson, &i.RichMessageJson, &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.FromUserUserID, &i.FromUserAccessHash, &i.FromUserPhone, &i.FromUserFirstName, &i.FromUserLastName, &i.FromUserUsername, &i.FromUserCountryCode, &i.FromUserVerified, &i.FromUserSupport, &i.FromUserIsBot, &i.FromUserBotInfoVersion, &i.FromUserPremiumUntil, &i.FromUserEmojiStatusDocumentID, &i.FromUserEmojiStatusUntil, &i.FwdUserID, &i.FwdUserAccessHash, &i.FwdUserPhone, &i.FwdUserFirstName, &i.FwdUserLastName, &i.FwdUserUsername, &i.FwdUserCountryCode, &i.FwdUserVerified, &i.FwdUserSupport, &i.FwdUserIsBot, &i.FwdUserBotInfoVersion, &i.FwdUserPremiumUntil, &i.FwdUserEmojiStatusDocumentID, &i.FwdUserEmojiStatusUntil, &i.ReplyUserID, &i.ReplyUserAccessHash, &i.ReplyUserPhone, &i.ReplyUserFirstName, &i.ReplyUserLastName, &i.ReplyUserUsername, &i.ReplyUserCountryCode, &i.ReplyUserVerified, &i.ReplyUserSupport, &i.ReplyUserIsBot, &i.ReplyUserBotInfoVersion, &i.ReplyUserPremiumUntil, &i.ReplyUserEmojiStatusDocumentID, &i.ReplyUserEmojiStatusUntil, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const claimDispatchOutbox = `-- name: ClaimDispatchOutbox :many WITH picked AS ( SELECT d.target_user_id, d.id FROM dispatch_outbox d WHERE ( d.status = 'pending' AND d.next_attempt_at <= now() ) OR ( d.status = 'dispatching' AND d.updated_at < now() - make_interval(secs => $1::int) ) ORDER BY d.next_attempt_at ASC, d.target_user_id ASC, d.pts ASC, d.id ASC LIMIT $2 FOR UPDATE SKIP LOCKED ) UPDATE dispatch_outbox d SET status = 'dispatching', attempts = d.attempts + 1, updated_at = now() FROM picked p WHERE d.target_user_id = p.target_user_id AND d.id = p.id RETURNING d.id, d.target_user_id, d.pts, d.event_type, d.exclude_auth_key_id, d.exclude_session_id, d.attempts ` type ClaimDispatchOutboxParams struct { LeaseSeconds int32 LimitCount int32 } type ClaimDispatchOutboxRow struct { ID int64 TargetUserID int64 Pts int32 EventType string ExcludeAuthKeyID int64 ExcludeSessionID int64 Attempts int32 } func (q *Queries) ClaimDispatchOutbox(ctx context.Context, arg ClaimDispatchOutboxParams) ([]ClaimDispatchOutboxRow, error) { rows, err := q.db.Query(ctx, claimDispatchOutbox, arg.LeaseSeconds, arg.LimitCount) if err != nil { return nil, err } defer rows.Close() var items []ClaimDispatchOutboxRow for rows.Next() { var i ClaimDispatchOutboxRow if err := rows.Scan( &i.ID, &i.TargetUserID, &i.Pts, &i.EventType, &i.ExcludeAuthKeyID, &i.ExcludeSessionID, &i.Attempts, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const deleteFailedDispatchOutbox = `-- name: DeleteFailedDispatchOutbox :one WITH doomed AS ( SELECT target_user_id, id FROM dispatch_outbox WHERE status = 'failed' AND updated_at < now() - make_interval(secs => $1::int) ORDER BY updated_at ASC, target_user_id ASC, id ASC LIMIT $2 ), deleted AS ( DELETE FROM dispatch_outbox d USING doomed x WHERE d.target_user_id = x.target_user_id AND d.id = x.id RETURNING d.id ) SELECT count(*)::int AS deleted_count FROM deleted ` type DeleteFailedDispatchOutboxParams struct { OlderThanSeconds int32 LimitCount int32 } func (q *Queries) DeleteFailedDispatchOutbox(ctx context.Context, arg DeleteFailedDispatchOutboxParams) (int32, error) { row := q.db.QueryRow(ctx, deleteFailedDispatchOutbox, arg.OlderThanSeconds, arg.LimitCount) var deleted_count int32 err := row.Scan(&deleted_count) return deleted_count, err } const enqueueDispatch = `-- name: EnqueueDispatch :exec INSERT INTO dispatch_outbox ( target_user_id, pts, event_type, exclude_auth_key_id, exclude_session_id ) VALUES ( $1, $2, $3, $4, $5 ) ON CONFLICT DO NOTHING ` type EnqueueDispatchParams struct { TargetUserID int64 Pts int32 EventType string ExcludeAuthKeyID int64 ExcludeSessionID int64 } func (q *Queries) EnqueueDispatch(ctx context.Context, arg EnqueueDispatchParams) error { _, err := q.db.Exec(ctx, enqueueDispatch, arg.TargetUserID, arg.Pts, arg.EventType, arg.ExcludeAuthKeyID, arg.ExcludeSessionID, ) return err } const getUserUpdateWatermark = `-- name: GetUserUpdateWatermark :one SELECT contiguous_pts FROM user_update_watermarks WHERE user_id = $1 ` func (q *Queries) GetUserUpdateWatermark(ctx context.Context, userID int64) (int32, error) { row := q.db.QueryRow(ctx, getUserUpdateWatermark, userID) var contiguous_pts int32 err := row.Scan(&contiguous_pts) return contiguous_pts, err } const listUserUpdateEventsAfter = `-- name: ListUserUpdateEventsAfter :many SELECT e.user_id, e.pts, e.pts_count, e.date, e.event_type, e.event_bool, COALESCE(e.event_peers::text, '[]')::text AS event_peers_json, COALESCE(e.peer_settings::text, '{}')::text AS peer_settings_json, COALESCE(e.message_ids::text, '[]')::text AS message_ids_json, COALESCE(e.dialog_filter::text, '{}')::text AS dialog_filter_json, COALESCE(e.filter_order::text, '[]')::text AS filter_order_json, COALESCE(e.folder_peers::text, '[]')::text AS folder_peers_json, COALESCE(e.story_payload::text, '{}')::text AS story_payload_json, COALESCE(e.reaction_payload::text, '{}')::text AS reaction_payload_json, COALESCE(e.peer_type, '')::text AS event_peer_type, COALESCE(e.peer_id, 0)::bigint AS event_peer_id, e.filter_id, e.max_id, e.still_unread_count, e.channel_pts, e.tags_enabled, e.folder_id, COALESCE(m.box_id, 0)::int AS message_id, COALESCE(m.private_message_id, 0)::bigint AS private_message_id, COALESCE(m.owner_user_id, 0)::bigint AS owner_user_id, COALESCE(m.peer_type, '')::text AS peer_type, COALESCE(m.peer_id, 0)::bigint AS peer_id, COALESCE(m.from_user_id, 0)::bigint AS from_user_id, COALESCE(m.message_date, 0)::int AS message_date, COALESCE(m.ttl_period, 0)::int AS ttl_period, COALESCE(m.expires_at, 0)::int AS expires_at, COALESCE(m.edit_date, 0)::int AS edit_date, COALESCE(m.hide_edited, false)::boolean AS hide_edited, COALESCE(m.outgoing, false)::boolean AS outgoing, COALESCE(m.body, '')::text AS body, COALESCE(m.entities::text, '[]')::text AS message_entities_json, COALESCE(m.silent, false)::boolean AS silent, COALESCE(m.noforwards, false)::boolean AS noforwards, COALESCE(m.reply_to_msg_id, 0)::int AS reply_to_msg_id, COALESCE(m.reply_to_peer_type, '')::text AS reply_to_peer_type, COALESCE(m.reply_to_peer_id, 0)::bigint AS reply_to_peer_id, COALESCE(m.reply_to_top_id, 0)::int AS reply_to_top_id, COALESCE(m.reply_to_story_id, 0)::int AS reply_to_story_id, COALESCE(m.quote_text, '')::text AS quote_text, COALESCE(m.quote_entities::text, '[]')::text AS quote_entities_json, COALESCE(m.quote_offset, 0)::int AS quote_offset, COALESCE(m.fwd_from_peer_type, '')::text AS fwd_from_peer_type, COALESCE(m.fwd_from_peer_id, 0)::bigint AS fwd_from_peer_id, COALESCE(m.fwd_from_name, '')::text AS fwd_from_name, COALESCE(m.fwd_date, 0)::int AS fwd_date, COALESCE(m.fwd_saved_from_peer_type, '')::text AS fwd_saved_from_peer_type, COALESCE(m.fwd_saved_from_peer_id, 0)::bigint AS fwd_saved_from_peer_id, COALESCE(m.fwd_saved_from_msg_id, 0)::int AS fwd_saved_from_msg_id, COALESCE(m.saved_peer_type, '')::text AS saved_peer_type, COALESCE(m.saved_peer_id, 0)::bigint AS saved_peer_id, COALESCE(m.media::text, '{}')::text AS media_json, COALESCE(m.media_unread, false)::boolean AS media_unread, COALESCE(m.reaction_unread, false)::boolean AS reaction_unread, COALESCE(m.pinned, false)::boolean AS pinned, COALESCE(m.via_bot_id, 0)::bigint AS via_bot_id, COALESCE(m.grouped_id, 0)::bigint AS grouped_id, COALESCE(m.effect, 0)::bigint AS effect, COALESCE(m.reply_markup::text, '{}')::text AS reply_markup_json, COALESCE(m.rich_message::text, '{}')::text AS rich_message_json, COALESCE(peer_u.id, 0)::bigint AS peer_user_id, COALESCE(peer_u.access_hash, 0)::bigint AS peer_access_hash, COALESCE(peer_u.phone, '')::text AS peer_phone, COALESCE(peer_u.first_name, '')::text AS peer_first_name, COALESCE(peer_u.last_name, '')::text AS peer_last_name, COALESCE(peer_u.username, '')::text AS peer_username, COALESCE(peer_u.country_code, '')::text AS peer_country_code, COALESCE(peer_u.verified, false)::boolean AS peer_verified, COALESCE(peer_u.support, false)::boolean AS peer_support, COALESCE(peer_u.is_bot, false)::boolean AS peer_is_bot, COALESCE(peer_u.bot_info_version, 0)::int AS peer_bot_info_version, COALESCE(EXTRACT(EPOCH FROM peer_u.premium_expires_at), 0)::bigint AS peer_premium_until, COALESCE(peer_u.emoji_status_document_id, 0)::bigint AS peer_emoji_status_document_id, COALESCE(peer_u.emoji_status_until, 0)::bigint AS peer_emoji_status_until, COALESCE(from_u.id, 0)::bigint AS from_user_user_id, COALESCE(from_u.access_hash, 0)::bigint AS from_user_access_hash, COALESCE(from_u.phone, '')::text AS from_user_phone, COALESCE(from_u.first_name, '')::text AS from_user_first_name, COALESCE(from_u.last_name, '')::text AS from_user_last_name, COALESCE(from_u.username, '')::text AS from_user_username, COALESCE(from_u.country_code, '')::text AS from_user_country_code, COALESCE(from_u.verified, false)::boolean AS from_user_verified, COALESCE(from_u.support, false)::boolean AS from_user_support, COALESCE(from_u.is_bot, false)::boolean AS from_user_is_bot, COALESCE(from_u.bot_info_version, 0)::int AS from_user_bot_info_version, COALESCE(EXTRACT(EPOCH FROM from_u.premium_expires_at), 0)::bigint AS from_user_premium_until, COALESCE(from_u.emoji_status_document_id, 0)::bigint AS from_user_emoji_status_document_id, COALESCE(from_u.emoji_status_until, 0)::bigint AS from_user_emoji_status_until, COALESCE(fwd_u.id, 0)::bigint AS fwd_user_id, COALESCE(fwd_u.access_hash, 0)::bigint AS fwd_user_access_hash, COALESCE(fwd_u.phone, '')::text AS fwd_user_phone, COALESCE(fwd_u.first_name, '')::text AS fwd_user_first_name, COALESCE(fwd_u.last_name, '')::text AS fwd_user_last_name, COALESCE(fwd_u.username, '')::text AS fwd_user_username, COALESCE(fwd_u.country_code, '')::text AS fwd_user_country_code, COALESCE(fwd_u.verified, false)::boolean AS fwd_user_verified, COALESCE(fwd_u.support, false)::boolean AS fwd_user_support, COALESCE(fwd_u.is_bot, false)::boolean AS fwd_user_is_bot, COALESCE(fwd_u.bot_info_version, 0)::int AS fwd_user_bot_info_version, COALESCE(EXTRACT(EPOCH FROM fwd_u.premium_expires_at), 0)::bigint AS fwd_user_premium_until, COALESCE(fwd_u.emoji_status_document_id, 0)::bigint AS fwd_user_emoji_status_document_id, COALESCE(fwd_u.emoji_status_until, 0)::bigint AS fwd_user_emoji_status_until, COALESCE(reply_u.id, 0)::bigint AS reply_user_id, COALESCE(reply_u.access_hash, 0)::bigint AS reply_user_access_hash, COALESCE(reply_u.phone, '')::text AS reply_user_phone, COALESCE(reply_u.first_name, '')::text AS reply_user_first_name, COALESCE(reply_u.last_name, '')::text AS reply_user_last_name, COALESCE(reply_u.username, '')::text AS reply_user_username, COALESCE(reply_u.country_code, '')::text AS reply_user_country_code, COALESCE(reply_u.verified, false)::boolean AS reply_user_verified, COALESCE(reply_u.support, false)::boolean AS reply_user_support, COALESCE(reply_u.is_bot, false)::boolean AS reply_user_is_bot, COALESCE(reply_u.bot_info_version, 0)::int AS reply_user_bot_info_version, COALESCE(EXTRACT(EPOCH FROM reply_u.premium_expires_at), 0)::bigint AS reply_user_premium_until, COALESCE(reply_u.emoji_status_document_id, 0)::bigint AS reply_user_emoji_status_document_id, COALESCE(reply_u.emoji_status_until, 0)::bigint AS reply_user_emoji_status_until FROM user_update_events e LEFT JOIN message_boxes m ON m.owner_user_id = e.user_id AND m.box_id = e.message_box_id LEFT JOIN users peer_u ON m.peer_type = 'user' AND peer_u.id = m.peer_id LEFT JOIN users from_u ON from_u.id = m.from_user_id LEFT JOIN users fwd_u ON m.fwd_from_peer_type = 'user' AND fwd_u.id = m.fwd_from_peer_id LEFT JOIN users reply_u ON m.reply_to_peer_type = 'user' AND reply_u.id = m.reply_to_peer_id WHERE e.user_id = $1 AND e.pts > $2 ORDER BY e.pts ASC LIMIT $3 ` type ListUserUpdateEventsAfterParams struct { UserID int64 Pts int32 LimitCount int32 } type ListUserUpdateEventsAfterRow struct { UserID int64 Pts int32 PtsCount int32 Date int32 EventType string EventBool bool EventPeersJson string PeerSettingsJson string MessageIdsJson string DialogFilterJson string FilterOrderJson string FolderPeersJson string StoryPayloadJson string ReactionPayloadJson string EventPeerType string EventPeerID int64 FilterID int32 MaxID int32 StillUnreadCount int32 ChannelPts int32 TagsEnabled bool FolderID int32 MessageID int32 PrivateMessageID int64 OwnerUserID int64 PeerType string PeerID int64 FromUserID int64 MessageDate int32 TtlPeriod int32 ExpiresAt int32 EditDate int32 HideEdited bool Outgoing bool Body string MessageEntitiesJson string Silent bool Noforwards bool ReplyToMsgID int32 ReplyToPeerType string ReplyToPeerID int64 ReplyToTopID int32 ReplyToStoryID int32 QuoteText string QuoteEntitiesJson string QuoteOffset int32 FwdFromPeerType string FwdFromPeerID int64 FwdFromName string FwdDate int32 FwdSavedFromPeerType string FwdSavedFromPeerID int64 FwdSavedFromMsgID int32 SavedPeerType string SavedPeerID int64 MediaJson string MediaUnread bool ReactionUnread bool Pinned bool ViaBotID int64 GroupedID int64 Effect int64 ReplyMarkupJson string RichMessageJson string 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 FromUserUserID int64 FromUserAccessHash int64 FromUserPhone string FromUserFirstName string FromUserLastName string FromUserUsername string FromUserCountryCode string FromUserVerified bool FromUserSupport bool FromUserIsBot bool FromUserBotInfoVersion int32 FromUserPremiumUntil int64 FromUserEmojiStatusDocumentID int64 FromUserEmojiStatusUntil int64 FwdUserID int64 FwdUserAccessHash int64 FwdUserPhone string FwdUserFirstName string FwdUserLastName string FwdUserUsername string FwdUserCountryCode string FwdUserVerified bool FwdUserSupport bool FwdUserIsBot bool FwdUserBotInfoVersion int32 FwdUserPremiumUntil int64 FwdUserEmojiStatusDocumentID int64 FwdUserEmojiStatusUntil int64 ReplyUserID int64 ReplyUserAccessHash int64 ReplyUserPhone string ReplyUserFirstName string ReplyUserLastName string ReplyUserUsername string ReplyUserCountryCode string ReplyUserVerified bool ReplyUserSupport bool ReplyUserIsBot bool ReplyUserBotInfoVersion int32 ReplyUserPremiumUntil int64 ReplyUserEmojiStatusDocumentID int64 ReplyUserEmojiStatusUntil int64 } func (q *Queries) ListUserUpdateEventsAfter(ctx context.Context, arg ListUserUpdateEventsAfterParams) ([]ListUserUpdateEventsAfterRow, error) { rows, err := q.db.Query(ctx, listUserUpdateEventsAfter, arg.UserID, arg.Pts, arg.LimitCount) if err != nil { return nil, err } defer rows.Close() var items []ListUserUpdateEventsAfterRow for rows.Next() { var i ListUserUpdateEventsAfterRow if err := rows.Scan( &i.UserID, &i.Pts, &i.PtsCount, &i.Date, &i.EventType, &i.EventBool, &i.EventPeersJson, &i.PeerSettingsJson, &i.MessageIdsJson, &i.DialogFilterJson, &i.FilterOrderJson, &i.FolderPeersJson, &i.StoryPayloadJson, &i.ReactionPayloadJson, &i.EventPeerType, &i.EventPeerID, &i.FilterID, &i.MaxID, &i.StillUnreadCount, &i.ChannelPts, &i.TagsEnabled, &i.FolderID, &i.MessageID, &i.PrivateMessageID, &i.OwnerUserID, &i.PeerType, &i.PeerID, &i.FromUserID, &i.MessageDate, &i.TtlPeriod, &i.ExpiresAt, &i.EditDate, &i.HideEdited, &i.Outgoing, &i.Body, &i.MessageEntitiesJson, &i.Silent, &i.Noforwards, &i.ReplyToMsgID, &i.ReplyToPeerType, &i.ReplyToPeerID, &i.ReplyToTopID, &i.ReplyToStoryID, &i.QuoteText, &i.QuoteEntitiesJson, &i.QuoteOffset, &i.FwdFromPeerType, &i.FwdFromPeerID, &i.FwdFromName, &i.FwdDate, &i.FwdSavedFromPeerType, &i.FwdSavedFromPeerID, &i.FwdSavedFromMsgID, &i.SavedPeerType, &i.SavedPeerID, &i.MediaJson, &i.MediaUnread, &i.ReactionUnread, &i.Pinned, &i.ViaBotID, &i.GroupedID, &i.Effect, &i.ReplyMarkupJson, &i.RichMessageJson, &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.FromUserUserID, &i.FromUserAccessHash, &i.FromUserPhone, &i.FromUserFirstName, &i.FromUserLastName, &i.FromUserUsername, &i.FromUserCountryCode, &i.FromUserVerified, &i.FromUserSupport, &i.FromUserIsBot, &i.FromUserBotInfoVersion, &i.FromUserPremiumUntil, &i.FromUserEmojiStatusDocumentID, &i.FromUserEmojiStatusUntil, &i.FwdUserID, &i.FwdUserAccessHash, &i.FwdUserPhone, &i.FwdUserFirstName, &i.FwdUserLastName, &i.FwdUserUsername, &i.FwdUserCountryCode, &i.FwdUserVerified, &i.FwdUserSupport, &i.FwdUserIsBot, &i.FwdUserBotInfoVersion, &i.FwdUserPremiumUntil, &i.FwdUserEmojiStatusDocumentID, &i.FwdUserEmojiStatusUntil, &i.ReplyUserID, &i.ReplyUserAccessHash, &i.ReplyUserPhone, &i.ReplyUserFirstName, &i.ReplyUserLastName, &i.ReplyUserUsername, &i.ReplyUserCountryCode, &i.ReplyUserVerified, &i.ReplyUserSupport, &i.ReplyUserIsBot, &i.ReplyUserBotInfoVersion, &i.ReplyUserPremiumUntil, &i.ReplyUserEmojiStatusDocumentID, &i.ReplyUserEmojiStatusUntil, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const markDispatchDelivered = `-- name: MarkDispatchDelivered :exec DELETE FROM dispatch_outbox WHERE target_user_id = $1 AND id = $2 ` type MarkDispatchDeliveredParams struct { TargetUserID int64 ID int64 } // 方案 A:投递成功即删除。outbox 是任务队列,delivered 行无保留价值 // (消息在 message_boxes、离线补偿在 user_update_events),删除让表维持「未完成任务」小稳态。 func (q *Queries) MarkDispatchDelivered(ctx context.Context, arg MarkDispatchDeliveredParams) error { _, err := q.db.Exec(ctx, markDispatchDelivered, arg.TargetUserID, arg.ID) return err } const markDispatchDeliveredBatch = `-- name: MarkDispatchDeliveredBatch :exec DELETE FROM dispatch_outbox d USING unnest($1::bigint[]) WITH ORDINALITY AS tu(target_user_id, ord) JOIN unnest($2::bigint[]) WITH ORDINALITY AS di(id, ord) USING (ord) WHERE d.target_user_id = tu.target_user_id AND d.id = di.id ` type MarkDispatchDeliveredBatchParams struct { TargetUserIds []int64 Ids []int64 } // 批量删除一批已投递的 (target_user_id, id);target_user_id 入 WHERE 命中唯一索引并避免串删。 func (q *Queries) MarkDispatchDeliveredBatch(ctx context.Context, arg MarkDispatchDeliveredBatchParams) error { _, err := q.db.Exec(ctx, markDispatchDeliveredBatch, arg.TargetUserIds, arg.Ids) return err } const markDispatchFailed = `-- name: MarkDispatchFailed :exec UPDATE dispatch_outbox SET status = CASE WHEN attempts >= 5 THEN 'failed' ELSE 'pending' END, next_attempt_at = CASE WHEN attempts >= 5 THEN next_attempt_at ELSE now() + make_interval(secs => LEAST(60, attempts * attempts)) END, last_error = $3, updated_at = now() WHERE target_user_id = $1 AND id = $2 ` type MarkDispatchFailedParams struct { TargetUserID int64 ID int64 LastError string } func (q *Queries) MarkDispatchFailed(ctx context.Context, arg MarkDispatchFailedParams) error { _, err := q.db.Exec(ctx, markDispatchFailed, arg.TargetUserID, arg.ID, arg.LastError) return err } const maxUserPts = `-- name: MaxUserPts :one SELECT COALESCE(MAX(pts), 0)::int AS max_pts FROM user_update_events WHERE user_id = $1 ` func (q *Queries) MaxUserPts(ctx context.Context, userID int64) (int32, error) { row := q.db.QueryRow(ctx, maxUserPts, userID) var max_pts int32 err := row.Scan(&max_pts) return max_pts, err }