mirror of
https://github.com/supabase/supabase.git
synced 2026-06-28 19:39:19 -04:00
3d101e2415
Closes #47015 ## What kind of change does this PR introduce? Bug fix. ## What is the current behavior? The queue message list paginates with `WHERE enqueued_at > <last>` and `ORDER BY enqueued_at`. `enqueued_at` is not unique: pgmq defaults it to `now()`, so every message sent in one `send_batch` shares a timestamp. When a group of same-timestamp messages straddles a page boundary, the strict cursor skips the rest of that group, so those messages never appear in the grid even though they are still in the queue. With 40 messages from one batch, only 30 render. ## What is the new behavior? Pagination uses a composite `(enqueued_at, msg_id)` keyset cursor and orders by the same pair. `msg_id` is unique within each queue/archive table and breaks the tie, so no rows are dropped between pages. After the change, all 40 messages render. This mirrors the cron-runs query, which already paginates on a unique key. ## Additional context Added a test in `apps/studio/data/database-queues/database-queue-messages-infinite-query.test.ts` asserting next pages use the composite cursor and order by `enqueued_at, msg_id`. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved database queue message pagination to reliably retrieve all messages, including those with identical enqueued timestamps, preventing potential message skipping during pagination. * **Tests** * Added test coverage for database queue message pagination behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Ali Waseem <waseema393@gmail.com>
48 lines
1.5 KiB
TypeScript
48 lines
1.5 KiB
TypeScript
import { beforeEach, expect, test, vi } from 'vitest'
|
|
|
|
import { getDatabaseQueue } from './database-queue-messages-infinite-query'
|
|
|
|
const mockExecuteSql = vi.fn()
|
|
|
|
vi.mock('@/data/sql/execute-sql-mutation', () => ({
|
|
executeSql: (...args: unknown[]) => mockExecuteSql(...args),
|
|
}))
|
|
|
|
beforeEach(() => {
|
|
mockExecuteSql.mockReset()
|
|
mockExecuteSql.mockResolvedValue({ result: [] })
|
|
})
|
|
|
|
const capturedSql = () => String(mockExecuteSql.mock.calls[0][0].sql)
|
|
|
|
test('next pages use a composite (enqueued_at, msg_id) cursor, not enqueued_at alone', async () => {
|
|
await getDatabaseQueue({
|
|
projectRef: 'ref',
|
|
connectionString: null,
|
|
queueName: 'my_queue',
|
|
status: ['available'],
|
|
after: { enqueuedAt: '2024-01-01T00:00:00.000Z', msgId: 42 },
|
|
})
|
|
|
|
const sql = capturedSql()
|
|
expect(sql).toContain('(enqueued_at, msg_id) > (')
|
|
expect(sql).toContain('order by enqueued_at, msg_id')
|
|
// The old single-column cursor (`WHERE enqueued_at > ...`) skipped rows sharing
|
|
// the last page's timestamp, dropping messages from the list.
|
|
expect(sql).not.toMatch(/WHERE enqueued_at >\s/)
|
|
})
|
|
|
|
test('first page omits the cursor predicate but still orders by the unique key', async () => {
|
|
await getDatabaseQueue({
|
|
projectRef: 'ref',
|
|
connectionString: null,
|
|
queueName: 'my_queue',
|
|
status: ['available'],
|
|
after: undefined,
|
|
})
|
|
|
|
const sql = capturedSql()
|
|
expect(sql).not.toContain('(enqueued_at, msg_id) >')
|
|
expect(sql).toContain('order by enqueued_at, msg_id')
|
|
})
|