Initial open source release

This commit is contained in:
A 2026-06-04 01:37:39 +08:00
commit 74992e893f
377 changed files with 118084 additions and 0 deletions

View file

@ -0,0 +1,22 @@
-- name: GetPasswordByUser :one
SELECT
user_id, has_recovery, has_secure_values, has_password, hint,
email_unconfirmed_pattern, login_email_pattern, secure_random
FROM account_passwords
WHERE user_id = $1;
-- name: UpsertPassword :exec
INSERT INTO account_passwords (
user_id, has_recovery, has_secure_values, has_password, hint,
email_unconfirmed_pattern, login_email_pattern, secure_random
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
ON CONFLICT (user_id) DO UPDATE SET
has_recovery = EXCLUDED.has_recovery,
has_secure_values = EXCLUDED.has_secure_values,
has_password = EXCLUDED.has_password,
hint = EXCLUDED.hint,
email_unconfirmed_pattern = EXCLUDED.email_unconfirmed_pattern,
login_email_pattern = EXCLUDED.login_email_pattern,
secure_random = EXCLUDED.secure_random,
updated_at = now();

View file

@ -0,0 +1,10 @@
-- name: GetAuthKey :one
SELECT auth_key_id, body, server_salt, created_at
FROM auth_keys
WHERE auth_key_id = $1;
-- name: UpsertAuthKey :exec
INSERT INTO auth_keys (auth_key_id, body, server_salt)
VALUES ($1, $2, $3)
ON CONFLICT (auth_key_id) DO UPDATE
SET body = EXCLUDED.body, server_salt = EXCLUDED.server_salt;

View file

@ -0,0 +1,24 @@
-- name: UpsertAuthorization :exec
INSERT INTO authorizations (auth_key_id, user_id, layer, device_model, platform, system_version, api_id, app_version, ip)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
ON CONFLICT (auth_key_id) DO UPDATE SET
user_id = EXCLUDED.user_id,
layer = EXCLUDED.layer,
device_model = EXCLUDED.device_model,
platform = EXCLUDED.platform,
system_version = EXCLUDED.system_version,
api_id = EXCLUDED.api_id,
app_version = EXCLUDED.app_version,
ip = EXCLUDED.ip,
active_at = now();
-- name: GetAuthorizationByAuthKey :one
SELECT * FROM authorizations WHERE auth_key_id = $1;
-- name: ListAuthorizationsByUser :many
SELECT * FROM authorizations
WHERE user_id = $1
ORDER BY active_at DESC, auth_key_id DESC;
-- name: DeleteAuthorization :exec
DELETE FROM authorizations WHERE auth_key_id = $1;

View file

@ -0,0 +1,168 @@
-- name: ListContactsByUser :many
SELECT
c.contact_user_id,
c.mutual,
c.contact_phone,
c.contact_first_name,
c.contact_last_name,
c.note,
COALESCE(c.note_entities::text, '[]')::text AS note_entities_json,
u.id,
u.access_hash,
COALESCE(NULLIF(c.contact_phone, ''), u.phone)::text AS phone,
COALESCE(NULLIF(c.contact_first_name, ''), u.first_name)::text AS first_name,
COALESCE(c.contact_last_name, u.last_name)::text AS last_name,
u.username,
u.country_code,
u.verified,
u.support,
u.last_seen_at
FROM contacts c
JOIN users u ON u.id = c.contact_user_id
WHERE c.user_id = $1
ORDER BY c.contact_first_name, c.contact_last_name, u.first_name, u.last_name, u.id;
-- name: GetContact :one
SELECT
c.contact_user_id,
c.mutual,
c.contact_phone,
c.contact_first_name,
c.contact_last_name,
c.note,
COALESCE(c.note_entities::text, '[]')::text AS note_entities_json,
u.id,
u.access_hash,
COALESCE(NULLIF(c.contact_phone, ''), u.phone)::text AS phone,
COALESCE(NULLIF(c.contact_first_name, ''), u.first_name)::text AS first_name,
COALESCE(c.contact_last_name, u.last_name)::text AS last_name,
u.username,
u.country_code,
u.verified,
u.support,
u.last_seen_at
FROM contacts c
JOIN users u ON u.id = c.contact_user_id
WHERE c.user_id = $1
AND c.contact_user_id = $2;
-- name: UpsertContact :one
WITH reverse AS (
SELECT EXISTS (
SELECT 1
FROM contacts
WHERE user_id = sqlc.arg(contact_user_id)::bigint
AND contact_user_id = sqlc.arg(user_id)::bigint
)::boolean AS mutual
),
upserted AS (
INSERT INTO contacts (
user_id,
contact_user_id,
contact_phone,
contact_first_name,
contact_last_name,
note,
note_entities,
mutual
)
SELECT
sqlc.arg(user_id)::bigint,
sqlc.arg(contact_user_id)::bigint,
sqlc.arg(contact_phone)::text,
sqlc.arg(contact_first_name)::text,
sqlc.arg(contact_last_name)::text,
sqlc.arg(note)::text,
sqlc.arg(note_entities)::jsonb,
reverse.mutual
FROM reverse
ON CONFLICT (user_id, contact_user_id) DO UPDATE SET
contact_phone = EXCLUDED.contact_phone,
contact_first_name = EXCLUDED.contact_first_name,
contact_last_name = EXCLUDED.contact_last_name,
note = EXCLUDED.note,
note_entities = EXCLUDED.note_entities,
mutual = contacts.mutual OR EXCLUDED.mutual,
updated_at = now()
RETURNING *
),
reverse_updated AS (
UPDATE contacts c
SET mutual = true,
updated_at = now()
WHERE c.user_id = sqlc.arg(contact_user_id)::bigint
AND c.contact_user_id = sqlc.arg(user_id)::bigint
AND NOT c.mutual
RETURNING c.user_id
)
SELECT
c.contact_user_id,
c.mutual,
c.contact_phone,
c.contact_first_name,
c.contact_last_name,
c.note,
COALESCE(c.note_entities::text, '[]')::text AS note_entities_json,
u.id,
u.access_hash,
COALESCE(NULLIF(c.contact_phone, ''), u.phone)::text AS phone,
COALESCE(NULLIF(c.contact_first_name, ''), u.first_name)::text AS first_name,
COALESCE(c.contact_last_name, u.last_name)::text AS last_name,
u.username,
u.country_code,
u.verified,
u.support,
u.last_seen_at,
EXISTS (SELECT 1 FROM reverse_updated)::boolean AS reverse_mutual_changed
FROM upserted c
JOIN users u ON u.id = c.contact_user_id;
-- name: UpdateContactNote :one
WITH updated AS (
UPDATE contacts c
SET note = sqlc.arg(note)::text,
note_entities = sqlc.arg(note_entities)::jsonb,
updated_at = now()
WHERE c.user_id = sqlc.arg(user_id)::bigint
AND c.contact_user_id = sqlc.arg(contact_user_id)::bigint
RETURNING *
)
SELECT
c.contact_user_id,
c.mutual,
c.contact_phone,
c.contact_first_name,
c.contact_last_name,
c.note,
COALESCE(c.note_entities::text, '[]')::text AS note_entities_json,
u.id,
u.access_hash,
COALESCE(NULLIF(c.contact_phone, ''), u.phone)::text AS phone,
COALESCE(NULLIF(c.contact_first_name, ''), u.first_name)::text AS first_name,
COALESCE(c.contact_last_name, u.last_name)::text AS last_name,
u.username,
u.country_code,
u.verified,
u.support,
u.last_seen_at
FROM updated c
JOIN users u ON u.id = c.contact_user_id;
-- name: DeleteContacts :one
WITH deleted AS (
DELETE FROM contacts
WHERE user_id = sqlc.arg(user_id)::bigint
AND contact_user_id = ANY(sqlc.arg(contact_user_ids)::bigint[])
RETURNING contact_user_id
),
reverse_updated AS (
UPDATE contacts c
SET mutual = false,
updated_at = now()
FROM deleted d
WHERE c.user_id = d.contact_user_id
AND c.contact_user_id = sqlc.arg(user_id)::bigint
RETURNING c.user_id
)
SELECT COUNT(*)::int AS deleted_count
FROM deleted;

View file

@ -0,0 +1,773 @@
-- 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.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.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.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
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 (
NOT sqlc.arg(has_folder_id)::boolean
OR (
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,
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_last_seen_at,
peer_contact,
peer_mutual,
message_id,
message_from_user_id,
message_date,
message_outgoing,
message_body,
message_entities_json
FROM paged
ORDER BY
pinned DESC,
CASE WHEN pinned THEN COALESCE(NULLIF(pinned_order, 0), 2147483647) ELSE 2147483647 END ASC,
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.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
OR (
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(NULLIF(d.pinned_order, 0), 2147483647) ELSE 2147483647 END ASC,
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.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.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.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,
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,
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_last_seen_at,
peer_contact,
peer_mutual,
message_id,
message_from_user_id,
message_date,
message_outgoing,
message_body,
message_entities_json
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
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,
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 = EXCLUDED.top_message_id,
top_message_date = EXCLUDED.top_message_date,
unread_count = dialogs.unread_count + 1,
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
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,
CASE WHEN sqlc.arg(max_id)::int > 0 THEN sqlc.arg(max_id)::int ELSE d.top_message_id END
),
unread_count = 0,
unread_mark = false,
unread_mentions_count = 0,
unread_reactions_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 (
CASE WHEN sqlc.arg(max_id)::int > 0 THEN sqlc.arg(max_id)::int ELSE target.top_message_id END
) > 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
WITH next_order AS (
SELECT COALESCE(MAX(pinned_order), 0)::int + 1 AS value
FROM dialogs
WHERE user_id = sqlc.arg(user_id)::bigint
AND pinned
),
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.user_id
)
SELECT EXISTS (SELECT 1 FROM updated)::boolean AS changed;
-- name: SetDialogUnreadMark :one
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
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 (
SELECT DISTINCT ON (peer_type, peer_id)
peer_type,
peer_id,
pos::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;
-- 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
SET folder_id = deduped.folder_id,
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: 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 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 = 0,
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: ClearDialogAfterHistoryDelete :exec
UPDATE dialogs d
SET
top_message_id = 0,
top_message_date = 0,
read_inbox_max_id = GREATEST(d.read_inbox_max_id, d.top_message_id),
read_outbox_max_id = GREATEST(d.read_outbox_max_id, d.top_message_id),
unread_count = 0,
unread_mark = false,
unread_mentions_count = 0,
unread_reactions_count = 0,
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
DELETE FROM dialogs
WHERE user_id = $1
AND peer_type = $2
AND peer_id = $3;
-- 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;

View file

@ -0,0 +1,43 @@
-- name: GetAppConfig :one
SELECT client, hash, config_json::text AS config_json
FROM app_configs
WHERE client = $1;
-- name: UpsertAppConfig :exec
INSERT INTO app_configs (client, hash, config_json)
VALUES ($1, $2, sqlc.arg(config_json)::jsonb)
ON CONFLICT (client) DO UPDATE SET
hash = EXCLUDED.hash,
config_json = EXCLUDED.config_json,
updated_at = now();
-- name: ListCountries :many
SELECT
c.iso2,
c.default_name,
c.name,
c.hidden,
cc.country_code,
cc.prefixes,
cc.patterns
FROM countries c
JOIN country_codes cc ON cc.iso2 = c.iso2
ORDER BY c.order_index, c.iso2, cc.order_index, cc.country_code;
-- name: UpsertCountry :exec
INSERT INTO countries (iso2, default_name, name, hidden, order_index)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (iso2) DO UPDATE SET
default_name = EXCLUDED.default_name,
name = EXCLUDED.name,
hidden = EXCLUDED.hidden,
order_index = EXCLUDED.order_index,
updated_at = now();
-- name: UpsertCountryCode :exec
INSERT INTO country_codes (iso2, country_code, prefixes, patterns, order_index)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (iso2, country_code) DO UPDATE SET
prefixes = EXCLUDED.prefixes,
patterns = EXCLUDED.patterns,
order_index = EXCLUDED.order_index;

View file

@ -0,0 +1,47 @@
-- name: GetLangPackMeta :one
SELECT lang_pack, lang_code, version, strings_count
FROM lang_packs
WHERE lang_pack = $1 AND lang_code = $2;
-- name: UpsertLangPackMeta :exec
INSERT INTO lang_packs (lang_pack, lang_code, version, strings_count)
VALUES ($1, $2, $3, $4)
ON CONFLICT (lang_pack, lang_code) DO UPDATE SET
version = EXCLUDED.version,
strings_count = EXCLUDED.strings_count,
updated_at = now();
-- name: UpsertLangPackString :exec
INSERT INTO lang_pack_strings (
lang_pack, lang_code, key, version, pluralized, value,
zero_value, one_value, two_value, few_value, many_value, other_value, deleted
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
ON CONFLICT (lang_pack, lang_code, key) DO UPDATE SET
version = EXCLUDED.version,
pluralized = EXCLUDED.pluralized,
value = EXCLUDED.value,
zero_value = EXCLUDED.zero_value,
one_value = EXCLUDED.one_value,
two_value = EXCLUDED.two_value,
few_value = EXCLUDED.few_value,
many_value = EXCLUDED.many_value,
other_value = EXCLUDED.other_value,
deleted = EXCLUDED.deleted,
updated_at = now();
-- name: ListLangPackStrings :many
SELECT
lang_pack, lang_code, key, version, pluralized, value,
zero_value, one_value, two_value, few_value, many_value, other_value, deleted
FROM lang_pack_strings
WHERE lang_pack = $1 AND lang_code = $2 AND NOT deleted
ORDER BY key;
-- name: GetLangPackStringsByKeys :many
SELECT
lang_pack, lang_code, key, version, pluralized, value,
zero_value, one_value, two_value, few_value, many_value, other_value, deleted
FROM lang_pack_strings
WHERE lang_pack = $1 AND lang_code = $2 AND key = ANY(sqlc.arg(keys)::text[]) AND NOT deleted
ORDER BY key;

View file

@ -0,0 +1,331 @@
-- upload_parts ----------------------------------------------------------------
-- name: SaveUploadPart :exec
INSERT INTO upload_parts (owner_user_id, file_id, part, total_parts, is_big, bytes)
VALUES (
sqlc.arg(owner_user_id)::bigint,
sqlc.arg(file_id)::bigint,
sqlc.arg(part)::int,
sqlc.arg(total_parts)::int,
sqlc.arg(is_big)::boolean,
sqlc.arg(bytes)::bytea
)
ON CONFLICT (owner_user_id, file_id, part) DO UPDATE SET
total_parts = EXCLUDED.total_parts,
is_big = EXCLUDED.is_big,
bytes = EXCLUDED.bytes;
-- name: ListUploadParts :many
SELECT part, total_parts, is_big, bytes
FROM upload_parts
WHERE owner_user_id = sqlc.arg(owner_user_id)::bigint
AND file_id = sqlc.arg(file_id)::bigint
ORDER BY part ASC;
-- name: DeleteUploadParts :exec
DELETE FROM upload_parts
WHERE owner_user_id = sqlc.arg(owner_user_id)::bigint
AND file_id = sqlc.arg(file_id)::bigint;
-- file_blobs ------------------------------------------------------------------
-- name: PutFileBlob :exec
INSERT INTO file_blobs (location_key, backend, object_key, size, sha256, mime_type)
VALUES (
sqlc.arg(location_key)::text,
sqlc.arg(backend)::text,
sqlc.arg(object_key)::text,
sqlc.arg(size)::bigint,
sqlc.arg(sha256)::bytea,
sqlc.arg(mime_type)::text
)
ON CONFLICT (location_key) DO UPDATE SET
backend = EXCLUDED.backend,
object_key = EXCLUDED.object_key,
size = EXCLUDED.size,
sha256 = EXCLUDED.sha256,
mime_type = EXCLUDED.mime_type;
-- name: GetFileBlob :one
SELECT location_key, backend, object_key, size, sha256, mime_type
FROM file_blobs
WHERE location_key = sqlc.arg(location_key)::text;
-- documents -------------------------------------------------------------------
-- name: PutDocument :exec
INSERT INTO documents (id, access_hash, file_reference, date, mime_type, size, dc_id, attributes, thumbs)
VALUES (
sqlc.arg(id)::bigint,
sqlc.arg(access_hash)::bigint,
sqlc.arg(file_reference)::bytea,
sqlc.arg(date)::int,
sqlc.arg(mime_type)::text,
sqlc.arg(size)::bigint,
sqlc.arg(dc_id)::int,
sqlc.arg(attributes_json)::jsonb,
sqlc.arg(thumbs_json)::jsonb
)
ON CONFLICT (id) DO UPDATE SET
access_hash = EXCLUDED.access_hash,
file_reference = EXCLUDED.file_reference,
date = EXCLUDED.date,
mime_type = EXCLUDED.mime_type,
size = EXCLUDED.size,
dc_id = EXCLUDED.dc_id,
attributes = EXCLUDED.attributes,
thumbs = EXCLUDED.thumbs;
-- name: GetDocument :one
SELECT id, access_hash, file_reference, date, mime_type, size, dc_id,
attributes::text AS attributes_json,
thumbs::text AS thumbs_json
FROM documents
WHERE id = sqlc.arg(id)::bigint;
-- name: GetDocuments :many
SELECT id, access_hash, file_reference, date, mime_type, size, dc_id,
attributes::text AS attributes_json,
thumbs::text AS thumbs_json
FROM documents
WHERE id = ANY(sqlc.arg(ids)::bigint[]);
-- photos ----------------------------------------------------------------------
-- name: PutPhoto :exec
INSERT INTO photos (id, access_hash, file_reference, date, dc_id, has_stickers, sizes)
VALUES (
sqlc.arg(id)::bigint,
sqlc.arg(access_hash)::bigint,
sqlc.arg(file_reference)::bytea,
sqlc.arg(date)::int,
sqlc.arg(dc_id)::int,
sqlc.arg(has_stickers)::boolean,
sqlc.arg(sizes_json)::jsonb
)
ON CONFLICT (id) DO UPDATE SET
access_hash = EXCLUDED.access_hash,
file_reference = EXCLUDED.file_reference,
date = EXCLUDED.date,
dc_id = EXCLUDED.dc_id,
has_stickers = EXCLUDED.has_stickers,
sizes = EXCLUDED.sizes;
-- name: GetPhoto :one
SELECT id, access_hash, file_reference, date, dc_id, has_stickers,
sizes::text AS sizes_json
FROM photos
WHERE id = sqlc.arg(id)::bigint;
-- sticker_sets ----------------------------------------------------------------
-- name: PutStickerSet :exec
INSERT INTO sticker_sets (
id, access_hash, short_name, title, count, hash, set_kind,
official, animated, videos, emojis, masks, installed, archived, installed_date,
thumb_document_id, thumbs, thumb_dc_id, thumb_version, document_ids, packs, sort_order, system_key
) VALUES (
sqlc.arg(id)::bigint,
sqlc.arg(access_hash)::bigint,
sqlc.arg(short_name)::text,
sqlc.arg(title)::text,
sqlc.arg(count)::int,
sqlc.arg(hash)::int,
sqlc.arg(set_kind)::text,
sqlc.arg(official)::boolean,
sqlc.arg(animated)::boolean,
sqlc.arg(videos)::boolean,
sqlc.arg(emojis)::boolean,
sqlc.arg(masks)::boolean,
sqlc.arg(installed)::boolean,
sqlc.arg(archived)::boolean,
sqlc.arg(installed_date)::int,
sqlc.arg(thumb_document_id)::bigint,
sqlc.arg(thumbs_json)::jsonb,
sqlc.arg(thumb_dc_id)::int,
sqlc.arg(thumb_version)::int,
sqlc.arg(document_ids_json)::jsonb,
sqlc.arg(packs_json)::jsonb,
sqlc.arg(sort_order)::int,
sqlc.arg(system_key)::text
)
ON CONFLICT (id) DO UPDATE SET
access_hash = EXCLUDED.access_hash,
short_name = EXCLUDED.short_name,
title = EXCLUDED.title,
count = EXCLUDED.count,
hash = EXCLUDED.hash,
set_kind = EXCLUDED.set_kind,
official = EXCLUDED.official,
animated = EXCLUDED.animated,
videos = EXCLUDED.videos,
emojis = EXCLUDED.emojis,
masks = EXCLUDED.masks,
installed = EXCLUDED.installed,
archived = EXCLUDED.archived,
installed_date = EXCLUDED.installed_date,
thumb_document_id = EXCLUDED.thumb_document_id,
thumbs = EXCLUDED.thumbs,
thumb_dc_id = EXCLUDED.thumb_dc_id,
thumb_version = EXCLUDED.thumb_version,
document_ids = EXCLUDED.document_ids,
packs = EXCLUDED.packs,
sort_order = EXCLUDED.sort_order,
system_key = EXCLUDED.system_key;
-- name: GetStickerSetByID :one
SELECT
id, access_hash, short_name, title, count, hash, set_kind,
official, animated, videos, emojis, masks, installed, archived, installed_date,
thumb_document_id, thumbs::text AS thumbs_json, thumb_dc_id, thumb_version,
document_ids::text AS document_ids_json, packs::text AS packs_json, sort_order, system_key
FROM sticker_sets
WHERE id = sqlc.arg(id)::bigint;
-- name: GetStickerSetByShortName :one
SELECT
id, access_hash, short_name, title, count, hash, set_kind,
official, animated, videos, emojis, masks, installed, archived, installed_date,
thumb_document_id, thumbs::text AS thumbs_json, thumb_dc_id, thumb_version,
document_ids::text AS document_ids_json, packs::text AS packs_json, sort_order, system_key
FROM sticker_sets
WHERE short_name = sqlc.arg(short_name)::text;
-- name: GetStickerSetBySystemKey :one
SELECT
id, access_hash, short_name, title, count, hash, set_kind,
official, animated, videos, emojis, masks, installed, archived, installed_date,
thumb_document_id, thumbs::text AS thumbs_json, thumb_dc_id, thumb_version,
document_ids::text AS document_ids_json, packs::text AS packs_json, sort_order, system_key
FROM sticker_sets
WHERE system_key = sqlc.arg(system_key)::text;
-- name: ListStickerSetsByKind :many
SELECT
id, access_hash, short_name, title, count, hash, set_kind,
official, animated, videos, emojis, masks, installed, archived, installed_date,
thumb_document_id, thumbs::text AS thumbs_json, thumb_dc_id, thumb_version,
document_ids::text AS document_ids_json, packs::text AS packs_json, sort_order, system_key
FROM sticker_sets
WHERE set_kind = sqlc.arg(set_kind)::text
ORDER BY sort_order ASC, id ASC;
-- name: CountStickerSets :one
SELECT count(*)::int AS total FROM sticker_sets;
-- available_reactions ---------------------------------------------------------
-- name: PutAvailableReaction :exec
INSERT INTO available_reactions (
reaction, title, inactive, premium,
static_icon_id, appear_animation_id, select_animation_id,
activate_animation_id, effect_animation_id, around_animation_id, center_icon_id, sort_order
) VALUES (
sqlc.arg(reaction)::text,
sqlc.arg(title)::text,
sqlc.arg(inactive)::boolean,
sqlc.arg(premium)::boolean,
sqlc.arg(static_icon_id)::bigint,
sqlc.arg(appear_animation_id)::bigint,
sqlc.arg(select_animation_id)::bigint,
sqlc.arg(activate_animation_id)::bigint,
sqlc.arg(effect_animation_id)::bigint,
sqlc.arg(around_animation_id)::bigint,
sqlc.arg(center_icon_id)::bigint,
sqlc.arg(sort_order)::int
)
ON CONFLICT (reaction) DO UPDATE SET
title = EXCLUDED.title,
inactive = EXCLUDED.inactive,
premium = EXCLUDED.premium,
static_icon_id = EXCLUDED.static_icon_id,
appear_animation_id = EXCLUDED.appear_animation_id,
select_animation_id = EXCLUDED.select_animation_id,
activate_animation_id = EXCLUDED.activate_animation_id,
effect_animation_id = EXCLUDED.effect_animation_id,
around_animation_id = EXCLUDED.around_animation_id,
center_icon_id = EXCLUDED.center_icon_id,
sort_order = EXCLUDED.sort_order;
-- name: ListAvailableReactions :many
SELECT
reaction, title, inactive, premium,
static_icon_id, appear_animation_id, select_animation_id,
activate_animation_id, effect_animation_id, around_animation_id, center_icon_id, sort_order
FROM available_reactions
ORDER BY sort_order ASC, reaction ASC;
-- name: CountAvailableReactions :one
SELECT count(*)::int AS total FROM available_reactions;
-- profile_photos --------------------------------------------------------------
-- name: AddProfilePhoto :exec
INSERT INTO profile_photos (owner_peer_type, owner_peer_id, photo_id, date, active, sort_order)
VALUES (
sqlc.arg(owner_peer_type)::text,
sqlc.arg(owner_peer_id)::bigint,
sqlc.arg(photo_id)::bigint,
sqlc.arg(date)::int,
true,
sqlc.arg(sort_order)::bigint
)
ON CONFLICT (owner_peer_type, owner_peer_id, photo_id) DO UPDATE SET
date = EXCLUDED.date,
active = true,
sort_order = EXCLUDED.sort_order;
-- name: NextProfilePhotoOrder :one
SELECT COALESCE(MAX(sort_order), 0)::bigint AS max_order
FROM profile_photos
WHERE owner_peer_type = sqlc.arg(owner_peer_type)::text
AND owner_peer_id = sqlc.arg(owner_peer_id)::bigint;
-- name: CurrentProfilePhoto :one
SELECT photo_id
FROM profile_photos
WHERE owner_peer_type = sqlc.arg(owner_peer_type)::text
AND owner_peer_id = sqlc.arg(owner_peer_id)::bigint
AND active
ORDER BY sort_order DESC
LIMIT 1;
-- name: CurrentProfilePhotosForOwners :many
SELECT DISTINCT ON (pp.owner_peer_id)
pp.owner_peer_id,
pp.photo_id,
ph.dc_id,
ph.sizes::text AS sizes_json
FROM profile_photos pp
JOIN photos ph ON ph.id = pp.photo_id
WHERE pp.owner_peer_type = sqlc.arg(owner_peer_type)::text
AND pp.owner_peer_id = ANY(sqlc.arg(owner_ids)::bigint[])
AND pp.active
ORDER BY pp.owner_peer_id, pp.sort_order DESC;
-- name: ListProfilePhotos :many
SELECT photo_id
FROM profile_photos
WHERE owner_peer_type = sqlc.arg(owner_peer_type)::text
AND owner_peer_id = sqlc.arg(owner_peer_id)::bigint
AND active
AND (sqlc.arg(max_id)::bigint <= 0 OR photo_id < sqlc.arg(max_id)::bigint)
ORDER BY sort_order DESC
OFFSET sqlc.arg(offset_count)::int
LIMIT sqlc.arg(limit_count)::int;
-- name: CountProfilePhotos :one
SELECT count(*)::int AS total
FROM profile_photos
WHERE owner_peer_type = sqlc.arg(owner_peer_type)::text
AND owner_peer_id = sqlc.arg(owner_peer_id)::bigint
AND active;
-- name: DeactivateProfilePhotos :many
UPDATE profile_photos
SET active = false
WHERE owner_peer_type = sqlc.arg(owner_peer_type)::text
AND owner_peer_id = sqlc.arg(owner_peer_id)::bigint
AND photo_id = ANY(sqlc.arg(photo_ids)::bigint[])
AND active
RETURNING photo_id;

View file

@ -0,0 +1,932 @@
-- name: CreateMessage :one
WITH pm AS (
INSERT INTO private_messages (
sender_user_id,
recipient_user_id,
random_id,
message_date,
body,
entities
) VALUES (
sqlc.arg(from_user_id),
sqlc.arg(owner_user_id),
0,
sqlc.arg(message_date),
sqlc.arg(body),
sqlc.arg(entities_json)::jsonb
)
RETURNING id, sender_user_id
),
box AS (
INSERT INTO message_boxes (
owner_user_id,
box_id,
private_message_id,
message_sender_id,
peer_type,
peer_id,
from_user_id,
message_date,
outgoing,
body,
entities,
pts
)
SELECT
sqlc.arg(owner_user_id),
sqlc.arg(box_id),
pm.id,
pm.sender_user_id,
sqlc.arg(peer_type),
sqlc.arg(peer_id),
sqlc.arg(from_user_id),
sqlc.arg(message_date),
sqlc.arg(outgoing),
sqlc.arg(body),
sqlc.arg(entities_json)::jsonb,
sqlc.arg(pts)
FROM pm
RETURNING
box_id,
private_message_id,
owner_user_id,
peer_type,
peer_id,
from_user_id,
message_date,
edit_date,
outgoing,
body,
entities::text AS entities_json,
pts
)
SELECT
box_id,
private_message_id,
owner_user_id,
peer_type,
peer_id,
from_user_id,
message_date,
edit_date,
outgoing,
body,
entities_json,
pts
FROM box;
-- name: CreatePrivateMessage :one
INSERT INTO private_messages (
sender_user_id,
recipient_user_id,
random_id,
message_date,
body,
entities,
silent,
noforwards,
reply_to_msg_id,
reply_to_peer_type,
reply_to_peer_id,
reply_to_top_id,
quote_text,
quote_entities,
quote_offset,
fwd_from_peer_type,
fwd_from_peer_id,
fwd_from_name,
fwd_date,
media
) VALUES (
$1, $2, $3, $4, $5, sqlc.arg(entities_json)::jsonb,
sqlc.arg(silent)::boolean,
sqlc.arg(noforwards)::boolean,
sqlc.arg(reply_to_msg_id)::int,
sqlc.arg(reply_to_peer_type)::text,
sqlc.arg(reply_to_peer_id)::bigint,
sqlc.arg(reply_to_top_id)::int,
sqlc.arg(quote_text)::text,
sqlc.arg(quote_entities_json)::jsonb,
sqlc.arg(quote_offset)::int,
sqlc.arg(fwd_from_peer_type)::text,
sqlc.arg(fwd_from_peer_id)::bigint,
sqlc.arg(fwd_from_name)::text,
sqlc.arg(fwd_date)::int,
sqlc.arg(media_json)::jsonb
)
ON CONFLICT (sender_user_id, random_id) WHERE random_id <> 0 DO NOTHING
RETURNING
id,
sender_user_id,
recipient_user_id,
random_id,
message_date,
edit_date,
body,
entities::text AS entities_json;
-- name: GetPrivateMessageByRandomID :one
SELECT
id,
sender_user_id,
recipient_user_id,
random_id,
message_date,
edit_date,
body,
entities::text AS entities_json
FROM private_messages
WHERE sender_user_id = $1
AND random_id = $2
AND random_id <> 0;
-- name: CreateMessageBox :one
INSERT INTO message_boxes (
owner_user_id,
box_id,
private_message_id,
message_sender_id,
peer_type,
peer_id,
from_user_id,
message_date,
outgoing,
body,
entities,
silent,
noforwards,
reply_to_msg_id,
reply_to_peer_type,
reply_to_peer_id,
reply_to_top_id,
quote_text,
quote_entities,
quote_offset,
fwd_from_peer_type,
fwd_from_peer_id,
fwd_from_name,
fwd_date,
pts,
media
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, sqlc.arg(entities_json)::jsonb,
sqlc.arg(silent)::boolean,
sqlc.arg(noforwards)::boolean,
sqlc.arg(reply_to_msg_id)::int,
sqlc.arg(reply_to_peer_type)::text,
sqlc.arg(reply_to_peer_id)::bigint,
sqlc.arg(reply_to_top_id)::int,
sqlc.arg(quote_text)::text,
sqlc.arg(quote_entities_json)::jsonb,
sqlc.arg(quote_offset)::int,
sqlc.arg(fwd_from_peer_type)::text,
sqlc.arg(fwd_from_peer_id)::bigint,
sqlc.arg(fwd_from_name)::text,
sqlc.arg(fwd_date)::int,
sqlc.arg(pts)::int,
sqlc.arg(media_json)::jsonb
)
RETURNING
box_id,
private_message_id,
owner_user_id,
peer_type,
peer_id,
from_user_id,
message_date,
edit_date,
outgoing,
body,
entities::text AS entities_json,
silent,
noforwards,
reply_to_msg_id,
reply_to_peer_type,
reply_to_peer_id,
reply_to_top_id,
quote_text,
quote_entities::text AS quote_entities_json,
quote_offset,
fwd_from_peer_type,
fwd_from_peer_id,
fwd_from_name,
fwd_date,
pts,
media::text AS media_json;
-- name: GetMessageBoxByPrivateMessage :one
SELECT
box_id,
private_message_id,
owner_user_id,
peer_type,
peer_id,
from_user_id,
message_date,
edit_date,
outgoing,
body,
entities::text AS entities_json,
silent,
noforwards,
reply_to_msg_id,
reply_to_peer_type,
reply_to_peer_id,
reply_to_top_id,
quote_text,
quote_entities::text AS quote_entities_json,
quote_offset,
fwd_from_peer_type,
fwd_from_peer_id,
fwd_from_name,
fwd_date,
pts,
media::text AS media_json
FROM message_boxes
WHERE owner_user_id = $1
AND private_message_id = $2
AND NOT deleted;
-- name: GetMessageBoxForReply :one
SELECT
box_id,
private_message_id,
message_sender_id
FROM message_boxes
WHERE owner_user_id = sqlc.arg(owner_user_id)::bigint
AND peer_type = sqlc.arg(peer_type)::text
AND peer_id = sqlc.arg(peer_id)::bigint
AND box_id = sqlc.arg(box_id)::int
AND NOT deleted
LIMIT 1;
-- name: GetMessageBoxesForForward :many
WITH requested AS (
SELECT
id::int AS box_id,
ord::int AS ord
FROM unnest(sqlc.arg(box_ids)::int[]) WITH ORDINALITY AS r(id, ord)
)
SELECT
r.ord,
m.box_id,
m.private_message_id,
m.owner_user_id,
m.message_sender_id,
m.peer_type,
m.peer_id,
m.from_user_id,
m.message_date,
m.edit_date,
m.outgoing,
m.body,
m.entities::text AS entities_json,
m.silent,
m.noforwards,
m.reply_to_msg_id,
m.reply_to_peer_type,
m.reply_to_peer_id,
m.reply_to_top_id,
m.quote_text,
m.quote_entities::text AS quote_entities_json,
m.quote_offset,
m.fwd_from_peer_type,
m.fwd_from_peer_id,
m.fwd_from_name,
m.fwd_date,
m.pts,
m.media::text AS media_json
FROM requested r
JOIN message_boxes m
ON m.owner_user_id = sqlc.arg(owner_user_id)::bigint
AND m.peer_type = sqlc.arg(peer_type)::text
AND m.peer_id = sqlc.arg(peer_id)::bigint
AND m.box_id = r.box_id
AND NOT m.deleted
ORDER BY r.ord ASC;
-- name: MaxMessageBoxID :one
SELECT COALESCE(MAX(box_id), 0)::int AS max_box_id
FROM message_boxes
WHERE owner_user_id = $1;
-- name: ListMessagesByUser :many
WITH load_params AS (
SELECT
sqlc.arg(offset_id)::int AS offset_id,
sqlc.arg(offset_date)::int AS offset_date,
sqlc.arg(add_offset)::int AS add_offset,
sqlc.arg(limit_count)::int AS limit_count,
CASE
WHEN sqlc.arg(add_offset)::int >= 0 THEN 'backward'
WHEN sqlc.arg(add_offset)::int + sqlc.arg(limit_count)::int > 0 THEN 'around'
ELSE 'forward'
END::text AS load_type
),
base AS NOT MATERIALIZED (
SELECT
m.box_id,
m.private_message_id,
m.owner_user_id,
m.peer_type,
m.peer_id,
m.from_user_id,
m.message_date,
m.edit_date,
m.outgoing,
m.body,
m.entities::text AS entities_json,
m.silent,
m.noforwards,
m.reply_to_msg_id,
m.reply_to_peer_type,
m.reply_to_peer_id,
m.reply_to_top_id,
m.quote_text,
m.quote_entities::text AS quote_entities_json,
m.quote_offset,
m.fwd_from_peer_type,
m.fwd_from_peer_id,
m.fwd_from_name,
m.fwd_date,
m.pts,
m.media::text AS media_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.last_seen_at, 0)::bigint AS peer_last_seen_at,
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.last_seen_at, 0)::bigint AS from_user_last_seen_at
FROM message_boxes m
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
WHERE m.owner_user_id = $1
AND NOT m.deleted
AND (
NOT sqlc.arg(has_peer)::boolean
OR (m.peer_type = sqlc.arg(peer_type)::text AND m.peer_id = sqlc.arg(peer_id)::bigint)
)
AND (
sqlc.arg(query)::text = ''
OR m.body ILIKE ('%' || sqlc.arg(query)::text || '%')
)
AND (sqlc.arg(max_id)::int <= 0 OR m.box_id < sqlc.arg(max_id)::int)
AND (sqlc.arg(min_id)::int <= 0 OR m.box_id > sqlc.arg(min_id)::int)
),
total AS (
SELECT count(*)::int AS total_count
FROM base
WHERE sqlc.arg(need_total_count)::boolean
),
backward AS (
SELECT b.*
FROM base b
CROSS JOIN load_params p
WHERE p.load_type = 'backward'
AND (
(p.offset_date > 0 AND b.message_date < p.offset_date)
OR (p.offset_date <= 0 AND (p.offset_id <= 0 OR b.box_id < p.offset_id))
)
ORDER BY b.box_id DESC
OFFSET GREATEST((SELECT add_offset FROM load_params), 0)
LIMIT (SELECT limit_count FROM load_params)
),
around_forward AS (
SELECT f.*
FROM (
SELECT b.*
FROM base b
CROSS JOIN load_params p
WHERE p.load_type = 'around'
AND (
(p.offset_date > 0 AND b.message_date >= p.offset_date)
OR (p.offset_date <= 0 AND p.offset_id > 0 AND b.box_id > p.offset_id)
)
ORDER BY b.box_id ASC
LIMIT LEAST(-(SELECT add_offset FROM load_params), (SELECT limit_count FROM load_params))
) f
),
around_backward AS (
SELECT b.*
FROM base b
CROSS JOIN load_params p
WHERE p.load_type = 'around'
AND (
(p.offset_date > 0 AND b.message_date < p.offset_date)
OR (p.offset_date <= 0 AND (p.offset_id <= 0 OR b.box_id <= p.offset_id))
)
ORDER BY b.box_id DESC
LIMIT GREATEST((SELECT limit_count + add_offset FROM load_params), 0)
),
forward AS (
SELECT f.*
FROM (
SELECT b.*
FROM base b
CROSS JOIN load_params p
WHERE p.load_type = 'forward'
AND (
(p.offset_date > 0 AND b.message_date >= p.offset_date)
OR (p.offset_date <= 0 AND p.offset_id > 0 AND b.box_id > p.offset_id)
)
ORDER BY b.box_id ASC
LIMIT (SELECT limit_count FROM load_params)
) f
),
paged AS (
SELECT * FROM backward
UNION ALL
SELECT * FROM around_forward
UNION ALL
SELECT * FROM around_backward
UNION ALL
SELECT * FROM forward
)
SELECT
box_id,
private_message_id,
owner_user_id,
peer_type,
peer_id,
from_user_id,
message_date,
edit_date,
outgoing,
body,
entities_json,
silent,
noforwards,
reply_to_msg_id,
reply_to_peer_type,
reply_to_peer_id,
reply_to_top_id,
quote_text,
quote_entities_json,
quote_offset,
fwd_from_peer_type,
fwd_from_peer_id,
fwd_from_name,
fwd_date,
pts,
media_json,
peer_user_id,
peer_access_hash,
peer_phone,
peer_first_name,
peer_last_name,
peer_username,
peer_country_code,
peer_verified,
peer_support,
peer_last_seen_at,
from_user_user_id,
from_user_access_hash,
from_user_phone,
from_user_first_name,
from_user_last_name,
from_user_username,
from_user_country_code,
from_user_verified,
from_user_support,
from_user_last_seen_at,
COALESCE(total.total_count, 0)::int AS total_count
FROM paged
CROSS JOIN total
ORDER BY box_id DESC;
-- name: GetMessageBoxesByIDs :many
SELECT
wanted.box_id AS requested_box_id,
m.box_id,
m.private_message_id,
m.owner_user_id,
m.peer_type,
m.peer_id,
m.from_user_id,
m.message_date,
m.edit_date,
m.outgoing,
m.body,
m.entities::text AS entities_json,
m.silent,
m.noforwards,
m.reply_to_msg_id,
m.reply_to_peer_type,
m.reply_to_peer_id,
m.reply_to_top_id,
m.quote_text,
m.quote_entities::text AS quote_entities_json,
m.quote_offset,
m.fwd_from_peer_type,
m.fwd_from_peer_id,
m.fwd_from_name,
m.fwd_date,
m.pts,
m.media::text AS media_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.last_seen_at, 0)::bigint AS peer_last_seen_at,
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.last_seen_at, 0)::bigint AS from_user_last_seen_at
FROM unnest(@box_ids::int[]) WITH ORDINALITY AS wanted(box_id, ord)
JOIN message_boxes m
ON m.owner_user_id = sqlc.arg(owner_user_id)::bigint
AND m.box_id = wanted.box_id
AND NOT m.deleted
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
ORDER BY wanted.ord ASC;
-- name: GetMessageBoxForEdit :one
SELECT
box_id,
private_message_id,
owner_user_id,
message_sender_id,
peer_type,
peer_id,
from_user_id,
message_date,
edit_date,
outgoing,
body,
entities::text AS entities_json,
silent,
noforwards,
reply_to_msg_id,
reply_to_peer_type,
reply_to_peer_id,
reply_to_top_id,
quote_text,
quote_entities::text AS quote_entities_json,
quote_offset,
fwd_from_peer_type,
fwd_from_peer_id,
fwd_from_name,
fwd_date,
pts,
media::text AS media_json
FROM message_boxes
WHERE owner_user_id = sqlc.arg(owner_user_id)::bigint
AND box_id = sqlc.arg(box_id)::int
AND peer_type = sqlc.arg(peer_type)::text
AND peer_id = sqlc.arg(peer_id)::bigint
AND NOT deleted
LIMIT 1
FOR UPDATE;
-- name: ListVisibleMessageBoxesByPrivateMessage :many
SELECT
box_id,
private_message_id,
owner_user_id,
message_sender_id,
peer_type,
peer_id,
from_user_id,
message_date,
edit_date,
outgoing,
body,
entities::text AS entities_json,
silent,
noforwards,
reply_to_msg_id,
reply_to_peer_type,
reply_to_peer_id,
reply_to_top_id,
quote_text,
quote_entities::text AS quote_entities_json,
quote_offset,
fwd_from_peer_type,
fwd_from_peer_id,
fwd_from_name,
fwd_date,
pts,
media::text AS media_json
FROM message_boxes
WHERE message_sender_id = sqlc.arg(message_sender_id)::bigint
AND private_message_id = sqlc.arg(private_message_id)::bigint
AND NOT deleted
ORDER BY owner_user_id ASC, box_id ASC
FOR UPDATE;
-- name: UpdatePrivateMessageEdit :exec
UPDATE private_messages
SET body = sqlc.arg(body)::text,
entities = sqlc.arg(entities_json)::jsonb,
edit_date = sqlc.arg(edit_date)::int
WHERE sender_user_id = sqlc.arg(sender_user_id)::bigint
AND id = sqlc.arg(private_message_id)::bigint;
-- name: UpdateMessageBoxEdit :one
UPDATE message_boxes
SET body = sqlc.arg(body)::text,
entities = sqlc.arg(entities_json)::jsonb,
edit_date = sqlc.arg(edit_date)::int,
pts = sqlc.arg(pts)::int
WHERE owner_user_id = sqlc.arg(owner_user_id)::bigint
AND box_id = sqlc.arg(box_id)::int
AND NOT deleted
RETURNING
box_id,
private_message_id,
owner_user_id,
message_sender_id,
peer_type,
peer_id,
from_user_id,
message_date,
edit_date,
outgoing,
body,
entities::text AS entities_json,
silent,
noforwards,
reply_to_msg_id,
reply_to_peer_type,
reply_to_peer_id,
reply_to_top_id,
quote_text,
quote_entities::text AS quote_entities_json,
quote_offset,
fwd_from_peer_type,
fwd_from_peer_id,
fwd_from_name,
fwd_date,
pts,
media::text AS media_json;
-- name: GetDialogReadStateForUpdate :one
SELECT
user_id,
peer_type,
peer_id,
top_message_id,
read_inbox_max_id,
unread_count
FROM dialogs
WHERE user_id = sqlc.arg(user_id)::bigint
AND peer_type = sqlc.arg(peer_type)::text
AND peer_id = sqlc.arg(peer_id)::bigint
FOR UPDATE;
-- name: LatestIncomingReadReceiptCandidate :one
SELECT
m.message_sender_id,
m.private_message_id,
sender_box.owner_user_id AS sender_owner_user_id,
sender_box.box_id AS sender_box_id
FROM message_boxes m
JOIN message_boxes sender_box
ON sender_box.message_sender_id = m.message_sender_id
AND sender_box.private_message_id = m.private_message_id
AND sender_box.owner_user_id = m.message_sender_id
AND sender_box.outgoing
AND NOT sender_box.deleted
WHERE m.owner_user_id = sqlc.arg(owner_user_id)::bigint
AND m.peer_type = sqlc.arg(peer_type)::text
AND m.peer_id = sqlc.arg(peer_id)::bigint
AND NOT m.outgoing
AND NOT m.deleted
AND m.box_id > sqlc.arg(old_read_inbox_max_id)::int
AND m.box_id <= sqlc.arg(new_read_inbox_max_id)::int
ORDER BY m.box_id DESC
LIMIT 1;
-- name: UpdateDialogReadInbox :one
UPDATE dialogs d
SET
read_inbox_max_id = GREATEST(d.read_inbox_max_id, sqlc.arg(read_inbox_max_id)::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 > GREATEST(d.read_inbox_max_id, sqlc.arg(read_inbox_max_id)::int)
),
unread_mark = false,
unread_mentions_count = 0,
unread_reactions_count = 0,
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.read_inbox_max_id,
d.unread_count;
-- name: UpdateDialogReadOutbox :one
UPDATE dialogs
SET
read_outbox_max_id = GREATEST(read_outbox_max_id, sqlc.arg(read_outbox_max_id)::int),
updated_at = now()
WHERE user_id = sqlc.arg(user_id)::bigint
AND peer_type = sqlc.arg(peer_type)::text
AND peer_id = sqlc.arg(peer_id)::bigint
AND read_outbox_max_id < sqlc.arg(read_outbox_max_id)::int
RETURNING read_outbox_max_id;
-- name: GetOutboxMessageForReadDate :one
SELECT box_id
FROM message_boxes
WHERE owner_user_id = sqlc.arg(owner_user_id)::bigint
AND peer_type = sqlc.arg(peer_type)::text
AND peer_id = sqlc.arg(peer_id)::bigint
AND box_id = sqlc.arg(box_id)::int
AND outgoing
AND NOT deleted
LIMIT 1;
-- name: GetOutboxReadDate :one
SELECT COALESCE(MIN(date), 0)::int AS read_date
FROM user_update_events
WHERE user_id = sqlc.arg(user_id)::bigint
AND event_type = 'read_history_outbox'
AND peer_type = sqlc.arg(peer_type)::text
AND peer_id = sqlc.arg(peer_id)::bigint
AND max_id >= sqlc.arg(message_id)::int;
-- name: DeleteMessageBoxesByIDs :many
WITH updated AS (
UPDATE message_boxes m
SET deleted = true
WHERE m.owner_user_id = sqlc.arg(owner_user_id)::bigint
AND m.box_id = ANY(sqlc.arg(box_ids)::int[])
AND NOT m.deleted
RETURNING
m.owner_user_id,
m.box_id,
m.private_message_id,
m.message_sender_id,
m.peer_type,
m.peer_id
)
SELECT
owner_user_id,
box_id,
private_message_id,
message_sender_id,
peer_type,
peer_id
FROM updated
ORDER BY box_id ASC;
-- name: DeleteMessageBoxesByPeer :many
WITH updated AS (
UPDATE message_boxes m
SET deleted = true
WHERE m.owner_user_id = sqlc.arg(owner_user_id)::bigint
AND m.peer_type = sqlc.arg(peer_type)::text
AND m.peer_id = sqlc.arg(peer_id)::bigint
AND (sqlc.arg(max_id)::int <= 0 OR m.box_id <= sqlc.arg(max_id)::int)
AND NOT m.deleted
RETURNING
m.owner_user_id,
m.box_id,
m.private_message_id,
m.message_sender_id,
m.peer_type,
m.peer_id
)
SELECT
owner_user_id,
box_id,
private_message_id,
message_sender_id,
peer_type,
peer_id
FROM updated
ORDER BY box_id ASC;
-- name: DeleteMessageBoxesByPeerBatch :many
WITH target AS (
SELECT
m.owner_user_id,
m.box_id
FROM message_boxes m
WHERE m.owner_user_id = sqlc.arg(owner_user_id)::bigint
AND m.peer_type = sqlc.arg(peer_type)::text
AND m.peer_id = sqlc.arg(peer_id)::bigint
AND (sqlc.arg(max_id)::int <= 0 OR m.box_id <= sqlc.arg(max_id)::int)
AND NOT m.deleted
ORDER BY m.box_id DESC
LIMIT sqlc.arg(limit_count)::int
FOR UPDATE SKIP LOCKED
),
updated AS (
UPDATE message_boxes m
SET deleted = true
FROM target t
WHERE m.owner_user_id = t.owner_user_id
AND m.box_id = t.box_id
RETURNING
m.owner_user_id,
m.box_id,
m.private_message_id,
m.message_sender_id,
m.peer_type,
m.peer_id
)
SELECT
owner_user_id,
box_id,
private_message_id,
message_sender_id,
peer_type,
peer_id
FROM updated
ORDER BY box_id ASC;
-- name: HasDeletableMessageBoxByPeer :one
SELECT EXISTS (
SELECT 1
FROM message_boxes m
WHERE m.owner_user_id = sqlc.arg(owner_user_id)::bigint
AND m.peer_type = sqlc.arg(peer_type)::text
AND m.peer_id = sqlc.arg(peer_id)::bigint
AND (sqlc.arg(max_id)::int <= 0 OR m.box_id <= sqlc.arg(max_id)::int)
AND NOT m.deleted
LIMIT 1
)::boolean AS more;
-- name: DeleteMessageBoxesByPrivateMessages :many
WITH requested AS (
SELECT
(sqlc.arg(message_sender_ids)::bigint[])[i] AS message_sender_id,
(sqlc.arg(private_message_ids)::bigint[])[i] AS private_message_id
FROM generate_subscripts(sqlc.arg(private_message_ids)::bigint[], 1) AS g(i)
WHERE i <= cardinality(sqlc.arg(message_sender_ids)::bigint[])
),
deduped AS (
SELECT DISTINCT message_sender_id, private_message_id
FROM requested
),
updated AS (
UPDATE message_boxes m
SET deleted = true
FROM deduped d
WHERE m.message_sender_id = d.message_sender_id
AND m.private_message_id = d.private_message_id
AND NOT m.deleted
RETURNING
m.owner_user_id,
m.box_id,
m.private_message_id,
m.message_sender_id,
m.peer_type,
m.peer_id
)
SELECT
owner_user_id,
box_id,
private_message_id,
message_sender_id,
peer_type,
peer_id
FROM updated
ORDER BY owner_user_id ASC, box_id ASC;
-- name: TopVisibleMessageBoxByPeer :one
SELECT
box_id,
message_date
FROM message_boxes
WHERE owner_user_id = $1
AND peer_type = $2
AND peer_id = $3
AND NOT deleted
ORDER BY box_id DESC
LIMIT 1;

View file

@ -0,0 +1,23 @@
-- name: UpsertTempAuthKeyBinding :exec
INSERT INTO temp_auth_key_bindings (
temp_auth_key_id, perm_auth_key_id, nonce, temp_session_id, expires_at, encrypted_message
)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (temp_auth_key_id) DO UPDATE SET
perm_auth_key_id = EXCLUDED.perm_auth_key_id,
nonce = EXCLUDED.nonce,
temp_session_id = EXCLUDED.temp_session_id,
expires_at = EXCLUDED.expires_at,
encrypted_message = EXCLUDED.encrypted_message,
created_at = now();
-- name: GetTempAuthKeyBinding :one
SELECT
temp_auth_key_id,
perm_auth_key_id,
nonce,
temp_session_id,
expires_at,
encrypted_message
FROM temp_auth_key_bindings
WHERE temp_auth_key_id = $1;

View file

@ -0,0 +1,24 @@
-- name: GetUpdateState :one
SELECT auth_key_id, user_id, pts, qts, date, seq
FROM update_states
WHERE auth_key_id = $1
AND user_id = $2;
-- name: UpsertUpdateState :exec
INSERT INTO update_states (auth_key_id, user_id, pts, qts, date, seq)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (auth_key_id, user_id) DO UPDATE SET
pts = EXCLUDED.pts,
qts = EXCLUDED.qts,
date = EXCLUDED.date,
seq = EXCLUDED.seq,
updated_at = now();
-- name: DeleteUpdateState :exec
DELETE FROM update_states
WHERE auth_key_id = $1
AND user_id = $2;
-- name: DeleteUpdateStatesByAuthKey :exec
DELETE FROM update_states
WHERE auth_key_id = $1;

View file

@ -0,0 +1,104 @@
-- name: GetUserByID :one
SELECT * FROM users WHERE id = $1;
-- name: GetUsersByIDs :many
SELECT *
FROM users
WHERE id = ANY(sqlc.arg(ids)::bigint[])
ORDER BY id;
-- name: GetUserByPhone :one
SELECT * FROM users WHERE phone = $1;
-- name: GetUsersByPhones :many
SELECT *
FROM users
WHERE phone = ANY(sqlc.arg(phones)::text[])
ORDER BY id;
-- name: GetUserByUsername :one
SELECT * FROM users WHERE lower(username) = lower($1) AND username <> '';
-- name: SearchUsers :many
WITH matched AS (
SELECT
u.id,
u.access_hash,
COALESCE(NULLIF(c.contact_phone, ''), u.phone)::text AS phone,
COALESCE(NULLIF(c.contact_first_name, ''), u.first_name)::text AS first_name,
COALESCE(c.contact_last_name, u.last_name)::text AS last_name,
u.about,
u.username,
u.country_code,
u.verified,
u.support,
u.last_seen_at,
(c.contact_user_id IS NOT NULL)::boolean AS contact,
COALESCE(c.mutual, false)::boolean AS mutual,
CASE
WHEN sqlc.arg(phone_query)::text <> '' AND u.phone = sqlc.arg(phone_query)::text THEN 0
WHEN lower(u.username) = sqlc.arg(query_lower)::text THEN 1
WHEN lower(COALESCE(NULLIF(c.contact_first_name, ''), u.first_name)) = sqlc.arg(query_lower)::text THEN 2
WHEN lower(u.first_name) = sqlc.arg(query_lower)::text THEN 3
WHEN c.contact_user_id IS NOT NULL THEN 4
ELSE 5
END AS rank
FROM users u
LEFT JOIN contacts c ON c.user_id = sqlc.arg(current_user_id)::bigint AND c.contact_user_id = u.id
WHERE u.id <> sqlc.arg(current_user_id)::bigint
AND sqlc.arg(query_lower)::text <> ''
AND (
(sqlc.arg(phone_query)::text <> '' AND u.phone LIKE sqlc.arg(phone_query)::text || '%')
OR lower(u.username) LIKE sqlc.arg(query_like)::text || '%' ESCAPE '\'
OR lower(u.first_name) LIKE '%' || sqlc.arg(query_like)::text || '%' ESCAPE '\'
OR lower(u.last_name) LIKE '%' || sqlc.arg(query_like)::text || '%' ESCAPE '\'
OR lower(trim(u.first_name || ' ' || u.last_name)) LIKE '%' || sqlc.arg(query_like)::text || '%' ESCAPE '\'
OR lower(c.contact_first_name) LIKE '%' || sqlc.arg(query_like)::text || '%' ESCAPE '\'
OR lower(c.contact_last_name) LIKE '%' || sqlc.arg(query_like)::text || '%' ESCAPE '\'
OR lower(trim(c.contact_first_name || ' ' || c.contact_last_name)) LIKE '%' || sqlc.arg(query_like)::text || '%' ESCAPE '\'
)
)
SELECT
id,
access_hash,
phone,
first_name,
last_name,
about,
username,
country_code,
verified,
support,
last_seen_at,
contact,
mutual
FROM matched
ORDER BY contact DESC, rank, id
LIMIT sqlc.arg(limit_count);
-- name: CreateUser :one
INSERT INTO users (access_hash, phone, first_name, last_name, username, country_code)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING *;
-- name: UpdateUserUsername :one
UPDATE users
SET username = $2,
updated_at = now()
WHERE id = $1
RETURNING *;
-- name: UpdateUserLastSeen :exec
UPDATE users
SET last_seen_at = GREATEST(last_seen_at, sqlc.arg(last_seen_at)::bigint),
updated_at = now()
WHERE id = sqlc.arg(id)::bigint;
-- name: UpdateUserProfile :one
UPDATE users
SET first_name = $2,
last_name = $3,
about = $4,
updated_at = now()
WHERE id = $1
RETURNING *;

View file

@ -0,0 +1,464 @@
-- 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,
message_box_id,
peer_type,
peer_id,
filter_id,
max_id,
still_unread_count,
tags_enabled
) VALUES (
$1,
$2,
$3,
$4,
$5,
sqlc.arg(event_bool)::boolean,
sqlc.arg(event_peers)::jsonb,
sqlc.arg(peer_settings)::jsonb,
sqlc.arg(message_ids)::jsonb,
sqlc.arg(dialog_filter)::jsonb,
sqlc.arg(filter_order)::jsonb,
sqlc.arg(folder_peers)::jsonb,
sqlc.narg(message_box_id),
sqlc.narg(peer_type)::text,
sqlc.narg(peer_id)::bigint,
sqlc.arg(filter_id)::int,
sqlc.arg(max_id)::int,
sqlc.arg(still_unread_count)::int,
sqlc.arg(tags_enabled)::boolean
)
ON CONFLICT (user_id, pts) DO NOTHING;
-- 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.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.tags_enabled,
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.edit_date, 0)::int AS edit_date,
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.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.media::text, '{}')::text AS media_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(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(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(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(fwd_ch.id, 0)::bigint AS fwd_channel_id,
COALESCE(fwd_ch.access_hash, 0)::bigint AS fwd_channel_access_hash,
COALESCE(fwd_ch.creator_user_id, 0)::bigint AS fwd_channel_creator_user_id,
COALESCE(fwd_ch.title, '')::text AS fwd_channel_title,
COALESCE(fwd_ch.about, '')::text AS fwd_channel_about,
COALESCE(fwd_ch.username, '')::text AS fwd_channel_username,
COALESCE(fwd_ch.broadcast, false)::boolean AS fwd_channel_broadcast,
COALESCE(fwd_ch.megagroup, false)::boolean AS fwd_channel_megagroup,
COALESCE(fwd_ch.forum, false)::boolean AS fwd_channel_forum,
COALESCE(fwd_ch.noforwards, false)::boolean AS fwd_channel_noforwards,
COALESCE(fwd_ch.signatures, false)::boolean AS fwd_channel_signatures,
COALESCE(fwd_ch.pre_history_hidden, false)::boolean AS fwd_channel_pre_history_hidden,
COALESCE(fwd_ch.slowmode_seconds, 0)::int AS fwd_channel_slowmode_seconds,
COALESCE(fwd_ch.default_banned_rights::text, '{}')::text AS fwd_channel_default_banned_rights,
COALESCE(fwd_ch.participants_count, 0)::int AS fwd_channel_participants_count,
COALESCE(fwd_ch.admins_count, 0)::int AS fwd_channel_admins_count,
COALESCE(fwd_ch.kicked_count, 0)::int AS fwd_channel_kicked_count,
COALESCE(fwd_ch.banned_count, 0)::int AS fwd_channel_banned_count,
COALESCE(fwd_ch.top_message_id, 0)::int AS fwd_channel_top_message_id,
COALESCE(fwd_ch.pinned_message_id, 0)::int AS fwd_channel_pinned_message_id,
COALESCE(fwd_ch.pts, 0)::int AS fwd_channel_pts,
COALESCE(fwd_ch.ttl_period, 0)::int AS fwd_channel_ttl_period,
COALESCE(fwd_ch.date, 0)::int AS fwd_channel_date,
COALESCE(fwd_ch.deleted, false)::boolean AS fwd_channel_deleted,
COALESCE(reply_ch.id, 0)::bigint AS reply_channel_id,
COALESCE(reply_ch.access_hash, 0)::bigint AS reply_channel_access_hash,
COALESCE(reply_ch.creator_user_id, 0)::bigint AS reply_channel_creator_user_id,
COALESCE(reply_ch.title, '')::text AS reply_channel_title,
COALESCE(reply_ch.about, '')::text AS reply_channel_about,
COALESCE(reply_ch.username, '')::text AS reply_channel_username,
COALESCE(reply_ch.broadcast, false)::boolean AS reply_channel_broadcast,
COALESCE(reply_ch.megagroup, false)::boolean AS reply_channel_megagroup,
COALESCE(reply_ch.forum, false)::boolean AS reply_channel_forum,
COALESCE(reply_ch.noforwards, false)::boolean AS reply_channel_noforwards,
COALESCE(reply_ch.signatures, false)::boolean AS reply_channel_signatures,
COALESCE(reply_ch.pre_history_hidden, false)::boolean AS reply_channel_pre_history_hidden,
COALESCE(reply_ch.slowmode_seconds, 0)::int AS reply_channel_slowmode_seconds,
COALESCE(reply_ch.default_banned_rights::text, '{}')::text AS reply_channel_default_banned_rights,
COALESCE(reply_ch.participants_count, 0)::int AS reply_channel_participants_count,
COALESCE(reply_ch.admins_count, 0)::int AS reply_channel_admins_count,
COALESCE(reply_ch.kicked_count, 0)::int AS reply_channel_kicked_count,
COALESCE(reply_ch.banned_count, 0)::int AS reply_channel_banned_count,
COALESCE(reply_ch.top_message_id, 0)::int AS reply_channel_top_message_id,
COALESCE(reply_ch.pinned_message_id, 0)::int AS reply_channel_pinned_message_id,
COALESCE(reply_ch.pts, 0)::int AS reply_channel_pts,
COALESCE(reply_ch.ttl_period, 0)::int AS reply_channel_ttl_period,
COALESCE(reply_ch.date, 0)::int AS reply_channel_date,
COALESCE(reply_ch.deleted, false)::boolean AS reply_channel_deleted
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
LEFT JOIN channels fwd_ch ON m.fwd_from_peer_type = 'channel' AND fwd_ch.id = m.fwd_from_peer_id
LEFT JOIN channels reply_ch ON m.reply_to_peer_type = 'channel' AND reply_ch.id = m.reply_to_peer_id
WHERE e.user_id = $1
AND e.pts > $2
ORDER BY e.pts ASC
LIMIT sqlc.arg(limit_count);
-- name: MaxUserPts :one
SELECT COALESCE(MAX(pts), 0)::int AS max_pts
FROM user_update_events
WHERE user_id = $1;
-- name: RecentUserPts :many
-- 取某 user 最近的一段 pts降序供计算「最大连续已提交 pts」用。
-- 只看顶部窗口:瞬时空洞只可能出现在最近在途事务区,窗口足够大即可覆盖其下方连续。
SELECT pts, pts_count
FROM user_update_events
WHERE user_id = $1
ORDER BY pts DESC
LIMIT sqlc.arg(window_size);
-- name: EnsureUserUpdateWatermark :exec
INSERT INTO user_update_watermarks (user_id, contiguous_pts)
VALUES ($1, 0)
ON CONFLICT (user_id) DO NOTHING;
-- name: GetUserUpdateWatermark :one
SELECT contiguous_pts
FROM user_update_watermarks
WHERE user_id = $1;
-- name: LockUserUpdateWatermark :one
SELECT contiguous_pts
FROM user_update_watermarks
WHERE user_id = $1
FOR UPDATE;
-- name: NextUserPtsAfter :many
SELECT pts, pts_count
FROM user_update_events
WHERE user_id = $1
AND pts > $2
ORDER BY pts ASC
LIMIT sqlc.arg(limit_count);
-- name: SaveUserUpdateWatermark :exec
INSERT INTO user_update_watermarks (user_id, contiguous_pts, updated_at)
VALUES ($1, $2, now())
ON CONFLICT (user_id) DO UPDATE SET
contiguous_pts = GREATEST(user_update_watermarks.contiguous_pts, EXCLUDED.contiguous_pts),
updated_at = now();
-- 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;
-- name: ClaimDispatchOutbox :many
WITH picked AS (
SELECT target_user_id, id
FROM dispatch_outbox
WHERE (
status = 'pending'
AND next_attempt_at <= now()
)
OR (
status = 'dispatching'
AND updated_at < now() - make_interval(secs => sqlc.arg(lease_seconds)::int)
)
ORDER BY next_attempt_at ASC, target_user_id ASC, id ASC
LIMIT sqlc.arg(limit_count)
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;
-- name: MarkDispatchDelivered :exec
-- 方案 A投递成功即删除。outbox 是任务队列delivered 行无保留价值
-- (消息在 message_boxes、离线补偿在 user_update_events删除让表维持「未完成任务」小稳态。
DELETE FROM dispatch_outbox
WHERE target_user_id = $1
AND id = $2;
-- 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;
-- name: BatchListDispatchEvents :many
-- 按 (user_id, pts) 精确批量取账号事件,供 outbox worker 一次性加载一批 claim 的事件详情,
-- 取代逐条 ListUserUpdateEventsAfter。列与 ListUserUpdateEventsAfter 完全一致以复用转换逻辑。
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.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.tags_enabled,
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.edit_date, 0)::int AS edit_date,
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.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.media::text, '{}')::text AS media_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(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(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(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(fwd_ch.id, 0)::bigint AS fwd_channel_id,
COALESCE(fwd_ch.access_hash, 0)::bigint AS fwd_channel_access_hash,
COALESCE(fwd_ch.creator_user_id, 0)::bigint AS fwd_channel_creator_user_id,
COALESCE(fwd_ch.title, '')::text AS fwd_channel_title,
COALESCE(fwd_ch.about, '')::text AS fwd_channel_about,
COALESCE(fwd_ch.username, '')::text AS fwd_channel_username,
COALESCE(fwd_ch.broadcast, false)::boolean AS fwd_channel_broadcast,
COALESCE(fwd_ch.megagroup, false)::boolean AS fwd_channel_megagroup,
COALESCE(fwd_ch.forum, false)::boolean AS fwd_channel_forum,
COALESCE(fwd_ch.noforwards, false)::boolean AS fwd_channel_noforwards,
COALESCE(fwd_ch.signatures, false)::boolean AS fwd_channel_signatures,
COALESCE(fwd_ch.pre_history_hidden, false)::boolean AS fwd_channel_pre_history_hidden,
COALESCE(fwd_ch.slowmode_seconds, 0)::int AS fwd_channel_slowmode_seconds,
COALESCE(fwd_ch.default_banned_rights::text, '{}')::text AS fwd_channel_default_banned_rights,
COALESCE(fwd_ch.participants_count, 0)::int AS fwd_channel_participants_count,
COALESCE(fwd_ch.admins_count, 0)::int AS fwd_channel_admins_count,
COALESCE(fwd_ch.kicked_count, 0)::int AS fwd_channel_kicked_count,
COALESCE(fwd_ch.banned_count, 0)::int AS fwd_channel_banned_count,
COALESCE(fwd_ch.top_message_id, 0)::int AS fwd_channel_top_message_id,
COALESCE(fwd_ch.pinned_message_id, 0)::int AS fwd_channel_pinned_message_id,
COALESCE(fwd_ch.pts, 0)::int AS fwd_channel_pts,
COALESCE(fwd_ch.ttl_period, 0)::int AS fwd_channel_ttl_period,
COALESCE(fwd_ch.date, 0)::int AS fwd_channel_date,
COALESCE(fwd_ch.deleted, false)::boolean AS fwd_channel_deleted,
COALESCE(reply_ch.id, 0)::bigint AS reply_channel_id,
COALESCE(reply_ch.access_hash, 0)::bigint AS reply_channel_access_hash,
COALESCE(reply_ch.creator_user_id, 0)::bigint AS reply_channel_creator_user_id,
COALESCE(reply_ch.title, '')::text AS reply_channel_title,
COALESCE(reply_ch.about, '')::text AS reply_channel_about,
COALESCE(reply_ch.username, '')::text AS reply_channel_username,
COALESCE(reply_ch.broadcast, false)::boolean AS reply_channel_broadcast,
COALESCE(reply_ch.megagroup, false)::boolean AS reply_channel_megagroup,
COALESCE(reply_ch.forum, false)::boolean AS reply_channel_forum,
COALESCE(reply_ch.noforwards, false)::boolean AS reply_channel_noforwards,
COALESCE(reply_ch.signatures, false)::boolean AS reply_channel_signatures,
COALESCE(reply_ch.pre_history_hidden, false)::boolean AS reply_channel_pre_history_hidden,
COALESCE(reply_ch.slowmode_seconds, 0)::int AS reply_channel_slowmode_seconds,
COALESCE(reply_ch.default_banned_rights::text, '{}')::text AS reply_channel_default_banned_rights,
COALESCE(reply_ch.participants_count, 0)::int AS reply_channel_participants_count,
COALESCE(reply_ch.admins_count, 0)::int AS reply_channel_admins_count,
COALESCE(reply_ch.kicked_count, 0)::int AS reply_channel_kicked_count,
COALESCE(reply_ch.banned_count, 0)::int AS reply_channel_banned_count,
COALESCE(reply_ch.top_message_id, 0)::int AS reply_channel_top_message_id,
COALESCE(reply_ch.pinned_message_id, 0)::int AS reply_channel_pinned_message_id,
COALESCE(reply_ch.pts, 0)::int AS reply_channel_pts,
COALESCE(reply_ch.ttl_period, 0)::int AS reply_channel_ttl_period,
COALESCE(reply_ch.date, 0)::int AS reply_channel_date,
COALESCE(reply_ch.deleted, false)::boolean AS reply_channel_deleted
FROM unnest(@user_ids::bigint[]) WITH ORDINALITY AS u(user_id, ord)
JOIN unnest(@pts_list::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
LEFT JOIN channels fwd_ch ON m.fwd_from_peer_type = 'channel' AND fwd_ch.id = m.fwd_from_peer_id
LEFT JOIN channels reply_ch ON m.reply_to_peer_type = 'channel' AND reply_ch.id = m.reply_to_peer_id;
-- name: MarkDispatchDeliveredBatch :exec
-- 批量删除一批已投递的 (target_user_id, id)target_user_id 入 WHERE 保证分区裁剪。
DELETE FROM dispatch_outbox d
USING unnest(@target_user_ids::bigint[]) WITH ORDINALITY AS tu(target_user_id, ord)
JOIN unnest(@ids::bigint[]) WITH ORDINALITY AS di(id, ord) USING (ord)
WHERE d.target_user_id = tu.target_user_id
AND d.id = di.id;
-- name: DeleteFailedDispatchOutbox :one
WITH doomed AS (
SELECT target_user_id, id
FROM dispatch_outbox
WHERE status = 'failed'
AND updated_at < now() - make_interval(secs => sqlc.arg(older_than_seconds)::int)
ORDER BY updated_at ASC, target_user_id ASC, id ASC
LIMIT sqlc.arg(limit_count)
),
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;