diff --git a/apps/studio/components/grid/SupabaseGrid.tsx b/apps/studio/components/grid/SupabaseGrid.tsx index c25b503426d..4d9472a26b9 100644 --- a/apps/studio/components/grid/SupabaseGrid.tsx +++ b/apps/studio/components/grid/SupabaseGrid.tsx @@ -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 diff --git a/apps/studio/components/grid/SupabaseGrid.utils.ts b/apps/studio/components/grid/SupabaseGrid.utils.ts index 1e6a6e8c5be..25fddc31895 100644 --- a/apps/studio/components/grid/SupabaseGrid.utils.ts +++ b/apps/studio/components/grid/SupabaseGrid.utils.ts @@ -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( diff --git a/apps/studio/components/grid/components/formatter/ForeignKeyFormatter.tsx b/apps/studio/components/grid/components/formatter/ForeignKeyFormatter.tsx index 7738c14b7d3..2948e7ff0f5 100644 --- a/apps/studio/components/grid/components/formatter/ForeignKeyFormatter.tsx +++ b/apps/studio/components/grid/components/formatter/ForeignKeyFormatter.tsx @@ -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' }} > - { - 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 (
- + {table.rls_enabled ? (
@@ -53,7 +57,7 @@ const PolicyTableRowHeader = ({
)}

{table.name}

- +
{isTableLocked && ( diff --git a/apps/studio/components/interfaces/Database/Tables/TableList.tsx b/apps/studio/components/interfaces/Database/Tables/TableList.tsx index 1242353bfee..519347c7858 100644 --- a/apps/studio/components/interfaces/Database/Tables/TableList.tsx +++ b/apps/studio/components/interfaces/Database/Tables/TableList.tsx @@ -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('') @@ -476,6 +478,9 @@ const TableList = ({ onClick={() => router.push(`/project/${project?.ref}/editor/${x.id}`) } + onMouseEnter={() => + prefetchEditorTablePage({ id: x.id ? String(x.id) : undefined }) + } >

View in Table Editor

diff --git a/apps/studio/components/interfaces/Database/Wrappers/WrapperRow.tsx b/apps/studio/components/interfaces/Database/Wrappers/WrapperRow.tsx index ffdb8ae54d9..6dc505446c8 100644 --- a/apps/studio/components/interfaces/Database/Wrappers/WrapperRow.tsx +++ b/apps/studio/components/interfaces/Database/Wrappers/WrapperRow.tsx @@ -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 = ({
{wrapper.tables ? ( wrapper.tables.map((table: any) => ( - +
{table.name}
- +
)) ) : (

No tables available

diff --git a/apps/studio/components/interfaces/Home/NewProjectPanel/NewProjectPanel.tsx b/apps/studio/components/interfaces/Home/NewProjectPanel/NewProjectPanel.tsx index 19b5708425e..e77fbe11591 100644 --- a/apps/studio/components/interfaces/Home/NewProjectPanel/NewProjectPanel.tsx +++ b/apps/studio/components/interfaces/Home/NewProjectPanel/NewProjectPanel.tsx @@ -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 = () => {
diff --git a/apps/studio/components/interfaces/TableGridEditor/TableGridEditor.tsx b/apps/studio/components/interfaces/TableGridEditor/TableGridEditor.tsx index 431ee1b7d5b..693f25789e5 100644 --- a/apps/studio/components/interfaces/TableGridEditor/TableGridEditor.tsx +++ b/apps/studio/components/interfaces/TableGridEditor/TableGridEditor.tsx @@ -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) => { diff --git a/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.utils.tsx b/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.utils.tsx index 159f5bb7dae..8b807064bc6 100644 --- a/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.utils.tsx +++ b/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.utils.tsx @@ -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: , link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/editor`), + linkComponent: , }, { key: 'sql', diff --git a/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationIconLink.tsx b/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationIconLink.tsx index ca8b6143169..758deedc315 100644 --- a/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationIconLink.tsx +++ b/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationIconLink.tsx @@ -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 { route: Route @@ -41,8 +46,18 @@ const NavigationIconLink = forwardRef>( + function LinkComponent(props, ref) { + if (route.linkComponent && isValidElement(route.linkComponent)) { + return cloneElement(route.linkComponent, { ...props, ref }) + } + + return + } + ) + const linkContent = ( - {route.label} - + ) if (!allowNavPanelToExpand) { diff --git a/apps/studio/components/layouts/TableEditorLayout/EntityListItem.tsx b/apps/studio/components/layouts/TableEditorLayout/EntityListItem.tsx index 2a2f463ea13..98cd65ec2b4 100644 --- a/apps/studio/components/layouts/TableEditorLayout/EntityListItem.tsx +++ b/apps/studio/components/layouts/TableEditorLayout/EntityListItem.tsx @@ -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 = ({ } return ( - = ({ )} - + ) } diff --git a/apps/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx b/apps/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx index c859e571e41..82ff2ae0376 100644 --- a/apps/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx +++ b/apps/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx @@ -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) { diff --git a/apps/studio/components/ui/ui.types.ts b/apps/studio/components/ui/ui.types.ts index 8ede34b7f18..2aee180817c 100644 --- a/apps/studio/components/ui/ui.types.ts +++ b/apps/studio/components/ui/ui.types.ts @@ -6,4 +6,5 @@ export interface Route { icon: ReactNode link?: string disabled?: boolean + linkComponent?: ReactNode } diff --git a/apps/studio/data/database/entity-definitions-query.ts b/apps/studio/data/database/entity-definitions-query.ts index e288f81fa0e..4506ea58ec9 100644 --- a/apps/studio/data/database/entity-definitions-query.ts +++ b/apps/studio/data/database/entity-definitions-query.ts @@ -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), diff --git a/apps/studio/data/database/foreign-key-constraints-query.ts b/apps/studio/data/database/foreign-key-constraints-query.ts index 3c348b11ec2..7c34c7894fd 100644 --- a/apps/studio/data/database/foreign-key-constraints-query.ts +++ b/apps/studio/data/database/foreign-key-constraints-query.ts @@ -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) +} diff --git a/apps/studio/data/database/schemas-query.ts b/apps/studio/data/database/schemas-query.ts index 911f8fc88fa..22e27c76092 100644 --- a/apps/studio/data/database/schemas-query.ts +++ b/apps/studio/data/database/schemas-query.ts @@ -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 = ( 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 + ) + ) +} diff --git a/apps/studio/data/encrypted-columns/encrypted-columns-query.ts b/apps/studio/data/encrypted-columns/encrypted-columns-query.ts new file mode 100644 index 00000000000..e21e2685631 --- /dev/null +++ b/apps/studio/data/encrypted-columns/encrypted-columns-query.ts @@ -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> +export type EncryptedColumnsError = ResponseError + +export const useEncryptedColumnsQuery = ( + { projectRef, connectionString, schema, tableName }: EncryptedColumnsVariables, + { + enabled = true, + ...options + }: UseQueryOptions = {} +) => + useQuery( + 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 + ) + ) +} diff --git a/apps/studio/data/encrypted-columns/keys.ts b/apps/studio/data/encrypted-columns/keys.ts new file mode 100644 index 00000000000..e73d19d7a53 --- /dev/null +++ b/apps/studio/data/encrypted-columns/keys.ts @@ -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, +} diff --git a/apps/studio/data/entity-types/entity-type-query.ts b/apps/studio/data/entity-types/entity-type-query.ts index c650159ce52..b0e26577073 100644 --- a/apps/studio/data/entity-types/entity-type-query.ts +++ b/apps/studio/data/entity-types/entity-type-query.ts @@ -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 = + executeSql( + { + projectRef, + connectionString, + sql: entityTypeSqlQuery({ id }), + queryKey: ['entity-type', id], + }, + signal + ) + ) + .then((data: { result: EntityTypeData[] }) => data?.result?.[0] ?? null) +} diff --git a/apps/studio/data/entity-types/entity-types-infinite-query.ts b/apps/studio/data/entity-types/entity-types-infinite-query.ts index 3931fb892e9..91d1b549bd9 100644 --- a/apps/studio/data/entity-types/entity-types-infinite-query.ts +++ b/apps/studio/data/entity-types/entity-types-infinite-query.ts @@ -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 = ( ...options, } ) + +export function prefetchEntityTypes( + client: QueryClient, + { + projectRef, + connectionString, + schemas = ['public'], + search, + limit = 100, + sort, + filterTypes, + }: Omit +) { + return client.prefetchInfiniteQuery( + entityTypeKeys.list(projectRef, { schemas, search, sort, limit, filterTypes }), + ({ signal, pageParam }) => + getEntityTypes( + { + projectRef, + connectionString, + schemas, + search, + limit, + page: pageParam, + sort, + filterTypes, + }, + signal + ) + ) +} diff --git a/apps/studio/data/foreign-tables/foreign-table-query.ts b/apps/studio/data/foreign-tables/foreign-table-query.ts index d6e3af3748f..cc7370d4ff8 100644 --- a/apps/studio/data/foreign-tables/foreign-table-query.ts +++ b/apps/studio/data/foreign-tables/foreign-table-query.ts @@ -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 = ( ...options, } ) + +export function prefetchForeignTable( + client: QueryClient, + { projectRef, connectionString, id }: ForeignTableVariables +) { + return client.fetchQuery(foreignTableKeys.foreignTable(projectRef, id), ({ signal }) => + getForeignTable({ id, projectRef, connectionString }, signal) + ) +} diff --git a/apps/studio/data/materialized-views/materialized-view-query.ts b/apps/studio/data/materialized-views/materialized-view-query.ts index 0b714667b9b..1d6e2411e24 100644 --- a/apps/studio/data/materialized-views/materialized-view-query.ts +++ b/apps/studio/data/materialized-views/materialized-view-query.ts @@ -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 = ( ...options, } ) + +export function prefetchMaterializedView( + client: QueryClient, + { projectRef, connectionString, id }: MaterializedViewVariables +) { + return client.fetchQuery(materializedViewKeys.materializedView(projectRef, id), ({ signal }) => + getMaterializedView({ id, projectRef, connectionString }, signal) + ) +} diff --git a/apps/studio/data/prefetchers/project.$ref.editor.$id.tsx b/apps/studio/data/prefetchers/project.$ref.editor.$id.tsx new file mode 100644 index 00000000000..ad4ed80b1fe --- /dev/null +++ b/apps/studio/data/prefetchers/project.$ref.editor.$id.tsx @@ -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, + Promise, + Promise, + ( + | Promise + | 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 + +interface EditorTablePageLinkProps extends Omit { + projectRef?: string + id?: string + sorts?: Sort[] + filters?: Filter[] + href?: LinkProps['href'] +} + +export function EditorTablePageLink({ + projectRef, + id, + sorts, + filters, + href, + children, + ...props +}: PropsWithChildren) { + const prefetch = usePrefetchEditorTablePage() + + return ( + prefetch({ id, sorts, filters })} + {...props} + > + {children} + + ) +} diff --git a/apps/studio/data/prefetchers/project.$ref.editor.tsx b/apps/studio/data/prefetchers/project.$ref.editor.tsx new file mode 100644 index 00000000000..e21d6fed797 --- /dev/null +++ b/apps/studio/data/prefetchers/project.$ref.editor.tsx @@ -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 + +interface EditorIndexPageLinkProps extends Omit { + projectRef?: string + href?: LinkProps['href'] +} + +export function EditorIndexPageLink({ + href, + projectRef, + children, + ...props +}: PropsWithChildren) { + const prefetch = usePrefetchEditorIndexPage() + + return ( + + {children} + + ) +} diff --git a/apps/studio/data/table-rows/table-rows-query.ts b/apps/studio/data/table-rows/table-rows-query.ts index dfa8e2b3dc4..39ecb7dce9a 100644 --- a/apps/studio/data/table-rows/table-rows-query.ts +++ b/apps/studio/data/table-rows/table-rows-query.ts @@ -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 = ( { projectRef, connectionString, queryKey, table, impersonatedRole, ...args }: TableRowsVariables, options: UseQueryOptions = {} @@ -218,18 +237,7 @@ export const useTableRowsQuery = ( 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 = ( } ) } + +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 + ) + ) +} diff --git a/apps/studio/data/tables/table-query.ts b/apps/studio/data/tables/table-query.ts index 2e8bea2b5dd..36b5d4195a3 100644 --- a/apps/studio/data/tables/table-query.ts +++ b/apps/studio/data/tables/table-query.ts @@ -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 = ( } ) -/** - * 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) { - const queryClient = useQueryClient() - - return useCallback( - (id: NonNullable) => { - 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) ) } diff --git a/apps/studio/data/views/view-query.ts b/apps/studio/data/views/view-query.ts index ae6d11b6c2d..9fc16cf2299 100644 --- a/apps/studio/data/views/view-query.ts +++ b/apps/studio/data/views/view-query.ts @@ -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 = ( ...options, } ) + +export function prefetchView( + client: QueryClient, + { projectRef, connectionString, id }: ViewVariables +) { + return client.fetchQuery(viewKeys.view(projectRef, id), ({ signal }) => + getView({ id, projectRef, connectionString }, signal) + ) +} diff --git a/apps/studio/hooks/misc/useTable.ts b/apps/studio/hooks/misc/useTable.ts index 17bf4042be1..891d25fe9df 100644 --- a/apps/studio/hooks/misc/useTable.ts +++ b/apps/studio/hooks/misc/useTable.ts @@ -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: diff --git a/apps/studio/pages/project/[ref]/editor/[id].tsx b/apps/studio/pages/project/[ref]/editor/[id].tsx index 69240d08908..9c232bb3883 100644 --- a/apps/studio/pages/project/[ref]/editor/[id].tsx +++ b/apps/studio/pages/project/[ref]/editor/[id].tsx @@ -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`) } }} /> diff --git a/apps/studio/state/table-editor.tsx b/apps/studio/state/table-editor.tsx index a55245d9ba3..05367f0153f 100644 --- a/apps/studio/state/table-editor.tsx +++ b/apps/studio/state/table-editor.tsx @@ -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 @@ -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 },