Files
Joshen Lim aba4e147eb Joshen/fe 3613 database tables query should have schema filter wherever appropriate (#46935)
## Context

There's certain areas in the dashboard where we're calling
`useTablesQuery` without a schema filter, in which case the dashboard
then fires a query against the project's database to fetch _all_ tables
across _all_ schemas - this could easily be a heavy query if there's a
large number of relations in the project's database.

Am hence opting to either add a schema filter if appropriate, or
otherwise opt to use the infinite loading behaviour

## Changes involved
- Add schema filter to `useTablesQuery` in database triggers and
publications
- Use infinite loading for tables in Cmd K for "Run query on table" and
"Search database tables"

## To test
- [x] Verify that database triggers + publications still function as
expected
- [x] Verify that CMD K "Run query on table" and "Search database
tables" still function as expected (including search)

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

* **New Features**
* Implemented debounced infinite-scrolling table search in the command
menu and SQL editor command flow.
* Added a schema selector dropdown to publications management for easier
navigation.
* **Improvements**
  * Removed the “Schema” column from the publications tables UI.
* Updated search guidance and table-picker status (counts/loading)
during infinite browsing.
  * Trigger table listings now follow the selected schema context.
* Refined command menu list height and improved the database-tables
placeholder text.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-16 15:45:39 +08:00

191 lines
5.4 KiB
TypeScript

import pgMeta, { getTablesPaginatedSql, type PGTable } from '@supabase/pg-meta'
import { InfiniteData, useInfiniteQuery, useQuery, useQueryClient } from '@tanstack/react-query'
import { sortBy } from 'lodash'
import { useCallback } from 'react'
import { z } from 'zod'
import { tableKeys } from './keys'
import { executeSql } from '@/data/sql/execute-sql-mutation'
import type { SafePostgresTable } from '@/lib/postgres-types'
import type { ResponseError, UseCustomInfiniteQueryOptions, UseCustomQueryOptions } from '@/types'
export type TablesVariables = {
projectRef?: string
connectionString?: string | null
schema?: string
/** Defaults to false */
includeColumns?: boolean
sortByProperty?: keyof PGTable
}
const pgMetaTablesList = pgMeta.tables.list()
export type TablesData = z.infer<typeof pgMetaTablesList.zod>
export async function getTables(
{
projectRef,
connectionString,
schema,
includeColumns = false,
sortByProperty = 'name',
}: TablesVariables,
signal?: AbortSignal
) {
if (!projectRef) throw new Error('projectRef is required')
const sql = pgMeta.tables.list({
includeColumns,
includedSchemas: schema ? [schema] : undefined,
}).sql
const queryKey = ['tables', schema]
const { result } = await executeSql({ projectRef, connectionString, sql, queryKey }, signal)
// Sort the data if the sortByName option is true
if (Array.isArray(result) && sortByProperty) {
return sortBy(result, (t) => t[sortByProperty]) as SafePostgresTable[]
}
return result as SafePostgresTable[]
}
export type TablesError = ResponseError
export const useTablesQuery = <TData = TablesData>(
vars: TablesVariables,
{ enabled = true, ...options }: UseCustomQueryOptions<TablesData, TablesError, TData> = {}
) => {
const { projectRef, schema, includeColumns } = vars
return useQuery<TablesData, TablesError, TData>({
queryKey: tableKeys.list(projectRef, schema, { includeColumns }),
queryFn: ({ signal }) => getTables(vars, signal),
enabled: enabled && typeof projectRef !== 'undefined',
...options,
})
}
/**
* useGetTables
* Tries to get tables from the react-query cache, or loads it from the server if it's not cached.
*/
export function useGetTables({
projectRef,
connectionString,
}: Pick<TablesVariables, 'projectRef' | 'connectionString'>) {
const queryClient = useQueryClient()
return useCallback(
(schema?: TablesVariables['schema'], includeColumns?: TablesVariables['includeColumns']) => {
return queryClient.fetchQuery({
queryKey: tableKeys.list(projectRef, schema, { includeColumns }),
queryFn: ({ signal }) =>
getTables({ projectRef, connectionString, schema, includeColumns }, signal),
})
},
[connectionString, projectRef, queryClient]
)
}
export function usePrefetchTables({
projectRef,
connectionString,
}: Pick<TablesVariables, 'projectRef' | 'connectionString'>) {
const queryClient = useQueryClient()
return useCallback(
(schema?: TablesVariables['schema'], includeColumns?: TablesVariables['includeColumns']) => {
return queryClient.prefetchQuery({
queryKey: tableKeys.list(projectRef, schema, { includeColumns }),
queryFn: ({ signal }) =>
getTables({ projectRef, connectionString, schema, includeColumns }, signal),
})
},
[connectionString, projectRef, queryClient]
)
}
export type InfiniteTablesVariables = Pick<
TablesVariables,
'projectRef' | 'connectionString' | 'schema' | 'includeColumns'
> & {
pageSize?: number
nameFilter?: string
}
export async function getTablesPage(
{
projectRef,
connectionString,
schema,
includeColumns = false,
limit,
afterOid,
nameFilter,
}: Pick<TablesVariables, 'projectRef' | 'connectionString' | 'schema' | 'includeColumns'> & {
limit: number
afterOid: number
nameFilter?: string
},
signal?: AbortSignal
) {
if (!projectRef) {
throw new Error('projectRef is required')
}
const sql = getTablesPaginatedSql({ schema, includeColumns, limit, afterOid, nameFilter })
const { result } = await executeSql(
{
projectRef,
connectionString,
sql,
queryKey: [
`project:${projectRef}`,
`schema:${schema}`,
`infinite_tables`,
includeColumns ? 'with_columns' : null,
nameFilter ? `search:${nameFilter}` : null,
limit ? `page_size:${limit}` : null,
afterOid ? `after:${afterOid}` : null,
],
},
signal
)
return result as SafePostgresTable[]
}
export const useInfiniteTablesQuery = <TData = InfiniteData<SafePostgresTable[]>>(
{
projectRef,
connectionString,
schema,
includeColumns,
pageSize = 50,
nameFilter,
}: InfiniteTablesVariables,
{
enabled = true,
...options
}: UseCustomInfiniteQueryOptions<TablesData, TablesError, TData, readonly unknown[], number> = {}
) => {
return useInfiniteQuery({
queryKey: tableKeys.infiniteList(projectRef, schema, { includeColumns, pageSize, nameFilter }),
queryFn: ({ signal, pageParam }) =>
getTablesPage(
{
projectRef,
connectionString,
schema,
includeColumns,
limit: pageSize,
afterOid: pageParam,
nameFilter,
},
signal
),
enabled: enabled && typeof projectRef !== 'undefined',
initialPageParam: 0,
getNextPageParam: (lastPage) =>
lastPage.length < pageSize ? undefined : lastPage[lastPage.length - 1].id,
...options,
})
}