chore: aggressive prefetching (#29987)

* chore: aggressive prefetching

* use abort signals in prefetchers

* move encrypted columns to react-query

* prefetching for filter and sort applied

* prefetch remaining entity types

* prefetch tables in more places

* prefetch editor page on project panel

* add feature flag

* fix typescript

* nit

* Nit

* fix imports

* remove views check on encrypted schemas

* use fetchQuery instead of prefetchQuery

* fix useEncryptedColumnsQuery ts error

* filter by schema on encrypted columns

* don't use pg_get_tabledef for foreign tables

* Remove unnecesary import

---------

Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
This commit is contained in:
Alaister YoungandJoshen Lim authored and GitHub committed 2024-10-24 20:59:35 +08:00
1 parent 837b424c72
commit 5781937739
33 files changed
+717 -238

No files matched your search

+11 -3
View File
@@ -28,8 +28,12 @@ import { InitialStateType } from './store/reducers'
import type { SupabaseGridProps } from './types'
import { getGridColumns } from './utils/gridColumns'
function onLoadStorage(storageRef: string, tableName: string, schema?: string | null) {
const storageKey = getStorageKey(STORAGE_KEY_PREFIX, storageRef)
export function loadTableEditorSortsAndFiltersFromLocalStorage(
projectRef: string,
tableName: string,
schema?: string | null
) {
const storageKey = getStorageKey(STORAGE_KEY_PREFIX, projectRef)
const jsonStr = localStorage.getItem(storageKey)
if (!jsonStr) return
const json = JSON.parse(jsonStr)
@@ -45,7 +49,11 @@ async function initTable(
filter?: string[] // Comes directly from URL param
): Promise<{ savedState: { sorts?: string[]; filters?: string[] } }> {
const savedState = props.projectRef
? onLoadStorage(props.projectRef, props.table.name, props.table.schema)
? loadTableEditorSortsAndFiltersFromLocalStorage(
props.projectRef,
props.table.name,
props.table.schema
)
: undefined
// Check for saved state on initial load and also, load sort and filters via URL param only if given
@@ -1,12 +1,20 @@
import type { PostgresRelationship, PostgresTable } from '@supabase/postgres-meta'
import AwesomeDebouncePromise from 'awesome-debounce-promise'
import type { Filter } from 'components/grid/types'
import { compact } from 'lodash'
import type { Dictionary } from 'types'
import type { Filter } from 'components/grid/types'
import { FOREIGN_KEY_CASCADE_ACTION } from 'data/database/database-query-constants'
import {
ForeignKeyConstraint,
ForeignKeyConstraintsData,
} from 'data/database/foreign-key-constraints-query'
import { ENTITY_TYPE } from 'data/entity-types/entity-type-constants'
import { TableLike } from 'hooks/misc/useTable'
import type { Dictionary, SchemaView } from 'types'
import { FilterOperatorOptions } from './components/header/filter/Filter.constants'
import { STORAGE_KEY_PREFIX } from './constants'
import { InitialStateType } from './store/reducers'
import type { Sort, SupabaseGridProps, SupaColumn, SupaTable } from './types'
/**
* Ensure that if editable is false, we should remove all editing actions
* to prevent rare-case bugs with the UI
@@ -124,6 +132,54 @@ export function parseSupaTable(
}
}
export function getSupaTable({
selectedTable,
entityType,
foreignKeyMeta,
encryptedColumns,
}: {
selectedTable: TableLike
entityType?: ENTITY_TYPE
foreignKeyMeta: ForeignKeyConstraintsData
encryptedColumns: string[]
}) {
// [Joshen] We can tweak below to eventually support composite keys as the data
// returned from foreignKeyMeta should be easy to deal with, rather than pg-meta
const formattedRelationships = (
('relationships' in selectedTable && selectedTable.relationships) ||
[]
).map((relationship: PostgresRelationship) => {
const relationshipMeta = foreignKeyMeta.find(
(fk: ForeignKeyConstraint) => fk.id === relationship.id
)
return {
...relationship,
deletion_action: relationshipMeta?.deletion_action ?? FOREIGN_KEY_CASCADE_ACTION.NO_ACTION,
}
})
const isViewSelected =
entityType === ENTITY_TYPE.VIEW || entityType === ENTITY_TYPE.MATERIALIZED_VIEW
const isForeignTableSelected = entityType === ENTITY_TYPE.FOREIGN_TABLE
return !isViewSelected && !isForeignTableSelected
? parseSupaTable(
{
table: selectedTable as PostgresTable,
columns: (selectedTable as PostgresTable).columns ?? [],
primaryKeys: (selectedTable as PostgresTable).primary_keys ?? [],
relationships: formattedRelationships,
},
encryptedColumns
)
: parseSupaTable({
table: selectedTable as SchemaView,
columns: (selectedTable as SchemaView).columns ?? [],
primaryKeys: [],
relationships: [],
})
}
export const saveStorageDebounced = AwesomeDebouncePromise(saveStorage, 500)
function saveStorage(
@@ -1,9 +1,9 @@
import { ArrowRight } from 'lucide-react'
import Link from 'next/link'
import type { PropsWithChildren } from 'react'
import type { RenderCellProps } from 'react-data-grid'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import { EditorTablePageLink } from 'data/prefetchers/project.$ref.editor.$id'
import { useTableQuery } from 'data/tables/table-query'
import { useTablesQuery } from 'data/tables/tables-query'
import { useQuerySchemaState } from 'hooks/misc/useSchemaQueryState'
@@ -62,11 +62,20 @@ export const ForeignKeyFormatter = (props: Props) => {
className="translate-y-[2px]"
style={{ padding: '3px' }}
>
<Link
<EditorTablePageLink
href={`/project/${projectRef}/editor/${targetTable?.id}?schema=${selectedSchema}&filter=${relationship?.target_column_name}%3Aeq%3A${value}`}
projectRef={projectRef}
id={targetTable && String(targetTable?.id)}
filters={[
{
column: relationship.target_column_name,
operator: '=',
value: String(value),
},
]}
>
<ArrowRight size={14} />
</Link>
</EditorTablePageLink>
</Button>
</TooltipTrigger_Shadcn_>
<TooltipContent_Shadcn_ side="bottom">View referencing record</TooltipContent_Shadcn_>
@@ -1,12 +1,13 @@
import type { PostgresTable } from '@supabase/postgres-meta'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { noop } from 'lodash'
import { Lock, Unlock } from 'lucide-react'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useQueryState } from 'nuqs'
import type { PostgresTable } from '@supabase/postgres-meta'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useParams } from 'common'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { EditorTablePageLink } from 'data/prefetchers/project.$ref.editor.$id'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import {
AiIconAnimation,
Badge,
@@ -29,8 +30,7 @@ const PolicyTableRowHeader = ({
onSelectToggleRLS = noop,
onSelectCreatePolicy,
}: PolicyTableRowHeaderProps) => {
const router = useRouter()
const { ref } = router.query
const { ref } = useParams()
const canToggleRLS = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables')
@@ -42,7 +42,11 @@ const PolicyTableRowHeader = ({
return (
<div id={table.id.toString()} className="flex w-full items-center justify-between">
<div className="flex gap-x-4 text-left">
<Link href={`/project/${ref}/editor/${table.id}`} className="flex items-center gap-x-2">
<EditorTablePageLink
projectRef={ref}
id={String(table.id)}
className="flex items-center gap-x-2"
>
{table.rls_enabled ? (
<div className="flex items-center gap-x-1 text-xs">
<Lock size={14} strokeWidth={2} className="text-brand" />
@@ -53,7 +57,7 @@ const PolicyTableRowHeader = ({
</div>
)}
<h4 className="m-0">{table.name}</h4>
</Link>
</EditorTablePageLink>
<div className="flex items-center gap-x-2">
{isTableLocked && (
<Badge>
@@ -30,12 +30,12 @@ import { useDatabasePublicationsQuery } from 'data/database-publications/databas
import { ENTITY_TYPE } from 'data/entity-types/entity-type-constants'
import { useForeignTablesQuery } from 'data/foreign-tables/foreign-tables-query'
import { useMaterializedViewsQuery } from 'data/materialized-views/materialized-views-query'
import { usePrefetchEditorTablePage } from 'data/prefetchers/project.$ref.editor.$id'
import { useTablesQuery } from 'data/tables/tables-query'
import { useViewsQuery } from 'data/views/views-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useQuerySchemaState } from 'hooks/misc/useSchemaQueryState'
import { EXCLUDED_SCHEMAS } from 'lib/constants/schemas'
import { useTableEditorStateSnapshot } from 'state/table-editor'
import {
Button,
Checkbox_Shadcn_,
@@ -74,6 +74,8 @@ const TableList = ({
const { ref } = useParams()
const { project } = useProjectContext()
const prefetchEditorTablePage = usePrefetchEditorTablePage()
const { selectedSchema, setSelectedSchema } = useQuerySchemaState()
const [filterString, setFilterString] = useState<string>('')
@@ -476,6 +478,9 @@ const TableList = ({
onClick={() =>
router.push(`/project/${project?.ref}/editor/${x.id}`)
}
onMouseEnter={() =>
prefetchEditorTablePage({ id: x.id ? String(x.id) : undefined })
}
>
<Eye size={12} />
<p>View in Table Editor</p>
@@ -7,6 +7,7 @@ import Link from 'next/link'
import { useParams } from 'common'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import type { FDW } from 'data/fdw/fdws-query'
import { EditorTablePageLink } from 'data/prefetchers/project.$ref.editor.$id'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { Collapsible } from 'ui'
import type { WrapperMeta } from './Wrappers.types'
@@ -119,11 +120,15 @@ const WrapperRow = ({
<div className="flex flex-wrap gap-2">
{wrapper.tables ? (
wrapper.tables.map((table: any) => (
<Link key={table.id} href={`/project/${ref}/editor/${table.id}`}>
<EditorTablePageLink
key={table.id}
projectRef={ref}
id={String(table.id)}
>
<div className="text-sm border rounded px-2 py-1 transition bg-surface-200 hover:bg-overlay-hover">
{table.name}
</div>
</Link>
</EditorTablePageLink>
))
) : (
<p className="text-sm text-foreground-light">No tables available</p>
@@ -1,8 +1,9 @@
import { ExternalLink } from 'lucide-react'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useParams } from 'common'
import Panel from 'components/ui/Panel'
import { EditorIndexPageLink } from 'data/prefetchers/project.$ref.editor'
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
import { Auth, EdgeFunctions, Realtime, SqlEditor, Storage, TableEditor } from 'icons'
import { Button } from 'ui'
@@ -10,8 +11,7 @@ import APIKeys from './APIKeys'
import GetStartedHero from './GetStartedHero'
const NewProjectPanel = () => {
const router = useRouter()
const { ref } = router.query
const { ref } = useParams()
const {
projectAuthAll: authEnabled,
@@ -47,7 +47,7 @@ const NewProjectPanel = () => {
</div>
<div className="flex flex-wrap items-center gap-2">
<Button asChild type="default" icon={<TableEditor strokeWidth={1.5} />}>
<Link href={`/project/${ref}/editor`}>Table Editor</Link>
<EditorIndexPageLink projectRef={ref}>Table Editor</EditorIndexPageLink>
</Button>
<Button asChild type="default" icon={<SqlEditor strokeWidth={1.5} />}>
<Link href={`/project/${ref}/sql/new`}>SQL editor</Link>
@@ -13,13 +13,13 @@ import RefreshButton from 'components/grid/components/header/RefreshButton'
import FilterPopover from 'components/grid/components/header/filter/FilterPopover'
import { SortPopover } from 'components/grid/components/header/sort'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import { useEncryptedColumnsQuery } from 'data/encrypted-columns/encrypted-columns-query'
import { useTableRowsQuery } from 'data/table-rows/table-rows-query'
import { useTableQuery } from 'data/tables/table-query'
import { useRoleImpersonationStateSnapshot } from 'state/role-impersonation-state'
import { SidePanel } from 'ui'
import ActionBar from '../../ActionBar'
import { ForeignKey } from '../../ForeignKeySelector/ForeignKeySelector.types'
import { useEncryptedColumns } from '../../SidePanelEditor.utils'
import Pagination from './Pagination'
import SelectorGrid from './SelectorGrid'
@@ -47,7 +47,12 @@ const ForeignRowSelector = ({
id: tableId,
})
const encryptedColumns = useEncryptedColumns({ schemaName, tableName })
const { data: encryptedColumns } = useEncryptedColumnsQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
schema: schemaName,
tableName,
})
const supaTable =
table &&
@@ -1,11 +1,9 @@
import type { PostgresPrimaryKey, PostgresTable } from '@supabase/postgres-meta'
import { chunk, find, isEmpty, isEqual } from 'lodash'
import Papa from 'papaparse'
import { useEffect, useState } from 'react'
import { toast } from 'sonner'
import { Query } from 'components/grid/query/Query'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import SparkBar from 'components/ui/SparkBar'
import { createDatabaseColumn } from 'data/database-columns/database-column-create-mutation'
import { deleteDatabaseColumn } from 'data/database-columns/database-column-delete-mutation'
@@ -23,7 +21,6 @@ import { deleteTable as deleteTableMutation } from 'data/tables/table-delete-mut
import { getTable } from 'data/tables/table-query'
import { updateTable as updateTableMutation } from 'data/tables/table-update-mutation'
import { getTables } from 'data/tables/tables-query'
import { getViews } from 'data/views/views-query'
import { timeout, tryParseJson } from 'lib/helpers'
import {
generateCreateColumnPayload,
@@ -37,67 +34,6 @@ import type { ImportContent } from './TableEditor/TableEditor.types'
const BATCH_SIZE = 1000
const CHUNK_SIZE = 1024 * 1024 * 0.1 // 0.1MB
export interface UseEncryptedColumnsArgs {
schemaName?: string
tableName?: string
}
const listEncryptedColumns = async (
projectRef: string,
connectionString: string | undefined = undefined,
schema: string,
table: string
) => {
if (!table) return []
const views = await getViews({ projectRef, connectionString, schema })
const decryptedView = views.find((view) => view.name === `decrypted_${table}`)
if (!decryptedView) return []
try {
const encryptedColumns = await executeSql({
projectRef,
connectionString,
sql: `SELECT column_name as name FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'decrypted_${table}' and column_name like 'decrypted_%'`,
})
return encryptedColumns.result.map((column: any) => column.name.split('decrypted_')[1])
} catch (error) {
console.error('Error fetching encrypted columns', error)
return []
}
}
export function useEncryptedColumns({ schemaName, tableName }: UseEncryptedColumnsArgs) {
const { project } = useProjectContext()
const [encryptedColumns, setEncryptedColumns] = useState<string[]>([])
useEffect(() => {
let isMounted = true
const getEncryptedColumns = async () => {
if (schemaName !== undefined && tableName !== undefined) {
const columns = await listEncryptedColumns(
project?.ref!,
project?.connectionString,
schemaName,
tableName
)
if (isMounted) {
setEncryptedColumns(columns)
}
}
}
getEncryptedColumns()
return () => {
isMounted = false
}
}, [schemaName, tableName])
return encryptedColumns
}
/**
* The functions below are basically just queries but may be supported directly
* from the pg-meta library in the future
@@ -1,10 +1,10 @@
import clsx from 'clsx'
import { useParams } from 'common'
import Link from 'next/link'
import { ArrowRight } from 'lucide-react'
import SVG from 'react-inlinesvg'
import { Badge, Button, cn } from 'ui'
import { ArrowRight } from 'lucide-react'
import { EditorTablePageLink } from 'data/prefetchers/project.$ref.editor.$id'
import { BASE_PATH } from 'lib/constants'
import type { ForeignKey } from '../../ForeignKeySelector/ForeignKeySelector.types'
@@ -77,12 +77,13 @@ export const ForeignKeyRow = ({
/>
}
>
<Link
href={`/project/${ref}/editor/${foreignKey.tableId}`}
<EditorTablePageLink
projectRef={ref}
id={String(foreignKey.tableId)}
onClick={() => closePanel()}
>
{foreignKey.schema}.{foreignKey.table}
</Link>
</EditorTablePageLink>
</Button>
</div>
</div>
@@ -1,4 +1,4 @@
import type { PostgresColumn, PostgresRelationship, PostgresTable } from '@supabase/postgres-meta'
import type { PostgresColumn, PostgresTable } from '@supabase/postgres-meta'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { QueryKey, useQueryClient } from '@tanstack/react-query'
import { find, isUndefined } from 'lodash'
@@ -8,14 +8,11 @@ import { toast } from 'sonner'
import { useParams } from 'common'
import { SupabaseGrid } from 'components/grid/SupabaseGrid'
import { parseSupaTable } from 'components/grid/SupabaseGrid.utils'
import { getSupaTable } from 'components/grid/SupabaseGrid.utils'
import { SupaTable } from 'components/grid/types'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import { FOREIGN_KEY_CASCADE_ACTION } from 'data/database/database-query-constants'
import {
ForeignKeyConstraint,
useForeignKeyConstraintsQuery,
} from 'data/database/foreign-key-constraints-query'
import { useForeignKeyConstraintsQuery } from 'data/database/foreign-key-constraints-query'
import { useEncryptedColumnsQuery } from 'data/encrypted-columns/encrypted-columns-query'
import { ENTITY_TYPE } from 'data/entity-types/entity-type-constants'
import { sqlKeys } from 'data/sql/keys'
import { useTableRowUpdateMutation } from 'data/table-rows/table-row-update-mutation'
@@ -28,13 +25,12 @@ import { EXCLUDED_SCHEMAS } from 'lib/constants/schemas'
import { EMPTY_ARR } from 'lib/void'
import { useGetImpersonatedRole } from 'state/role-impersonation-state'
import { useTableEditorStateSnapshot } from 'state/table-editor'
import type { Dictionary, SchemaView } from 'types'
import type { Dictionary } from 'types'
import { Button } from 'ui'
import GridHeaderActions from './GridHeaderActions'
import { TableGridSkeletonLoader } from './LoadingState'
import NotFoundState from './NotFoundState'
import SidePanelEditor from './SidePanelEditor/SidePanelEditor'
import { useEncryptedColumns } from './SidePanelEditor/SidePanelEditor.utils'
import TableDefinition from './TableDefinition'
export interface TableGridEditorProps {
@@ -62,8 +58,10 @@ const TableGridEditor = ({
const canEditColumns = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'columns')
const isReadOnly = !canEditTables && !canEditColumns
const encryptedColumns = useEncryptedColumns({
schemaName: selectedTable?.schema,
const { data: encryptedColumns } = useEncryptedColumnsQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
schema: selectedTable?.schema,
tableName: selectedTable?.name,
})
@@ -148,43 +146,16 @@ const TableGridEditor = ({
const isViewSelected =
entityType?.type === ENTITY_TYPE.VIEW || entityType?.type === ENTITY_TYPE.MATERIALIZED_VIEW
const isTableSelected = entityType?.type === ENTITY_TYPE.TABLE
const isForeignTableSelected = entityType?.type === ENTITY_TYPE.FOREIGN_TABLE
const isLocked = EXCLUDED_SCHEMAS.includes(entityType?.schema ?? '')
const canEditViaTableEditor = isTableSelected && !isLocked
// [Joshen] We can tweak below to eventually support composite keys as the data
// returned from foreignKeyMeta should be easy to deal with, rather than pg-meta
const formattedRelationships = (
('relationships' in selectedTable && selectedTable.relationships) ||
[]
).map((relationship: PostgresRelationship) => {
const relationshipMeta = foreignKeyMeta.find(
(fk: ForeignKeyConstraint) => fk.id === relationship.id
)
return {
...relationship,
deletion_action: relationshipMeta?.deletion_action ?? FOREIGN_KEY_CASCADE_ACTION.NO_ACTION,
}
const gridTable = getSupaTable({
encryptedColumns,
foreignKeyMeta,
selectedTable,
entityType: entityType?.type,
})
const gridTable =
!isViewSelected && !isForeignTableSelected
? parseSupaTable(
{
table: selectedTable as PostgresTable,
columns: (selectedTable as PostgresTable).columns ?? [],
primaryKeys: (selectedTable as PostgresTable).primary_keys ?? [],
relationships: formattedRelationships,
},
encryptedColumns
)
: parseSupaTable({
table: selectedTable as SchemaView,
columns: (selectedTable as SchemaView).columns ?? [],
primaryKeys: [],
relationships: [],
})
const gridKey = `${selectedTable.schema}_${selectedTable.name}`
const onTableCreated = (table: PostgresTable) => {
@@ -1,6 +1,8 @@
import { Blocks, FileText, Lightbulb, List, Settings } from 'lucide-react'
import type { Route } from 'components/ui/ui.types'
import { EditorIndexPageLink } from 'data/prefetchers/project.$ref.editor'
import type { Project } from 'data/projects/project-detail-query'
import { IS_PLATFORM, PROJECT_STATUS } from 'lib/constants'
import {
Auth,
Database,
@@ -11,8 +13,8 @@ import {
Storage,
TableEditor,
} from 'icons'
import { IS_PLATFORM, PROJECT_STATUS } from 'lib/constants'
import { ICON_SIZE, ICON_STROKE_WIDTH } from './NavigationBar'
import { Settings, FileText, List, Lightbulb, Blocks } from 'lucide-react'
export const generateToolRoutes = (ref?: string, project?: Project): Route[] => {
const isProjectBuilding = project?.status === PROJECT_STATUS.COMING_UP
@@ -24,6 +26,7 @@ export const generateToolRoutes = (ref?: string, project?: Project): Route[] =>
label: 'Table Editor',
icon: <TableEditor size={ICON_SIZE} strokeWidth={ICON_STROKE_WIDTH} />,
link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/editor`),
linkComponent: <EditorIndexPageLink projectRef={ref} />,
},
{
key: 'sql',
@@ -1,13 +1,18 @@
import { noop } from 'lodash'
import Link from 'next/link'
import { AnchorHTMLAttributes, forwardRef } from 'react'
import { cn } from 'ui'
import { Tooltip_Shadcn_, TooltipContent_Shadcn_, TooltipTrigger_Shadcn_ } from 'ui'
import {
AnchorHTMLAttributes,
cloneElement,
ComponentPropsWithoutRef,
forwardRef,
isValidElement,
} from 'react'
import { cn, Tooltip_Shadcn_, TooltipContent_Shadcn_, TooltipTrigger_Shadcn_ } from 'ui'
import type { Route } from 'components/ui/ui.types'
import { useAppStateSnapshot } from 'state/app-state'
import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
import { LOCAL_STORAGE_KEYS } from 'lib/constants'
import { useAppStateSnapshot } from 'state/app-state'
interface NavigationIconButtonProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
route: Route
@@ -41,8 +46,18 @@ const NavigationIconLink = forwardRef<HTMLAnchorElement, NavigationIconButtonPro
`${isActive && '!bg-selection shadow-sm'}`,
]
const LinkComponent = forwardRef<HTMLAnchorElement, ComponentPropsWithoutRef<typeof Link>>(
function LinkComponent(props, ref) {
if (route.linkComponent && isValidElement(route.linkComponent)) {
return cloneElement<any>(route.linkComponent, { ...props, ref })
}
return <Link ref={ref} {...props} />
}
)
const linkContent = (
<Link
<LinkComponent
role="button"
aria-current={isActive}
ref={ref}
@@ -73,7 +88,7 @@ const NavigationIconLink = forwardRef<HTMLAnchorElement, NavigationIconButtonPro
>
{route.label}
</span>
</Link>
</LinkComponent>
)
if (!allowNavPanelToExpand) {
@@ -46,6 +46,7 @@ import {
} from 'ui'
import { useProjectContext } from '../ProjectLayout/ProjectContext'
import { Markdown } from 'components/interfaces/Markdown'
import { EditorTablePageLink } from 'data/prefetchers/project.$ref.editor.$id'
export interface EntityListItemProps {
id: number
@@ -277,8 +278,9 @@ const EntityListItem: ItemRenderer<Entity, EntityListItemProps> = ({
}
return (
<Link
<EditorTablePageLink
title={entity.name}
id={String(entity.id)}
href={`/project/${projectRef}/editor/${entity.id}?schema=${selectedSchema}`}
role="button"
aria-label={`View ${entity.name}`}
@@ -447,7 +449,7 @@ const EntityListItem: ItemRenderer<Entity, EntityListItemProps> = ({
</DropdownMenuContent>
</DropdownMenu>
)}
</Link>
</EditorTablePageLink>
)
}
@@ -12,10 +12,10 @@ import SchemaSelector from 'components/ui/SchemaSelector'
import { useSchemasQuery } from 'data/database/schemas-query'
import { ENTITY_TYPE } from 'data/entity-types/entity-type-constants'
import { useEntityTypesQuery } from 'data/entity-types/entity-types-infinite-query'
import { useTableQuery } from 'data/tables/table-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useLocalStorage } from 'hooks/misc/useLocalStorage'
import { useQuerySchemaState } from 'hooks/misc/useSchemaQueryState'
import useTable from 'hooks/misc/useTable'
import { EXCLUDED_SCHEMAS } from 'lib/constants/schemas'
import { useTableEditorStateSnapshot } from 'state/table-editor'
import {
@@ -96,15 +96,7 @@ const TableEditorMenu = () => {
)
const isLocked = protectedSchemas.some((s) => s.id === schema?.id)
const { data: selectedTable } = useTableQuery(
{
projectRef: project?.ref,
connectionString: project?.connectionString,
id: Number(id),
},
// only run if we have a selected table
{ enabled: Boolean(id) }
)
const { data: selectedTable } = useTable(id !== undefined ? Number(id) : undefined)
useEffect(() => {
if (selectedTable) {
+1
View File
@@ -6,4 +6,5 @@ export interface Route {
icon: ReactNode
link?: string
disabled?: boolean
linkComponent?: ReactNode
}
@@ -32,13 +32,7 @@ with records as (
'create materialized view ', concat(nc.nspname, '.', c.relname), ' as',
pg_get_viewdef(concat(nc.nspname, '.', c.relname), true)
)
when 'f' then pg_temp.pg_get_tabledef(
concat(nc.nspname),
concat(c.relname),
false,
'FKEYS_INTERNAL',
'NO_TRIGGERS'
)
when 'f' then concat('create foreign table ', nc.nspname, '.', c.relname, ' ( ... )')
when 'p' then pg_temp.pg_get_tabledef(
concat(nc.nspname),
concat(c.relname),
@@ -1,5 +1,12 @@
import { UseQueryOptions } from '@tanstack/react-query'
import { ExecuteSqlData, ExecuteSqlError, useExecuteSqlQuery } from '../sql/execute-sql-query'
import { QueryClient, UseQueryOptions } from '@tanstack/react-query'
import { sqlKeys } from 'data/sql/keys'
import {
executeSql,
ExecuteSqlData,
ExecuteSqlError,
useExecuteSqlQuery,
} from '../sql/execute-sql-query'
type GetForeignKeyConstraintsVariables = {
schema?: string
@@ -96,6 +103,22 @@ export type ForeignKeyConstraintsVariables = GetForeignKeyConstraintsVariables &
export type ForeignKeyConstraintsData = ForeignKeyConstraint[]
export type ForeignKeyConstraintsError = ExecuteSqlError
function select(
data:
| {
result: any
}
| undefined
) {
return (data?.result ?? []).map((foreignKey: ForeignKeyConstraintRaw) => {
return {
...foreignKey,
source_columns: foreignKey.source_columns.replace('{', '').replace('}', '').split(','),
target_columns: foreignKey.target_columns.replace('{', '').replace('}', '').split(','),
}
})
}
export const useForeignKeyConstraintsQuery = <
TData extends ForeignKeyConstraintsData = ForeignKeyConstraintsData,
>(
@@ -107,19 +130,30 @@ export const useForeignKeyConstraintsQuery = <
projectRef,
connectionString,
sql: getForeignKeyConstraintsQuery({ schema }),
queryKey: ['foreign-key-constraints'],
queryKey: ['foreign-key-constraints', schema],
},
{
select(data) {
return ((data as any)?.result ?? []).map((foreignKey: ForeignKeyConstraintRaw) => {
return {
...foreignKey,
source_columns: foreignKey.source_columns.replace('{', '').replace('}', '').split(','),
target_columns: foreignKey.target_columns.replace('{', '').replace('}', '').split(','),
}
})
},
select,
...options,
}
)
}
export function prefetchForeignKeyConstraints(
client: QueryClient,
{ projectRef, connectionString, schema }: ForeignKeyConstraintsVariables
) {
return client
.fetchQuery(sqlKeys.query(projectRef, ['foreign-key-constraints', schema]), ({ signal }) =>
executeSql(
{
projectRef,
connectionString,
sql: getForeignKeyConstraintsQuery({ schema }),
queryKey: ['foreign-key-constraints', schema],
},
signal
)
)
.then((data) => select(data) as ForeignKeyConstraintsData)
}
+23 -1
View File
@@ -2,7 +2,12 @@ import pgMeta from '@supabase/pg-meta'
import { QueryClient, UseQueryOptions } from '@tanstack/react-query'
import { z } from 'zod'
import { ExecuteSqlData, ExecuteSqlError, useExecuteSqlQuery } from 'data/sql/execute-sql-query'
import {
executeSql,
ExecuteSqlData,
ExecuteSqlError,
useExecuteSqlQuery,
} from 'data/sql/execute-sql-query'
import { sqlKeys } from 'data/sql/keys'
export type SchemasVariables = {
@@ -39,3 +44,20 @@ export const useSchemasQuery = <TData = SchemasData>(
export function invalidateSchemasQuery(client: QueryClient, projectRef: string | undefined) {
return client.invalidateQueries(sqlKeys.query(projectRef, ['schemas', 'list']))
}
export function prefetchSchemas(
client: QueryClient,
{ projectRef, connectionString }: SchemasVariables
) {
return client.fetchQuery(sqlKeys.query(projectRef, ['schemas', 'list']), ({ signal }) =>
executeSql(
{
projectRef,
connectionString,
sql: pgMetaSchemasList.sql,
queryKey: ['schemas', 'list'],
},
signal
)
)
}
@@ -0,0 +1,69 @@
import { QueryClient, useQuery, UseQueryOptions } from '@tanstack/react-query'
import { executeSql } from 'data/sql/execute-sql-query'
import type { ResponseError } from 'types'
import { encryptedColumnKeys } from './keys'
export type EncryptedColumnsVariables = {
projectRef?: string
connectionString?: string
schema?: string
tableName?: string
}
export async function getEncryptedColumns(
{ projectRef, connectionString, schema, tableName }: EncryptedColumnsVariables,
signal?: AbortSignal
) {
if (!tableName) return []
try {
const encryptedColumns = await executeSql(
{
projectRef,
connectionString,
sql: `select column_name as name from information_schema.columns where table_schema = '${schema}' and table_name = 'decrypted_${tableName}' and column_name like 'decrypted_%'`,
queryKey: ['encrypted-columns', tableName],
},
signal
)
return encryptedColumns.result.map((column: any) => column.name.split('decrypted_')[1])
} catch (error) {
console.error('Error fetching encrypted columns', error)
return []
}
}
export type EncryptedColumnsData = Awaited<ReturnType<typeof getEncryptedColumns>>
export type EncryptedColumnsError = ResponseError
export const useEncryptedColumnsQuery = <TData = EncryptedColumnsData>(
{ projectRef, connectionString, schema, tableName }: EncryptedColumnsVariables,
{
enabled = true,
...options
}: UseQueryOptions<EncryptedColumnsData, EncryptedColumnsError, TData> = {}
) =>
useQuery<EncryptedColumnsData, EncryptedColumnsError, TData>(
encryptedColumnKeys.list(projectRef, schema, tableName),
({ signal }) =>
getEncryptedColumns({ projectRef, connectionString, schema, tableName }, signal),
{ enabled: enabled && typeof projectRef !== 'undefined', ...options }
)
export function prefetchEncryptedColumns(
client: QueryClient,
{ projectRef, connectionString, schema, tableName }: EncryptedColumnsVariables
) {
return client.fetchQuery(encryptedColumnKeys.list(projectRef, schema, tableName), ({ signal }) =>
getEncryptedColumns(
{
projectRef,
connectionString,
schema,
tableName,
},
signal
)
)
}
@@ -0,0 +1,7 @@
export const encryptedColumnKeys = {
list: (
projectRef: string | undefined,
schema: string | undefined,
tableName: string | undefined
) => ['projects', projectRef, 'encrypted-columns', schema, tableName] as const,
}
@@ -1,5 +1,12 @@
import { UseQueryOptions } from '@tanstack/react-query'
import { ExecuteSqlData, ExecuteSqlError, useExecuteSqlQuery } from '../sql/execute-sql-query'
import { QueryClient, UseQueryOptions } from '@tanstack/react-query'
import { sqlKeys } from 'data/sql/keys'
import {
executeSql,
ExecuteSqlData,
ExecuteSqlError,
useExecuteSqlQuery,
} from '../sql/execute-sql-query'
import { ENTITY_TYPE } from './entity-type-constants'
type EntityTypeArgs = {
@@ -75,3 +82,22 @@ export const useEntityTypeQuery = <TData extends EntityTypeData = EntityTypeData
...options,
}
)
export function prefetchEntityType(
client: QueryClient,
{ projectRef, connectionString, id }: EntityTypeVariables
) {
return client
.fetchQuery(sqlKeys.query(projectRef, ['entity-type', id]), ({ signal }) =>
executeSql(
{
projectRef,
connectionString,
sql: entityTypeSqlQuery({ id }),
queryKey: ['entity-type', id],
},
signal
)
)
.then((data: { result: EntityTypeData[] }) => data?.result?.[0] ?? null)
}
@@ -1,7 +1,8 @@
import { useInfiniteQuery, UseInfiniteQueryOptions } from '@tanstack/react-query'
import { QueryClient, useInfiniteQuery, UseInfiniteQueryOptions } from '@tanstack/react-query'
import { executeSql, ExecuteSqlVariables } from 'data/sql/execute-sql-query'
import type { Entity } from './entity-type-query'
import { entityTypeKeys } from './keys'
import { ENTITY_TYPE } from './entity-type-constants'
export type EntityTypesVariables = {
projectRef?: string
@@ -29,7 +30,7 @@ export async function getEntityTypes(
limit = 100,
page = 0,
sort = 'alphabetical',
filterTypes,
filterTypes = Object.values(ENTITY_TYPE),
}: EntityTypesVariables,
signal?: AbortSignal
) {
@@ -57,7 +58,7 @@ export async function getEntityTypes(
pg_namespace nc
join pg_class c on nc.oid = c.relnamespace
where
c.relkind in (${filterTypes === undefined ? `'r', 'v', 'm', 'f', 'p'` : filterTypes.map((x) => `'${x}'`).join(', ')})
c.relkind in (${filterTypes.map((x) => `'${x}'`).join(', ')})
and not pg_is_other_temp_schema(nc.oid)
and (
pg_has_role(c.relowner, 'USAGE')
@@ -155,3 +156,34 @@ export const useEntityTypesQuery = <TData = EntityTypesData>(
...options,
}
)
export function prefetchEntityTypes(
client: QueryClient,
{
projectRef,
connectionString,
schemas = ['public'],
search,
limit = 100,
sort,
filterTypes,
}: Omit<EntityTypesVariables, 'page'>
) {
return client.prefetchInfiniteQuery(
entityTypeKeys.list(projectRef, { schemas, search, sort, limit, filterTypes }),
({ signal, pageParam }) =>
getEntityTypes(
{
projectRef,
connectionString,
schemas,
search,
limit,
page: pageParam,
sort,
filterTypes,
},
signal
)
)
}
@@ -1,5 +1,5 @@
import type { PostgresTable } from '@supabase/postgres-meta'
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { QueryClient, useQuery, UseQueryOptions } from '@tanstack/react-query'
import { get } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { foreignTableKeys } from './keys'
@@ -56,3 +56,12 @@ export const useForeignTableQuery = <TData = ForeignTableData>(
...options,
}
)
export function prefetchForeignTable(
client: QueryClient,
{ projectRef, connectionString, id }: ForeignTableVariables
) {
return client.fetchQuery(foreignTableKeys.foreignTable(projectRef, id), ({ signal }) =>
getForeignTable({ id, projectRef, connectionString }, signal)
)
}
@@ -1,5 +1,6 @@
import type { PostgresMaterializedView } from '@supabase/postgres-meta'
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { QueryClient, useQuery, UseQueryOptions } from '@tanstack/react-query'
import { get } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { materializedViewKeys } from './keys'
@@ -59,3 +60,12 @@ export const useMaterializedViewQuery = <TData = MaterializedViewData>(
...options,
}
)
export function prefetchMaterializedView(
client: QueryClient,
{ projectRef, connectionString, id }: MaterializedViewVariables
) {
return client.fetchQuery(materializedViewKeys.materializedView(projectRef, id), ({ signal }) =>
getMaterializedView({ id, projectRef, connectionString }, signal)
)
}
@@ -0,0 +1,182 @@
import { useQueryClient } from '@tanstack/react-query'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { ComponentProps, PropsWithChildren, useCallback } from 'react'
import { loadTableEditorSortsAndFiltersFromLocalStorage } from 'components/grid/SupabaseGrid'
import {
formatFilterURLParams,
formatSortURLParams,
getSupaTable,
} from 'components/grid/SupabaseGrid.utils'
import { Filter, Sort } from 'components/grid/types'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import {
ForeignKeyConstraintsData,
prefetchForeignKeyConstraints,
} from 'data/database/foreign-key-constraints-query'
import { prefetchEncryptedColumns } from 'data/encrypted-columns/encrypted-columns-query'
import { ENTITY_TYPE } from 'data/entity-types/entity-type-constants'
import { Entity, prefetchEntityType } from 'data/entity-types/entity-type-query'
import { ForeignTableData, prefetchForeignTable } from 'data/foreign-tables/foreign-table-query'
import {
MaterializedViewData,
prefetchMaterializedView,
} from 'data/materialized-views/materialized-view-query'
import { prefetchTableRows } from 'data/table-rows/table-rows-query'
import { prefetchTable, TableData } from 'data/tables/table-query'
import { prefetchView, ViewData } from 'data/views/view-query'
import { useFlag } from 'hooks/ui/useFlag'
import { useRoleImpersonationStateSnapshot } from 'state/role-impersonation-state'
import { TABLE_EDITOR_DEFAULT_ROWS_PER_PAGE } from 'state/table-editor'
export function usePrefetchEditorTablePage() {
const router = useRouter()
const queryClient = useQueryClient()
const { project } = useProjectContext()
const roleImpersonationState = useRoleImpersonationStateSnapshot()
const tableEditorPrefetchingEnabled = useFlag('tableEditorPrefetching')
return useCallback(
({ id: _id, filters, sorts }: { id?: string; filters?: Filter[]; sorts?: Sort[] }) => {
if (!tableEditorPrefetchingEnabled) return
const id = _id ? Number(_id) : undefined
if (!project || !id || isNaN(id)) return
// Prefetch the code
router.prefetch(`/project/${project.ref}/editor/${id}`)
// Prefetch the data
prefetchEntityType(queryClient, {
projectRef: project.ref,
connectionString: project.connectionString,
id,
})
.then((entity) => {
let promises: [
Promise<Entity | null>,
Promise<ForeignKeyConstraintsData>,
Promise<string[]>,
(
| Promise<TableData | ViewData | MaterializedViewData | ForeignTableData | undefined>
| undefined
),
] = [
Promise.resolve(entity),
prefetchForeignKeyConstraints(queryClient, {
projectRef: project.ref,
connectionString: project.connectionString,
schema: entity?.schema,
}),
prefetchEncryptedColumns(queryClient, {
projectRef: project.ref,
connectionString: project.connectionString,
schema: entity?.schema,
tableName: entity?.name,
}),
undefined,
]
switch (entity?.type) {
case ENTITY_TYPE.TABLE:
case ENTITY_TYPE.PARTITIONED_TABLE:
promises[3] = prefetchTable(queryClient, {
projectRef: project.ref,
connectionString: project.connectionString,
id,
})
break
case ENTITY_TYPE.VIEW:
promises[3] = prefetchView(queryClient, {
projectRef: project.ref,
connectionString: project.connectionString,
id,
})
break
case ENTITY_TYPE.MATERIALIZED_VIEW:
promises[3] = prefetchMaterializedView(queryClient, {
projectRef: project.ref,
connectionString: project.connectionString,
id,
})
break
case ENTITY_TYPE.FOREIGN_TABLE:
promises[3] = prefetchForeignTable(queryClient, {
projectRef: project.ref,
connectionString: project.connectionString,
id,
})
break
}
return Promise.all(promises)
})
.then(([entity, foreignKeyConstraints, encryptedColumns, table]) => {
if (entity && table) {
const supaTable = getSupaTable({
selectedTable: table,
encryptedColumns,
foreignKeyMeta: foreignKeyConstraints,
entityType: entity.type,
})
const { sorts: localSorts = [], filters: localFilters = [] } =
loadTableEditorSortsAndFiltersFromLocalStorage(
project.ref,
entity.name,
entity.schema
) ?? {}
prefetchTableRows(queryClient, {
queryKey: [supaTable.schema, supaTable.name],
projectRef: project?.ref,
connectionString: project?.connectionString,
table: supaTable,
sorts: sorts ?? formatSortURLParams(supaTable.name, localSorts),
filters: filters ?? formatFilterURLParams(localFilters),
page: 1,
limit: TABLE_EDITOR_DEFAULT_ROWS_PER_PAGE,
impersonatedRole: roleImpersonationState.role,
})
}
})
},
[project, queryClient, router]
)
}
type LinkProps = ComponentProps<typeof Link>
interface EditorTablePageLinkProps extends Omit<LinkProps, 'href'> {
projectRef?: string
id?: string
sorts?: Sort[]
filters?: Filter[]
href?: LinkProps['href']
}
export function EditorTablePageLink({
projectRef,
id,
sorts,
filters,
href,
children,
...props
}: PropsWithChildren<EditorTablePageLinkProps>) {
const prefetch = usePrefetchEditorTablePage()
return (
<Link
href={href || `/project/${projectRef}/editor/${id}`}
onMouseEnter={() => prefetch({ id, sorts, filters })}
{...props}
>
{children}
</Link>
)
}
@@ -0,0 +1,64 @@
import { useQueryClient } from '@tanstack/react-query'
import Link from 'next/link'
import { ComponentProps, PropsWithChildren, useCallback } from 'react'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import { prefetchSchemas } from 'data/database/schemas-query'
import { ENTITY_TYPE } from 'data/entity-types/entity-type-constants'
import { prefetchEntityTypes } from 'data/entity-types/entity-types-infinite-query'
import { useLocalStorage } from 'hooks/misc/useLocalStorage'
import { useFlag } from 'hooks/ui/useFlag'
import { useRouter } from 'next/router'
export function usePrefetchEditorIndexPage() {
const router = useRouter()
const queryClient = useQueryClient()
const { project } = useProjectContext()
const tableEditorPrefetchingEnabled = useFlag('tableEditorPrefetching')
const [entityTypesSort] = useLocalStorage<'alphabetical' | 'grouped-alphabetical'>(
'table-editor-sort',
'alphabetical'
)
return useCallback(() => {
if (!tableEditorPrefetchingEnabled || !project) return
// Prefetch code
router.prefetch(`/project/${project.ref}/editor`)
// Prefetch data
prefetchSchemas(queryClient, {
projectRef: project.ref,
connectionString: project.connectionString,
})
prefetchEntityTypes(queryClient, {
projectRef: project?.ref,
connectionString: project?.connectionString,
sort: entityTypesSort,
filterTypes: Object.values(ENTITY_TYPE),
})
}, [entityTypesSort, project, queryClient, router])
}
type LinkProps = ComponentProps<typeof Link>
interface EditorIndexPageLinkProps extends Omit<LinkProps, 'href'> {
projectRef?: string
href?: LinkProps['href']
}
export function EditorIndexPageLink({
href,
projectRef,
children,
...props
}: PropsWithChildren<EditorIndexPageLinkProps>) {
const prefetch = usePrefetchEditorIndexPage()
return (
<Link href={href || `/project/${projectRef}/editor`} onMouseEnter={prefetch} {...props}>
{children}
</Link>
)
}
+45 -14
View File
@@ -1,4 +1,4 @@
import type { QueryKey, UseQueryOptions } from '@tanstack/react-query'
import type { QueryClient, QueryKey, UseQueryOptions } from '@tanstack/react-query'
import { IS_PLATFORM } from 'common'
import { Query } from 'components/grid/query/Query'
@@ -7,13 +7,17 @@ import {
JSON_TYPES,
TEXT_TYPES,
} from 'components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.constants'
import { sqlKeys } from 'data/sql/keys'
import { KB } from 'lib/constants'
import {
ImpersonationRole,
ROLE_IMPERSONATION_NO_RESULTS,
wrapWithRoleImpersonation,
} from 'lib/role-impersonation'
import { useIsRoleImpersonationEnabled } from 'state/role-impersonation-state'
import {
isRoleImpersonationEnabled,
useIsRoleImpersonationEnabled,
} from 'state/role-impersonation-state'
import {
ExecuteSqlData,
ExecuteSqlError,
@@ -204,6 +208,21 @@ export type TableRowsVariables = GetTableRowsArgs & {
export type TableRowsData = TableRows
export type TableRowsError = ExecuteSqlError
function getTableRowsQueryKey({ queryKey, table, impersonatedRole, ...args }: TableRowsVariables) {
return [
...(queryKey ?? []),
{
table: {
name: table?.name,
schema: table?.schema,
columns: table?.columns.map((c) => c.name),
},
impersonatedRole,
...args,
},
]
}
export const useTableRowsQuery = <TData extends TableRowsData = TableRowsData>(
{ projectRef, connectionString, queryKey, table, impersonatedRole, ...args }: TableRowsVariables,
options: UseQueryOptions<ExecuteSqlData, TableRowsError, TData> = {}
@@ -218,18 +237,7 @@ export const useTableRowsQuery = <TData extends TableRowsData = TableRowsData>(
projectRef: projectRef ?? 'ref',
role: impersonatedRole,
}),
queryKey: [
...(queryKey ?? []),
{
table: {
name: table?.name,
schema: table?.schema,
columns: table?.columns.map((c) => c.name),
},
impersonatedRole,
...args,
},
],
queryKey: getTableRowsQueryKey({ queryKey, table, impersonatedRole, ...args }),
isRoleImpersonationEnabled,
},
{
@@ -247,3 +255,26 @@ export const useTableRowsQuery = <TData extends TableRowsData = TableRowsData>(
}
)
}
export function prefetchTableRows(
client: QueryClient,
{ projectRef, connectionString, queryKey, table, impersonatedRole, ...args }: TableRowsVariables
) {
return client.fetchQuery(
sqlKeys.query(projectRef, getTableRowsQueryKey({ queryKey, table, impersonatedRole, ...args })),
({ signal }) =>
executeSql(
{
projectRef,
connectionString,
sql: wrapWithRoleImpersonation(getTableRowsSqlQuery({ table, ...args }), {
projectRef: projectRef ?? 'ref',
role: impersonatedRole,
}),
queryKey: getTableRowsQueryKey({ queryKey, table, impersonatedRole, ...args }),
isRoleImpersonationEnabled: isRoleImpersonationEnabled(impersonatedRole),
},
signal
)
)
}
+7 -20
View File
@@ -1,6 +1,5 @@
import type { PostgresTable } from '@supabase/postgres-meta'
import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query'
import { useCallback } from 'react'
import { QueryClient, useQuery, UseQueryOptions } from '@tanstack/react-query'
import { get, handleError } from 'data/fetchers'
import { tableKeys } from './keys'
@@ -57,23 +56,11 @@ export const useTableQuery = <TData = TableData>(
}
)
/**
* useGetTable
* Tries to get a table from the react-query cache, or loads it from the server if it's not cached.
*/
export function useGetTable({
projectRef,
connectionString,
}: Pick<TableVariables, 'projectRef' | 'connectionString'>) {
const queryClient = useQueryClient()
return useCallback(
(id: NonNullable<TableVariables['id']>) => {
return queryClient.fetchQuery({
queryKey: tableKeys.table(projectRef, id),
queryFn: ({ signal }) => getTable({ id, projectRef, connectionString }, signal),
})
},
[connectionString, projectRef, queryClient]
export function prefetchTable(
client: QueryClient,
{ projectRef, connectionString, id }: TableVariables
) {
return client.fetchQuery(tableKeys.table(projectRef, id), ({ signal }) =>
getTable({ id, projectRef, connectionString }, signal)
)
}
+10 -1
View File
@@ -1,5 +1,5 @@
import type { PostgresView } from '@supabase/postgres-meta'
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { QueryClient, useQuery, UseQueryOptions } from '@tanstack/react-query'
import { get, handleError } from 'data/fetchers'
import type { ResponseError } from 'types'
@@ -55,3 +55,12 @@ export const useViewQuery = <TData = ViewData>(
...options,
}
)
export function prefetchView(
client: QueryClient,
{ projectRef, connectionString, id }: ViewVariables
) {
return client.fetchQuery(viewKeys.view(projectRef, id), ({ signal }) =>
getView({ id, projectRef, connectionString }, signal)
)
}
+2 -14
View File
@@ -25,18 +25,7 @@ function useTable(id?: number) {
id: entity?.id,
},
{
enabled: entity?.type === ENTITY_TYPE.TABLE,
}
)
const partitionTableResult = useTableQuery(
{
projectRef: project?.ref,
connectionString: project?.connectionString,
id: entity?.id,
},
{
enabled: entity?.type === ENTITY_TYPE.PARTITIONED_TABLE,
enabled: entity?.type === ENTITY_TYPE.TABLE || entity?.type === ENTITY_TYPE.PARTITIONED_TABLE,
}
)
@@ -75,9 +64,8 @@ function useTable(id?: number) {
switch (entity?.type) {
case ENTITY_TYPE.TABLE:
return tableResult
case ENTITY_TYPE.PARTITIONED_TABLE:
return partitionTableResult
return tableResult
case ENTITY_TYPE.VIEW:
return viewResult
case ENTITY_TYPE.MATERIALIZED_VIEW:
@@ -31,7 +31,7 @@ const TableEditorPage: NextPageWithLayout = () => {
if (tables.length > 0) {
router.push(`/project/${projectRef}/editor/${tables[0].id}`)
} else {
router.push(`/project/${projectRef}/editor/`)
router.push(`/project/${projectRef}/editor`)
}
}}
/>
+3 -1
View File
@@ -6,6 +6,8 @@ import { PropsWithChildren, createContext, useContext, useRef } from 'react'
import type { Dictionary } from 'types'
import { proxy, useSnapshot } from 'valtio'
export const TABLE_EDITOR_DEFAULT_ROWS_PER_PAGE = 100
type ForeignKeyState = {
foreignKey: ForeignKey
row: Dictionary<any>
@@ -63,7 +65,7 @@ export const createTableEditorState = () => {
setPage: (page: number) => {
state.page = page
},
rowsPerPage: 100,
rowsPerPage: TABLE_EDITOR_DEFAULT_ROWS_PER_PAGE,
setRowsPerPage: (rowsPerPage: number) => {
state.rowsPerPage = rowsPerPage
},