* 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>
429 lines
14 KiB
SQL
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(?);
|