Files
gochat/channels/shangwutong/db/queries/outbound.sql
T
Rogeeandrogee f77a007f62 H-162: add recoverable XST outbound delivery (#37)
* H-162: add recoverable XST outbound delivery

* H-162: close XST echo delivery races

* H-162: match echoes during XST retry

---------

Co-authored-by: Rogee <rogee@ipao.vip>
2026-08-16 17:37:13 +08:00

429 lines
14 KiB
SQL

-- name: InsertOutboundMessage :one
INSERT INTO outbound_messages (
account_id, swt_sid, event_id, occurred_at, gochat_message_id, retry_version,
message_type, content, payload
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(gochat_message_id) DO NOTHING
RETURNING *;
-- name: GetOutboundByGoChatMessageID :one
SELECT * FROM outbound_messages WHERE gochat_message_id = ? LIMIT 1;
-- name: InsertOutboundPart :one
INSERT INTO outbound_parts (
outbound_message_id, part_index, part_type, attachment_id,
content, data_url, file_name, file_size, voice
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
RETURNING *;
-- name: ListOutboundParts :many
SELECT * FROM outbound_parts
WHERE outbound_message_id = ?
ORDER BY part_index;
-- name: CompleteOutboundPart :exec
UPDATE outbound_parts SET
delivery_status = 'delivered',
external_id = ?,
last_error = NULL,
updated_at = CURRENT_TIMESTAMP
WHERE id = ? AND delivery_status = 'pending';
-- name: AwaitOutboundPartEcho :exec
UPDATE outbound_parts SET
delivery_status = 'uncertain',
external_id = NULL,
last_error = NULL,
updated_at = CURRENT_TIMESTAMP
WHERE id = ? AND delivery_status = 'pending';
-- name: MarkOutboundPartUncertain :exec
UPDATE outbound_parts SET
delivery_status = 'uncertain',
last_error = ?,
updated_at = CURRENT_TIMESTAMP
WHERE id = ? AND delivery_status = 'pending';
-- name: FailOutboundPart :exec
UPDATE outbound_parts SET
delivery_status = 'failed',
last_error = ?,
updated_at = CURRENT_TIMESTAMP
WHERE id = ? AND delivery_status = 'pending';
-- name: ResetUndeliveredOutboundParts :exec
UPDATE outbound_parts SET
delivery_status = 'pending',
external_id = NULL,
last_error = NULL,
updated_at = CURRENT_TIMESTAMP
WHERE outbound_message_id = ?
AND delivery_status IN ('failed', 'uncertain');
-- name: ClaimOutboundMessage :one
UPDATE outbound_messages SET
delivery_status = 'delivering',
claimed_at = CURRENT_TIMESTAMP,
attempts = attempts + 1,
updated_at = CURRENT_TIMESTAMP
WHERE id = (
SELECT candidate.id
FROM outbound_messages AS candidate
JOIN accounts AS account ON account.id = candidate.account_id
WHERE candidate.delivery_status = 'pending'
AND account.enabled = 1
AND account.desired_presence <> 'offline'
AND account.deleted_at IS NULL
AND (candidate.next_attempt_at IS NULL OR julianday(candidate.next_attempt_at) IS NULL OR julianday(candidate.next_attempt_at) <= julianday('now'))
AND NOT EXISTS (
SELECT 1 FROM outbound_messages AS earlier
WHERE earlier.account_id = candidate.account_id
AND earlier.id < candidate.id
AND earlier.delivery_status IN ('pending', 'delivering', 'uncertain')
)
AND NOT EXISTS (
SELECT 1 FROM outbound_operations AS operation
WHERE operation.account_id = candidate.account_id
AND operation.delivery_status IN ('delivering', 'uncertain')
)
AND NOT EXISTS (
SELECT 1 FROM outbound_operations AS earlier_operation
WHERE earlier_operation.account_id = candidate.account_id
AND earlier_operation.delivery_status = 'pending'
AND (
julianday(earlier_operation.occurred_at) < julianday(candidate.occurred_at) OR
(julianday(earlier_operation.occurred_at) = julianday(candidate.occurred_at) AND earlier_operation.event_id < candidate.event_id)
)
)
AND NOT EXISTS (
SELECT 1 FROM outbound_messages AS active
WHERE active.account_id = candidate.account_id
AND active.delivery_status = 'delivering'
)
ORDER BY candidate.id
LIMIT 1
)
AND delivery_status = 'pending'
RETURNING *;
-- name: CompleteOutboundMessage :exec
UPDATE outbound_messages SET
delivery_status = 'delivered',
result_version = result_version + 1,
external_id = ?,
external_error_code = NULL,
status_sync_status = 'pending',
claimed_at = NULL,
last_error = NULL,
updated_at = CURRENT_TIMESTAMP
WHERE id = ? AND delivery_status = 'delivering';
-- name: AwaitOutboundEcho :exec
UPDATE outbound_messages SET
delivery_status = 'uncertain',
external_id = NULL,
external_error_code = NULL,
status_sync_status = 'not_required',
claimed_at = NULL,
last_error = NULL,
updated_at = CURRENT_TIMESTAMP
WHERE id = ? AND delivery_status = 'delivering';
-- name: RequireOutboundXST :execrows
UPDATE outbound_messages SET
xst_delivery_status = 'pending',
updated_at = CURRENT_TIMESTAMP
WHERE outbound_messages.id = ?
AND delivery_status = 'delivering'
AND xst_delivery_status = 'not_required';
-- name: MarkOutboundSWTSent :execrows
UPDATE outbound_messages SET
swt_sent_at = CURRENT_TIMESTAMP,
updated_at = CURRENT_TIMESTAMP
WHERE id = ?
AND delivery_status IN ('delivering', 'uncertain')
AND swt_sent_at IS NULL;
-- name: ClaimOutboundXST :execrows
UPDATE outbound_messages SET
xst_delivery_status = 'delivering',
updated_at = CURRENT_TIMESTAMP
WHERE id = ?
AND delivery_status IN ('delivering', 'uncertain')
AND xst_delivery_status = 'pending';
-- name: CompleteOutboundXST :execrows
UPDATE outbound_messages SET
xst_delivery_status = 'delivered',
delivery_status = CASE
WHEN NOT EXISTS (
SELECT 1 FROM outbound_parts
WHERE outbound_message_id = outbound_messages.id
AND delivery_status <> 'delivered'
) THEN 'delivered'
ELSE delivery_status
END,
result_version = result_version + CASE
WHEN delivery_status <> 'delivered' AND NOT EXISTS (
SELECT 1 FROM outbound_parts
WHERE outbound_message_id = outbound_messages.id
AND delivery_status <> 'delivered'
) THEN 1 ELSE 0 END,
status_sync_status = CASE
WHEN NOT EXISTS (
SELECT 1 FROM outbound_parts
WHERE outbound_message_id = outbound_messages.id
AND delivery_status <> 'delivered'
) THEN 'pending'
ELSE status_sync_status
END,
status_sync_next_at = NULL,
external_error_code = NULL,
last_error = NULL,
claimed_at = CASE
WHEN NOT EXISTS (
SELECT 1 FROM outbound_parts
WHERE outbound_message_id = outbound_messages.id
AND delivery_status <> 'delivered'
) THEN NULL ELSE claimed_at END,
updated_at = CURRENT_TIMESTAMP
WHERE outbound_messages.id = ?
AND delivery_status IN ('delivering', 'uncertain')
AND xst_delivery_status = 'delivering';
-- name: RetryOutboundXST :execrows
UPDATE outbound_messages SET
delivery_status = 'pending',
xst_delivery_status = 'pending',
claimed_at = NULL,
next_attempt_at = ?,
last_error = ?,
updated_at = CURRENT_TIMESTAMP
WHERE id = ?
AND delivery_status IN ('delivering', 'uncertain')
AND xst_delivery_status = 'delivering';
-- name: MarkOutboundXSTUncertain :execrows
UPDATE outbound_messages SET
delivery_status = 'uncertain',
xst_delivery_status = 'uncertain',
result_version = result_version + 1,
external_error_code = ?,
status_sync_status = 'pending',
claimed_at = NULL,
last_error = ?,
updated_at = CURRENT_TIMESTAMP
WHERE id = ?
AND delivery_status IN ('delivering', 'uncertain')
AND xst_delivery_status = 'delivering';
-- name: FailOutboundXST :execrows
UPDATE outbound_messages SET
delivery_status = 'failed',
xst_delivery_status = 'failed',
result_version = result_version + 1,
external_error_code = ?,
status_sync_status = 'pending',
claimed_at = NULL,
last_error = ?,
updated_at = CURRENT_TIMESTAMP
WHERE id = ?
AND delivery_status IN ('delivering', 'uncertain');
-- name: MarkOutboundUncertain :exec
UPDATE outbound_messages SET
delivery_status = 'uncertain',
result_version = result_version + 1,
external_error_code = ?,
status_sync_status = 'pending',
claimed_at = NULL,
last_error = ?,
updated_at = CURRENT_TIMESTAMP
WHERE id = ? AND delivery_status = 'delivering';
-- name: RetryOutboundDelivery :exec
UPDATE outbound_messages SET
delivery_status = 'pending',
claimed_at = NULL,
next_attempt_at = ?,
last_error = ?,
updated_at = CURRENT_TIMESTAMP
WHERE id = ? AND delivery_status = 'delivering';
-- name: FailOutboundMessage :exec
UPDATE outbound_messages SET
delivery_status = 'failed',
result_version = result_version + 1,
external_error_code = ?,
status_sync_status = 'pending',
claimed_at = NULL,
last_error = ?,
updated_at = CURRENT_TIMESTAMP
WHERE id = ? AND delivery_status IN ('delivering', 'uncertain');
-- name: RetryOutboundMessage :execrows
UPDATE outbound_messages SET
event_id = sqlc.arg(event_id),
occurred_at = sqlc.arg(occurred_at),
retry_version = sqlc.arg(retry_version),
message_type = sqlc.arg(message_type),
content = sqlc.narg(content),
payload = sqlc.arg(payload),
delivery_status = 'pending',
xst_delivery_status = CASE
WHEN xst_delivery_status IN ('failed', 'uncertain') THEN 'pending'
ELSE xst_delivery_status
END,
status_sync_status = 'not_required',
next_attempt_at = NULL,
claimed_at = NULL,
last_error = NULL,
updated_at = CURRENT_TIMESTAMP
WHERE gochat_message_id = sqlc.arg(gochat_message_id)
AND delivery_status = 'failed'
AND retry_version < sqlc.arg(retry_version);
-- name: RecoverOutboundDeliveriesAsUncertain :execrows
UPDATE outbound_messages SET
delivery_status = 'uncertain',
xst_delivery_status = CASE
WHEN xst_delivery_status = 'delivering' THEN 'uncertain'
ELSE xst_delivery_status
END,
result_version = result_version + 1,
external_error_code = 'connector_restart_uncertain',
status_sync_status = 'pending',
claimed_at = NULL,
last_error = 'connector restarted while request was delivering',
updated_at = CURRENT_TIMESTAMP
WHERE delivery_status = 'delivering';
-- name: RecoverOutboundPartDeliveriesAsUncertain :execrows
UPDATE outbound_parts SET
delivery_status = 'uncertain',
last_error = 'connector restarted while part request may have been in flight',
updated_at = CURRENT_TIMESTAMP
WHERE id IN (
SELECT MIN(candidate.id)
FROM outbound_parts AS candidate
JOIN outbound_messages AS message ON message.id = candidate.outbound_message_id
WHERE message.delivery_status = 'delivering'
AND candidate.delivery_status = 'pending'
AND NOT EXISTS (
SELECT 1 FROM outbound_parts AS uncertain
WHERE uncertain.outbound_message_id = candidate.outbound_message_id
AND uncertain.delivery_status = 'uncertain'
)
GROUP BY candidate.outbound_message_id
);
-- name: RecoverOutboundStatusSyncs :execrows
UPDATE outbound_messages SET
status_sync_status = 'pending',
status_sync_next_at = NULL,
updated_at = CURRENT_TIMESTAMP
WHERE status_sync_status = 'syncing';
-- name: ClaimStatusSync :one
UPDATE outbound_messages SET
status_sync_status = 'syncing',
status_sync_attempts = status_sync_attempts + 1,
updated_at = CURRENT_TIMESTAMP
WHERE id = (
SELECT id FROM outbound_messages
WHERE status_sync_status = 'pending'
AND (status_sync_next_at IS NULL OR julianday(status_sync_next_at) IS NULL OR julianday(status_sync_next_at) <= julianday('now'))
ORDER BY id
LIMIT 1
)
AND status_sync_status = 'pending'
RETURNING *;
-- name: CompleteStatusSync :exec
UPDATE outbound_messages SET
status_sync_status = 'synced',
status_reported_at = CURRENT_TIMESTAMP,
updated_at = CURRENT_TIMESTAMP
WHERE id = ? AND status_sync_status = 'syncing';
-- name: RetryStatusSync :exec
UPDATE outbound_messages SET
status_sync_status = 'pending',
status_sync_next_at = ?,
last_error = ?,
updated_at = CURRENT_TIMESTAMP
WHERE id = ? AND status_sync_status = 'syncing';
-- name: ListOutboundEchoCandidates :many
SELECT * FROM outbound_messages
WHERE account_id = ?
AND swt_sid = ?
AND (
delivery_status IN ('delivering', 'delivered', 'uncertain', 'failed')
OR (delivery_status = 'pending' AND swt_sent_at IS NOT NULL AND xst_delivery_status = 'pending')
)
AND (delivery_status <> 'failed' OR external_error_code = 'uncertain_timeout')
AND EXISTS (
SELECT 1 FROM outbound_parts
WHERE outbound_message_id = outbound_messages.id
AND external_id IS NULL
AND delivery_status IN ('pending', 'delivered', 'uncertain')
)
ORDER BY id
LIMIT 20;
-- name: ConfirmOutboundEcho :execrows
UPDATE outbound_messages SET
delivery_status = CASE
WHEN xst_delivery_status NOT IN ('not_required', 'delivered') OR EXISTS (
SELECT 1 FROM outbound_parts
WHERE outbound_message_id = outbound_messages.id
AND delivery_status <> 'delivered'
) THEN CASE WHEN delivery_status = 'pending' THEN 'pending' ELSE 'uncertain' END
ELSE 'delivered'
END,
result_version = result_version + 1,
external_id = sqlc.narg(external_id),
external_error_code = NULL,
status_sync_status = CASE
WHEN xst_delivery_status NOT IN ('not_required', 'delivered') OR EXISTS (
SELECT 1 FROM outbound_parts
WHERE outbound_message_id = outbound_messages.id
AND delivery_status <> 'delivered'
) THEN 'not_required'
ELSE 'pending'
END,
status_sync_next_at = NULL,
claimed_at = NULL,
last_error = NULL,
updated_at = CURRENT_TIMESTAMP
WHERE outbound_messages.id = sqlc.arg(id)
AND (
delivery_status IN ('delivering', 'delivered', 'uncertain', 'failed')
OR (delivery_status = 'pending' AND swt_sent_at IS NOT NULL AND xst_delivery_status = 'pending')
)
AND (delivery_status <> 'failed' OR external_error_code = 'uncertain_timeout');
-- name: ConfirmOutboundPartEcho :execrows
UPDATE outbound_parts SET
delivery_status = 'delivered',
external_id = ?,
last_error = NULL,
updated_at = CURRENT_TIMESTAMP
WHERE id = ?
AND external_id IS NULL
AND delivery_status IN ('pending', 'delivered', 'uncertain');
-- name: FailExpiredUncertainMessages :execrows
UPDATE outbound_messages SET
delivery_status = 'failed',
result_version = result_version + 1,
external_error_code = 'uncertain_timeout',
status_sync_status = 'pending',
claimed_at = NULL,
last_error = 'no matching Shangwutong echo arrived before the observation window expired',
updated_at = CURRENT_TIMESTAMP
WHERE delivery_status = 'uncertain'
AND julianday(updated_at) <= julianday(?);