mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
Fix table creation transaction (#43909)
Fixes #42089 Supersedes #43352 and https://github.com/supabase/postgres-meta/pull/1049 ## Problem When creating a table in the Dashboard, if a column-specific error occurs (e.g., invalid enum value or broken foreign key), the Dashboard displays an error toast, but the side panel remains open. In the background, the table is actually created (partially), leading to a _42P07: relation already exists_ error if the user tries to click _Save_ again. ## Solution We actually had nested transactions which is not supported by Postgres. - [x] Allow generating SQL without transactions from `@supabase/pg-meta` - [x] Fix the table creation - [x] Fix some accessibility issues - [x] Add tests ## How to test 1. Click _New table_ in the Table editor 2. Add a column with an _int8_ type and set its default value to `bazinga` 3. Click _Save_ You should see an error. 4. Fix the default value by setting it to `10` 5. Click _Save_ Results: - You should see a success message about the table creation - The table should have the column with the correct default value
This commit is contained in:
1 parent
8499d30e98
commit
df7e98bae6
7 files changed
+103
-36
No files matched your search
@@ -258,7 +258,7 @@ test.describe('Database', () => {
|
||||
|
||||
// create a new table
|
||||
await page.getByRole('button', { name: 'New table' }).click()
|
||||
await page.getByLabel('Name').fill(databaseTableNameNew)
|
||||
await page.getByLabel('Name', { exact: true }).fill(databaseTableNameNew)
|
||||
const createTableWait = createApiResponseWaiter(
|
||||
page,
|
||||
'pg-meta',
|
||||
|
||||
@@ -90,17 +90,16 @@ testRunner('table editor', () => {
|
||||
.getByRole('button')
|
||||
.nth(2)
|
||||
.click()
|
||||
const schemaPromise = waitForApiResponse(page, 'pg-meta', ref, 'query?key=table-definition-')
|
||||
await page.getByRole('menuitem', { name: 'Copy table schema' }).click()
|
||||
await schemaPromise // wait for endpoint to generate schema
|
||||
await page.waitForTimeout(500)
|
||||
const copiedSchemaResult = await page.evaluate(() => navigator.clipboard.readText())
|
||||
expect(copiedSchemaResult).toBe(`create table public.pw_table_actions (
|
||||
await expect(async () => {
|
||||
const copiedSchemaResult = await page.evaluate(() => navigator.clipboard.readText())
|
||||
expect(copiedSchemaResult).toBe(`create table public.pw_table_actions (
|
||||
id bigint generated by default as identity not null,
|
||||
created_at timestamp with time zone null default now(),
|
||||
pw_column text null,
|
||||
constraint pw_table_actions_pkey primary key (id)
|
||||
) TABLESPACE pg_default;`)
|
||||
}).toPass({ timeout: 2000 })
|
||||
|
||||
// duplicates table
|
||||
await page
|
||||
@@ -213,7 +212,7 @@ testRunner('table editor', () => {
|
||||
await page.getByText('Is Nullable').click()
|
||||
await page.getByTestId('created_at-extra-options').click()
|
||||
await page.getByRole('button', { name: 'Add column' }).click()
|
||||
await page.getByRole('textbox', { name: 'column_name' }).fill(columnNameEnum)
|
||||
await page.getByLabel('Column name').nth(2).fill(columnNameEnum)
|
||||
await page.getByRole('combobox').filter({ hasText: 'Choose a column type...' }).click()
|
||||
await page.getByPlaceholder('Search types...').fill(enum_name)
|
||||
// wait for response, then click
|
||||
@@ -302,7 +301,7 @@ testRunner('table editor', () => {
|
||||
.click()
|
||||
await page.getByRole('menuitem', { name: 'Edit table' }).click()
|
||||
await page.getByTestId('table-name-input').fill(tableNameUpdated)
|
||||
await page.getByRole('textbox', { name: 'pw_column' }).fill(columnNameUpdated)
|
||||
await page.getByLabel('Column name').nth(2).fill(columnNameUpdated)
|
||||
const updateTablePromise = waitForApiResponse(page, 'pg-meta', ref, 'query?key=column-update', {
|
||||
method: 'POST',
|
||||
})
|
||||
@@ -814,7 +813,7 @@ testRunner('table editor', () => {
|
||||
|
||||
// Add boolean column
|
||||
await page.getByRole('button', { name: 'Add column' }).click()
|
||||
await page.getByRole('textbox', { name: 'column_name' }).fill(boolColName)
|
||||
await page.getByLabel('Column name').nth(3).fill(boolColName)
|
||||
await page.getByText('Choose a column type...').click()
|
||||
await page.getByPlaceholder('Search types...').fill('bool')
|
||||
await page.getByRole('option', { name: 'bool' }).first().click()
|
||||
@@ -928,7 +927,7 @@ testRunner('table editor', () => {
|
||||
|
||||
// Add nullable boolean column
|
||||
await page.getByRole('button', { name: 'Add column' }).click()
|
||||
await page.getByRole('textbox', { name: 'column_name' }).fill(boolColName)
|
||||
await page.getByLabel('Column name').nth(3).fill(boolColName)
|
||||
await page.getByText('Choose a column type...').click()
|
||||
await page.getByPlaceholder('Search types...').fill('bool')
|
||||
await page.getByRole('option', { name: 'bool' }).first().click()
|
||||
@@ -1214,4 +1213,49 @@ testRunner('table editor', () => {
|
||||
).toBeVisible()
|
||||
await expect(page.getByRole('gridcell', { name: 'drag drop value 1' })).toBeVisible()
|
||||
})
|
||||
|
||||
test('create a table in a single transaction', async ({ page, ref }) => {
|
||||
const tableName = 'pw_table_create_transaction'
|
||||
|
||||
await using _ = await withSetupCleanup(
|
||||
async () => {
|
||||
// Nothing
|
||||
},
|
||||
async () => {
|
||||
await dropTable(tableName)
|
||||
}
|
||||
)
|
||||
await page.goto(toUrl(`/project/${ref}/editor?schema=public`))
|
||||
await page.getByRole('button', { name: 'New table' }).click()
|
||||
await page.getByLabel('Name', { exact: true }).fill(tableName)
|
||||
await page.getByRole('button', { name: 'Add column' }).click()
|
||||
await page.getByLabel('Column name').nth(2).fill('pw_column')
|
||||
await page.getByRole('combobox').filter({ hasText: 'Choose a column type...' }).click()
|
||||
await page.getByRole('option').filter({ hasText: 'int8' }).click()
|
||||
await page.getByLabel('Column default value').nth(2).fill('invalid')
|
||||
|
||||
await page.getByRole('button', { name: 'Save' }).click()
|
||||
await expect(page.getByText('invalid input syntax')).toBeVisible()
|
||||
await page.getByLabel('Column default value').nth(2).fill('10')
|
||||
await page.getByRole('button', { name: 'Save' }).click()
|
||||
await expect(page.getByText(`Table ${tableName} is good to go!`)).toBeVisible()
|
||||
await expect(page.getByRole('button', { name: `View ${tableName}`, exact: true })).toBeVisible()
|
||||
|
||||
// copies table schema to clipboard when copy schema option is clicked
|
||||
await page
|
||||
.getByRole('button', { name: `View ${tableName}`, exact: true })
|
||||
.getByRole('button')
|
||||
.nth(2)
|
||||
.click()
|
||||
await page.getByRole('menuitem', { name: 'Copy table schema' }).click()
|
||||
await expect(async () => {
|
||||
const copiedSchemaResult = await page.evaluate(() => navigator.clipboard.readText())
|
||||
expect(copiedSchemaResult).toBe(`create table public.${tableName} (
|
||||
id bigint generated by default as identity not null,
|
||||
created_at timestamp with time zone not null default now(),
|
||||
pw_column bigint null default '10'::bigint,
|
||||
constraint ${tableName}_pkey primary key (id)
|
||||
) TABLESPACE pg_default;`)
|
||||
}).toPass({ timeout: 2000 })
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user