Files
supabase/apps/studio/tests/data/tables/table-metadata-invalidation.test.ts
Aaditya Bhusal b9d22fa237 fix(studio): stale table metadata cache invalidation after table edits (#47541)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Bug fix

## What is the current behavior?

Fixes stale table metadata after saving edits from the table editor
drawer.

Previously, metadata-only table changes, such as column updates, primary
key/foreign key changes, table renames, or schema moves, could leave
cached table data stale. This affected the Database tables list, schema
visualizer, and reopening the edit drawer.

Fixes #47540

## What is the new behavior?

Table metadata caches are now invalidated consistently after table
create, update, delete, column delete, and queue table creation flows.

The Database tables list, schema visualizer, table editor drawer, table
definitions, constraints, foreign keys, table columns, rows, and lint
data now refresh correctly after relevant table metadata changes.

## Additional context


https://github.com/user-attachments/assets/de849710-8d7b-4d8b-af3b-5232154e996b


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Table metadata now refreshes consistently after table creation,
updates, duplication, and deletion.

* **Bug Fixes**
* Improved synchronization for table lists, lint results, constraints,
and row counts after changes.
* Renaming or moving tables now refreshes both the previous and updated
locations.
* Column and queue changes now trigger the appropriate table metadata
updates.

* **Tests**
* Added coverage for metadata refresh behavior across edits, moves,
optional lint updates, and row counts.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Ali Waseem <waseema393@gmail.com>
2026-08-25 07:43:45 -06:00

106 lines
3.9 KiB
TypeScript

import type { QueryClient } from '@tanstack/react-query'
import { describe, expect, test, vi } from 'vitest'
import { databaseKeys } from '@/data/database/keys'
import { entityTypeKeys } from '@/data/entity-types/keys'
import { lintKeys } from '@/data/lint/keys'
import { tableEditorKeys } from '@/data/table-editor/keys'
import { tableRowKeys } from '@/data/table-rows/keys'
import { tableKeys } from '@/data/tables/keys'
import { invalidateTableMetadata } from '@/data/tables/table-metadata-invalidation'
const createQueryClient = () => {
return {
invalidateQueries: vi.fn().mockResolvedValue(undefined),
} as unknown as QueryClient
}
const getInvalidatedQueryKeys = (queryClient: QueryClient) => {
return vi.mocked(queryClient.invalidateQueries).mock.calls.map(([filters]) => filters?.queryKey)
}
describe('invalidateTableMetadata', () => {
test('invalidates table metadata query variants for the current table', async () => {
const queryClient = createQueryClient()
await invalidateTableMetadata(queryClient, {
projectRef: 'project-ref',
schema: 'public',
tableId: 1,
tableName: 'todos',
})
const queryKeys = getInvalidatedQueryKeys(queryClient)
expect(queryKeys).toEqual(
expect.arrayContaining([
tableKeys.names('project-ref'),
entityTypeKeys.list('project-ref'),
databaseKeys.tableColumnsPrefix('project-ref'),
tableEditorKeys.tableEditor('project-ref', 1),
databaseKeys.tableDefinition('project-ref', 1),
databaseKeys.tableConstraints('project-ref', 1),
tableKeys.list('project-ref', 'public'),
tableKeys.infiniteListPrefix('project-ref', 'public'),
databaseKeys.foreignKeyConstraintsPrefix('project-ref', 'public'),
tableKeys.retrieve('project-ref', 'todos', 'public'),
])
)
expect(queryKeys).not.toContainEqual(lintKeys.lint('project-ref'))
expect(queryKeys).not.toContainEqual(tableRowKeys.tableRowsAndCount('project-ref', 1))
})
test('invalidates old and new schema and table keys when a table is moved or renamed', async () => {
const queryClient = createQueryClient()
await invalidateTableMetadata(queryClient, {
projectRef: 'project-ref',
schema: 'public',
tableId: 1,
tableName: 'todos',
newSchema: 'private',
newTableName: 'tasks',
includeRows: true,
includeLint: true,
})
const queryKeys = getInvalidatedQueryKeys(queryClient)
expect(queryKeys).toEqual(
expect.arrayContaining([
tableKeys.list('project-ref', 'public'),
tableKeys.infiniteListPrefix('project-ref', 'public'),
databaseKeys.foreignKeyConstraintsPrefix('project-ref', 'public'),
tableKeys.list('project-ref', 'private'),
tableKeys.infiniteListPrefix('project-ref', 'private'),
databaseKeys.foreignKeyConstraintsPrefix('project-ref', 'private'),
tableKeys.retrieve('project-ref', 'todos', 'public'),
tableKeys.retrieve('project-ref', 'tasks', 'private'),
lintKeys.lint('project-ref'),
tableRowKeys.tableRowsAndCount('project-ref', 1),
])
)
expect(queryKeys[queryKeys.length - 1]).toEqual(
tableRowKeys.tableRowsAndCount('project-ref', 1)
)
})
test('does not invalidate id-based keys when a table id is unavailable', async () => {
const queryClient = createQueryClient()
await invalidateTableMetadata(queryClient, {
projectRef: 'project-ref',
schema: 'public',
tableName: 'todos',
includeRows: true,
})
const queryKeys = getInvalidatedQueryKeys(queryClient)
expect(queryKeys).not.toContainEqual(tableEditorKeys.tableEditor('project-ref', 1))
expect(queryKeys).not.toContainEqual(databaseKeys.tableDefinition('project-ref', 1))
expect(queryKeys).not.toContainEqual(databaseKeys.tableConstraints('project-ref', 1))
expect(queryKeys).not.toContainEqual(tableRowKeys.tableRowsAndCount('project-ref', 1))
})
})