mirror of
https://github.com/supabase/supabase.git
synced 2026-05-06 08:56:46 -04:00
fix: paste seq sync (#45116)
## TL;DR - extends https://github.com/supabase/supabase/pull/45076 - closes https://github.com/supabase/supabase/issues/45113 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Sequence values now properly sync after bulk row inserts, ensuring correct auto-increment behavior for subsequent inserts. * **Tests** * Added end-to-end coverage for CSV import, including a "Paste text" import path that verifies custom-owned sequences are synchronized before the next insert. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Gildas Garcia <1122076+djhi@users.noreply.github.com> Co-authored-by: Ali Waseem <waseema393@gmail.com>
This commit is contained in:
+41
-3
@@ -1163,11 +1163,49 @@ export async function insertTableRows({
|
||||
const batchedPromises = chunk(tasks, 10)
|
||||
for (const batchedPromise of batchedPromises) {
|
||||
const res = await Promise.allSettled(batchedPromise.map((batch) => batch()))
|
||||
const hasFailedBatch = find(res, { status: 'rejected' })
|
||||
if (hasFailedBatch) break
|
||||
const failedBatch = res.find((result) => result.status === 'rejected')
|
||||
if (failedBatch?.status === 'rejected') {
|
||||
if (insertError === undefined) insertError = failedBatch.reason
|
||||
break
|
||||
}
|
||||
onProgressUpdate(insertProgress * 100)
|
||||
}
|
||||
return { error: insertError }
|
||||
|
||||
if (insertError !== undefined) {
|
||||
return { error: insertError }
|
||||
}
|
||||
|
||||
const sequenceColumns = (table.columns ?? []).filter(
|
||||
(column) =>
|
||||
column.is_identity ||
|
||||
(typeof column.default_value === 'string' && column.default_value.includes('nextval('))
|
||||
)
|
||||
|
||||
if (sequenceColumns.length === 0) {
|
||||
return { error: insertError }
|
||||
}
|
||||
|
||||
const updateSequenceSQL = sequenceColumns
|
||||
.map((column) =>
|
||||
getUpdateIdentitySequenceSQL({
|
||||
schema: table.schema,
|
||||
table: table.name,
|
||||
column: column.name,
|
||||
})
|
||||
)
|
||||
.join(';\n')
|
||||
|
||||
try {
|
||||
await executeSql({
|
||||
projectRef,
|
||||
connectionString,
|
||||
sql: updateSequenceSQL,
|
||||
queryKey: ['sequences', 'update-batch'],
|
||||
})
|
||||
return { error: insertError }
|
||||
} catch (error) {
|
||||
return { error }
|
||||
}
|
||||
}
|
||||
|
||||
const updateForeignKeys = async ({
|
||||
|
||||
@@ -1369,6 +1369,85 @@ testRunner('table editor', () => {
|
||||
.toBe('230')
|
||||
})
|
||||
|
||||
test('pasted CSV text syncs custom owned sequences before the next insert', async ({
|
||||
page,
|
||||
ref,
|
||||
}) => {
|
||||
const tableName = 'pw_table_paste_sequence_sync'
|
||||
const sequenceName = 'pw_table_paste_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};`)
|
||||
}
|
||||
)
|
||||
|
||||
const waitForTable = waitForTableToLoad(page, ref)
|
||||
await page.goto(toUrl(`/project/${ref}/editor?schema=public`))
|
||||
await waitForTable
|
||||
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')
|
||||
const csvText = fs.readFileSync(csvFilePath, 'utf-8')
|
||||
await page.getByRole('button', { name: 'Import data from CSV' }).click()
|
||||
await page.getByRole('tab', { name: 'Paste text' }).click()
|
||||
await page.getByRole('textbox').fill(csvText)
|
||||
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'
|
||||
|
||||
Reference in New Issue
Block a user