Select a table from the navigation panel on the left to view its data
@@ -65,4 +63,4 @@ const EmptyState = ({ selectedSchema, onAddTable }: EmptyStateProps) => {
)
}
-export default observer(EmptyState)
+export default EmptyState
diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx
index 3e049682945..ba2a066eb09 100644
--- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx
+++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx
@@ -4,18 +4,19 @@ import type {
PostgresTable,
PostgresType,
} from '@supabase/postgres-meta'
-import { useParams } from 'common'
-import { Dictionary } from 'components/grid'
import { isEmpty, noop } from 'lodash'
import Link from 'next/link'
import { useEffect, useState } from 'react'
-import { Button, Checkbox, IconExternalLink, Input, SidePanel, Toggle } from 'ui'
+import { useParams } from 'common'
+import { Dictionary } from 'components/grid'
import { EncryptionKeySelector } from 'components/interfaces/Settings/Vault'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import { FormSection, FormSectionContent, FormSectionLabel } from 'components/ui/Forms'
import { useForeignKeyConstraintsQuery } from 'data/database/foreign-key-constraints-query'
import { useStore } from 'hooks'
+import { EXCLUDED_SCHEMAS } from 'lib/constants/schemas'
+import { Button, Checkbox, IconExternalLink, Input, SidePanel, Toggle } from 'ui'
import { ForeignKeySelector } from '..'
import ActionBar from '../ActionBar'
import { TEXT_TYPES } from '../SidePanelEditor.constants'
@@ -82,9 +83,7 @@ const ColumnEditor = ({
const foreignKeyMeta = data || []
const keys = vault.listKeys()
- const enumTypes = meta.types.list(
- (type: PostgresType) => !meta.excludedSchemas.includes(type.schema)
- )
+ const enumTypes = meta.types.list((type: PostgresType) => !EXCLUDED_SCHEMAS.includes(type.schema))
const [pgsodiumExtension] = meta.extensions.list(
(ext: PostgresExtension) => ext.name.toLowerCase() === 'pgsodium'
diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx
index df17d3f14e7..ff06e68fd95 100644
--- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx
+++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx
@@ -1,7 +1,13 @@
import type { PostgresColumn, PostgresSchema, PostgresTable } from '@supabase/postgres-meta'
-import { Dictionary } from 'components/grid'
import { find, get, isEmpty, sortBy } from 'lodash'
import { useEffect, useState } from 'react'
+
+import { Dictionary } from 'components/grid'
+import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
+import InformationBox from 'components/ui/InformationBox'
+import { FOREIGN_KEY_CASCADE_ACTION } from 'data/database/database-query-constants'
+import { useSchemasQuery } from 'data/database/schemas-query'
+import { useTablesQuery } from 'data/tables/tables-query'
import {
AlertDescription_Shadcn_,
AlertTitle_Shadcn_,
@@ -13,10 +19,6 @@ import {
Listbox,
SidePanel,
} from 'ui'
-
-import InformationBox from 'components/ui/InformationBox'
-import { FOREIGN_KEY_CASCADE_ACTION } from 'data/database/database-query-constants'
-import { useStore } from 'hooks'
import ActionBar from '../ActionBar'
import { ColumnField } from '../SidePanelEditor.types'
import { FOREIGN_KEY_CASCADE_OPTIONS } from './ForeignKeySelector.constants'
@@ -46,7 +48,7 @@ const ForeignKeySelector = ({
closePanel,
saveChanges,
}: ForeignKeySelectorProps) => {
- const { meta } = useStore()
+ const { project } = useProjectContext()
const [errors, setErrors] = useState({})
const [selectedForeignKey, setSelectedForeignKey] = useState({
schema: 'public',
@@ -56,10 +58,15 @@ const ForeignKeySelector = ({
updateAction: FOREIGN_KEY_CASCADE_ACTION.NO_ACTION,
})
- const schemas = meta.schemas.list()
- const tables = meta.tables.list(
- (table: PostgresTable) => table.schema === selectedForeignKey.schema
- )
+ const { data: schemas } = useSchemasQuery({
+ projectRef: project?.ref,
+ connectionString: project?.connectionString,
+ })
+ const { data: tables } = useTablesQuery({
+ projectRef: project?.ref,
+ connectionString: project?.connectionString,
+ schema: selectedForeignKey.schema,
+ })
const foreignKey = column?.foreignKey
const selectedTable: PostgresTable | undefined = find(tables, {
@@ -70,11 +77,6 @@ const ForeignKeySelector = ({
name: selectedForeignKey?.column,
})
- useEffect(() => {
- // make sure the public schemas are loaded initially
- meta.tables.loadBySchema('public')
- }, [])
-
useEffect(() => {
// Reset the state of the side panel
if (visible) {
@@ -101,7 +103,6 @@ const ForeignKeySelector = ({
}, [visible])
const updateSelectedSchema = (schema: string) => {
- meta.tables.loadBySchema(schema)
const updatedForeignKey = {
schema,
table: '',
@@ -200,8 +201,6 @@ const ForeignKeySelector = ({
customFooter={
updateSelectedSchema(value)}
>
- {schemas.map((schema: PostgresSchema) => {
+ {schemas?.map((schema: PostgresSchema) => {
return (
{
- const { meta } = useStore()
const { project } = useProjectContext()
const {
- target_id: tableId,
+ target_id: _tableId,
target_schema: schemaName,
target_table: tableName,
target_columns: columnName,
} = foreignKey ?? {}
+ const tableId = _tableId ? Number(_tableId) : undefined
- const table = tableId ? meta.tables.byId(tableId) : undefined
-
- useEffect(() => {
- if (!table && tableId) {
- meta.tables.loadById(tableId)
- }
- }, [table, tableId])
+ const { data: table } = useTableQuery({
+ projectRef: project?.ref,
+ connectionString: project?.connectionString,
+ id: tableId,
+ })
const encryptedColumns = useEncryptedColumns({ schemaName, tableName })
diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx
index d84c021d106..0e8e621b338 100644
--- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx
+++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx
@@ -1,23 +1,22 @@
import type { PostgresColumn, PostgresTable } from '@supabase/postgres-meta'
import { QueryKey, useQueryClient } from '@tanstack/react-query'
-import { Dictionary } from 'components/grid'
-import { find, isEmpty, isUndefined, noop } from 'lodash'
+import { isEmpty, isUndefined, noop } from 'lodash'
import { useState } from 'react'
-import { Modal } from 'ui'
+import { Dictionary } from 'components/grid'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import ConfirmationModal from 'components/ui/ConfirmationModal'
import { entityTypeKeys } from 'data/entity-types/keys'
import { sqlKeys } from 'data/sql/keys'
import { useTableRowCreateMutation } from 'data/table-rows/table-row-create-mutation'
import { useTableRowUpdateMutation } from 'data/table-rows/table-row-update-mutation'
+import { tableKeys } from 'data/tables/keys'
import { useStore, useUrlState } from 'hooks'
+import { useTableEditorStateSnapshot } from 'state/table-editor'
+import { Modal } from 'ui'
import { ColumnEditor, RowEditor, SpreadsheetImport, TableEditor } from '.'
-import ForeignRowSelector, {
- ForeignRowSelectorProps,
-} from './RowEditor/ForeignRowSelector/ForeignRowSelector'
+import ForeignRowSelector from './RowEditor/ForeignRowSelector/ForeignRowSelector'
import JsonEdit from './RowEditor/JsonEditor/JsonEditor'
-import { JsonEditValue } from './RowEditor/RowEditor.types'
import {
ColumnField,
CreateColumnPayload,
@@ -27,20 +26,7 @@ import {
import { ImportContent } from './TableEditor/TableEditor.types'
export interface SidePanelEditorProps {
- selectedSchema: string
selectedTable?: PostgresTable
- selectedRowToEdit?: Dictionary
- selectedColumnToEdit?: PostgresColumn
- selectedTableToEdit?: PostgresTable
- selectedValueForJsonEdit?: JsonEditValue
- selectedForeignKeyToEdit?: {
- foreignKey: NonNullable
- row: any
- column: any
- }
- sidePanelKey?: 'row' | 'column' | 'table' | 'json' | 'foreign-row-selector' | 'csv-import'
- isDuplicating?: boolean
- closePanel: () => void
onRowCreated?: (row: Dictionary) => void
onRowUpdated?: (row: Dictionary, idx: number) => void
@@ -51,21 +37,13 @@ export interface SidePanelEditorProps {
}
const SidePanelEditor = ({
- selectedSchema,
selectedTable,
- selectedRowToEdit,
- selectedColumnToEdit,
- selectedTableToEdit,
- selectedValueForJsonEdit,
- selectedForeignKeyToEdit,
- sidePanelKey,
- isDuplicating = false,
- closePanel,
onRowCreated = noop,
onRowUpdated = noop,
onTableCreated = noop,
onColumnSaved = noop,
}: SidePanelEditorProps) => {
+ const snap = useTableEditorStateSnapshot()
const [_, setParams] = useUrlState({ arrayKeys: ['filter', 'sort'] })
const { meta, ui } = useStore()
const queryClient = useQueryClient()
@@ -73,7 +51,6 @@ const SidePanelEditor = ({
const [isEdited, setIsEdited] = useState(false)
const [isClosingPanel, setIsClosingPanel] = useState(false)
- const tables = meta.tables.list()
const enumArrayColumns = (selectedTable?.columns ?? [])
.filter((column) => {
return (column?.enums ?? []).length > 0 && column.data_type.toLowerCase() === 'array'
@@ -84,6 +61,8 @@ const SidePanelEditor = ({
const { mutateAsync: createTableRows } = useTableRowCreateMutation()
const { mutateAsync: updateTableRow } = useTableRowUpdateMutation({
async onMutate({ projectRef, table, configuration, payload }) {
+ snap.closeSidePanel()
+
const primaryKeyColumns = new Set(Object.keys(configuration.identifiers))
const queryKey = sqlKeys.query(projectRef, [
table.schema,
@@ -192,12 +171,13 @@ const SidePanelEditor = ({
onComplete()
if (!saveRowError) {
setIsEdited(false)
- closePanel()
+ snap.closeSidePanel()
}
}
const onSaveJSON = async (value: string | number) => {
- if (selectedTable === undefined || selectedValueForJsonEdit === undefined) return
+ if (selectedTable === undefined || !(snap.sidePanel?.type === 'json')) return
+ const selectedValueForJsonEdit = snap.sidePanel.jsonValue
try {
const { row, column } = selectedValueForJsonEdit
@@ -213,7 +193,8 @@ const SidePanelEditor = ({
}
const onSaveForeignRow = async (value: any) => {
- if (selectedTable === undefined || selectedForeignKeyToEdit === undefined) return
+ if (selectedTable === undefined || !(snap.sidePanel?.type === 'foreign-row-selector')) return
+ const selectedForeignKeyToEdit = snap.sidePanel.foreignKey
try {
const { row, column } = selectedForeignKeyToEdit
@@ -235,6 +216,8 @@ const SidePanelEditor = ({
configuration: { columnId?: string; isEncrypted: boolean; keyId?: string; keyName?: string },
resolve: any
) => {
+ const selectedColumnToEdit = snap.sidePanel?.type === 'column' && snap.sidePanel.column
+
const { columnId, ...securityConfig } = configuration
const response = isNewRecord
? await meta.createColumn(
@@ -261,9 +244,10 @@ const SidePanelEditor = ({
) {
reAddRenamedColumnSortAndFilter(selectedColumnToEdit.name, payload.name)
}
- queryClient.invalidateQueries(sqlKeys.query(project?.ref, ['foreign-key-constraints']))
+
await Promise.all([
- meta.tables.loadById(selectedTable!.id),
+ queryClient.invalidateQueries(sqlKeys.query(project?.ref, ['foreign-key-constraints'])),
+ queryClient.invalidateQueries(tableKeys.table(project?.ref, selectedTable!.id)),
queryClient.invalidateQueries(
sqlKeys.query(project?.ref, [selectedTable!.schema, selectedTable!.name])
),
@@ -278,7 +262,7 @@ const SidePanelEditor = ({
])
onColumnSaved(configuration.isEncrypted)
setIsEdited(false)
- closePanel()
+ snap.closeSidePanel()
}
if (configuration.isEncrypted && selectedTable?.schema) {
@@ -329,8 +313,12 @@ const SidePanelEditor = ({
configuration
try {
- if (isDuplicating) {
- const duplicateTable = find(tables, { id: tableId }) as PostgresTable
+ if (
+ snap.sidePanel?.type === 'table' &&
+ snap.sidePanel.mode === 'duplicate' &&
+ selectedTable
+ ) {
+ const duplicateTable = selectedTable
toastId = ui.setNotification({
category: 'loading',
@@ -344,7 +332,10 @@ const SidePanelEditor = ({
duplicateTable,
})
- await queryClient.invalidateQueries(entityTypeKeys.list(project?.ref))
+ await Promise.all([
+ queryClient.invalidateQueries(tableKeys.list(project?.ref, table.schema)),
+ queryClient.invalidateQueries(entityTypeKeys.list(project?.ref)),
+ ])
ui.setNotification({
id: toastId,
@@ -368,7 +359,10 @@ const SidePanelEditor = ({
importContent
)
- await queryClient.invalidateQueries(entityTypeKeys.list(project?.ref))
+ await Promise.all([
+ queryClient.invalidateQueries(tableKeys.list(project?.ref, table.schema)),
+ queryClient.invalidateQueries(entityTypeKeys.list(project?.ref)),
+ ])
ui.setNotification({
id: toastId,
@@ -377,15 +371,15 @@ const SidePanelEditor = ({
})
onTableCreated(table)
- } else if (selectedTableToEdit) {
+ } else if (selectedTable) {
toastId = ui.setNotification({
category: 'loading',
- message: `Updating table: ${selectedTableToEdit?.name}...`,
+ message: `Updating table: ${selectedTable?.name}...`,
})
const { table, hasError }: any = await meta.updateTable(
toastId,
- selectedTableToEdit,
+ selectedTable,
payload,
columns,
isRealtimeEnabled
@@ -401,16 +395,17 @@ const SidePanelEditor = ({
queryClient.invalidateQueries(sqlKeys.query(project?.ref, ['foreign-key-constraints']))
await Promise.all([
queryClient.invalidateQueries(
- sqlKeys.query(project?.ref, [selectedTableToEdit.schema, selectedTableToEdit.name])
+ sqlKeys.query(project?.ref, [selectedTable.schema, selectedTable.name])
),
queryClient.invalidateQueries(
sqlKeys.query(project?.ref, [
'table-definition',
- selectedTableToEdit.schema,
- selectedTableToEdit.name,
+ selectedTable.schema,
+ selectedTable.name,
])
),
queryClient.invalidateQueries(entityTypeKeys.list(project?.ref)),
+ queryClient.invalidateQueries(tableKeys.table(project?.ref, table.schema)),
])
ui.setNotification({
@@ -427,7 +422,7 @@ const SidePanelEditor = ({
if (!saveTableError) {
setIsEdited(false)
- closePanel()
+ snap.closeSidePanel()
}
resolve()
@@ -507,14 +502,14 @@ const SidePanelEditor = ({
message: `Successfully imported ${rowCount} rows of data into ${selectedTable.name}`,
})
resolve()
- closePanel()
+ snap.closeSidePanel()
}
const onClosePanel = () => {
if (isEdited) {
setIsClosingPanel(true)
} else {
- closePanel()
+ snap.closeSidePanel()
}
}
@@ -522,9 +517,9 @@ const SidePanelEditor = ({
<>
{!isUndefined(selectedTable) && (
setIsEdited(true)}
@@ -532,55 +527,71 @@ const SidePanelEditor = ({
)}
{!isUndefined(selectedTable) && (
setIsEdited(true)}
/>
)}
setIsEdited(true)}
/>
setIsClosingPanel(false)}
onSelectConfirm={() => {
setIsClosingPanel(false)
setIsEdited(false)
- closePanel()
+ snap.closeSidePanel()
}}
>
diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/HeaderTitle.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/HeaderTitle.tsx
index 81e4bb4566c..9c71dcce12c 100644
--- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/HeaderTitle.tsx
+++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/HeaderTitle.tsx
@@ -2,7 +2,7 @@ import type { PostgresTable } from '@supabase/postgres-meta'
interface HeaderTitleProps {
schema: string
- table: PostgresTable
+ table?: { name: string }
isDuplicating: boolean
}
@@ -17,13 +17,13 @@ const HeaderTitle = ({ schema, table, isDuplicating }: HeaderTitleProps) => {
if (isDuplicating) {
return (
<>
- Duplicate table {table.name}
+ Duplicate table {table?.name}
>
)
}
return (
<>
- Update table {table.name}
+ Update table {table?.name}
>
)
}
diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx
index c2b14a957a1..dd97c467436 100644
--- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx
+++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx
@@ -2,12 +2,14 @@ import type { PostgresTable, PostgresType } from '@supabase/postgres-meta'
import { isEmpty, isUndefined, noop } from 'lodash'
import Link from 'next/link'
import { useEffect, useState } from 'react'
-import { Alert, Badge, Button, Checkbox, IconBookOpen, Input, Modal, SidePanel } from 'ui'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import ConfirmationModal from 'components/ui/ConfirmationModal'
import { useForeignKeyConstraintsQuery } from 'data/database/foreign-key-constraints-query'
import { useStore } from 'hooks'
+import { EXCLUDED_SCHEMAS } from 'lib/constants/schemas'
+import { useTableEditorStateSnapshot } from 'state/table-editor'
+import { Alert, Badge, Button, Checkbox, IconBookOpen, Input, Modal, SidePanel } from 'ui'
import { SpreadsheetImport } from '../'
import ActionBar from '../ActionBar'
import { ColumnField, CreateTablePayload, UpdateTablePayload } from '../SidePanelEditor.types'
@@ -23,9 +25,8 @@ import {
validateFields,
} from './TableEditor.utils'
-interface TableEditorProps {
+export interface TableEditorProps {
table?: PostgresTable
- selectedSchema: string
isDuplicating: boolean
visible: boolean
closePanel: () => void
@@ -47,20 +48,18 @@ interface TableEditorProps {
const TableEditor = ({
table,
- selectedSchema,
isDuplicating,
visible = false,
closePanel = noop,
saveChanges = noop,
updateEditorDirty = noop,
}: TableEditorProps) => {
+ const snap = useTableEditorStateSnapshot()
const { ui, meta } = useStore()
const { project } = useProjectContext()
const isNewRecord = isUndefined(table)
- const enumTypes = meta.types.list(
- (type: PostgresType) => !meta.excludedSchemas.includes(type.schema)
- )
+ const enumTypes = meta.types.list((type: PostgresType) => !EXCLUDED_SCHEMAS.includes(type.schema))
const publications = meta.publications.list()
const realtimePublication = publications.find(
@@ -135,7 +134,7 @@ const TableEditor = ({
if (isEmpty(errors)) {
const payload: CreateTablePayload | UpdateTablePayload = {
name: tableFields.name,
- schema: selectedSchema,
+ schema: snap.selectedSchemaName,
comment: tableFields.comment,
...(!isNewRecord && { rls_enabled: tableFields.isRLSEnabled }),
}
@@ -170,8 +169,9 @@ const TableEditor = ({
size="large"
key="TableEditor"
visible={visible}
- // @ts-ignore
- header={ }
+ header={
+
+ }
className={`transition-all duration-100 ease-in ${isImportingSpreadsheet ? ' mr-32' : ''}`}
onCancel={closePanel}
onConfirm={() => (resolve: () => void) => onSaveChanges(resolve)}
@@ -297,7 +297,7 @@ const TableEditor = ({
{!isDuplicating && (
- selectedColumnToEdit?: PostgresColumn
- selectedTableToEdit?: PostgresTable
- selectedValueForJsonEdit?: JsonEditValue
- selectedForeignKeyToEdit?: {
- foreignKey: NonNullable
- row: any
- column: any
- }
-
- onAddRow: () => void
- onEditRow: (row: Dictionary) => void
- onAddColumn: () => void
- onEditColumn: (column: PostgresColumn) => void
- onDeleteColumn: (column: PostgresColumn) => void
- onExpandJSONEditor: (column: string, row: any) => void
- onEditForeignKeyColumnValue: (args: {
- foreignKey: NonNullable
- row: any
- column: any
- }) => void
- onClosePanel: () => void
- onImportData: () => void
+ isLoadingSelectedTable?: boolean
+ selectedTable?: TableLike
}
const TableGridEditor = ({
theme = 'dark',
-
- selectedSchema,
+ isLoadingSelectedTable = false,
selectedTable,
- sidePanelKey,
- isDuplicating,
- selectedRowToEdit,
- selectedColumnToEdit,
- selectedTableToEdit,
- selectedValueForJsonEdit,
- selectedForeignKeyToEdit,
-
- onAddRow = noop,
- onEditRow = noop,
- onAddColumn = noop,
- onEditColumn = noop,
- onDeleteColumn = noop,
- onExpandJSONEditor = noop,
- onEditForeignKeyColumnValue = noop,
- onClosePanel = noop,
- onImportData = noop,
}: TableGridEditorProps) => {
+ const { project } = useProjectContext()
+ const snap = useTableEditorStateSnapshot()
const { meta, ui, vault } = useStore()
const router = useRouter()
const { ref: projectRef, id } = useParams()
const gridRef = useRef(null)
- const { project } = useProjectContext()
const [encryptedColumns, setEncryptedColumns] = useState([])
const [apiPreviewPanelOpen, setApiPreviewPanelOpen] = useState(false)
@@ -201,34 +158,38 @@ const TableGridEditor = ({
}, [selectedTable?.id])
const entityType = useEntityType(selectedTable?.id)
+ const columnsRef = useLatest(selectedTable?.columns ?? EMPTY_ARR)
// NOTE: DO NOT PUT HOOKS AFTER THIS LINE
+ if (isLoadingSelectedTable) {
+ return
+ }
+
if (isUndefined(selectedTable)) {
return
}
- const tableId = selectedTable?.id
-
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 = meta.excludedSchemas.includes(entityType?.schema ?? '')
+ 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 = (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 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 =
!isViewSelected && !isForeignTableSelected
@@ -275,27 +236,31 @@ const TableGridEditor = ({
}
}
- const onSelectEditColumn = async (name: string) => {
- // For some reason, selectedTable here is stale after adding a table
- // temporary workaround is to list grab the selected table again
- const tables: PostgresTable[] = meta.tables.list()
- // @ts-ignore
- const table = tables.find((table) => table.id === Number(tableId))
- const column = find(table!.columns, { name }) as PostgresColumn
+ // columns must be accessed via columnsRef.current as these two functions immediately become
+ // stale as they are accessed via some react-tracked madness
+ // [TODO]: refactor out all of react-tracked
+ const onSelectEditColumn = (name: string) => {
+ const column = find(columnsRef.current, { name }) as PostgresColumn
if (column) {
- onEditColumn(column)
+ snap.onEditColumn(column)
} else {
- console.error(`Unable to find column ${name} in ${table?.name}`)
+ ui.setNotification({
+ category: 'error',
+ message: `Unable to find column ${name} in ${selectedTable?.name}`,
+ })
}
}
- const onSelectDeleteColumn = async (name: string) => {
- // For some reason, selectedTable here is stale after adding a table
- // temporary workaround is to list grab the selected table again
- const tables: PostgresTable[] = meta.tables.list()
- const table = tables.find((table) => table.id === Number(tableId))
- const column = find(table!.columns, { name }) as PostgresColumn
- onDeleteColumn(column)
+ const onSelectDeleteColumn = (name: string) => {
+ const column = find(columnsRef.current ?? [], { name }) as PostgresColumn
+ if (column) {
+ snap.onDeleteColumn(column)
+ } else {
+ ui.setNotification({
+ category: 'error',
+ message: `Unable to find column ${name} in ${selectedTable?.name}`,
+ })
+ }
}
const onError = (error: any) => {
@@ -308,11 +273,14 @@ const TableGridEditor = ({
const updateTableRow = (previousRow: any, updatedData: any) => {
if (!project) return
- const enumArrayColumns = selectedTable.columns
- .filter((column: any) => {
- return (column?.enums ?? []).length > 0 && column.data_type.toLowerCase() === 'array'
- })
- .map((column: any) => column.name)
+ const enumArrayColumns =
+ ('columns' in selectedTable &&
+ selectedTable.columns
+ ?.filter((column) => {
+ return (column?.enums ?? []).length > 0 && column.data_type.toLowerCase() === 'array'
+ })
+ .map((column) => column.name)) ||
+ []
const identifiers = {} as Dictionary
;(selectedTable as PostgresTable).primary_keys.forEach(
@@ -384,17 +352,19 @@ const TableGridEditor = ({
>
) : null
}
- onAddColumn={onAddColumn}
+ onAddColumn={snap.onAddColumn}
onEditColumn={onSelectEditColumn}
onDeleteColumn={onSelectDeleteColumn}
- onAddRow={onAddRow}
+ onAddRow={snap.onAddRow}
updateTableRow={updateTableRow}
- onEditRow={onEditRow}
- onImportData={onImportData}
+ onEditRow={snap.onEditRow}
+ onImportData={snap.onImportData}
onError={onError}
onSqlQuery={onSqlQuery}
- onExpandJSONEditor={onExpandJSONEditor}
- onEditForeignKeyColumnValue={onEditForeignKeyColumnValue}
+ onExpandJSONEditor={(column, row) =>
+ snap.onExpandJSONEditor({ column, row, jsonString: JSON.stringify(row[column]) || '' })
+ }
+ onEditForeignKeyColumnValue={snap.onEditForeignKeyColumnValue}
showCustomChildren={(isViewSelected || isTableSelected) && selectedView === 'definition'}
customHeader={
(isViewSelected || isTableSelected) && selectedView === 'definition' ? (
@@ -410,22 +380,13 @@ const TableGridEditor = ({
{(isViewSelected || isTableSelected) && }
- {!isUndefined(selectedSchema) && (
+ {snap.selectedSchemaName !== undefined && (
)}
diff --git a/studio/components/layouts/AuthLayout/AuthLayout.tsx b/studio/components/layouts/AuthLayout/AuthLayout.tsx
index dd060084a53..2dd7e989901 100644
--- a/studio/components/layouts/AuthLayout/AuthLayout.tsx
+++ b/studio/components/layouts/AuthLayout/AuthLayout.tsx
@@ -1,9 +1,8 @@
import { observer } from 'mobx-react-lite'
import { useRouter } from 'next/router'
-import { PropsWithChildren, useEffect, useState } from 'react'
+import { PropsWithChildren, useEffect } from 'react'
import { useParams } from 'common'
-import Error from 'components/ui/Error'
import ProductMenu from 'components/ui/ProductMenu'
import { useAuthConfigPrefetch } from 'data/auth/auth-config-query'
import { useStore, withAuth } from 'hooks'
@@ -15,45 +14,26 @@ export interface AuthLayoutProps {
}
const AuthLayout = ({ title, children }: PropsWithChildren) => {
- const { ui, meta } = useStore()
- const { isInitialized, isLoading, error } = meta.tables
const { ref: projectRef = 'default' } = useParams()
+ const { ui, meta } = useStore()
+
useAuthConfigPrefetch({ projectRef })
const router = useRouter()
const page = router.pathname.split('/')[4]
- const [loaded, setLoaded] = useState(isInitialized)
-
useEffect(() => {
if (ui.selectedProjectRef) {
meta.policies.load()
- meta.tables.load()
meta.roles.load()
- meta.schemas.load()
}
}, [ui.selectedProjectRef])
- useEffect(() => {
- if (!isLoading && !loaded) {
- setLoaded(true)
- }
- }, [isLoading])
-
- if (error) {
- return (
-
-
-
- )
- }
-
return (
}
+ productMenu={ }
>
{children}
diff --git a/studio/components/layouts/DatabaseLayout/DatabaseLayout.tsx b/studio/components/layouts/DatabaseLayout/DatabaseLayout.tsx
index 68d87ffa499..d5d33ad212e 100644
--- a/studio/components/layouts/DatabaseLayout/DatabaseLayout.tsx
+++ b/studio/components/layouts/DatabaseLayout/DatabaseLayout.tsx
@@ -1,8 +1,7 @@
import { observer } from 'mobx-react-lite'
import { useRouter } from 'next/router'
-import { PropsWithChildren, useEffect, useState } from 'react'
+import { PropsWithChildren, useEffect } from 'react'
-import Error from 'components/ui/Error'
import ProductMenu from 'components/ui/ProductMenu'
import { useSelectedProject, useStore, withAuth } from 'hooks'
import ProjectLayout from '../'
@@ -14,10 +13,6 @@ export interface DatabaseLayoutProps {
const DatabaseLayout = ({ children }: PropsWithChildren) => {
const { ui, meta, vault } = useStore()
- const { isLoading: isSchemasLoading } = meta.schemas
- const { isLoading: isVaultLoading } = vault
-
- const { isInitialized, error } = meta.tables
const project = useSelectedProject()
const router = useRouter()
@@ -27,15 +22,8 @@ const DatabaseLayout = ({ children }: PropsWithChildren) =>
const isVaultEnabled = vaultExtension !== undefined && vaultExtension.installed_version !== null
const pgNetExtensionExists = meta.extensions.byId('pg_net') !== undefined
- const isLoading = isSchemasLoading || (isVaultEnabled && isVaultLoading)
- const [loaded, setLoaded] = useState(isInitialized)
-
useEffect(() => {
if (ui.selectedProjectRef) {
- // Eventually should only load the required stores based on the pages
- meta.schemas.load()
- meta.tables.load()
-
meta.roles.load()
meta.triggers.load()
meta.extensions.load()
@@ -49,25 +37,8 @@ const DatabaseLayout = ({ children }: PropsWithChildren) =>
}
}, [ui.selectedProjectRef, isVaultEnabled])
- // Optimization required: load logic should be at the page level
- // e.g backups page is waiting for meta.tables to load finish when it doesnt even need that data
- useEffect(() => {
- if (!isLoading && !loaded) {
- setLoaded(true)
- }
- }, [isLoading])
-
- if (error) {
- return (
-
-
-
- )
- }
-
return (
diff --git a/studio/components/layouts/ProjectLayout/ProjectContext.tsx b/studio/components/layouts/ProjectLayout/ProjectContext.tsx
index b06457c84fe..c9d06b45b55 100644
--- a/studio/components/layouts/ProjectLayout/ProjectContext.tsx
+++ b/studio/components/layouts/ProjectLayout/ProjectContext.tsx
@@ -1,10 +1,12 @@
+import { createContext, PropsWithChildren, useContext, useMemo } from 'react'
+
import { useParams } from 'common/hooks'
import { useProjectDetailQuery } from 'data/projects/project-detail-query'
import { PROJECT_STATUS } from 'lib/constants'
-import { PropsWithChildren, createContext, useContext, useMemo } from 'react'
+import { TableEditorStateContextProvider } from 'state/table-editor'
import { Project } from 'types'
-type ProjectContextType = {
+export interface ProjectContextType {
project: Project | undefined
isLoading: boolean
}
@@ -35,7 +37,13 @@ export const ProjectContextProvider = ({
}
}, [selectedProject, isLoading])
- return {children}
+ return (
+
+
+ {children}
+
+
+ )
}
export const ProjectContextFromParamsProvider = ({ children }: PropsWithChildren<{}>) => {
diff --git a/studio/components/layouts/TableEditorLayout/EntityListItem.tsx b/studio/components/layouts/TableEditorLayout/EntityListItem.tsx
index 2e0d5fdeb19..2af073982ed 100644
--- a/studio/components/layouts/TableEditorLayout/EntityListItem.tsx
+++ b/studio/components/layouts/TableEditorLayout/EntityListItem.tsx
@@ -6,28 +6,17 @@ import { Entity } from 'data/entity-types/entity-type-query'
import Link from 'next/link'
import { Dropdown, IconEdit, IconCopy, IconLock, IconTrash, IconChevronDown } from 'ui'
import { BASE_PATH } from 'lib/constants'
+import { useTableEditorStateSnapshot } from 'state/table-editor'
export interface EntityListItemProps {
id: number
projectRef: string
item: Entity
isLocked: boolean
- onEditTable: (table: Entity) => void
- onDeleteTable: (table: Entity) => void
- onDuplicateTable: (table: Entity) => void
- isLoadingTableMetadata?: boolean
}
-const EntityListItem = ({
- id,
- projectRef,
- item: entity,
- isLocked,
- onEditTable,
- onDeleteTable,
- onDuplicateTable,
- isLoadingTableMetadata,
-}: EntityListItemProps) => {
+const EntityListItem = ({ id, projectRef, item: entity, isLocked }: EntityListItemProps) => {
+ const snap = useTableEditorStateSnapshot()
const isActive = Number(id) === entity.id
const formatTooltipText = (entityType: string) => {
return Object.entries(ENTITY_TYPE)
@@ -137,9 +126,8 @@ const EntityListItem = ({
icon={ }
onClick={(e) => {
e.stopPropagation()
- onEditTable(entity)
+ snap.onEditTable()
}}
- disabled={isLoadingTableMetadata}
>
Edit Table
,
@@ -148,9 +136,8 @@ const EntityListItem = ({
icon={ }
onClick={(e) => {
e.stopPropagation()
- onDuplicateTable(entity)
+ snap.onDuplicateTable()
}}
- disabled={isLoadingTableMetadata}
>
Duplicate Table
,
@@ -170,9 +157,8 @@ const EntityListItem = ({
icon={ }
onClick={(e) => {
e.stopPropagation()
- onDeleteTable(entity)
+ snap.onDeleteTable()
}}
- disabled={isLoadingTableMetadata}
>
Delete Table
,
diff --git a/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx b/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx
index 2cf50e4643f..bccfbc672bb 100644
--- a/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx
+++ b/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx
@@ -1,49 +1,15 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
-import { noop } from 'lodash'
import { observer } from 'mobx-react-lite'
-import { useRouter } from 'next/router'
import { PropsWithChildren, useEffect } from 'react'
-import { useParams } from 'common/hooks'
-import Connecting from 'components/ui/Loading/Loading'
import NoPermission from 'components/ui/NoPermission'
-import { ENTITY_TYPE } from 'data/entity-types/entity-type-constants'
-import { Entity } from 'data/entity-types/entity-type-query'
-import { useCheckPermissions, useSelectedProject, useStore } from 'hooks'
-import useEntityType from 'hooks/misc/useEntityType'
-import useLatest from 'hooks/misc/useLatest'
-import { useIsTableLoaded, useTableEditorStateSnapshot } from 'state/table-editor'
-import ProjectLayout from '../'
-import useTableRowsPrefetchWrapper from './TableEditorLayout.utils'
+import { useCheckPermissions, useStore } from 'hooks'
+import { ProjectLayoutWithAuth } from '../'
import TableEditorMenu from './TableEditorMenu'
-export interface TableEditorLayoutProps {
- selectedSchema?: string
- selectedTable?: string
- onSelectSchema: (schema: string) => void
- onAddTable: () => void
- onEditTable: (table: Entity) => void
- onDeleteTable: (table: Entity) => void
- onDuplicateTable: (table: Entity) => void
-}
+const TableEditorLayout = ({ children }: PropsWithChildren<{}>) => {
+ const { vault, meta, ui } = useStore()
-const TableEditorLayout = ({
- selectedSchema,
- selectedTable,
- onSelectSchema = noop,
- onAddTable = noop,
- onEditTable = noop,
- onDeleteTable = noop,
- onDuplicateTable = noop,
- children,
-}: PropsWithChildren) => {
- const { ui, vault, meta } = useStore()
- const selectedProject = useSelectedProject()
- const router = useRouter()
- const { ref, id: _id } = useParams()
- const id = _id ? Number(_id) : undefined
-
- const snap = useTableEditorStateSnapshot()
const canReadTables = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_READ, 'tables')
const vaultExtension = meta.extensions.byId('supabase_vault')
@@ -51,7 +17,6 @@ const TableEditorLayout = ({
useEffect(() => {
if (ui.selectedProjectRef) {
- meta.schemas.load()
meta.types.load()
meta.policies.load()
meta.publications.load()
@@ -59,87 +24,24 @@ const TableEditorLayout = ({
}
}, [ui.selectedProjectRef])
- const isLoaded = useIsTableLoaded(ref, id)
-
- const entity = useEntityType(id, function onNotFound(id) {
- if (ref) snap.addLoadedId(ref, id)
- })
-
- const prefetch = useLatest(useTableRowsPrefetchWrapper())
-
- useEffect(() => {
- let mounted = true
-
- function loadTable() {
- if (entity?.type) {
- switch (entity.type) {
- case ENTITY_TYPE.MATERIALIZED_VIEW:
- return meta.materializedViews.loadById(entity.id)
-
- case ENTITY_TYPE.VIEW:
- return meta.views.loadById(entity.id)
-
- case ENTITY_TYPE.FOREIGN_TABLE:
- return meta.foreignTables.loadById(entity.id)
-
- default:
- return meta.tables.loadById(entity.id)
- }
- }
- }
-
- loadTable()
- ?.then(async (entity: any) => {
- await prefetch.current(entity)
- return entity
- })
- .then((entity: any) => {
- if (mounted && ref) {
- snap.addLoadedId(ref, entity.id)
- }
- })
- .catch(() => {
- if (mounted && entity?.id && ref) {
- snap.addLoadedId(ref, entity.id)
- }
- })
-
- return () => {
- mounted = false
- }
- }, [entity?.id])
-
useEffect(() => {
if (isVaultEnabled) {
vault.load()
}
- }, [selectedProject?.ref, isVaultEnabled])
+ }, [ui.selectedProjectRef, isVaultEnabled])
if (!canReadTables) {
return (
-
+
-
+
)
}
return (
-
- }
- >
- {router.isReady && id !== undefined ? isLoaded ? children : : children}
-
+ }>
+ {children}
+
)
}
diff --git a/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx b/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx
index 7e7190a73e9..ffe27f2fa52 100644
--- a/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx
+++ b/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx
@@ -1,6 +1,15 @@
+import * as Tooltip from '@radix-ui/react-tooltip'
+import { PermissionAction } from '@supabase/shared-types/out/constants'
+import { partition } from 'lodash'
import { useMemo, useState } from 'react'
-import { noop, partition } from 'lodash'
-import { observer } from 'mobx-react-lite'
+
+import { useParams } from 'common/hooks'
+import InfiniteList from 'components/ui/InfiniteList'
+import { useSchemasQuery } from 'data/database/schemas-query'
+import { useEntityTypesQuery } from 'data/entity-types/entity-types-infinite-query'
+import { useCheckPermissions, useLocalStorage } from 'hooks'
+import { EXCLUDED_SCHEMAS } from 'lib/constants/schemas'
+import { useTableEditorStateSnapshot } from 'state/table-editor'
import {
Alert,
Button,
@@ -16,37 +25,12 @@ import {
Listbox,
Menu,
} from 'ui'
-import * as Tooltip from '@radix-ui/react-tooltip'
-import { PermissionAction } from '@supabase/shared-types/out/constants'
-import type { PostgresSchema } from '@supabase/postgres-meta'
-
-import { useParams } from 'common/hooks'
-import { useCheckPermissions, useStore, useLocalStorage } from 'hooks'
-import { useEntityTypesQuery } from 'data/entity-types/entity-types-infinite-query'
-import { Entity } from 'data/entity-types/entity-type-query'
import { useProjectContext } from '../ProjectLayout/ProjectContext'
-import InfiniteList from 'components/ui/InfiniteList'
import EntityListItem from './EntityListItem'
-export interface TableEditorMenuProps {
- selectedSchema?: string
- onSelectSchema: (schema: string) => void
- onAddTable: () => void
- onEditTable: (table: Entity) => void
- onDeleteTable: (table: Entity) => void
- onDuplicateTable: (table: Entity) => void
-}
-
-const TableEditorMenu = ({
- selectedSchema,
- onSelectSchema = noop,
- onAddTable = noop,
- onEditTable = noop,
- onDeleteTable = noop,
- onDuplicateTable = noop,
-}: TableEditorMenuProps) => {
- const { meta } = useStore()
+const TableEditorMenu = () => {
const { id } = useParams()
+ const snap = useTableEditorStateSnapshot()
const [searchText, setSearchText] = useState('')
const [sort, setSort] = useLocalStorage<'alphabetical' | 'grouped-alphabetical'>(
@@ -68,7 +52,7 @@ const TableEditorMenu = ({
{
projectRef: project?.ref,
connectionString: project?.connectionString,
- schema: selectedSchema,
+ schema: snap.selectedSchemaName,
search: searchText || undefined,
sort,
},
@@ -83,18 +67,27 @@ const TableEditorMenu = ({
[data?.pages]
)
- const schemas: PostgresSchema[] = meta.schemas.list()
- const schema = schemas.find((schema) => schema.name === selectedSchema)
- const canCreateTables = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables')
+ const {
+ data: schemas,
+ isLoading: isSchemasLoading,
+ isSuccess: isSchemasSuccess,
+ isError: isSchemasError,
+ error: schemasError,
+ refetch: refetchSchemas,
+ } = useSchemasQuery({
+ projectRef: project?.ref,
+ connectionString: project?.connectionString,
+ })
- const isLoadingTableMetadata = id ? !meta.tables.byId(id) : true
+ const schema = schemas?.find((schema) => schema.name === snap.selectedSchemaName)
+ const canCreateTables = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables')
const refreshTables = async () => {
await refetch()
}
const [protectedSchemas, openSchemas] = partition(schemas, (schema) =>
- meta.excludedSchemas.includes(schema?.name ?? '')
+ EXCLUDED_SCHEMAS.includes(schema?.name ?? '')
)
const isLocked = protectedSchemas.some((s) => s.id === schema?.id)
@@ -105,25 +98,29 @@ const TableEditorMenu = ({
>
{/* Schema selection dropdown */}
- {!meta.schemas.isInitialized ? (
+ {isSchemasLoading && (
Loading schemas...
- ) : meta.schemas.hasError ? (
+ )}
+
+ {isSchemasError && (
- Error: {meta.schemas?.error?.message}
- meta.schemas.load()}>
+ Error: {schemasError.message}
+ refetchSchemas()}>
Reload schemas
- ) : (
+ )}
+
+ {isSchemasSuccess && (
{
setSearchText('')
- onSelectSchema(name)
+ snap.setSelectedSchemaName(name)
}}
>
New table
@@ -352,10 +349,6 @@ const TableEditorMenu = ({
itemProps={{
projectRef: project?.ref,
id: Number(id),
- onEditTable,
- onDeleteTable,
- onDuplicateTable,
- isLoadingTableMetadata,
}}
getItemSize={() => 28}
hasNextPage={hasNextPage}
@@ -369,4 +362,4 @@ const TableEditorMenu = ({
)
}
-export default observer(TableEditorMenu)
+export default TableEditorMenu
diff --git a/studio/data/database/keys.ts b/studio/data/database/keys.ts
index 4d75d55bee4..dbaccaee8f9 100644
--- a/studio/data/database/keys.ts
+++ b/studio/data/database/keys.ts
@@ -1,4 +1,5 @@
export const databaseKeys = {
+ schemaList: (projectRef: string | undefined) => ['projects', projectRef, 'schemas'] as const,
backups: (projectRef: string | undefined) => [projectRef, 'database', 'backups'] as const,
poolingConfiguration: (projectRef: string | undefined) =>
[projectRef, 'database', 'pooling-configuration'] as const,
diff --git a/studio/data/database/schemas-query.ts b/studio/data/database/schemas-query.ts
index 896dd2df016..1d90cf9f95a 100644
--- a/studio/data/database/schemas-query.ts
+++ b/studio/data/database/schemas-query.ts
@@ -1,61 +1,61 @@
-import { useCallback } from 'react'
-import { UseQueryOptions } from '@tanstack/react-query'
-import { ExecuteSqlData, useExecuteSqlPrefetch, useExecuteSqlQuery } from '../sql/execute-sql-query'
+import { QueryClient, useQuery, UseQueryOptions } from '@tanstack/react-query'
+
+import { components } from 'data/api'
+import { get } from 'data/fetchers'
import { ResponseError } from 'types'
-
-export type Schema = {
- name: string
-}
-
-export const getSchemasQuery = () => {
- const sql = /* SQL */ `
- SELECT nspname as name
- FROM pg_namespace
- WHERE
- nspname not in ('information_schema', 'pg_catalog', 'pg_toast')
- AND nspname not like 'pg_temp_%'
- AND nspname not like 'pg_toast_temp_%'
- AND has_schema_privilege(oid, 'CREATE, USAGE')
- ORDER BY nspname;
-`.trim()
-
- return sql
-}
+import { databaseKeys } from './keys'
export type SchemasVariables = {
projectRef?: string
connectionString?: string
}
-export type SchemasData = { result: Schema[] }
-export type SchemasError = unknown
+export type Schema = components['schemas']['PostgresSchema']
-export const useSchemasQuery = (
+export async function getSchemas(
{ projectRef, connectionString }: SchemasVariables,
- options: UseQueryOptions = {}
-) => {
- return useExecuteSqlQuery(
- {
- projectRef,
- connectionString,
- sql: getSchemasQuery(),
- queryKey: ['schemas'],
+ signal?: AbortSignal
+) {
+ if (!projectRef) {
+ throw new Error('projectRef is required')
+ }
+
+ let headers = new Headers()
+ if (connectionString) headers.set('x-connection-encrypted', connectionString)
+
+ const { data, error } = await get('/platform/pg-meta/{ref}/schemas', {
+ params: {
+ header: {
+ 'x-connection-encrypted': connectionString!,
+ },
+ path: {
+ ref: projectRef,
+ },
},
- options
- )
+ headers: Object.fromEntries(headers),
+ signal,
+ })
+
+ if (error) {
+ throw error
+ }
+
+ return data
}
-export const useSchemasPrefetch = () => {
- const prefetch = useExecuteSqlPrefetch()
+export type SchemasData = Awaited>
+export type SchemasError = ResponseError
- return useCallback(
- ({ projectRef, connectionString }: SchemasVariables) =>
- prefetch({
- projectRef,
- connectionString,
- sql: getSchemasQuery(),
- queryKey: ['schemas'],
- }),
- [prefetch]
+export const useSchemasQuery = (
+ { projectRef, connectionString }: SchemasVariables,
+ { enabled = true, ...options }: UseQueryOptions = {}
+) =>
+ useQuery(
+ databaseKeys.schemaList(projectRef),
+ ({ signal }) => getSchemas({ projectRef, connectionString }, signal),
+ { enabled: enabled && typeof projectRef !== 'undefined', ...options }
)
+
+export function invalidateSchemasQuery(client: QueryClient, projectRef: string | undefined) {
+ return client.invalidateQueries(databaseKeys.schemaList(projectRef))
}
diff --git a/studio/data/foreign-tables/foreign-table-query.ts b/studio/data/foreign-tables/foreign-table-query.ts
new file mode 100644
index 00000000000..b10d74e0928
--- /dev/null
+++ b/studio/data/foreign-tables/foreign-table-query.ts
@@ -0,0 +1,57 @@
+import { PostgresTable } from '@supabase/postgres-meta'
+import { useQuery, UseQueryOptions } from '@tanstack/react-query'
+import { get } from 'lib/common/fetch'
+import { API_URL } from 'lib/constants'
+import { foreignTableKeys } from './keys'
+
+export type ForeignTableVariables = {
+ id?: number
+ projectRef?: string
+ connectionString?: string
+}
+
+export type ForeignTable = PostgresTable
+
+export type ForeignTableResponse = ForeignTable | { error?: any }
+
+export async function getForeignTable(
+ { id, projectRef, connectionString }: ForeignTableVariables,
+ signal?: AbortSignal
+) {
+ if (!projectRef) {
+ throw new Error('projectRef is required')
+ }
+ if (!id) {
+ throw new Error('id is required')
+ }
+
+ let headers = new Headers()
+ if (connectionString) headers.set('x-connection-encrypted', connectionString)
+
+ const response = (await get(`${API_URL}/pg-meta/${projectRef}/foreign-tables?id=${id}`, {
+ headers: Object.fromEntries(headers),
+ signal,
+ })) as ForeignTableResponse
+
+ if ('error' in response) {
+ throw response.error
+ }
+
+ return response as PostgresTable
+}
+
+export type ForeignTableData = Awaited>
+export type ForeignTableError = unknown
+
+export const useForeignTableQuery = (
+ { projectRef, id }: ForeignTableVariables,
+ { enabled = true, ...options }: UseQueryOptions = {}
+) =>
+ useQuery(
+ foreignTableKeys.foreignTable(projectRef, id),
+ ({ signal }) => getForeignTable({ projectRef, id }, signal),
+ {
+ enabled: enabled && typeof projectRef !== 'undefined' && typeof id !== 'undefined',
+ ...options,
+ }
+ )
diff --git a/studio/data/foreign-tables/foreign-tables-query.ts b/studio/data/foreign-tables/foreign-tables-query.ts
new file mode 100644
index 00000000000..99ce73a2a0f
--- /dev/null
+++ b/studio/data/foreign-tables/foreign-tables-query.ts
@@ -0,0 +1,49 @@
+import { PostgresTable } from '@supabase/postgres-meta'
+import { useQuery, UseQueryOptions } from '@tanstack/react-query'
+import { get } from 'lib/common/fetch'
+import { API_URL } from 'lib/constants'
+import { ForeignTable } from './foreign-table-query'
+import { foreignTableKeys } from './keys'
+
+export type ForeignTablesVariables = {
+ projectRef?: string
+ connectionString?: string
+}
+
+export type ForeignTablesResponse = ForeignTable[] | { error?: any }
+
+export async function getForeignTables(
+ { projectRef, connectionString }: ForeignTablesVariables,
+ signal?: AbortSignal
+) {
+ if (!projectRef) {
+ throw new Error('projectRef is required')
+ }
+
+ let headers = new Headers()
+ if (connectionString) headers.set('x-connection-encrypted', connectionString)
+
+ const response = (await get(`${API_URL}/pg-meta/${projectRef}/foreign-tables`, {
+ headers: Object.fromEntries(headers),
+ signal,
+ })) as ForeignTablesResponse
+
+ if (!Array.isArray(response) && response.error) {
+ throw response.error
+ }
+
+ return response as PostgresTable[]
+}
+
+export type ForeignTablesData = Awaited>
+export type ForeignTablesError = unknown
+
+export const useForeignTablesQuery = (
+ { projectRef, connectionString }: ForeignTablesVariables,
+ { enabled = true, ...options }: UseQueryOptions = {}
+) =>
+ useQuery(
+ foreignTableKeys.list(projectRef),
+ ({ signal }) => getForeignTables({ projectRef, connectionString }, signal),
+ { enabled: enabled && typeof projectRef !== 'undefined', ...options }
+ )
diff --git a/studio/data/foreign-tables/keys.ts b/studio/data/foreign-tables/keys.ts
new file mode 100644
index 00000000000..869cd2ccfad
--- /dev/null
+++ b/studio/data/foreign-tables/keys.ts
@@ -0,0 +1,5 @@
+export const foreignTableKeys = {
+ list: (projectRef: string | undefined) => ['projects', projectRef, 'foreignTables'] as const,
+ foreignTable: (projectRef: string | undefined, id: number | undefined) =>
+ ['projects', projectRef, 'foreignTables', id] as const,
+}
diff --git a/studio/data/materialized-views/keys.ts b/studio/data/materialized-views/keys.ts
new file mode 100644
index 00000000000..6e34f29b960
--- /dev/null
+++ b/studio/data/materialized-views/keys.ts
@@ -0,0 +1,5 @@
+export const materializedViewKeys = {
+ list: (projectRef: string | undefined) => ['projects', projectRef, 'materializedViews'] as const,
+ materializedView: (projectRef: string | undefined, id: number | undefined) =>
+ ['projects', projectRef, 'materializedViews', id] as const,
+}
diff --git a/studio/data/materialized-views/materialized-view-query.ts b/studio/data/materialized-views/materialized-view-query.ts
new file mode 100644
index 00000000000..6efd95ded42
--- /dev/null
+++ b/studio/data/materialized-views/materialized-view-query.ts
@@ -0,0 +1,60 @@
+import { PostgresMaterializedView } from '@supabase/postgres-meta'
+import { useQuery, UseQueryOptions } from '@tanstack/react-query'
+import { get } from 'lib/common/fetch'
+import { API_URL } from 'lib/constants'
+import { materializedViewKeys } from './keys'
+
+export type MaterializedViewVariables = {
+ id?: number
+ projectRef?: string
+ connectionString?: string
+}
+
+export type MaterializedView = PostgresMaterializedView
+
+export type MaterializedViewResponse = MaterializedView | { error?: any }
+
+export async function getMaterializedView(
+ { id, projectRef, connectionString }: MaterializedViewVariables,
+ signal?: AbortSignal
+) {
+ if (!projectRef) {
+ throw new Error('projectRef is required')
+ }
+ if (!id) {
+ throw new Error('id is required')
+ }
+
+ let headers = new Headers()
+ if (connectionString) headers.set('x-connection-encrypted', connectionString)
+
+ const response = (await get(`${API_URL}/pg-meta/${projectRef}/materialized-views?id=${id}`, {
+ headers: Object.fromEntries(headers),
+ signal,
+ })) as MaterializedViewResponse
+
+ if ('error' in response) {
+ throw response.error
+ }
+
+ return response as PostgresMaterializedView
+}
+
+export type MaterializedViewData = Awaited>
+export type MaterializedViewError = unknown
+
+export const useMaterializedViewQuery = (
+ { projectRef, connectionString, id }: MaterializedViewVariables,
+ {
+ enabled = true,
+ ...options
+ }: UseQueryOptions = {}
+) =>
+ useQuery(
+ materializedViewKeys.materializedView(projectRef, id),
+ ({ signal }) => getMaterializedView({ projectRef, connectionString, id }, signal),
+ {
+ enabled: enabled && typeof projectRef !== 'undefined' && typeof id !== 'undefined',
+ ...options,
+ }
+ )
diff --git a/studio/data/materialized-views/materialized-views-query.ts b/studio/data/materialized-views/materialized-views-query.ts
new file mode 100644
index 00000000000..5f84c001834
--- /dev/null
+++ b/studio/data/materialized-views/materialized-views-query.ts
@@ -0,0 +1,52 @@
+import { PostgresMaterializedView } from '@supabase/postgres-meta'
+import { useQuery, UseQueryOptions } from '@tanstack/react-query'
+import { get } from 'lib/common/fetch'
+import { API_URL } from 'lib/constants'
+import { materializedViewKeys } from './keys'
+import { MaterializedView } from './materialized-view-query'
+
+export type MaterializedViewsVariables = {
+ projectRef?: string
+ connectionString?: string
+}
+
+export type MaterializedViewsResponse = MaterializedView[] | { error?: any }
+
+export async function getMaterializedViews(
+ { projectRef, connectionString }: MaterializedViewsVariables,
+ signal?: AbortSignal
+) {
+ if (!projectRef) {
+ throw new Error('projectRef is required')
+ }
+
+ let headers = new Headers()
+ if (connectionString) headers.set('x-connection-encrypted', connectionString)
+
+ const response = (await get(`${API_URL}/pg-meta/${projectRef}/materialized-views`, {
+ headers: Object.fromEntries(headers),
+ signal,
+ })) as MaterializedViewsResponse
+
+ if (!Array.isArray(response) && response.error) {
+ throw response.error
+ }
+
+ return response as PostgresMaterializedView[]
+}
+
+export type MaterializedViewsData = Awaited>
+export type MaterializedViewsError = unknown
+
+export const useMaterializedViewsQuery = (
+ { projectRef, connectionString }: MaterializedViewsVariables,
+ {
+ enabled = true,
+ ...options
+ }: UseQueryOptions = {}
+) =>
+ useQuery(
+ materializedViewKeys.list(projectRef),
+ ({ signal }) => getMaterializedViews({ projectRef, connectionString }, signal),
+ { enabled: enabled && typeof projectRef !== 'undefined', ...options }
+ )
diff --git a/studio/data/projects/project-detail-query.ts b/studio/data/projects/project-detail-query.ts
index cab2475d06c..282c5d5d546 100644
--- a/studio/data/projects/project-detail-query.ts
+++ b/studio/data/projects/project-detail-query.ts
@@ -35,3 +35,16 @@ export const useProjectDetailQuery = (
export function invalidateProjectDetailsQuery(client: QueryClient, ref: string) {
return client.invalidateQueries(projectKeys.detail(ref))
}
+
+// get the cached value or fallback to fetching it
+export async function getCachedProjectDetail(
+ client: QueryClient,
+ ref: string | undefined
+): Promise {
+ if (!ref) return undefined
+
+ const cached = client.getQueryData(projectKeys.detail(ref))
+ if (cached) return cached
+
+ return await client.fetchQuery(projectKeys.detail(ref))
+}
diff --git a/studio/data/tables/keys.ts b/studio/data/tables/keys.ts
new file mode 100644
index 00000000000..2947ecc11f8
--- /dev/null
+++ b/studio/data/tables/keys.ts
@@ -0,0 +1,6 @@
+export const tableKeys = {
+ list: (projectRef: string | undefined, schema: string | undefined) =>
+ ['projects', projectRef, 'tables', { schema }] as const,
+ table: (projectRef: string | undefined, id: number | undefined) =>
+ ['projects', projectRef, 'tables', id] as const,
+}
diff --git a/studio/data/tables/table-query.ts b/studio/data/tables/table-query.ts
new file mode 100644
index 00000000000..eaa7a417e5b
--- /dev/null
+++ b/studio/data/tables/table-query.ts
@@ -0,0 +1,79 @@
+import { PostgresTable } from '@supabase/postgres-meta'
+import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query'
+import { get } from 'lib/common/fetch'
+import { API_URL } from 'lib/constants'
+import { tableKeys } from './keys'
+import { useCallback } from 'react'
+
+export type TableVariables = {
+ id?: number
+ projectRef?: string
+ connectionString?: string
+}
+
+export type Table = PostgresTable
+
+export type TableResponse = Table | { error?: any }
+
+export async function getTable(
+ { id, projectRef, connectionString }: TableVariables,
+ signal?: AbortSignal
+) {
+ if (!projectRef) {
+ throw new Error('projectRef is required')
+ }
+ if (!id) {
+ throw new Error('id is required')
+ }
+
+ let headers = new Headers()
+ if (connectionString) headers.set('x-connection-encrypted', connectionString)
+
+ const response = (await get(`${API_URL}/pg-meta/${projectRef}/tables?id=${id}`, {
+ headers: Object.fromEntries(headers),
+ signal,
+ })) as TableResponse
+
+ if ('error' in response) {
+ throw response.error
+ }
+
+ return response as PostgresTable
+}
+
+export type TableData = Awaited>
+export type TableError = unknown
+
+export const useTableQuery = (
+ { projectRef, connectionString, id }: TableVariables,
+ { enabled = true, ...options }: UseQueryOptions = {}
+) =>
+ useQuery(
+ tableKeys.table(projectRef, id),
+ ({ signal }) => getTable({ projectRef, connectionString, id }, signal),
+ {
+ enabled: enabled && typeof projectRef !== 'undefined' && typeof id !== 'undefined',
+ ...options,
+ }
+ )
+
+/**
+ * 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]
+ )
+}
diff --git a/studio/data/tables/tables-query.ts b/studio/data/tables/tables-query.ts
new file mode 100644
index 00000000000..eefe5c92879
--- /dev/null
+++ b/studio/data/tables/tables-query.ts
@@ -0,0 +1,80 @@
+import { PostgresTable } from '@supabase/postgres-meta'
+import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query'
+import { get } from 'lib/common/fetch'
+import { API_URL } from 'lib/constants'
+import { useCallback } from 'react'
+import { ResponseError } from 'types'
+import { tableKeys } from './keys'
+import { Table } from './table-query'
+
+export type TablesVariables = {
+ projectRef?: string
+ connectionString?: string
+ schema?: string
+}
+
+export type TablesResponse = Table[] | { error?: any }
+
+export async function getTables(
+ { projectRef, connectionString, schema }: TablesVariables,
+ signal?: AbortSignal
+) {
+ if (!projectRef) {
+ throw new Error('projectRef is required')
+ }
+
+ let headers = new Headers()
+ if (connectionString) headers.set('x-connection-encrypted', connectionString)
+
+ let queryParams = new URLSearchParams()
+ if (schema) queryParams.set('included_schemas', schema)
+ const searchStr = queryParams.toString()
+
+ const response = (await get(
+ `${API_URL}/pg-meta/${projectRef}/tables${searchStr ? `?${searchStr}` : ''}`,
+ {
+ headers: Object.fromEntries(headers),
+ signal,
+ }
+ )) as TablesResponse
+
+ if (!Array.isArray(response) && response.error) {
+ throw response.error
+ }
+
+ return response as PostgresTable[]
+}
+
+export type TablesData = Awaited>
+export type TablesError = ResponseError
+
+export const useTablesQuery = (
+ { projectRef, connectionString, schema }: TablesVariables,
+ { enabled = true, ...options }: UseQueryOptions = {}
+) =>
+ useQuery(
+ tableKeys.list(projectRef, schema),
+ ({ signal }) => getTables({ projectRef, connectionString, schema }, signal),
+ { enabled: enabled && typeof projectRef !== 'undefined', ...options }
+ )
+
+/**
+ * useGetTables
+ * Tries to get tables from the react-query cache, or loads it from the server if it's not cached.
+ */
+export function useGetTables({
+ projectRef,
+ connectionString,
+}: Pick) {
+ const queryClient = useQueryClient()
+
+ return useCallback(
+ (schema?: TablesVariables['schema']) => {
+ return queryClient.fetchQuery({
+ queryKey: tableKeys.list(projectRef, schema),
+ queryFn: ({ signal }) => getTables({ projectRef, connectionString, schema }, signal),
+ })
+ },
+ [connectionString, projectRef, queryClient]
+ )
+}
diff --git a/studio/data/views/keys.ts b/studio/data/views/keys.ts
new file mode 100644
index 00000000000..943f32500f1
--- /dev/null
+++ b/studio/data/views/keys.ts
@@ -0,0 +1,5 @@
+export const viewKeys = {
+ list: (projectRef: string | undefined) => ['projects', projectRef, 'views'] as const,
+ view: (projectRef: string | undefined, id: number | undefined) =>
+ ['projects', projectRef, 'views', id] as const,
+}
diff --git a/studio/data/views/view-query.ts b/studio/data/views/view-query.ts
new file mode 100644
index 00000000000..7b7b12a4793
--- /dev/null
+++ b/studio/data/views/view-query.ts
@@ -0,0 +1,57 @@
+import { PostgresView } from '@supabase/postgres-meta'
+import { useQuery, UseQueryOptions } from '@tanstack/react-query'
+import { get } from 'lib/common/fetch'
+import { API_URL } from 'lib/constants'
+import { viewKeys } from './keys'
+
+export type ViewVariables = {
+ id?: number
+ projectRef?: string
+ connectionString?: string
+}
+
+export type View = PostgresView
+
+export type ViewResponse = View | { error?: any }
+
+export async function getView(
+ { id, projectRef, connectionString }: ViewVariables,
+ signal?: AbortSignal
+) {
+ if (!projectRef) {
+ throw new Error('projectRef is required')
+ }
+ if (!id) {
+ throw new Error('id is required')
+ }
+
+ let headers = new Headers()
+ if (connectionString) headers.set('x-connection-encrypted', connectionString)
+
+ const response = (await get(`${API_URL}/pg-meta/${projectRef}/views?id=${id}`, {
+ headers: Object.fromEntries(headers),
+ signal,
+ })) as ViewResponse
+
+ if ('error' in response) {
+ throw response.error
+ }
+
+ return response as PostgresView
+}
+
+export type ViewData = Awaited>
+export type ViewError = unknown
+
+export const useViewQuery = (
+ { projectRef, connectionString, id }: ViewVariables,
+ { enabled = true, ...options }: UseQueryOptions = {}
+) =>
+ useQuery(
+ viewKeys.view(projectRef, id),
+ ({ signal }) => getView({ projectRef, connectionString, id }, signal),
+ {
+ enabled: enabled && typeof projectRef !== 'undefined' && typeof id !== 'undefined',
+ ...options,
+ }
+ )
diff --git a/studio/data/views/views-query.ts b/studio/data/views/views-query.ts
new file mode 100644
index 00000000000..08300ba9dfd
--- /dev/null
+++ b/studio/data/views/views-query.ts
@@ -0,0 +1,49 @@
+import { PostgresView } from '@supabase/postgres-meta'
+import { useQuery, UseQueryOptions } from '@tanstack/react-query'
+import { get } from 'lib/common/fetch'
+import { API_URL } from 'lib/constants'
+import { viewKeys } from './keys'
+import { View } from './view-query'
+
+export type ViewsVariables = {
+ projectRef?: string
+ connectionString?: string
+}
+
+export type ViewsResponse = View[] | { error?: any }
+
+export async function getViews(
+ { projectRef, connectionString }: ViewsVariables,
+ signal?: AbortSignal
+) {
+ if (!projectRef) {
+ throw new Error('projectRef is required')
+ }
+
+ let headers = new Headers()
+ if (connectionString) headers.set('x-connection-encrypted', connectionString)
+
+ const response = (await get(`${API_URL}/pg-meta/${projectRef}/views`, {
+ headers: Object.fromEntries(headers),
+ signal,
+ })) as ViewsResponse
+
+ if (!Array.isArray(response) && response.error) {
+ throw response.error
+ }
+
+ return response as PostgresView[]
+}
+
+export type ViewsData = Awaited>
+export type ViewsError = unknown
+
+export const useViewsQuery = (
+ { projectRef, connectionString }: ViewsVariables,
+ { enabled = true, ...options }: UseQueryOptions = {}
+) =>
+ useQuery(
+ viewKeys.list(projectRef),
+ ({ signal }) => getViews({ projectRef, connectionString }, signal),
+ { enabled: enabled && typeof projectRef !== 'undefined', ...options }
+ )
diff --git a/studio/hooks/misc/useTable.ts b/studio/hooks/misc/useTable.ts
new file mode 100644
index 00000000000..19647e03c88
--- /dev/null
+++ b/studio/hooks/misc/useTable.ts
@@ -0,0 +1,81 @@
+import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
+import { ENTITY_TYPE } from 'data/entity-types/entity-type-constants'
+import { ForeignTable, useForeignTableQuery } from 'data/foreign-tables/foreign-table-query'
+import {
+ MaterializedView,
+ useMaterializedViewQuery,
+} from 'data/materialized-views/materialized-view-query'
+import { Table, useTableQuery } from 'data/tables/table-query'
+import { View, useViewQuery } from 'data/views/view-query'
+import useEntityType from './useEntityType'
+
+export type TableLike = Table | View | MaterializedView | ForeignTable
+
+/**
+ * A hook that loads all table-like objects. e.g. tables, views, materialized views, etc...
+ */
+function useTable(id?: number) {
+ const { project } = useProjectContext()
+ const entity = useEntityType(id)
+
+ const tableResult = useTableQuery(
+ {
+ projectRef: project?.ref,
+ connectionString: project?.connectionString,
+ id: entity?.id,
+ },
+ {
+ enabled: entity?.type === ENTITY_TYPE.TABLE,
+ }
+ )
+
+ const viewResult = useViewQuery(
+ {
+ projectRef: project?.ref,
+ connectionString: project?.connectionString,
+ id: entity?.id,
+ },
+ {
+ enabled: entity?.type === ENTITY_TYPE.VIEW,
+ }
+ )
+
+ const materializedViewResult = useMaterializedViewQuery(
+ {
+ projectRef: project?.ref,
+ connectionString: project?.connectionString,
+ id: entity?.id,
+ },
+ {
+ enabled: entity?.type === ENTITY_TYPE.MATERIALIZED_VIEW,
+ }
+ )
+
+ const foreignTableResult = useForeignTableQuery(
+ {
+ projectRef: project?.ref,
+ connectionString: project?.connectionString,
+ id: entity?.id,
+ },
+ {
+ enabled: entity?.type === ENTITY_TYPE.FOREIGN_TABLE,
+ }
+ )
+
+ switch (entity?.type) {
+ case ENTITY_TYPE.TABLE:
+ return tableResult
+ case ENTITY_TYPE.VIEW:
+ return viewResult
+ case ENTITY_TYPE.MATERIALIZED_VIEW:
+ return materializedViewResult
+ case ENTITY_TYPE.FOREIGN_TABLE:
+ return foreignTableResult
+ default:
+ // While entity is loading, we can fallback
+ // to the loading state of the table query.
+ return tableResult
+ }
+}
+
+export default useTable
diff --git a/studio/lib/constants/schemas.ts b/studio/lib/constants/schemas.ts
new file mode 100644
index 00000000000..5f773e13b88
--- /dev/null
+++ b/studio/lib/constants/schemas.ts
@@ -0,0 +1,20 @@
+/**
+ * A list of system schemas that users should not interact with
+ */
+export const EXCLUDED_SCHEMAS = [
+ 'auth',
+ 'extensions',
+ 'information_schema',
+ 'net',
+ 'pgsodium',
+ 'pgsodium_masks',
+ 'pgbouncer',
+ 'pgtle',
+ 'realtime',
+ 'storage',
+ 'supabase_functions',
+ 'supabase_migrations',
+ 'vault',
+ 'graphql',
+ 'graphql_public',
+]
diff --git a/studio/pages/project/[ref]/api/graphiql.tsx b/studio/pages/project/[ref]/api/graphiql.tsx
index 5f4821e5e44..d1040650eca 100644
--- a/studio/pages/project/[ref]/api/graphiql.tsx
+++ b/studio/pages/project/[ref]/api/graphiql.tsx
@@ -34,7 +34,6 @@ const GraphiQLPage: NextPageWithLayout = () => {
useEffect(() => {
if (ui.selectedProjectRef) {
// Schemas may be needed when enabling the GraphQL extension
- meta.schemas.load()
meta.extensions.load()
}
}, [ui.selectedProjectRef])
diff --git a/studio/pages/project/[ref]/auth/policies.tsx b/studio/pages/project/[ref]/auth/policies.tsx
index 67e6995429c..c66ea97a317 100644
--- a/studio/pages/project/[ref]/auth/policies.tsx
+++ b/studio/pages/project/[ref]/auth/policies.tsx
@@ -1,16 +1,23 @@
-import { useState, useEffect } from 'react'
-import { partition } from 'lodash'
-import { Button, Listbox, IconSearch, Input, IconExternalLink, IconLock } from 'ui'
-import { observer } from 'mobx-react-lite'
-import { PostgresTable, PostgresPolicy } from '@supabase/postgres-meta'
+import { PostgresPolicy, PostgresTable } from '@supabase/postgres-meta'
import { PermissionAction } from '@supabase/shared-types/out/constants'
+import { partition } from 'lodash'
+import { observer } from 'mobx-react-lite'
+import { useEffect, useState } from 'react'
-import { NextPageWithLayout } from 'types'
-import { useCheckPermissions, useStore } from 'hooks'
import { useParams } from 'common/hooks'
-import { AuthLayout } from 'components/layouts'
import { Policies } from 'components/interfaces/Auth/Policies'
+import { AuthLayout } from 'components/layouts'
+import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
+import AlertError from 'components/ui/AlertError'
import NoPermission from 'components/ui/NoPermission'
+import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
+import { useSchemasQuery } from 'data/database/schemas-query'
+import { useTablesQuery } from 'data/tables/tables-query'
+import { useCheckPermissions, useStore } from 'hooks'
+import { EXCLUDED_SCHEMAS } from 'lib/constants/schemas'
+import { useTableEditorStateSnapshot } from 'state/table-editor'
+import { NextPageWithLayout } from 'types'
+import { Button, IconExternalLink, IconLock, IconSearch, Input, Listbox } from 'ui'
/**
* Filter tables by table name and policy name
@@ -48,27 +55,47 @@ const onFilterTables = (
}
const AuthPoliciesPage: NextPageWithLayout = () => {
+ const { project } = useProjectContext()
+ const snap = useTableEditorStateSnapshot()
const { meta } = useStore()
const { search } = useParams()
- const [selectedSchema, setSelectedSchema] = useState('public')
const [searchString, setSearchString] = useState('')
useEffect(() => {
if (search) setSearchString(search)
}, [search])
- const schemas = meta.schemas.list()
+ const {
+ data: schemas,
+ isLoading: isLoadingSchemas,
+ isSuccess: isSuccessSchemas,
+ isError: isErrorSchemas,
+ error: errorSchemas,
+ } = useSchemasQuery({
+ projectRef: project?.ref,
+ connectionString: project?.connectionString,
+ })
const [protectedSchemas, openSchemas] = partition(schemas, (schema) =>
- meta.excludedSchemas.includes(schema?.name ?? '')
+ EXCLUDED_SCHEMAS.includes(schema?.name ?? '')
)
- // @ts-ignore
- const schema = schemas.find((schema) => schema.name === selectedSchema)
+ const schema = schemas?.find((schema) => schema.name === snap.selectedSchemaName)
const isLocked = protectedSchemas.some((s) => s.id === schema?.id)
const policies = meta.policies.list()
- const tables = meta.tables.list((table: { schema: string }) => table.schema === selectedSchema)
- const filteredTables = onFilterTables(tables, policies, searchString)
+ const {
+ data: tables,
+ isLoading,
+ isSuccess,
+ isError,
+ error,
+ } = useTablesQuery({
+ projectRef: project?.ref,
+ connectionString: project?.connectionString,
+ schema: snap.selectedSchemaName,
+ })
+
+ const filteredTables = onFilterTables(tables ?? [], policies, searchString)
const canReadPolicies = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_READ, 'policies')
@@ -82,53 +109,62 @@ const AuthPoliciesPage: NextPageWithLayout = () => {
-
{
- setSelectedSchema(schema)
- setSearchString('')
- }}
- icon={isLocked && }
- >
-
+ )}
+
+ {isErrorSchemas && (
+
+ )}
+
+ {isSuccessSchemas && (
+ {
+ snap.setSelectedSchemaName(schema)
+ setSearchString('')
+ }}
+ icon={isLocked && }
>
- Schemas
-
- {/* @ts-ignore */}
- {openSchemas.map((schema) => (
schema }
+ disabled
+ key="normal-schemas"
+ value="normal-schemas"
+ label="Schemas"
>
- {schema.name}
+ Schemas
- ))}
-
- Protected schemas
-
- {protectedSchemas.map((schema) => (
+ {openSchemas.map((schema) => (
+ schema }
+ >
+ {schema.name}
+
+ ))}
schema }
+ disabled
+ key="protected-schemas"
+ value="protected-schemas"
+ label="Protected schemas"
>
- {schema.name}
+ Protected schemas
- ))}
-
+ {protectedSchemas.map((schema) => (
+ schema }
+ >
+ {schema.name}
+
+ ))}
+
+ )}
{
- 0} isLocked={isLocked} />
+ {isLoading && }
+ {isError && }
+ {isSuccess && (
+ 0} isLocked={isLocked} />
+ )}
)
}
diff --git a/studio/pages/project/[ref]/database/functions.tsx b/studio/pages/project/[ref]/database/functions.tsx
index f0dc7fa55f2..dfd219a8326 100644
--- a/studio/pages/project/[ref]/database/functions.tsx
+++ b/studio/pages/project/[ref]/database/functions.tsx
@@ -5,15 +5,13 @@ import { useEffect, useState } from 'react'
import { CreateFunction, DeleteFunction } from 'components/interfaces/Database'
import FunctionsList from 'components/interfaces/Database/Functions/FunctionsList/FunctionsList'
import { DatabaseLayout } from 'components/layouts'
-import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
+import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold'
import NoPermission from 'components/ui/NoPermission'
import { useCheckPermissions, useStore } from 'hooks'
import { NextPageWithLayout } from 'types'
-import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold'
const FunctionsPage: NextPageWithLayout = () => {
- const { meta } = useStore()
- const { project } = useProjectContext()
+ const { ui, meta } = useStore()
const [selectedFunction, setSelectedFunction] = useState()
const [showCreateFunctionForm, setShowCreateFunctionForm] = useState(false)
const [showDeleteFunctionForm, setShowDeleteFunctionForm] = useState(false)
@@ -21,10 +19,10 @@ const FunctionsPage: NextPageWithLayout = () => {
const canReadFunctions = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_READ, 'functions')
useEffect(() => {
- if (project?.ref) {
+ if (ui.selectedProjectRef) {
fetchFunctions()
}
- }, [project?.ref])
+ }, [ui.selectedProjectRef])
const fetchFunctions = async () => {
meta.functions.load()
diff --git a/studio/pages/project/[ref]/database/hooks.tsx b/studio/pages/project/[ref]/database/hooks.tsx
index c7477d9d259..121004351a1 100644
--- a/studio/pages/project/[ref]/database/hooks.tsx
+++ b/studio/pages/project/[ref]/database/hooks.tsx
@@ -1,39 +1,48 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
-import clsx from 'clsx'
-import { useParams } from 'common'
import { observer } from 'mobx-react-lite'
import { useEffect, useState } from 'react'
-import { IconLoader } from 'ui'
+import { useParams } from 'common'
import DeleteHookModal from 'components/interfaces/Database/Hooks/DeleteHookModal'
import EditHookPanel from 'components/interfaces/Database/Hooks/EditHookPanel'
import HooksList from 'components/interfaces/Database/Hooks/HooksList/HooksList'
import { DatabaseLayout } from 'components/layouts'
+import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
+import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold'
import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState'
import NoPermission from 'components/ui/NoPermission'
import { useHooksEnableMutation } from 'data/database/hooks-enable-mutation'
+import { useSchemasQuery } from 'data/database/schemas-query'
import { useCheckPermissions, useStore } from 'hooks'
import { NextPageWithLayout } from 'types'
-import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold'
+import { IconLoader } from 'ui'
const HooksPage: NextPageWithLayout = () => {
+ const { project } = useProjectContext()
const { meta, ui } = useStore()
- const { ref } = useParams()
- const schemas = meta.schemas.list()
- const { isLoading: isLoadingSchemas } = meta.schemas
+ const { ref: projectRef } = useParams()
+
+ const {
+ data: schemas,
+ isLoading: isLoadingSchemas,
+ refetch,
+ } = useSchemasQuery({
+ projectRef: project?.ref,
+ connectionString: project?.connectionString,
+ })
const [selectedHook, setSelectedHook] = useState()
const [showCreateHookForm, setShowCreateHookForm] = useState(false)
const [showDeleteHookForm, setShowDeleteHookForm] = useState(false)
- const isHooksEnabled = schemas.some((schema: any) => schema.name === 'supabase_functions')
+ const isHooksEnabled = schemas?.some((schema) => schema.name === 'supabase_functions')
const canReadWebhooks = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_READ, 'triggers')
const canCreateWebhooks = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'triggers')
const { mutate: enableHooks, isLoading: isEnablingHooks } = useHooksEnableMutation({
onSuccess: () => {
- meta.schemas.load()
+ refetch()
ui.setNotification({
category: 'success',
message: `Successfully enabled webhooks`,
@@ -46,8 +55,8 @@ const HooksPage: NextPageWithLayout = () => {
}, [ui.selectedProjectRef])
const enableHooksForProject = async () => {
- if (!ref) return console.error('Project ref is required')
- enableHooks({ ref })
+ if (!projectRef) return console.error('Project ref is required')
+ enableHooks({ ref: projectRef })
}
const createHook = () => {
diff --git a/studio/pages/project/[ref]/database/schemas.tsx b/studio/pages/project/[ref]/database/schemas.tsx
index 1f7058bd3dc..d9e67017988 100644
--- a/studio/pages/project/[ref]/database/schemas.tsx
+++ b/studio/pages/project/[ref]/database/schemas.tsx
@@ -1,66 +1,85 @@
-import { observer } from 'mobx-react-lite'
import { partition } from 'lodash'
import { useState } from 'react'
import SchemaGraph from 'components/interfaces/Database/Schemas/SchemaGraph'
import { DatabaseLayout } from 'components/layouts'
-import { useStore } from 'hooks'
+import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
+import AlertError from 'components/ui/AlertError'
+import { useSchemasQuery } from 'data/database/schemas-query'
+import { EXCLUDED_SCHEMAS } from 'lib/constants/schemas'
import { NextPageWithLayout } from 'types'
import { IconLock, Listbox } from 'ui'
const SchemasPage: NextPageWithLayout = () => {
- const { meta } = useStore()
- const schemas = meta.schemas.list()
+ const { project } = useProjectContext()
+ const {
+ data: schemas,
+ isSuccess,
+ isLoading,
+ isError,
+ error,
+ } = useSchemasQuery({
+ projectRef: project?.ref,
+ connectionString: project?.connectionString,
+ })
const [selectedSchema, setSelectedSchema] = useState('public')
const [protectedSchemas, openSchemas] = partition(schemas, (schema) =>
- meta.excludedSchemas.includes(schema?.name ?? '')
+ EXCLUDED_SCHEMAS.includes(schema?.name ?? '')
)
- const schema = schemas.find((schema) => schema.name === selectedSchema)
+ const schema = schemas?.find((schema) => schema.name === selectedSchema)
const isLocked = protectedSchemas.some((s) => s.id === schema?.id)
return (
<>
-
}
- >
-
- Schemas
-
- {openSchemas.map((schema) => (
-
schema }
- >
- {schema.name}
-
- ))}
-
+ )}
+
+ {isError &&
}
+
+ {isSuccess && (
+
}
>
-
Protected schemas
-
- {protectedSchemas.map((schema) => (
-
schema }
- >
- {schema.name}
+
+ Schemas
- ))}
-
+ {openSchemas.map((schema) => (
+ schema }
+ >
+ {schema.name}
+
+ ))}
+
+ Protected schemas
+
+ {protectedSchemas.map((schema) => (
+ schema }
+ >
+ {schema.name}
+
+ ))}
+
+ )}
@@ -71,4 +90,4 @@ const SchemasPage: NextPageWithLayout = () => {
SchemasPage.getLayout = (page) =>
{page}
-export default observer(SchemasPage)
+export default SchemasPage
diff --git a/studio/pages/project/[ref]/database/tables.tsx b/studio/pages/project/[ref]/database/tables.tsx
index a37cf910e14..088a06fde15 100644
--- a/studio/pages/project/[ref]/database/tables.tsx
+++ b/studio/pages/project/[ref]/database/tables.tsx
@@ -1,166 +1,56 @@
-import type { PostgresColumn, PostgresTable } from '@supabase/postgres-meta'
-import { useQueryClient } from '@tanstack/react-query'
-import { isUndefined } from 'lodash'
import { observer } from 'mobx-react-lite'
import { useEffect, useState } from 'react'
-import { Modal } from 'ui'
+import { useParams } from 'common'
import { ColumnList, TableList } from 'components/interfaces/Database'
import { SidePanelEditor } from 'components/interfaces/TableGridEditor'
+import DeleteConfirmationDialogs from 'components/interfaces/TableGridEditor/DeleteConfirmationDialogs'
import { DatabaseLayout } from 'components/layouts'
-import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold'
-import ConfirmationModal from 'components/ui/ConfirmationModal'
-import { sqlKeys } from 'data/sql/keys'
+import { Table } from 'data/tables/table-query'
import { useStore } from 'hooks'
+import { useTableEditorStateSnapshot } from 'state/table-editor'
import { NextPageWithLayout } from 'types'
const DatabaseTables: NextPageWithLayout = () => {
- const { meta, ui } = useStore()
- const { project } = useProjectContext()
+ const snap = useTableEditorStateSnapshot()
+ const { ui, meta } = useStore()
- const queryClient = useQueryClient()
-
- const [isDeleting, setIsDeleting] = useState
(false)
- const [selectedSchema, setSelectedSchema] = useState('public')
- const [selectedTable, setSelectedTable] = useState()
- const [sidePanelKey, setSidePanelKey] = useState<'column' | 'table'>()
-
- const [selectedColumnToEdit, setSelectedColumnToEdit] = useState()
- const [selectedTableToEdit, setSelectedTableToEdit] = useState()
-
- const [selectedColumnToDelete, setSelectedColumnToDelete] = useState()
- const [selectedTableToDelete, setSelectedTableToDelete] = useState()
+ const { ref: projectRef } = useParams()
useEffect(() => {
- if (project?.ref) {
+ if (ui.selectedProjectRef) {
meta.types.load()
}
- }, [project?.ref])
+ }, [ui.selectedProjectRef])
- const onAddTable = () => {
- setSidePanelKey('table')
- setSelectedTableToEdit(undefined)
- }
-
- const onEditTable = (table: PostgresTable) => {
- setSidePanelKey('table')
- setSelectedTableToEdit(table)
- }
-
- const onDeleteTable = (table: PostgresTable) => {
- setIsDeleting(true)
- setSelectedTableToDelete(table)
- }
-
- const onAddColumn = () => {
- setSidePanelKey('column')
- setSelectedColumnToEdit(undefined)
- }
-
- const onEditColumn = (column: PostgresColumn) => {
- setSidePanelKey('column')
- setSelectedColumnToEdit(column)
- }
-
- const onDeleteColumn = (column: PostgresColumn) => {
- setIsDeleting(true)
- setSelectedColumnToDelete(column)
- }
-
- const onColumnUpdated = async () => {
- if (selectedTable === undefined) return
-
- const updatedTable = await meta.tables.loadById(selectedTable.id)
- setSelectedTable(updatedTable)
- }
-
- const onClosePanel = () => setSidePanelKey(undefined)
-
- const onConfirmDeleteTable = async () => {
- try {
- if (isUndefined(selectedTableToDelete)) return
-
- const response: any = await meta.tables.del(selectedTableToDelete.id)
- if (response.error) {
- throw response.error
- } else {
- ui.setNotification({
- category: 'success',
- message: `Successfully removed ${selectedTableToDelete.name}.`,
- })
- }
- } catch (error: any) {
- ui.setNotification({
- category: 'error',
- message: `Failed to delete ${selectedTableToDelete?.name}: ${error.message}`,
- })
- } finally {
- setIsDeleting(false)
- setSelectedTableToDelete(undefined)
- }
- }
-
- const onConfirmDeleteColumn = async () => {
- try {
- if (isUndefined(selectedColumnToDelete)) return
-
- const response: any = await meta.columns.del(selectedColumnToDelete.id)
- if (response.error) {
- throw response.error
- } else {
- onColumnUpdated()
- ui.setNotification({
- category: 'success',
- message: `Successfully removed ${selectedColumnToDelete.name}.`,
- })
-
- queryClient.invalidateQueries(sqlKeys.query(project?.ref, ['foreign-key-constraints']))
- await Promise.all([
- meta.tables.loadById(selectedColumnToDelete!.table_id),
- queryClient.invalidateQueries(
- sqlKeys.query(project?.ref, [selectedTable!.schema, selectedTable!.name])
- ),
- queryClient.invalidateQueries(
- sqlKeys.query(project?.ref, [
- 'table-definition',
- selectedTable!.schema,
- selectedTable!.name,
- ])
- ),
- ])
- }
- } catch (error: any) {
- ui.setNotification({
- category: 'error',
- message: `Failed to delete ${selectedColumnToDelete?.name}: ${error.message}`,
- })
- } finally {
- setIsDeleting(false)
- setSelectedColumnToDelete(undefined)
- }
- }
+ const [selectedTable, setSelectedTable] = useState(undefined)
+ const [selectedTableToEdit, setSelectedTableToEdit] = useState(undefined)
return (
<>
- {isUndefined(selectedTable) ? (
+ {selectedTable === undefined ? (
{
+ setSelectedTableToEdit(table)
+ snap.onEditTable()
+ }}
+ onDeleteTable={(table) => {
+ setSelectedTableToEdit(table)
+ snap.onDeleteTable()
+ }}
onOpenTable={setSelectedTable}
/>
) : (
setSelectedTable(undefined)}
/>
)}
@@ -168,49 +58,8 @@ const DatabaseTables: NextPageWithLayout = () => {
- {`Confirm deletion of table "${selectedTableToDelete?.name}"`}
- }
- buttonLabel="Delete"
- buttonLoadingLabel="Deleting"
- onSelectCancel={() => setIsDeleting(false)}
- onSelectConfirm={onConfirmDeleteTable}
- >
-
-
- Are you sure you want to delete the selected table? This action cannot be undone.
-
-
-
-
- setIsDeleting(false)}
- onSelectConfirm={onConfirmDeleteColumn}
- >
-
-
- Are you sure you want to delete the selected column? This action cannot be undone.
-
-
-
-
-
+
+
>
)
}
diff --git a/studio/pages/project/[ref]/editor/[id].tsx b/studio/pages/project/[ref]/editor/[id].tsx
index 9cf12e1ac2b..0b31931bf2b 100644
--- a/studio/pages/project/[ref]/editor/[id].tsx
+++ b/studio/pages/project/[ref]/editor/[id].tsx
@@ -1,402 +1,48 @@
-import type { PostgresColumn, PostgresTable } from '@supabase/postgres-meta'
-import { useQueryClient } from '@tanstack/react-query'
-import { useParams, useTheme } from 'common'
-import { isNaN, isUndefined } from 'lodash'
-import { observer } from 'mobx-react-lite'
-import Link from 'next/link'
-import { useRouter } from 'next/router'
-import { useEffect, useState } from 'react'
-import { Alert, Button, Checkbox, IconExternalLink, Modal } from 'ui'
-
-import { Dictionary } from 'components/grid'
+import { useTheme } from 'common'
+import { useParams } from 'common/hooks'
import { TableGridEditor } from 'components/interfaces'
-import { ForeignRowSelectorProps } from 'components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/ForeignRowSelector'
-import { JsonEditValue } from 'components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.types'
+import DeleteConfirmationDialogs from 'components/interfaces/TableGridEditor/DeleteConfirmationDialogs'
import { TableEditorLayout } from 'components/layouts'
-import {
- ProjectContextFromParamsProvider,
- useProjectContext,
-} from 'components/layouts/ProjectLayout/ProjectContext'
-import ConfirmationModal from 'components/ui/ConfirmationModal'
-import { Entity } from 'data/entity-types/entity-type-query'
-import { entityTypeKeys } from 'data/entity-types/keys'
-import { sqlKeys } from 'data/sql/keys'
-import { useStore, useUrlState, withAuth } from 'hooks'
-import { NextPageWithLayout, SchemaView } from 'types'
+import { ProjectContextFromParamsProvider } from 'components/layouts/ProjectLayout/ProjectContext'
+import useTable from 'hooks/misc/useTable'
+import { useRouter } from 'next/router'
+import { NextPageWithLayout } from 'types'
const TableEditorPage: NextPageWithLayout = () => {
const router = useRouter()
const { isDarkMode } = useTheme()
- const { id, ref: projectRef } = useParams()
- const [_, setParams] = useUrlState({ arrayKeys: ['filter', 'sort'] })
+ const { id: _id, ref: projectRef } = useParams()
+ const id = _id ? Number(_id) : undefined
- const { project } = useProjectContext()
-
- const queryClient = useQueryClient()
-
- const { meta, ui } = useStore()
- const [selectedSchema, setSelectedSchema] = useState()
-
- const [isDeleting, setIsDeleting] = useState(false)
- const [isDuplicating, setIsDuplicating] = useState(false)
- const [isDeleteWithCascade, setIsDeleteWithCascade] = useState(false)
-
- const [selectedColumnToDelete, setSelectedColumnToDelete] = useState()
- const [selectedTableToDelete, setSelectedTableToDelete] = useState()
-
- const [sidePanelKey, setSidePanelKey] = useState<
- 'row' | 'column' | 'table' | 'json' | 'foreign-row-selector' | 'csv-import'
- >()
- const [selectedRowToEdit, setSelectedRowToEdit] = useState>()
- const [selectedColumnToEdit, setSelectedColumnToEdit] = useState()
- const [selectedTableToEdit, setSelectedTableToEdit] = useState()
- const [selectedValueForJsonEdit, setSelectedValueForJsonEdit] = useState()
- const [selectedForeignKeyToEdit, setSelectedForeignKeyToEdit] = useState<{
- foreignKey: NonNullable
- row: any
- column: any
- }>()
-
- const tables: PostgresTable[] = meta.tables.list()
- const views: SchemaView[] = meta.views.list()
- const materializedViews = meta.materializedViews.list()
- const foreignTables: Partial[] = meta.foreignTables.list()
-
- const selectedTable = !isNaN(Number(id))
- ? // @ts-ignore
- tables
- // @ts-ignore
- .concat(views)
- // @ts-ignore
- .concat(materializedViews)
- // @ts-ignore
- .concat(foreignTables)
- .find((table) => table.id === Number(id))
- : undefined
-
- useEffect(() => {
- if (selectedTable && 'schema' in selectedTable) {
- setSelectedSchema(selectedTable.schema)
- }
- }, [selectedTable?.name])
-
- const onAddRow = () => {
- setSidePanelKey('row')
- setSelectedRowToEdit(undefined)
- }
-
- const onEditRow = (row: Dictionary) => {
- setSidePanelKey('row')
- setSelectedRowToEdit(row)
- }
-
- const onAddColumn = () => {
- setSidePanelKey('column')
- setSelectedColumnToEdit(undefined)
- }
-
- const onEditColumn = (column: PostgresColumn) => {
- setSidePanelKey('column')
- setSelectedColumnToEdit(column)
- }
-
- const onDeleteColumn = (column: PostgresColumn) => {
- setIsDeleting(true)
- setSelectedColumnToDelete(column)
- setIsDeleteWithCascade(false)
- }
-
- const onAddTable = () => {
- setSidePanelKey('table')
- setIsDuplicating(false)
- setSelectedTableToEdit(undefined)
- }
-
- const onEditTable = (entity: Entity) => {
- setSidePanelKey('table')
- setIsDuplicating(false)
-
- const table = meta.tables.byId(entity.id)
- setSelectedTableToEdit(table)
- }
-
- const onDeleteTable = (entity: Entity) => {
- setIsDeleting(true)
-
- const table = meta.tables.byId(entity.id)
- setSelectedTableToDelete(table)
- setIsDeleteWithCascade(false)
- }
-
- const onDuplicateTable = (entity: Entity) => {
- setSidePanelKey('table')
- setIsDuplicating(true)
-
- const table = meta.tables.byId(entity.id)
- setSelectedTableToEdit(table)
- }
-
- const onExpandJSONEditor = (column: string, row: any) => {
- setSidePanelKey('json')
- setSelectedValueForJsonEdit({ column, row, jsonString: JSON.stringify(row[column]) || '' })
- }
-
- const onImportData = () => {
- if (id) {
- setSidePanelKey('csv-import')
- const table = meta.tables.byId(id)
- setSelectedTableToEdit(table)
- } else {
- console.error('Table ID not found')
- }
- }
-
- const onEditForeignKeyColumnValue = ({
- foreignKey,
- row,
- column,
- }: {
- foreignKey: NonNullable
- row: any
- column: any
- }) => {
- setSidePanelKey('foreign-row-selector')
- setSelectedForeignKeyToEdit({
- foreignKey,
- row,
- column,
- })
- }
-
- const onClosePanel = () => {
- setSidePanelKey(undefined)
- }
-
- const removeDeletedColumnFromFiltersAndSorts = (columnName: string) => {
- setParams((prevParams) => {
- const existingFilters = (prevParams?.filter ?? []) as string[]
- const existingSorts = (prevParams?.sort ?? []) as string[]
-
- return {
- ...prevParams,
- filter: existingFilters.filter((filter: string) => {
- const [column] = filter.split(':')
- if (column !== columnName) return filter
- }),
- sort: existingSorts.filter((sort: string) => {
- const [column] = sort.split(':')
- if (column !== columnName) return sort
- }),
- }
- })
- }
-
- const onConfirmDeleteColumn = async () => {
- try {
- if (selectedColumnToDelete === undefined) return
-
- const response: any = await meta.columns.del(selectedColumnToDelete.id, isDeleteWithCascade)
- if (response.error) throw response.error
-
- removeDeletedColumnFromFiltersAndSorts(selectedColumnToDelete.name)
-
- ui.setNotification({
- category: 'success',
- message: `Successfully deleted column "${selectedColumnToDelete.name}"`,
- })
-
- queryClient.invalidateQueries(sqlKeys.query(project?.ref, ['foreign-key-constraints']))
- await Promise.all([
- meta.tables.loadById(selectedColumnToDelete!.table_id),
- queryClient.invalidateQueries(
- sqlKeys.query(project?.ref, [selectedTable!.schema, selectedTable!.name])
- ),
- queryClient.invalidateQueries(
- sqlKeys.query(project?.ref, [
- 'table-definition',
- selectedTable!.schema,
- selectedTable!.name,
- ])
- ),
- ])
-
- if (selectedSchema) await meta.views.loadBySchema(selectedSchema)
- } catch (error: any) {
- ui.setNotification({
- category: 'error',
- message: `Failed to delete ${selectedColumnToDelete!.name}: ${error.message}`,
- })
- } finally {
- setIsDeleting(false)
- setSelectedColumnToDelete(undefined)
- }
- }
-
- const onConfirmDeleteTable = async () => {
- try {
- if (selectedTableToDelete === undefined) return
-
- const response: any = await meta.tables.del(selectedTableToDelete.id, isDeleteWithCascade)
- if (response.error) throw response.error
-
- const tables = meta.tables.list((table: PostgresTable) => table.schema === selectedSchema)
-
- await queryClient.invalidateQueries(entityTypeKeys.list(projectRef))
-
- // For simplicity for now, we just open the first table within the same schema
- if (tables.length > 0) {
- router.push(`/project/${projectRef}/editor/${tables[0].id}`)
- } else {
- router.push(`/project/${projectRef}/editor/`)
- }
- ui.setNotification({
- category: 'success',
- message: `Successfully deleted table "${selectedTableToDelete.name}"`,
- })
- if (selectedSchema) await meta.views.loadBySchema(selectedSchema)
- } catch (error: any) {
- ui.setNotification({
- error,
- category: 'error',
- message: `Failed to delete ${selectedTableToDelete?.name}: ${error.message}`,
- })
- } finally {
- setIsDeleting(false)
- setSelectedTableToDelete(undefined)
- }
- }
+ const { data: selectedTable, isLoading } = useTable(id)
return (
-
+ <>
- {
- setIsDeleting(false)
- setSelectedColumnToDelete(undefined)
+ {
+ // For simplicity for now, we just open the first table within the same schema
+ if (tables.length > 0) {
+ router.push(`/project/${projectRef}/editor/${tables[0].id}`)
+ } else {
+ router.push(`/project/${projectRef}/editor/`)
+ }
}}
- onSelectConfirm={onConfirmDeleteColumn}
- >
-
-
-
- Are you sure you want to delete the selected column? This action cannot be undone.
-
-
setIsDeleteWithCascade(!isDeleteWithCascade)}
- />
- {isDeleteWithCascade && (
-
-
- All dependent objects will be removed, as will any objects that depend on them,
- recursively.
-
-
-
- }>
- About dependency tracking
-
-
-
-
- )}
-
-
-
- {`Confirm deletion of table "${selectedTableToDelete?.name}"`}
- }
- buttonLabel="Delete"
- buttonLoadingLabel="Deleting"
- onSelectCancel={() => {
- setIsDeleting(false)
- setSelectedTableToDelete(undefined)
- }}
- onSelectConfirm={onConfirmDeleteTable}
- >
-
-
-
- Are you sure you want to delete the selected table? This action cannot be undone.
-
-
setIsDeleteWithCascade(!isDeleteWithCascade)}
- />
- {isDeleteWithCascade && (
-
-
- All dependent objects will be removed, as will any objects that depend on them,
- recursively.
-
-
-
- }>
- About dependency tracking
-
-
-
-
- )}
-
-
-
-
+ />
+ >
)
}
TableEditorPage.getLayout = (page) => (
- {page}
+
+ {page}
+
)
-export default withAuth(observer(TableEditorPage))
+export default TableEditorPage
diff --git a/studio/pages/project/[ref]/editor/index.tsx b/studio/pages/project/[ref]/editor/index.tsx
index 6cb80edcfdb..55c2cfe0189 100644
--- a/studio/pages/project/[ref]/editor/index.tsx
+++ b/studio/pages/project/[ref]/editor/index.tsx
@@ -1,119 +1,32 @@
-import type { PostgresTable } from '@supabase/postgres-meta'
-import { isUndefined } from 'lodash'
-import { observer } from 'mobx-react-lite'
-import router from 'next/router'
-import { useState } from 'react'
+import { useRouter } from 'next/router'
+import { useParams } from 'common'
import { EmptyState, SidePanelEditor } from 'components/interfaces/TableGridEditor'
import { TableEditorLayout } from 'components/layouts'
-import {
- ProjectContextFromParamsProvider,
- useProjectContext,
-} from 'components/layouts/ProjectLayout/ProjectContext'
-import ConfirmationModal from 'components/ui/ConfirmationModal'
-import { Entity } from 'data/entity-types/entity-type-query'
-import { useStore, withAuth } from 'hooks'
+import { ProjectContextFromParamsProvider } from 'components/layouts/ProjectLayout/ProjectContext'
+import { Table } from 'data/tables/table-query'
import { NextPageWithLayout } from 'types'
const TableEditorPage: NextPageWithLayout = () => {
- const { meta, ui } = useStore()
- const { project } = useProjectContext()
- const projectRef = project?.ref
+ const { ref: projectRef } = useParams()
+ const router = useRouter()
- const [sidePanelKey, setSidePanelKey] = useState<'row' | 'column' | 'table'>()
- const [isDeleting, setIsDeleting] = useState(false)
- const [isDuplicating, setIsDuplicating] = useState(false)
- const [selectedSchema, setSelectedSchema] = useState('public')
- const [selectedTableToEdit, setSelectedTableToEdit] = useState()
- const [selectedTableToDelete, setSelectedTableToDelete] = useState()
-
- const onAddTable = () => {
- setSidePanelKey('table')
- setIsDuplicating(false)
- setSelectedTableToEdit(undefined)
- }
-
- const onEditTable = (entity: Entity) => {
- setSidePanelKey('table')
- setIsDuplicating(false)
-
- const table = meta.tables.byId(entity.id)
- setSelectedTableToEdit(table)
- }
-
- const onDeleteTable = (entity: Entity) => {
- setIsDeleting(true)
-
- const table = meta.tables.byId(entity.id)
- setSelectedTableToDelete(table)
- }
-
- const onDuplicateTable = (entity: Entity) => {
- setSidePanelKey('table')
- setIsDuplicating(true)
-
- const table = meta.tables.byId(entity.id)
- setSelectedTableToEdit(table)
- }
-
- const onClosePanel = () => {
- setSidePanelKey(undefined)
- }
-
- const onConfirmDeleteTable = async () => {
- try {
- await meta.tables.del(selectedTableToDelete!.id)
- setIsDeleting(false)
- ui.setNotification({
- category: 'success',
- message: `Successfully deleted ${selectedTableToDelete!.name}`,
- })
- await meta.views.loadBySchema(selectedSchema)
- } catch (error: any) {
- ui.setNotification({
- category: 'error',
- message: `Failed to delete ${selectedTableToDelete!.name}`,
- })
- }
+ const onTableCreated = (table: Table) => {
+ router.push(`/project/${projectRef}/editor/${table.id}`)
}
return (
-
-
- {/* On this page it'll only handle tables */}
- {`Confirm deletion of table "${selectedTableToDelete?.name}"`}
- }
- description={`Are you sure you want to delete the selected table? This action cannot be undone`}
- buttonLabel="Delete"
- buttonLoadingLabel="Deleting"
- onSelectCancel={() => setIsDeleting(false)}
- onSelectConfirm={onConfirmDeleteTable}
- />
- router.push(`/project/${projectRef}/editor/${table.id}`)}
- />
-
+ <>
+
+
+ >
)
}
TableEditorPage.getLayout = (page) => (
- {page}
+
+ {page}
+
)
-export default withAuth(observer(TableEditorPage))
+export default TableEditorPage
diff --git a/studio/pages/project/[ref]/settings/api.tsx b/studio/pages/project/[ref]/settings/api.tsx
index e430e0cb57c..3cbb7fddc8a 100644
--- a/studio/pages/project/[ref]/settings/api.tsx
+++ b/studio/pages/project/[ref]/settings/api.tsx
@@ -1,5 +1,5 @@
import { observer, useLocalObservable } from 'mobx-react-lite'
-import { createContext, useEffect } from 'react'
+import { createContext } from 'react'
import { useParams } from 'common/hooks'
import ServiceList from 'components/interfaces/Settings/API/ServiceList'
@@ -31,12 +31,6 @@ const ApiSettings: NextPageWithLayout = () => {
PageContext.project = project
if (meta) PageState.meta = meta
- useEffect(() => {
- if (ui.selectedProjectRef) {
- meta.schemas.load()
- }
- }, [ui.selectedProjectRef])
-
return (
diff --git a/studio/pages/project/[ref]/sql/[id].tsx b/studio/pages/project/[ref]/sql/[id].tsx
index d0b3ffb8828..fc9e513dd9d 100644
--- a/studio/pages/project/[ref]/sql/[id].tsx
+++ b/studio/pages/project/[ref]/sql/[id].tsx
@@ -67,7 +67,7 @@ const SqlEditor: NextPageWithLayout = () => {
pgInfoRef.current = {}
}
pgInfoRef.current.tableColumns = tableColumns?.result
- pgInfoRef.current.schemas = schemas?.result
+ pgInfoRef.current.schemas = schemas
pgInfoRef.current.keywords = keywords?.result
pgInfoRef.current.functions = functions?.result
}
diff --git a/studio/state/table-editor-global.ts b/studio/state/table-editor-global.ts
new file mode 100644
index 00000000000..f8d1ae7e04d
--- /dev/null
+++ b/studio/state/table-editor-global.ts
@@ -0,0 +1,28 @@
+import { proxy, snapshot, useSnapshot } from 'valtio'
+import { proxySet } from 'valtio/utils'
+
+export const tableEditorGlobalState = proxy({
+ projectRef: undefined as undefined | string,
+ loadedIds: proxySet
([]),
+ addLoadedId: (ref: string, id: number) => {
+ if (ref !== tableEditorGlobalState.projectRef) {
+ tableEditorGlobalState.loadedIds.clear()
+ tableEditorGlobalState.projectRef = ref
+ }
+
+ tableEditorGlobalState.loadedIds.add(id)
+ },
+})
+
+export const getTableEditorGlobalStateSnapshot = () => snapshot(tableEditorGlobalState)
+
+export const useTableEditorGlobalStateSnapshot = (options?: Parameters[1]) =>
+ useSnapshot(tableEditorGlobalState, options)
+
+export const useIsTableLoaded = (ref?: string, id?: number) => {
+ const snap = useTableEditorGlobalStateSnapshot()
+
+ if (!ref || !id) return false
+ if (ref !== snap.projectRef) return false
+ return snap.loadedIds.has(id)
+}
diff --git a/studio/state/table-editor.ts b/studio/state/table-editor.ts
deleted file mode 100644
index 99fe3309946..00000000000
--- a/studio/state/table-editor.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { proxy, snapshot, useSnapshot } from 'valtio'
-import { proxySet } from 'valtio/utils'
-
-export const tableEditorState = proxy({
- projectRef: undefined as undefined | string,
- loadedIds: proxySet([]),
- addLoadedId: (ref: string, id: number) => {
- if (ref !== tableEditorState.projectRef) {
- tableEditorState.loadedIds.clear()
- tableEditorState.projectRef = ref
- }
-
- tableEditorState.loadedIds.add(id)
- },
-})
-
-export const getTableEditorStateSnapshot = () => snapshot(tableEditorState)
-
-export const useTableEditorStateSnapshot = (options?: Parameters[1]) =>
- useSnapshot(tableEditorState, options)
-
-export const useIsTableLoaded = (ref?: string, id?: number) => {
- const snap = useTableEditorStateSnapshot()
-
- if (!ref || !id) return false
- if (ref !== snap.projectRef) return false
- return snap.loadedIds.has(id)
-}
diff --git a/studio/state/table-editor.tsx b/studio/state/table-editor.tsx
new file mode 100644
index 00000000000..3b38c94f5e1
--- /dev/null
+++ b/studio/state/table-editor.tsx
@@ -0,0 +1,171 @@
+import { PostgresColumn } from '@supabase/postgres-meta'
+import { Dictionary } from 'components/grid'
+import { ForeignRowSelectorProps } from 'components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/ForeignRowSelector'
+import { JsonEditValue } from 'components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.types'
+import { PropsWithChildren, createContext, useContext, useRef } from 'react'
+import { proxy, useSnapshot } from 'valtio'
+
+type ForeignKey = {
+ foreignKey: NonNullable
+ row: Dictionary
+ column: PostgresColumn
+}
+
+export type SidePanel =
+ | { type: 'row'; row?: Dictionary }
+ | { type: 'column'; column?: PostgresColumn }
+ | { type: 'table'; mode: 'new' | 'edit' | 'duplicate' }
+ | { type: 'json'; jsonValue: JsonEditValue }
+ | {
+ type: 'foreign-row-selector'
+ foreignKey: ForeignKey
+ }
+ | { type: 'csv-import' }
+
+export type ConfirmationDialog =
+ | { type: 'table'; isDeleteWithCascade: boolean }
+ | { type: 'column'; column: PostgresColumn; isDeleteWithCascade: boolean }
+
+export type UIState =
+ | {
+ open: 'none'
+ }
+ | {
+ open: 'side-panel'
+ sidePanel: SidePanel
+ }
+ | {
+ open: 'confirmation-dialog'
+ confirmationDialog: ConfirmationDialog
+ }
+
+export const createTableEditorState = () => {
+ const state = proxy({
+ selectedSchemaName: 'public',
+ setSelectedSchemaName: (schemaName: string) => {
+ state.selectedSchemaName = schemaName
+ },
+
+ ui: { open: 'none' } as UIState,
+ get sidePanel() {
+ return state.ui.open === 'side-panel' ? state.ui.sidePanel : undefined
+ },
+ get confirmationDialog() {
+ return state.ui.open === 'confirmation-dialog' ? state.ui.confirmationDialog : undefined
+ },
+
+ closeSidePanel: () => {
+ state.ui = { open: 'none' }
+ },
+ closeConfirmationDialog: () => {
+ state.ui = { open: 'none' }
+ },
+
+ /* Tables */
+ onAddTable: () => {
+ state.ui = {
+ open: 'side-panel',
+ sidePanel: { type: 'table', mode: 'new' },
+ }
+ },
+ onEditTable: () => {
+ state.ui = {
+ open: 'side-panel',
+ sidePanel: { type: 'table', mode: 'edit' },
+ }
+ },
+ onDuplicateTable: () => {
+ state.ui = {
+ open: 'side-panel',
+ sidePanel: { type: 'table', mode: 'duplicate' },
+ }
+ },
+ onDeleteTable: () => {
+ state.ui = {
+ open: 'confirmation-dialog',
+ confirmationDialog: { type: 'table', isDeleteWithCascade: false },
+ }
+ },
+
+ /* Columns */
+ onAddColumn: () => {
+ state.ui = {
+ open: 'side-panel',
+ sidePanel: { type: 'column' },
+ }
+ },
+ onEditColumn: (column: PostgresColumn) => {
+ state.ui = {
+ open: 'side-panel',
+ sidePanel: { type: 'column', column },
+ }
+ },
+ onDeleteColumn: (column: PostgresColumn) => {
+ state.ui = {
+ open: 'confirmation-dialog',
+ confirmationDialog: { type: 'column', column, isDeleteWithCascade: false },
+ }
+ },
+
+ /* Rows */
+ onAddRow: () => {
+ state.ui = {
+ open: 'side-panel',
+ sidePanel: { type: 'row' },
+ }
+ },
+ onEditRow: (row: Dictionary) => {
+ state.ui = {
+ open: 'side-panel',
+ sidePanel: { type: 'row', row },
+ }
+ },
+
+ /* Misc */
+ onExpandJSONEditor: (jsonValue: JsonEditValue) => {
+ state.ui = {
+ open: 'side-panel',
+ sidePanel: { type: 'json', jsonValue },
+ }
+ },
+ onEditForeignKeyColumnValue: (foreignKey: ForeignKey) => {
+ state.ui = {
+ open: 'side-panel',
+ sidePanel: { type: 'foreign-row-selector', foreignKey },
+ }
+ },
+ onImportData: () => {
+ state.ui = {
+ open: 'side-panel',
+ sidePanel: { type: 'csv-import' },
+ }
+ },
+
+ /* Utils */
+ toggleConfirmationIsWithCascade: (overrideIsDeleteWithCascade?: boolean) => {
+ if (state.ui.open === 'confirmation-dialog') {
+ state.ui.confirmationDialog.isDeleteWithCascade =
+ overrideIsDeleteWithCascade ?? !state.ui.confirmationDialog.isDeleteWithCascade
+ }
+ },
+ })
+
+ return state
+}
+
+export type TableEditorState = ReturnType
+
+export const TableEditorStateContext = createContext(createTableEditorState())
+
+export const TableEditorStateContextProvider = ({ children }: PropsWithChildren<{}>) => {
+ const state = useRef(createTableEditorState()).current
+
+ return (
+ {children}
+ )
+}
+
+export const useTableEditorStateSnapshot = (options?: Parameters[1]) => {
+ const state = useContext(TableEditorStateContext)
+ return useSnapshot(state, options)
+}
diff --git a/studio/stores/pgmeta/MetaStore.ts b/studio/stores/pgmeta/MetaStore.ts
index d62d39c278b..b57d818bf49 100644
--- a/studio/stores/pgmeta/MetaStore.ts
+++ b/studio/stores/pgmeta/MetaStore.ts
@@ -1,62 +1,62 @@
-import Papa from 'papaparse'
-import { makeObservable, observable } from 'mobx'
-import { find, isUndefined, isEqual, isEmpty, chunk } from 'lodash'
import { Query } from 'components/grid/query/Query'
+import { chunk, find, isEmpty, isEqual, isUndefined } from 'lodash'
+import { makeObservable } from 'mobx'
+import Papa from 'papaparse'
import type {
PostgresColumn,
- PostgresTable,
- PostgresRelationship,
PostgresPrimaryKey,
- PostgresSchema,
+ PostgresRelationship,
+ PostgresTable,
} from '@supabase/postgres-meta'
-import { IS_PLATFORM, API_URL } from 'lib/constants'
import { post } from 'lib/common/fetch'
+import { API_URL, IS_PLATFORM } from 'lib/constants'
import { timeout, tryParseJson } from 'lib/helpers'
import { ResponseError } from 'types'
import { IRootStore } from '../RootStore'
-import ColumnStore from './ColumnStore'
-import SchemaStore from './SchemaStore'
-import TableStore, { ITableStore } from './TableStore'
-import OpenApiStore, { IOpenApiStore } from './OpenApiStore'
import { IPostgresMetaInterface } from '../common/PostgresMetaInterface'
+import ColumnStore from './ColumnStore'
+import OpenApiStore, { IOpenApiStore } from './OpenApiStore'
+import TableStore, { ITableStore } from './TableStore'
+import {
+ generateCreateColumnPayload,
+ generateUpdateColumnPayload,
+} from 'components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.utils'
import {
ColumnField,
CreateColumnPayload,
ExtendedPostgresRelationship,
UpdateColumnPayload,
} from 'components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.types'
-import {
- generateCreateColumnPayload,
- generateUpdateColumnPayload,
-} from 'components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.utils'
import { ImportContent } from 'components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.types'
-import RolesStore, { IRolesStore } from './RolesStore'
-import PoliciesStore from './PoliciesStore'
-import TriggersStore from './TriggersStore'
-import PublicationStore from './PublicationStore'
+import { FOREIGN_KEY_CASCADE_ACTION } from 'data/database/database-query-constants'
+import { getCachedProjectDetail } from 'data/projects/project-detail-query'
+import { getQueryClient } from 'data/query-client'
+import { tableKeys } from 'data/tables/keys'
+import { getTable } from 'data/tables/table-query'
+import { getTables } from 'data/tables/tables-query'
+import ExtensionsStore from './ExtensionsStore'
+import ForeignTableStore, { IForeignTableStore } from './ForeignTableStore'
import FunctionsStore from './FunctionsStore'
import HooksStore from './HooksStore'
-import ExtensionsStore from './ExtensionsStore'
-import TypesStore from './TypesStore'
-import ForeignTableStore, { IForeignTableStore } from './ForeignTableStore'
-import ViewStore, { IViewStore } from './ViewStore'
import MaterializedViewStore, { IMaterializedViewStore } from './MaterializedViewStore'
-import { FOREIGN_KEY_CASCADE_ACTION } from 'data/database/database-query-constants'
+import PoliciesStore from './PoliciesStore'
+import PublicationStore from './PublicationStore'
+import RolesStore, { IRolesStore } from './RolesStore'
+import TriggersStore from './TriggersStore'
+import TypesStore from './TypesStore'
+import ViewStore, { IViewStore } from './ViewStore'
const BATCH_SIZE = 1000
const CHUNK_SIZE = 1024 * 1024 * 0.1 // 0.1MB
export interface IMetaStore {
- excludedSchemas: string[]
-
openApi: IOpenApiStore
tables: ITableStore
columns: IPostgresMetaInterface
- schemas: IPostgresMetaInterface
views: IViewStore
materializedViews: IMaterializedViewStore
foreignTables: IForeignTableStore
@@ -150,7 +150,6 @@ export default class MetaStore implements IMetaStore {
openApi: OpenApiStore
tables: TableStore
columns: ColumnStore
- schemas: SchemaStore
views: ViewStore
materializedViews: MaterializedViewStore
foreignTables: ForeignTableStore
@@ -169,25 +168,6 @@ export default class MetaStore implements IMetaStore {
baseUrl: string
headers: { [prop: string]: any }
- // [Joshen] I'm going to treat this as a list of system schemas
- excludedSchemas = [
- 'auth',
- 'extensions',
- 'information_schema',
- 'net',
- 'pgsodium',
- 'pgsodium_masks',
- 'pgbouncer',
- 'pgtle',
- 'realtime',
- 'storage',
- 'supabase_functions',
- 'supabase_migrations',
- 'vault',
- 'graphql',
- 'graphql_public',
- ]
-
constructor(rootStore: IRootStore, options: { projectRef: string; connectionString?: string }) {
const { projectRef, connectionString } = options
this.rootStore = rootStore
@@ -206,7 +186,6 @@ export default class MetaStore implements IMetaStore {
)
this.tables = new TableStore(this.rootStore, `${this.baseUrl}/tables`, this.headers)
this.columns = new ColumnStore(this.rootStore, `${this.baseUrl}/columns`, this.headers)
- this.schemas = new SchemaStore(this.rootStore, `${this.baseUrl}/schemas`, this.headers)
this.views = new ViewStore(this.rootStore, `${this.baseUrl}/views`, this.headers)
this.materializedViews = new MaterializedViewStore(
this.rootStore,
@@ -239,9 +218,7 @@ export default class MetaStore implements IMetaStore {
)
this.types = new TypesStore(this.rootStore, `${this.baseUrl}/types`, this.headers)
- makeObservable(this, {
- excludedSchemas: observable,
- })
+ makeObservable(this, {})
}
/**
@@ -349,7 +326,16 @@ export default class MetaStore implements IMetaStore {
async updateTableRealtime(table: PostgresTable, enable: boolean) {
let publicationUpdateError
const publications = this.publications.list()
- const publicTables = this.tables.list((table: PostgresTable) => table.schema === 'public')
+
+ const queryClient = getQueryClient()
+ const project = await getCachedProjectDetail(queryClient, this.rootStore.ui.selectedProjectRef)
+ const projectRef = project?.ref
+ const connectionString = project?.connectionString
+ const publicTables = await queryClient.fetchQuery({
+ queryKey: tableKeys.list(projectRef, 'public'),
+ queryFn: ({ signal }) =>
+ getTables({ projectRef, connectionString, schema: 'public' }, signal),
+ })
let realtimePublication = publications.find((pub) => pub.name === 'supabase_realtime')
if (realtimePublication === undefined) {
@@ -596,8 +582,15 @@ export default class MetaStore implements IMetaStore {
})
}
- await this.tables.load()
- const tables = this.tables.list()
+ const queryClient = getQueryClient()
+ const project = await getCachedProjectDetail(queryClient, this.rootStore.ui.selectedProjectRef)
+ const projectRef = project?.ref
+ const connectionString = project?.connectionString
+ const tables = await queryClient.fetchQuery({
+ queryKey: tableKeys.list(projectRef, 'public'),
+ queryFn: ({ signal }) => getTables({ projectRef, connectionString }, signal),
+ })
+
const duplicatedTable = find(tables, { schema: sourceTableSchema, name: duplicatedTableName })
if (isRLSEnabled) {
@@ -621,8 +614,8 @@ export default class MetaStore implements IMetaStore {
importContent?: ImportContent
) {
// Create the table first
- const table: any = await this.tables.create(payload)
- if (table.error) throw table.error
+ const table = await this.tables.create(payload)
+ if ('error' in table) throw table.error
// If we face any errors during this process after the actual table creation
// We'll delete the table as a way to clean up and not leave behind bits that
@@ -745,7 +738,7 @@ export default class MetaStore implements IMetaStore {
}
// Finally, return the created table
- return await this.tables.loadById(table.id)
+ return table
} catch (error: any) {
this.tables.del(table.id)
throw error
@@ -865,7 +858,21 @@ export default class MetaStore implements IMetaStore {
// Update table's realtime configuration
await this.updateTableRealtime(table, isRealtimeEnabled)
- return { table: await this.tables.loadById(table.id), hasError }
+ const queryClient = getQueryClient()
+ const project = await getCachedProjectDetail(queryClient, this.rootStore.ui.selectedProjectRef)
+ const projectRef = project?.ref
+ const connectionString = project?.connectionString
+
+ queryClient.invalidateQueries(tableKeys.table(projectRef, table.id))
+
+ return {
+ table: await getTable({
+ projectRef,
+ connectionString,
+ id: table.id,
+ }),
+ hasError,
+ }
}
async insertRowsViaSpreadsheet(
@@ -1000,9 +1007,6 @@ export default class MetaStore implements IMetaStore {
this.columns.setUrl(`${this.baseUrl}/columns`)
this.columns.setHeaders(this.headers)
- this.schemas.setUrl(`${this.baseUrl}/schemas`)
- this.schemas.setHeaders(this.headers)
-
this.views.setUrl(`${this.baseUrl}/views`)
this.views.setHeaders(this.headers)
diff --git a/studio/stores/pgmeta/SchemaStore.ts b/studio/stores/pgmeta/SchemaStore.ts
deleted file mode 100644
index 2d9a0cc38bf..00000000000
--- a/studio/stores/pgmeta/SchemaStore.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import type { PostgresSchema } from '@supabase/postgres-meta'
-import PostgresMetaInterface from '../common/PostgresMetaInterface'
-import { IRootStore } from '../RootStore'
-
-export default class SchemaStore extends PostgresMetaInterface {
- constructor(
- rootStore: IRootStore,
- dataUrl: string,
- headers?: {
- [prop: string]: any
- },
- options?: { identifier: string }
- ) {
- super(rootStore, dataUrl, headers, options)
- }
-
- // Dashboard to hide schemas with pg_ prefix
- list(filter: any) {
- const schemasFilter = (schema: PostgresSchema) =>
- !schema.name.startsWith('pg_') && (typeof filter === 'function' ? filter(schema) : true)
-
- return super.list(schemasFilter)
- }
-}