Files
supabase/apps/studio/data/table-rows/operation-queue-save-mutation.test.ts
Aaditya Bhusal 719434a7fd fix(studio): batched table edits issues (#47319)
## 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 #47318

Supabase Studio's batched table edit queue has a few related row
identity issues:

- Editing a row's primary key can make later queued edits or deletes
lose track of the original row.
- Editing a primary key and another column in the same row before saving
can save only the primary key change, because later updates still use
the old primary key in the `WHERE` clause.
- Adding a row in batched edit mode and then deleting it before saving
may not remove the pending row correctly.

## What is the new behavior?

- Preserves the original row identity for queued operations after
primary key edits.
- Applies multiple queued edits for the same row as a single update when
saving.
- Correctly deletes newly added pending rows before they are saved.
- Adds regression coverage for these batched table edit cases.

## Additional context


https://github.com/user-attachments/assets/75672361-d781-4fe5-a542-071574ad57bd


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

* **Bug Fixes**
* Improved row identity handling for grid edits, optimistic updates, and
queued operations so changes stay correctly attached when primary keys
are edited, reverted, or “taken” by another row.
* Updated header row deletion to delete from the currently
visible/targeted rows rather than relying on the full dataset.
* Reduced retry noise for missing tables by clearing conflicting sorts
and preventing repeated retries for the same “does not exist” error.
* More reliably consolidated queued edits for the same row into fewer
combined save statements.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Ali Waseem <waseema393@gmail.com>
2026-06-29 08:12:18 -06:00

66 lines
2.1 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { getOperationSqlStatements } from './operation-queue-save-mutation'
import type { Entity } from '@/data/table-editor/table-editor-types'
import { QueuedOperation, QueuedOperationType } from '@/state/table-editor-operation-queue.types'
const usersTable = { id: 1, name: 'users', schema: 'public' } as Entity
function createEditOperation(
columnName: string,
newValue: unknown,
rowIdentifiers: Record<string, unknown> = { id: 1 }
): QueuedOperation {
return {
id: `${columnName}-${String(newValue)}`,
tableId: 1,
timestamp: 1,
type: QueuedOperationType.EDIT_CELL_CONTENT,
payload: {
rowIdentifiers,
columnName,
oldValue: undefined,
newValue,
table: usersTable,
enumArrayColumns: [],
},
}
}
describe('getOperationSqlStatements', () => {
it('merges edits for the same row into one update statement', () => {
const statements = getOperationSqlStatements([
createEditOperation('id', '4'),
createEditOperation('name', 'Ram 1'),
])
expect(statements).toHaveLength(1)
const sql = String(statements[0])
expect(sql).toContain('update public.users set (id,name)')
expect(sql).toContain('"id":"4"')
expect(sql).toContain('"name":"Ram 1"')
expect(sql.match(/where id = 1/g)).toHaveLength(1)
})
it('keeps edits for different rows as separate update statements', () => {
const statements = getOperationSqlStatements([
createEditOperation('name', 'Ram 1', { id: 1 }),
createEditOperation('name', 'Shyam 1', { id: 2 }),
])
expect(statements).toHaveLength(2)
expect(String(statements[0])).toContain('where id = 1')
expect(String(statements[1])).toContain('where id = 2')
})
it('does not merge rows whose identifier values would collide with delimiter keys', () => {
const statements = getOperationSqlStatements([
createEditOperation('name', 'Row 1', { a: 'x', b: 'y|b:z' }),
createEditOperation('name', 'Row 2', { a: 'x|b:y', b: 'z' }),
])
expect(statements).toHaveLength(2)
})
})