From 2e3ddc3a52f4f0d816a9849ee08cecbb4079b069 Mon Sep 17 00:00:00 2001
From: Vaibhav <117663341+7ttp@users.noreply.github.com>
Date: Tue, 21 Apr 2026 19:29:20 +0530
Subject: [PATCH] fix: csv seq sync (#45076)
## TL;DR
fixes csv imports with explicit id values leaving auto generated primary
keys out of sync
by resolving the primary key sequence correctly after import
## ex:
| Before | After |
| --- | --- |
|
|
|
## ref:
- closes https://github.com/supabase/supabase/issues/45073
## Summary by CodeRabbit
## Release Notes
* **Bug Fixes**
* Fixed identity sequence synchronization during CSV imports. After
spreadsheet data is inserted, the system now properly updates sequence
values, ensuring subsequent rows receive correct identifiers.
---
.../SidePanelEditor/SidePanelEditor.utils.tsx | 34 +++++++++
.../files/table-editor-import-sequence.csv | 4 +
e2e/studio/features/table-editor.spec.ts | 74 +++++++++++++++++++
.../src/sql/studio/table-editor/identity.ts | 10 ++-
4 files changed, 121 insertions(+), 1 deletion(-)
create mode 100644 e2e/studio/features/files/table-editor-import-sequence.csv
diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.utils.tsx b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.utils.tsx
index ebe48d6929c..1177f73978e 100644
--- a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.utils.tsx
+++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.utils.tsx
@@ -1072,6 +1072,40 @@ export async function insertRowsViaSpreadsheet({
console.log(
`Total time taken for importing spreadsheet: ${(t2.getTime() - t1.getTime()) / 1000} seconds`
)
+ if (insertError === undefined) {
+ const sequenceColumns = (table.columns ?? []).filter(
+ (column) =>
+ column.is_identity ||
+ (typeof column.default_value === 'string' &&
+ column.default_value.includes('nextval('))
+ )
+
+ if (sequenceColumns.length === 0) {
+ resolve({ error: insertError })
+ return
+ }
+
+ const updateSequenceSQL = sequenceColumns
+ .map((column) =>
+ getUpdateIdentitySequenceSQL({
+ schema: table.schema,
+ table: table.name,
+ column: column.name,
+ })
+ )
+ .join(';\n')
+
+ executeSql({
+ projectRef,
+ connectionString,
+ sql: updateSequenceSQL,
+ queryKey: ['sequences', 'update-batch'],
+ })
+ .then(() => resolve({ error: insertError }))
+ .catch((error) => resolve({ error }))
+ return
+ }
+
resolve({ error: insertError })
},
})
diff --git a/e2e/studio/features/files/table-editor-import-sequence.csv b/e2e/studio/features/files/table-editor-import-sequence.csv
new file mode 100644
index 00000000000..53a575b6a52
--- /dev/null
+++ b/e2e/studio/features/files/table-editor-import-sequence.csv
@@ -0,0 +1,4 @@
+id,name
+227,Alice
+228,Bob
+229,Carol
diff --git a/e2e/studio/features/table-editor.spec.ts b/e2e/studio/features/table-editor.spec.ts
index 5d061129a48..b3047dd9766 100644
--- a/e2e/studio/features/table-editor.spec.ts
+++ b/e2e/studio/features/table-editor.spec.ts
@@ -1295,6 +1295,80 @@ testRunner('table editor', () => {
await expect(page.getByRole('gridcell', { name: 'value 1' })).toBeVisible()
})
+ test('CSV import syncs custom owned sequences before the next insert', async ({ page, ref }) => {
+ const tableName = 'pw_table_csv_sequence_sync'
+ const sequenceName = 'pw_table_csv_import_owned_seq'
+
+ await using _ = await withSetupCleanup(
+ async () => {
+ await query(`drop table if exists public.${tableName} cascade;`)
+ await query(`drop sequence if exists public.${sequenceName};`)
+ await query(`create sequence public.${sequenceName};`)
+ await query(`create table public.${tableName} (
+ id bigint primary key default nextval('public.${sequenceName}'),
+ name text
+ );`)
+ await query(`alter sequence public.${sequenceName} owned by public.${tableName}.id;`)
+ },
+ async () => {
+ await query(`drop table if exists public.${tableName} cascade;`)
+ await query(`drop sequence if exists public.${sequenceName};`)
+ }
+ )
+
+ await page.goto(toUrl(`/project/${ref}/editor?schema=public`))
+ await waitForTableToLoad(page, ref)
+ await page.getByRole('button', { name: `View ${tableName}`, exact: true }).click()
+ await page.waitForURL(/\/editor\/\d+\?schema=public$/)
+
+ const csvFilePath = path.join(import.meta.dirname, 'files', 'table-editor-import-sequence.csv')
+ await page.getByRole('button', { name: 'Import data from CSV' }).click()
+ await page.getByRole('tab', { name: 'Upload CSV' }).click()
+ await page.setInputFiles('input[type="file"]', csvFilePath)
+ await expect(page.getByText('A total of 3 rows will be')).toBeVisible()
+
+ const waitForCsvInsert = createApiResponseWaiter(page, 'pg-meta', ref, 'query?key=', {
+ method: 'POST',
+ })
+ await page.getByRole('button', { name: 'Import data' }).click()
+ await waitForCsvInsert
+ await waitForGridDataToLoad(page, ref)
+ await expect(page.getByText('3 records')).toBeVisible()
+
+ await expect
+ .poll(async () => {
+ const [{ state }] = await query<{ state: string }>(`
+ select format(
+ '%s|%s|%s',
+ (select coalesce(max(id), 0) from public.${tableName}),
+ last_value,
+ is_called
+ ) as state
+ from public.${sequenceName};
+ `)
+ return state
+ })
+ .toBe('229|229|t')
+
+ await page.getByTestId('table-editor-insert-new-row').click()
+ await page.getByRole('menuitem', { name: 'Insert row Insert a new row' }).click()
+ await page.getByTestId('name-input').fill('Dave')
+ const insertPromise = waitForApiResponse(page, 'pg-meta', ref, 'query?key=', {
+ method: 'POST',
+ })
+ await page.getByTestId('action-bar-save-row').click()
+ await insertPromise
+
+ await expect
+ .poll(async () => {
+ const [{ id }] = await query<{ id: string }>(
+ `select id::text as id from public.${tableName} where name = 'Dave'`
+ )
+ return id
+ })
+ .toBe('230')
+ })
+
test('row insert via side panel saves immediately', async ({ page, ref }) => {
const tableName = 'pw_table_row_insert'
const columnName = 'name'
diff --git a/packages/pg-meta/src/sql/studio/table-editor/identity.ts b/packages/pg-meta/src/sql/studio/table-editor/identity.ts
index cf5c6601825..b3daffbec1e 100644
--- a/packages/pg-meta/src/sql/studio/table-editor/identity.ts
+++ b/packages/pg-meta/src/sql/studio/table-editor/identity.ts
@@ -9,7 +9,15 @@ export const getUpdateIdentitySequenceSQL = ({
table: string
column: string
}): SafeSqlFragment => {
- return safeSql`SELECT setval(${literal(`${ident(schema)}.${ident(`${table}_${column}_seq`)}`)}::regclass, (SELECT COALESCE(MAX(${ident(column)}), 1) FROM ${ident(schema)}.${ident(table)}))`
+ return safeSql`WITH sequence_reference AS (
+ SELECT pg_get_serial_sequence(${literal(`${schema}.${table}`)}, ${literal(column)}) AS sequence_name
+)
+SELECT setval(
+ sequence_reference.sequence_name,
+ COALESCE((SELECT MAX(${ident(column)}) FROM ${ident(schema)}.${ident(table)}), 1)
+)
+FROM sequence_reference
+WHERE sequence_reference.sequence_name IS NOT NULL`
}
export const getDuplicateIdentitySequenceSQL = ({