[FE-3909] fix(studio): exclude generated columns from row insert form (#48195)

Inserting a row through the table editor failed on any table with a
`GENERATED ALWAYS AS (...) STORED` column — the row editor sent an
explicit value for the generated column (e.g. `false` for booleans,
since the bool `Select` never hits the empty-string default heuristic
from #46826), which Postgres rejects with `428C9: cannot insert a
non-DEFAULT value into column`.

**Changed:**
- `RowField` now carries `isGenerated` (from pg-meta's `is_generated`,
previously unused by Studio)
- Generated columns are hidden from the row editor form (they're always
computed by the database, so there's nothing to input) but stay in
`rowFields` state so primary-key identifier logic is unaffected
- `generateRowObjectFromFields` skips generated fields, so they're
omitted from both insert and update payloads
- `validateFields` skips generated fields — an error on a hidden field
would be unfixable

**Added:**
- e2e test covering inserting a row into a table with a generated
boolean column
- unit tests for generated-column omission in insert/update payloads and
validation

## To test

1. Create a table with a generated column:
   ```sql
   create table t (
     id bigint generated by default as identity primary key,
     base_price int,
     discounted_price int,
     is_discounted boolean generated always as (
       base_price is distinct from discounted_price
     ) stored
   );
   ```
2. Table Editor → `t` → Insert row — `is_discounted` should not appear
in the form
3. Fill the other fields and save — the insert should succeed and the
grid should show the computed value
4. Edit an existing row and save — should still work (generated column
untouched)
5. Sanity-check a normal table with identity/default columns — clearing
a default field on insert should still fall back to the default (#46826
behavior)

Addresses
[FE-3909](https://linear.app/supabase/issue/FE-3909/studio-insert-form-fails-on-generated-boolean-columns)

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

## Summary by CodeRabbit

* **New Features**
  * Added support for generated columns in the table editor.
* Generated columns are automatically computed and excluded from insert
and update forms.
* Generated values now appear correctly in the table after saving a row.

* **Bug Fixes**
  * Prevented validation errors for non-editable generated fields.
  * Ensured generated columns are excluded from submitted row data.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
This commit is contained in:
Alaister YoungandAlaister Young authored and GitHub committed 2026-07-22 22:19:36 +08:00
1 parent 1144b83885
commit badf16be07
5 files changed
+163 -1

No files matched your search

+59
View File
@@ -1153,6 +1153,65 @@ testRunner('table editor', () => {
).toBeVisible()
})
test('can insert a row into a table with a generated column', async ({ page, ref }) => {
const tableName = 'pw_table_generated_column'
await using _ = await withSetupCleanup(
async () => {
await query(`
create table public.${tableName} (
id bigint generated by default as identity primary key,
base_price int,
discounted_price int,
is_discounted boolean generated always as (
base_price is not null
and discounted_price is not null
and base_price is distinct from discounted_price
) stored
);
`)
},
async () => {
await dropTable(tableName)
}
)
await page.goto(toUrl(`/project/${ref}/editor?schema=public`))
await page.getByRole('button', { name: `View ${tableName}`, exact: true }).click()
await page.waitForURL(/\/editor\/\d+\?schema=public$/)
// insert a row, filling only the non-generated columns
await page.getByTestId('table-editor-insert-new-row').click()
await page.getByRole('menuitem', { name: 'Insert row' }).click()
const sidePanel = page.getByTestId('side-panel-row-editor')
await page.getByTestId('base_price-input').fill('100')
await page.getByTestId('discounted_price-input').fill('80')
const insertPromise = waitForApiResponse(page, 'pg-meta', ref, 'query?key=', {
method: 'POST',
})
await page.getByTestId('action-bar-save-row').click()
await insertPromise
await expect(
sidePanel,
'Row editor should close after inserting a row into a table with a generated column'
).not.toBeVisible()
await expect(
page.getByRole('gridcell', { name: 'TRUE' }),
'Generated column value should be computed by the database'
).toBeVisible()
// the generated column should not be editable in the insert form
await page.getByTestId('table-editor-insert-new-row').click()
await page.getByRole('menuitem', { name: 'Insert row' }).click()
await expect(sidePanel.getByText('base_price')).toBeVisible()
await expect(
sidePanel.getByText('is_discounted'),
'Generated column should not be shown in the insert form'
).not.toBeVisible()
})
test('can create and remove foreign key with column selection', async ({ page, ref }) => {
const sourceTableName = 'pw_table_fk_source'
const targetTableName = 'pw_table_fk_target'