fix: Better error handling for filters (#43027)

## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES/NO

## What kind of change does this PR introduce?

Update error handling for filter related errors on the table editor
This commit is contained in:
Ali Waseem authored and GitHub committed 2026-02-20 07:23:38 -07:00
1 parent e561196f07
commit 41a7b34985
4 files changed
+175 -21

No files matched your search

@@ -1,14 +1,18 @@
import { useParams } from 'common'
import { useTableFilter } from 'components/grid/hooks/useTableFilter'
import { useTableFilterNew } from 'components/grid/hooks/useTableFilterNew'
import { useTableSort } from 'components/grid/hooks/useTableSort'
import AlertError from 'components/ui/AlertError'
import { InlineLink } from 'components/ui/InlineLink'
import { ENTITY_TYPE } from 'data/entity-types/entity-type-constants'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { useCallback } from 'react'
import { useTableEditorTableStateSnapshot } from 'state/table-editor-table'
import { Button } from 'ui'
import { Admonition } from 'ui-patterns'
import { isFilterRelatedError } from './GridError.utils'
import { useIsTableFilterBarEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { HighCostError } from '@/components/ui/HighQueryCost'
import { COST_THRESHOLD_ERROR } from '@/data/sql/execute-sql-query'
import { useTableEditorStateSnapshot } from '@/state/table-editor'
@@ -18,12 +22,22 @@ export const GridError = ({ error }: { error?: ResponseError | null }) => {
const { id: _id } = useParams()
const tableId = _id ? Number(_id) : undefined
const { filters } = useTableFilter()
const newFilterBarEnabled = useIsTableFilterBarEnabled()
const { filters: oldFilters, clearFilters: clearOldFilters } = useTableFilter()
const { filters: newFilters, clearFilters: clearNewFilters } = useTableFilterNew()
const { sorts } = useTableSort()
const snap = useTableEditorTableStateSnapshot()
const tableEditorSnap = useTableEditorStateSnapshot()
const removeAllFilters = useCallback(() => {
if (newFilterBarEnabled) {
clearNewFilters()
} else {
clearOldFilters()
}
}, [clearOldFilters, clearNewFilters, newFilterBarEnabled])
if (!error) return null
const tableEntityType = snap.originalTable?.entity_type
@@ -32,8 +46,9 @@ export const GridError = ({ error }: { error?: ResponseError | null }) => {
const isForeignTableMissingVaultKeyError =
isForeignTable && error?.message?.includes('query vault failed')
const isInvalidSyntaxError =
filters.length > 0 && error?.message?.includes('invalid input syntax')
const hasActiveFilters = oldFilters.length > 0 || newFilters.length > 0
const hasFilterRelatedError = hasActiveFilters && isFilterRelatedError(error?.message)
const isInvalidOrderingOperatorError =
sorts.length > 0 && error?.message?.includes('identify an ordering operator')
@@ -55,8 +70,8 @@ export const GridError = ({ error }: { error?: ResponseError | null }) => {
)
} else if (isForeignTableMissingVaultKeyError) {
return <ForeignTableMissingVaultKeyError />
} else if (isInvalidSyntaxError) {
return <InvalidSyntaxError error={error} />
} else if (hasFilterRelatedError) {
return <FilterError removeAllFilters={removeAllFilters} />
} else if (isInvalidOrderingOperatorError) {
return <InvalidOrderingOperatorError error={error} />
}
@@ -94,27 +109,18 @@ const ForeignTableMissingVaultKeyError = () => {
)
}
const InvalidSyntaxError = ({ error }: { error: ResponseError }) => {
const { onApplyFilters } = useTableFilter()
const FilterError = ({ removeAllFilters }: { removeAllFilters: () => void }) => {
return (
<Admonition
type="warning"
type="note"
className="pointer-events-auto"
title="Invalid input syntax provided in filter value(s)"
title="No results found — check your filter values"
>
<p className="!mb-0">
Unable to retrieve results as the provided value in your filter(s) doesn't match it's column
data type.
<p className="!mb-4">
One or more of your filters may have a value or operator that doesn't match the column's
data type. Try updating or removing the filter.
</p>
<p className="!mb-2">
Verify that your filter values are correct before applying the filters again.
</p>
<p className="text-sm text-foreground-lighter prose max-w-full !mb-4">
Error: <code className="text-code-inline">{error.message}</code>
</p>
<Button type="default" onClick={() => onApplyFilters([])}>
<Button type="default" onClick={removeAllFilters}>
Remove filters
</Button>
</Admonition>
@@ -0,0 +1,87 @@
import { describe, expect, test } from 'vitest'
import { isFilterRelatedError } from './GridError.utils'
describe('isFilterRelatedError', () => {
test('returns false for null or undefined', () => {
expect(isFilterRelatedError(null)).toBe(false)
expect(isFilterRelatedError(undefined)).toBe(false)
})
test('returns false for empty string', () => {
expect(isFilterRelatedError('')).toBe(false)
})
test('returns false for unrelated error messages', () => {
expect(isFilterRelatedError('connection refused')).toBe(false)
expect(isFilterRelatedError('permission denied for table users')).toBe(false)
expect(isFilterRelatedError('relation "users" does not exist')).toBe(false)
expect(isFilterRelatedError('Query cost exceeds threshold')).toBe(false)
})
test('detects invalid input syntax errors', () => {
expect(
isFilterRelatedError(
'Failed to run sql query: ERROR: 22P02: invalid input syntax for type inet: "192.168.3"'
)
).toBe(true)
expect(
isFilterRelatedError(
'Failed to run sql query: ERROR: 22P02: invalid input syntax for type integer: "abc"'
)
).toBe(true)
expect(
isFilterRelatedError(
'Failed to run sql query: ERROR: 22P02: invalid input syntax for type uuid: "not-a-uuid"'
)
).toBe(true)
})
test('detects operator does not exist errors', () => {
expect(
isFilterRelatedError(
'Failed to run sql query: ERROR: 42883: operator does not exist: text > integer'
)
).toBe(true)
})
test('detects collation errors', () => {
expect(
isFilterRelatedError(
'Failed to run sql query: ERROR: could not determine which collation to use for string comparison'
)
).toBe(true)
})
test('detects invalid enum value errors', () => {
expect(
isFilterRelatedError(
'Failed to run sql query: ERROR: 22P02: invalid input value for enum status: "badvalue"'
)
).toBe(true)
})
test('detects malformed array literal errors', () => {
expect(
isFilterRelatedError(
'Failed to run sql query: ERROR: 22P02: malformed array literal: "not-an-array"'
)
).toBe(true)
})
test('detects invalid byte sequence errors', () => {
expect(
isFilterRelatedError(
'Failed to run sql query: ERROR: 22021: invalid byte sequence for encoding "UTF8"'
)
).toBe(true)
})
test('detects syntax errors from invalid IS operator values', () => {
expect(
isFilterRelatedError(
'Failed to run sql query: ERROR: 42601: syntax error at or near "sdfsdf"'
)
).toBe(true)
})
})
@@ -0,0 +1,14 @@
const FILTER_ERROR_PATTERNS = [
'invalid input syntax',
'operator does not exist',
'could not determine which collation',
'invalid input value for enum',
'malformed array literal',
'invalid byte sequence',
'syntax error',
]
export function isFilterRelatedError(errorMessage: string | undefined | null): boolean {
if (!errorMessage) return false
return FILTER_ERROR_PATTERNS.some((pattern) => errorMessage.includes(pattern))
}
+47
View File
@@ -13,6 +13,7 @@ import {
} from '../utils/filter-bar-helpers.js'
import { test } from '../utils/test.js'
import { toUrl } from '../utils/to-url.js'
import { createApiResponseWaiter } from '../utils/wait-for-response.js'
const tableNamePrefix = 'pw_filter_bar'
@@ -811,4 +812,50 @@ test.describe('Filter Bar', () => {
}
})
})
test.describe('Filter Error Feedback', () => {
test('invalid filter value shows friendly error and remove button clears filters', async ({
page,
ref,
}) => {
const tableName = `${tableNamePrefix}_err_fb`
await createTable(tableName, 'name', [{ name: 'Alice' }, { name: 'Bob' }])
try {
await setupFilterBarPage(page, ref, toUrl(`/project/${ref}/editor?schema=public`))
await navigateToTable(page, ref, tableName)
// Apply a filter using the 'is' operator with an invalid value
// 'is' only accepts null/not null/true/false, so 'badvalue' triggers a syntax error
await selectColumnFilter(page, 'name')
await selectOperatorByClick(page, 'name', 'is')
const valueInput = page.getByTestId('filter-value-name')
const rowsWaiter = createApiResponseWaiter(page, 'pg-meta', ref, 'query?key=table-rows-')
await valueInput.fill('badvalue')
await page.keyboard.press('Enter')
await rowsWaiter
// Should show the friendly filter error, not the scary general error
await expect(page.getByText('No results found — check your filter values')).toBeVisible({
timeout: 10000,
})
await expect(page.getByText("doesn't match the column's data type")).toBeVisible()
const removeButton = page.getByRole('button', { name: 'Remove filters' })
await expect(removeButton).toBeVisible()
// Clicking "Remove filters" should clear filters and restore data
await removeButton.click()
// Filter pill should be gone and data should be visible again
await expect(page.getByTestId('filter-condition-name')).not.toBeVisible({ timeout: 10000 })
await expect(page.getByRole('gridcell', { name: 'Alice' })).toBeVisible({ timeout: 10000 })
await expect(page.getByRole('gridcell', { name: 'Bob' })).toBeVisible()
} finally {
await dropTable(tableName)
}
})
})
})