From a1abc2d00fb39a730fd0a25bacfc5023850046c8 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Mon, 30 Mar 2026 23:33:53 +0800 Subject: [PATCH] Refactor table editor logic for handling null and undefined values (#44331) ## Context Resolves https://github.com/supabase/supabase/issues/43548 There's currently an issue with the Table Editor where if you have, for example, a nullable `text` column with a default value, inserting a new row and selecting "Set to NULL" doesn't do anything, and saving will insert the row with the default value image This stems from a legacy logic in the Table Editor whereby we treat `null` values as "no input" - which is incorrect as `null` values are also valid values. So the PR here changes a few things to resolve this properly: ## Changes involved Main fix: - `undefined` will be the "no input" value instead, and it'll be the default value when generating the row object for inserting a new row - `NULL` or even empty string like `''` will be treated as they are (valid inputs) Secondary adjustments: - (Queue operations) Queueing an insert with no value but default value is NULL, will show the placeholder as `DEFAULT` instead of `NULL` for better accuracy in representation image - Added a `Set to Default` CTA here, but will only show up if adding a new row or updating a queued insert row operation, which will set the value of the input field back to `undefined` for PG to handle it as the default value image ## To test Verify within the Table Editor (along with queue operation feature preview) - For inserting a new row, setting value to NULL and setting value to Default works - For updating a row, setting value to NULL works --- .../components/grid/utils/gridColumns.tsx | 11 +- .../SidePanelEditor/RowEditor/InputField.tsx | 36 +++++-- .../SidePanelEditor/RowEditor/RowEditor.tsx | 4 +- .../RowEditor/RowEditor.types.ts | 2 +- .../RowEditor/RowEditor.utils.test.ts | 102 +++++++++++------- .../RowEditor/RowEditor.utils.ts | 52 +++++---- .../SidePanelEditor/SidePanelEditor.tsx | 2 +- .../table-rows/table-row-create-mutation.ts | 6 +- e2e/studio/features/table-editor.spec.ts | 56 ++++++++++ 9 files changed, 187 insertions(+), 84 deletions(-) diff --git a/apps/studio/components/grid/utils/gridColumns.tsx b/apps/studio/components/grid/utils/gridColumns.tsx index 54d50cdc72b..a2f2bad8ccf 100644 --- a/apps/studio/components/grid/utils/gridColumns.tsx +++ b/apps/studio/components/grid/utils/gridColumns.tsx @@ -200,15 +200,12 @@ function withPendingAddPlaceholders( const value = props.row[props.column.key] if (isPendingAddRow(props.row) && (value === undefined || value === null || value === '')) { - if (columnDef.defaultValue !== undefined && columnDef.defaultValue !== null) { - return - } - if (columnDef.isIdentity || columnDef.isGeneratable) { - return - } - if (columnDef.isNullable) { + if (value === null) { return } + if (columnDef.defaultValue !== undefined || columnDef.isIdentity || columnDef.isGeneratable) { + return + } } return diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx index 7c72debe95d..aeecd1677c9 100644 --- a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx +++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx @@ -1,11 +1,11 @@ import { includes, noop } from 'lodash' import { Edit, Eye } from 'lucide-react' - import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, + DropdownMenuSeparator, DropdownMenuTrigger, Input, Select, @@ -17,6 +17,7 @@ import { SelectValue_Shadcn_, } from 'ui' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' + import { DATETIME_TYPES, JSON_TYPES, TEXT_TYPES } from '../SidePanelEditor.constants' import { DateTimeInput } from './DateTimeInput' import type { EditValue, RowField } from './RowEditor.types' @@ -28,6 +29,7 @@ const TRUNCATE_DESCRIPTION = export interface InputFieldProps { field: RowField errors: any + isNewRow?: boolean isEditable?: boolean onUpdateField?: (changes: object) => void onEditJson?: (data: any) => void @@ -38,6 +40,7 @@ export interface InputFieldProps { export const InputField = ({ field, errors, + isNewRow = false, isEditable = true, onUpdateField = noop, onEditJson = noop, @@ -146,6 +149,11 @@ export const InputField = ({ if (includes(TEXT_TYPES, field.format)) { const isTruncated = isValueTruncated(field.value) + /** + * Handle `undefined` as the default value of the input field + * Otherwise, NULL should be treated as NULL, empty strings should be treated as empty strings + */ + return (
-