Files
Joshen Lim 65c570e85b Fix copy / export large values in table editor (#46268)
## Context

There's an issue with copying / exporting rows from the table editor
with the following conditions:
- Row has a column value that exceeds 10,240 and hence is truncated for
performance reasons
<img width="300" alt="image"
src="https://github.com/user-attachments/assets/4639edbb-ece6-4028-89b6-769ac314c3f3"
/>
- User is trying to copy/export selected rows (not all rows in the
table)
<img width="300" alt="image"
src="https://github.com/user-attachments/assets/e7c0da77-051c-4c46-af0d-f9510e0b37c4"
/>

The copy/export action will return the truncated data which is incorrect
(Should return the full data)

## Problem

This is happening as if we're only copying/exporting selected rows,
we're just using what's been loaded in the table editor to export (as
opposed to if the user is copying/exporting all rows in the table, we'd
be fetching the data from the database first before doing so)

Hence am opting to add a data hydration logic, such that if there's a
selected row that's been truncated, we'd fetch them on demand first
before copying/exporting.

There's limitations to this though - e.g if the table doesn't have a
primary key we can't do this (since we need to run a query to fetch the
data). This is already an existing behaviour when trying to load the
column value in the table editor in the grid so no issues I believe.
We'll just show this toast:
<img width="300" alt="image"
src="https://github.com/user-attachments/assets/442637bb-4b9b-492d-b202-bbf6e5ae7512"
/>

## To test
You'll need a column with a really large value - the way I do it is to
load the data directly into the DB via TablePlus
- [ ] Verify that copying / exporting selected rows with really large
column values copies/exports all the data correctly (there shouldn't be
any truncated value)

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

## Summary by CodeRabbit

* **New Features**
* Improved handling of truncated cell values during copy and export
operations

* **Bug Fixes**
  * Copy and export operations now require an active project selection
  * Fixed data export for tables without primary keys

* **Style**
  * Updated grid header copy and export control layout

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46268?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-25 11:26:43 +07:00

103 lines
3.1 KiB
TypeScript

const normalizeCauseAsError = (cause: unknown): Error | undefined => {
if (!cause) return undefined
if (cause instanceof Error) {
return cause
}
if (typeof cause === 'object' && 'message' in cause) {
return new Error(String(cause.message))
}
return new Error(String(cause))
}
export class ExportAllRowsErrorFamily extends Error {
cause?: Error
constructor(message: string, options: { cause?: unknown } = {}) {
super(message, options)
}
}
export class NoConnectionStringError extends ExportAllRowsErrorFamily {
constructor() {
super('No connection string provided for database connection.')
this.name = 'NoConnectionStringError'
}
}
export class TableDetailsFetchError extends ExportAllRowsErrorFamily {
constructor(tableName: string, _cause?: unknown) {
const cause = normalizeCauseAsError(_cause)
super(`Failed to fetch table details from the database for table ${tableName}.`, { cause })
this.name = 'TableDetailsFetchError'
}
}
export class NoTableError extends ExportAllRowsErrorFamily {
constructor(tableName: string) {
super(`The specified table "${tableName}" does not exist in the database.`)
this.name = 'NoTableError'
}
}
export class NoRowsToExportError extends ExportAllRowsErrorFamily {
constructor(tableName: string) {
super(`There are no rows to export from the table "${tableName}".`)
this.name = 'NoRowsToExportError'
}
}
export class TableTooLargeError extends ExportAllRowsErrorFamily {
constructor(tableName: string, rowCount: number, maxAllowed: number) {
super(
`The table "${tableName}" has ${rowCount} rows, which exceeds the maximum allowed limit of ${maxAllowed} rows for export.`
)
this.name = 'TableTooLargeError'
}
}
export class FetchRowsError extends ExportAllRowsErrorFamily {
constructor(tableName: string, _cause?: unknown) {
const cause = normalizeCauseAsError(_cause)
super(`An error occurred while fetching rows from the table "${tableName}".`, { cause })
this.name = 'FetchRowsError'
}
}
export class OutputConversionError extends ExportAllRowsErrorFamily {
constructor(_cause?: unknown) {
const cause = normalizeCauseAsError(_cause)
super('Failed to convert the fetched rows into the desired output format.', {
cause,
})
this.name = 'OutputConversionError'
}
}
export class BlobCreationError extends ExportAllRowsErrorFamily {
constructor(_cause?: unknown) {
const cause = normalizeCauseAsError(_cause)
super('An error occurred while creating a Blob for the exported data.', { cause })
this.name = 'BlobCreationError'
}
}
export class DownloadSaveError extends ExportAllRowsErrorFamily {
constructor(_cause?: unknown) {
const cause = normalizeCauseAsError(_cause)
super('An error occurred while saving the exported data to a file.', { cause })
this.name = 'DownloadSaveError'
}
}
export class NoPrimaryKeyForTruncatedRowsError extends ExportAllRowsErrorFamily {
constructor(tableName: string) {
super(
`Cannot export rows with truncated values from "${tableName}" because the table has no primary key.`
)
this.name = 'NoPrimaryKeyForTruncatedRowsError'
}
}