chore: remove useExecuteSqlQuery() part 2 (#30467)

* foreign-key-constraints

* update entity-types stale time

* schemas query

* deprecate useExecuteSqlQuery

* users count query

* database size query

* indexes query

* keywords query

* migrations query

* table columns

* database functions

* database roles query

* fdws query

* replication lag query

* ongoing queries query

* vault secrets query

* remove unneeded staleTime: 0

* max connections query

* fix entity types key in tests

* Some fixes

---------

Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
This commit is contained in:
Alaister Young
2024-11-18 13:15:37 +08:00
committed by GitHub
parent af4cc07a36
commit 6c592dec99
65 changed files with 640 additions and 480 deletions

View File

@@ -123,8 +123,7 @@ export const UsersV2 = () => {
providers: selectedProviders,
})
// eslint-disable-next-line react-hooks/exhaustive-deps
const totalUsers = useMemo(() => countData?.result[0].count ?? 0, [countData?.result[0].count])
const totalUsers = countData ?? 0
const users = useMemo(() => data?.pages.flatMap((page) => page.result) ?? [], [data?.pages])
const handleScroll = (event: UIEvent<HTMLDivElement>) => {

View File

@@ -1,3 +1,4 @@
import { useQueryClient } from '@tanstack/react-query'
import { Check, ChevronsUpDown, Loader2 } from 'lucide-react'
import Link from 'next/link'
import { useEffect, useMemo, useState } from 'react'
@@ -6,7 +7,7 @@ import { toast } from 'sonner'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import CodeEditor from 'components/ui/CodeEditor/CodeEditor'
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
import { useIndexesQuery } from 'data/database/indexes-query'
import { databaseKeys } from 'data/database/keys'
import { useSchemasQuery } from 'data/database/schemas-query'
import { useTableColumnsQuery } from 'data/database/table-columns-query'
import { useEntityTypesQuery } from 'data/entity-types/entity-types-infinite-query'
@@ -43,6 +44,7 @@ interface CreateIndexSidePanelProps {
}
const CreateIndexSidePanel = ({ visible, onClose }: CreateIndexSidePanelProps) => {
const queryClient = useQueryClient()
const { project } = useProjectContext()
const [selectedSchema, setSelectedSchema] = useState('public')
const [selectedEntity, setSelectedEntity] = useState<string | undefined>(undefined)
@@ -51,11 +53,7 @@ const CreateIndexSidePanel = ({ visible, onClose }: CreateIndexSidePanelProps) =
const [schemaDropdownOpen, setSchemaDropdownOpen] = useState(false)
const [tableDropdownOpen, setTableDropdownOpen] = useState(false)
const [searchTerm, setSearchTerm] = useState('')
const { refetch: refetchIndexes } = useIndexesQuery({
schema: selectedSchema,
projectRef: project?.ref,
connectionString: project?.connectionString,
})
const { data: schemas } = useSchemasQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
@@ -80,7 +78,7 @@ const CreateIndexSidePanel = ({ visible, onClose }: CreateIndexSidePanelProps) =
const { mutate: execute, isLoading: isExecuting } = useExecuteSqlMutation({
onSuccess: async () => {
await refetchIndexes()
await queryClient.invalidateQueries(databaseKeys.indexes(project?.ref, selectedSchema))
onClose()
toast.success(`Successfully created index`)
},
@@ -98,7 +96,7 @@ const CreateIndexSidePanel = ({ visible, onClose }: CreateIndexSidePanelProps) =
setSearchTerm(value)
}
const columns = tableColumns?.result[0]?.columns ?? []
const columns = tableColumns?.[0]?.columns ?? []
const columnOptions: MultiSelectOption[] = columns
.filter((column): column is NonNullable<typeof column> => column !== null)
.map((column) => ({

View File

@@ -53,12 +53,12 @@ const Indexes = () => {
})
const { mutate: execute, isLoading: isExecuting } = useExecuteSqlMutation({
onSuccess() {
refetchIndexes()
onSuccess: async () => {
await refetchIndexes()
setSelectedIndexToDelete(undefined)
toast.success('Successfully deleted index')
},
onError(error) {
onError: (error) => {
toast.error(`Failed to delete index: ${error.message}`)
},
})
@@ -69,7 +69,7 @@ const Indexes = () => {
const schema = schemas?.find((schema) => schema.name === selectedSchema)
const isLocked = protectedSchemas.some((s) => s.id === schema?.id)
const sortedIndexes = sortBy(allIndexes?.result ?? [], (index) => index.name.toLocaleLowerCase())
const sortedIndexes = sortBy(allIndexes ?? [], (index) => index.name.toLocaleLowerCase())
const indexes =
search.length > 0
? sortedIndexes.filter((index) => index.name.includes(search) || index.table.includes(search))

View File

@@ -22,8 +22,8 @@ const Migrations = () => {
})
const migrations =
search.length === 0
? data?.result ?? []
: data?.result.filter(
? data ?? []
: data?.filter(
(migration) => migration.version.includes(search) || migration.name?.includes(search)
) ?? []
@@ -62,9 +62,9 @@ const Migrations = () => {
)}
{isSuccess && (
<div>
{data.result.length <= 0 && <MigrationsEmptyState />}
{data.length <= 0 && <MigrationsEmptyState />}
{data.result.length > 0 && (
{data.length > 0 && (
<>
<div className="w-80 mb-4">
<Input

View File

@@ -16,6 +16,7 @@ import DeleteRoleModal from './DeleteRoleModal'
import RoleRow from './RoleRow'
import RoleRowSkeleton from './RoleRowSkeleton'
import { SUPABASE_ROLES } from './Roles.constants'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
type SUPABASE_ROLE = (typeof SUPABASE_ROLES)[number]
@@ -151,73 +152,60 @@ const RolesList = () => {
</div>
</Tooltip.Content>
</Tooltip.Root>
<Tooltip.Root delayDuration={0}>
<Tooltip.Trigger asChild>
<Button
type="primary"
disabled={!canUpdateRoles}
icon={<Plus size={12} />}
onClick={() => setIsCreatingRole(true)}
>
Add role
</Button>
</Tooltip.Trigger>
{!canUpdateRoles && (
<Tooltip.Content align="start" side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-alternative py-1 px-2 leading-none shadow',
'border border-background text-xs',
].join(' ')}
>
You need additional permissions to add a new role
</div>
</Tooltip.Content>
)}
</Tooltip.Root>
<ButtonTooltip
type="primary"
disabled={!canUpdateRoles}
icon={<Plus size={12} />}
onClick={() => setIsCreatingRole(true)}
tooltip={{
content: {
side: 'bottom',
text: !canUpdateRoles
? 'You need additional permissions to add a new role'
: undefined,
},
}}
>
Add role
</ButtonTooltip>
</div>
</div>
<div className="space-y-4">
{supabaseRoles.length > 0 && (
<div>
<div className="bg-surface-100 border border-default px-6 py-3 rounded-t flex items-center space-x-4">
<p className="text-sm text-foreground-light">Roles managed by Supabase</p>
<Badge variant="brand">Protected</Badge>
</div>
{isLoading
? Array.from({ length: 5 }).map((_, i) => <RoleRowSkeleton key={i} index={i} />)
: supabaseRoles.map((role) => (
<RoleRow
disabled
key={role.id}
role={role}
onSelectDelete={setSelectedRoleToDelete}
/>
))}
<div>
<div className="bg-surface-100 border border-default px-6 py-3 rounded-t flex items-center space-x-4">
<p className="text-sm text-foreground-light">Roles managed by Supabase</p>
<Badge variant="brand">Protected</Badge>
</div>
)}
{otherRoles.length > 0 && (
<div>
<div className="bg-surface-100 border border-default px-6 py-3 rounded-t">
<p className="text-sm text-foreground-light">Other database roles</p>
</div>
{isLoading
? Array.from({ length: 5 }).map((_, i) => <RoleRowSkeleton key={i} index={i} />)
: supabaseRoles.map((role) => (
<RoleRow
disabled
key={role.id}
role={role}
onSelectDelete={setSelectedRoleToDelete}
/>
))}
</div>
{isLoading
? Array.from({ length: 3 }).map((_, i) => <RoleRowSkeleton key={i} index={i} />)
: otherRoles.map((role) => (
<RoleRow
key={role.id}
disabled={!canUpdateRoles}
role={role}
onSelectDelete={setSelectedRoleToDelete}
/>
))}
<div>
<div className="bg-surface-100 border border-default px-6 py-3 rounded-t">
<p className="text-sm text-foreground-light">Other database roles</p>
</div>
)}
{isLoading
? Array.from({ length: 3 }).map((_, i) => <RoleRowSkeleton key={i} index={i} />)
: otherRoles.map((role) => (
<RoleRow
key={role.id}
disabled={!canUpdateRoles}
role={role}
onSelectDelete={setSelectedRoleToDelete}
/>
))}
</div>
</div>
{filterString.length > 0 && filteredRoles.length === 0 && (

View File

@@ -49,7 +49,7 @@ const EditWrapper = () => {
connectionString: project?.connectionString,
})
const wrappers = data?.result ?? []
const wrappers = data ?? []
const foundWrapper = wrappers.find((w) => Number(w.id) === Number(id))
// this call to useImmutableValue should be removed if the redirect after update is also removed
const wrapper = useImmutableValue(foundWrapper)

View File

@@ -19,7 +19,7 @@ const Wrappers = ({ isEnabled }: { isEnabled: boolean }) => {
const [open, setOpen] = useState<string>('')
const [selectedWrapperToDelete, setSelectedWrapperToDelete] = useState<FDW>()
const wrappers = data?.result ?? []
const wrappers = data ?? []
return (
<>

View File

@@ -6,6 +6,7 @@ import { UseFormReturn } from 'react-hook-form'
import { useParams } from 'common'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import { useRemainingDurationForDiskAttributeUpdate } from 'data/config/disk-attributes-query'
import { useDiskUtilizationQuery } from 'data/config/disk-utilization-query'
import { useDatabaseSizeQuery } from 'data/database/database-size-query'
import { GB } from 'lib/constants'
@@ -19,7 +20,6 @@ import {
} from 'ui'
import { DiskStorageSchemaType } from '../DiskManagement.schema'
import { AUTOSCALING_THRESHOLD } from './DiskManagement.constants'
import { useRemainingDurationForDiskAttributeUpdate } from 'data/config/disk-attributes-query'
interface DiskSpaceBarProps {
form: UseFormReturn<DiskStorageSchemaType>
@@ -56,7 +56,7 @@ export default function DiskSpaceBar({ form }: DiskSpaceBarProps) {
})
const { remainingDuration } = useRemainingDurationForDiskAttributeUpdate({ projectRef: ref })
const databaseSizeBytes = data?.result[0].db_size ?? 0
const databaseSizeBytes = data ?? 0
return (
<div className="flex flex-col gap-2">
<div className="flex items-center h-6 gap-3">

View File

@@ -62,7 +62,7 @@ const DiskSizeConfiguration = ({ disabled = false }: DiskSizeConfigurationProps)
projectRef: project?.ref,
connectionString: project?.connectionString,
})
const databaseSizeBytesUsed = data?.result[0].db_size ?? 0
const databaseSizeBytesUsed = data ?? 0
return (
<div id="diskManagement">

View File

@@ -103,9 +103,7 @@ const SecretRow = ({ secret, onSelectEdit, onSelectRemove }: SecretRowProps) =>
</p>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button type="text" className="px-1">
<MoreVertical />
</Button>
<Button type="text" className="px-1" icon={<MoreVertical />} />
</DropdownMenuTrigger>
<DropdownMenuContent side="bottom">
<Tooltip.Root delayDuration={0}>

View File

@@ -12,7 +12,6 @@ import type { Constraint } from 'data/database/constraints-query'
import type { ForeignKeyConstraint } from 'data/database/foreign-key-constraints-query'
import { databaseKeys } from 'data/database/keys'
import { entityTypeKeys } from 'data/entity-types/keys'
import { sqlKeys } from 'data/sql/keys'
import { tableEditorKeys } from 'data/table-editor/keys'
import { isTableLike } from 'data/table-editor/table-editor-types'
import { tableRowKeys } from 'data/table-rows/keys'
@@ -258,7 +257,9 @@ const SidePanelEditor = ({
await Promise.all([
queryClient.invalidateQueries(tableEditorKeys.tableEditor(project?.ref, selectedTable?.id)),
queryClient.invalidateQueries(sqlKeys.query(project?.ref, ['foreign-key-constraints'])),
queryClient.invalidateQueries(
databaseKeys.foreignKeyConstraints(project?.ref, selectedTable?.schema)
),
queryClient.invalidateQueries(
databaseKeys.tableDefinition(project?.ref, selectedTable?.id)
),

View File

@@ -16,7 +16,6 @@ import { entityTypeKeys } from 'data/entity-types/keys'
import { prefetchEditorTablePage } from 'data/prefetchers/project.$ref.editor.$id'
import { getQueryClient } from 'data/query-client'
import { executeSql } from 'data/sql/execute-sql-query'
import { sqlKeys } from 'data/sql/keys'
import { tableEditorKeys } from 'data/table-editor/keys'
import { prefetchTableEditor } from 'data/table-editor/table-editor-query'
import { tableRowKeys } from 'data/table-rows/keys'
@@ -760,7 +759,7 @@ export const updateTable = async ({
await Promise.all([
queryClient.invalidateQueries(tableEditorKeys.tableEditor(projectRef, table.id)),
queryClient.invalidateQueries(sqlKeys.query(projectRef, ['foreign-key-constraints'])),
queryClient.invalidateQueries(databaseKeys.foreignKeyConstraints(projectRef, table.schema)),
queryClient.invalidateQueries(databaseKeys.tableDefinition(projectRef, table.id)),
queryClient.invalidateQueries(entityTypeKeys.list(projectRef)),
])

View File

@@ -77,7 +77,7 @@ export function useDatabaseGotoCommands(options?: CommandOptions) {
id: 'nav-database-hooks',
name: 'Webhooks',
value: 'Database: Webhooks',
route: `/project/${ref}/database/hooks`,
route: `/project/${ref}/integrations/hooks`,
defaultHidden: true,
},
{

View File

@@ -77,7 +77,7 @@ export function useDatabaseGotoCommands(options?: CommandOptions) {
id: 'nav-database-hooks',
name: 'Webhooks',
value: 'Database: Webhooks',
route: `/project/${ref}/database/hooks`,
route: `/project/${ref}/integrations/hooks`,
defaultHidden: true,
},
{

View File

@@ -17,8 +17,8 @@ import Results from 'components/interfaces/SQLEditor/UtilityPanel/Results'
import { useSqlDebugMutation } from 'data/ai/sql-debug-mutation'
import { databasePoliciesKeys } from 'data/database-policies/keys'
import { useEntityDefinitionQuery } from 'data/database/entity-definition-query'
import { databaseKeys } from 'data/database/keys'
import { QueryResponseError, useExecuteSqlMutation } from 'data/sql/execute-sql-mutation'
import { sqlKeys } from 'data/sql/keys'
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
import { usePrevious } from 'hooks/deprecated'
import { useLocalStorage } from 'hooks/misc/useLocalStorage'
@@ -113,7 +113,7 @@ export const AiAssistantPanel = () => {
if (editor !== null) {
switch (editor) {
case 'functions':
await queryClient.invalidateQueries(sqlKeys.query(ref, ['functions-list']))
await queryClient.invalidateQueries(databaseKeys.databaseFunctions(ref))
break
case 'rls-policies':
await queryClient.invalidateQueries(databasePoliciesKeys.list(ref))

View File

@@ -11,9 +11,7 @@ Referencing the other files in the `data` directory is a good way to see how to
### SQL queries and mutations
The dashboard often needs to query the users database directly. There are already some reusable hooks for this in `data/sql`. Reference the `data/fdw` directory for an example of how to use them.
Note that the query key of the `useExecuteSqlQuery` hook will be automatically filled with an md5 hash of the SQL query, unless you provide a name for the query.
The dashboard often needs to query the users database directly. You can use the `executeSql()` function to do this.
## Files

View File

@@ -2,9 +2,9 @@ import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react
import { toast } from 'sonner'
import { configKeys } from 'data/config/keys'
import { databaseKeys } from 'data/database/keys'
import { handleError, patch } from 'data/fetchers'
import { executeSql } from 'data/sql/execute-sql-query'
import { sqlKeys } from 'data/sql/keys'
import type { ResponseError } from 'types'
export type CreateAndExposeAPISchemaVariables = {
@@ -69,7 +69,7 @@ export const useCreateAndExposeAPISchemaMutation = ({
async onSuccess(data, variables, context) {
const { projectRef } = variables
await Promise.all([
queryClient.invalidateQueries(sqlKeys.query(projectRef, ['schemas', 'list'])),
queryClient.invalidateQueries(databaseKeys.schemas(projectRef)),
queryClient.invalidateQueries(configKeys.postgrest(projectRef)),
])
await onSuccess?.(data, variables, context)

View File

@@ -6,7 +6,7 @@ export const authKeys = {
keywords: string | undefined
filter: string | undefined
}
) => ['auth', projectRef, 'users', ...(params ? [params] : [])] as const,
) => ['projects', projectRef, 'users', ...(params ? [params] : [])] as const,
usersInfinite: (
projectRef: string | undefined,
@@ -17,7 +17,13 @@ export const authKeys = {
sort: string | undefined
order: string | undefined
}
) => ['auth', projectRef, 'users-infinite', ...(params ? [params].filter(Boolean) : [])] as const,
) =>
[
'projects',
projectRef,
'users-infinite',
...(params ? [params].filter(Boolean) : []),
] as const,
usersCount: (
projectRef: string | undefined,
params?: {
@@ -25,8 +31,9 @@ export const authKeys = {
filter: string | undefined
providers: string[] | undefined
}
) => ['auth', projectRef, 'users-count', ...(params ? [params].filter(Boolean) : [])] as const,
) =>
['projects', projectRef, 'users-count', ...(params ? [params].filter(Boolean) : [])] as const,
authConfig: (projectRef: string | undefined) => ['auth', projectRef, 'config'] as const,
authConfig: (projectRef: string | undefined) => ['projects', projectRef, 'auth-config'] as const,
accessToken: () => ['access-token'] as const,
}

View File

@@ -2,7 +2,6 @@ import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react
import { toast } from 'sonner'
import { handleError, post } from 'data/fetchers'
import { sqlKeys } from 'data/sql/keys'
import type { ResponseError } from 'types'
import { authKeys } from './keys'
@@ -46,9 +45,9 @@ export const useUserCreateMutation = ({
async onSuccess(data, variables, context) {
const { projectRef } = variables
Promise.all([
await Promise.all([
queryClient.invalidateQueries(authKeys.usersInfinite(projectRef)),
queryClient.invalidateQueries(sqlKeys.query(projectRef, authKeys.usersCount(projectRef))),
queryClient.invalidateQueries(authKeys.usersCount(projectRef)),
])
await onSuccess?.(data, variables, context)

View File

@@ -2,7 +2,6 @@ import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react
import { toast } from 'sonner'
import { del, handleError } from 'data/fetchers'
import { sqlKeys } from 'data/sql/keys'
import type { ResponseError } from 'types'
import { authKeys } from './keys'
import type { User } from './users-infinite-query'
@@ -41,7 +40,7 @@ export const useUserDeleteMutation = ({
await Promise.all([
queryClient.invalidateQueries(authKeys.usersInfinite(projectRef)),
queryClient.invalidateQueries(sqlKeys.query(projectRef, authKeys.usersCount(projectRef))),
queryClient.invalidateQueries(authKeys.usersCount(projectRef)),
])
await onSuccess?.(data, variables, context)

View File

@@ -2,8 +2,6 @@ import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react
import { toast } from 'sonner'
import { handleError, post } from 'data/fetchers'
import { sqlKeys } from 'data/sql/keys'
import { useFlag } from 'hooks/ui/useFlag'
import type { ResponseError } from 'types'
import { authKeys } from './keys'
@@ -41,7 +39,7 @@ export const useUserInviteMutation = ({
await Promise.all([
queryClient.invalidateQueries(authKeys.usersInfinite(projectRef)),
queryClient.invalidateQueries(sqlKeys.query(projectRef, authKeys.usersCount(projectRef))),
queryClient.invalidateQueries(authKeys.usersCount(projectRef)),
])
await onSuccess?.(data, variables, context)

View File

@@ -1,6 +1,6 @@
import { UseQueryOptions } from '@tanstack/react-query'
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { ExecuteSqlData, ExecuteSqlError, useExecuteSqlQuery } from 'data/sql/execute-sql-query'
import { executeSql, ExecuteSqlError } from 'data/sql/execute-sql-query'
import { authKeys } from './keys'
import { Filter } from './users-infinite-query'
@@ -12,12 +12,12 @@ type UsersCountVariables = {
providers?: string[]
}
const getUsersCountSQl = ({
verified,
const getUsersCountSql = ({
filter,
keywords,
providers,
}: {
verified?: Filter
filter?: Filter
keywords?: string
providers?: string[]
}) => {
@@ -32,11 +32,11 @@ const getUsersCountSQl = ({
)
}
if (verified === 'verified') {
if (filter === 'verified') {
conditions.push(`email_confirmed_at IS NOT NULL or phone_confirmed_at IS NOT NULL`)
} else if (verified === 'anonymous') {
} else if (filter === 'anonymous') {
conditions.push(`is_anonymous is true`)
} else if (verified === 'unverified') {
} else if (filter === 'unverified') {
conditions.push(`email_confirmed_at IS NULL AND phone_confirmed_at IS NULL`)
}
@@ -59,20 +59,44 @@ const getUsersCountSQl = ({
return `${baseQueryCount}${conditions.length > 0 ? ` where ${combinedConditions}` : ''};`
}
export type UsersCountData = { result: [{ count: number }] }
export type UsersCountError = ExecuteSqlError
export const useUsersCountQuery = <TData extends UsersCountData = UsersCountData>(
export async function getUsersCount(
{ projectRef, connectionString, keywords, filter, providers }: UsersCountVariables,
options: UseQueryOptions<ExecuteSqlData, UsersCountError, TData> = {}
) => {
return useExecuteSqlQuery(
signal?: AbortSignal
) {
const sql = getUsersCountSql({ filter, keywords, providers })
const { result } = await executeSql(
{
projectRef,
connectionString,
sql: getUsersCountSQl({ keywords, verified: filter, providers }),
queryKey: authKeys.usersCount(projectRef, { keywords, filter, providers }),
sql,
queryKey: ['users-count'],
},
options
signal
)
const count = result?.[0]?.count
if (typeof count !== 'number') {
throw new Error('Error fetching users count')
}
return count
}
export type UsersCountData = Awaited<ReturnType<typeof getUsersCount>>
export type UsersCountError = ExecuteSqlError
export const useUsersCountQuery = <TData = UsersCountData>(
{ projectRef, connectionString, keywords, filter, providers }: UsersCountVariables,
{ enabled = true, ...options }: UseQueryOptions<UsersCountData, UsersCountError, TData> = {}
) =>
useQuery<UsersCountData, UsersCountError, TData>(
authKeys.usersCount(projectRef, { keywords, filter, providers }),
({ signal }) =>
getUsersCount({ projectRef, connectionString, keywords, filter, providers }, signal),
{
enabled: enabled && typeof projectRef !== 'undefined',
...options,
}
)

View File

@@ -109,9 +109,7 @@ export const useUsersInfiniteQuery = <TData = UsersData>(
)
},
{
staleTime: 0,
enabled: enabled && typeof projectRef !== 'undefined' && isActive,
getNextPageParam(lastPage, pages) {
const page = pages.length
const hasNextPage = lastPage.result.length <= USERS_PAGE_LIMIT

View File

@@ -4,7 +4,6 @@ import { toast } from 'sonner'
import { databaseKeys } from 'data/database/keys'
import { entityTypeKeys } from 'data/entity-types/keys'
import { del, handleError } from 'data/fetchers'
import { sqlKeys } from 'data/sql/keys'
import { tableEditorKeys } from 'data/table-editor/keys'
import { tableRowKeys } from 'data/table-rows/keys'
import { viewKeys } from 'data/views/keys'
@@ -58,11 +57,13 @@ export const useDatabaseColumnDeleteMutation = ({
async onSuccess(data, variables, context) {
const { projectRef, table } = variables
await Promise.all([
queryClient.invalidateQueries(sqlKeys.query(projectRef, ['foreign-key-constraints'])),
// refetch all entities in the sidebar because deleting a column may regenerate a view (and change its id)
queryClient.invalidateQueries(entityTypeKeys.list(projectRef)),
...(table !== undefined
? [
queryClient.invalidateQueries(
databaseKeys.foreignKeyConstraints(projectRef, table?.schema)
),
queryClient.invalidateQueries(tableEditorKeys.tableEditor(projectRef, table.id)),
queryClient.invalidateQueries(databaseKeys.tableDefinition(projectRef, table.id)),
// invalidate all views from this schema, not sure if this is needed since you can't actually delete a column

View File

@@ -3,8 +3,8 @@ import { toast } from 'sonner'
import { z } from 'zod'
import pgMeta from '@supabase/pg-meta'
import { databaseKeys } from 'data/database/keys'
import { executeSql } from 'data/sql/execute-sql-query'
import { sqlKeys } from 'data/sql/keys'
import type { ResponseError } from 'types'
export type DatabaseFunctionCreateVariables = {
@@ -47,7 +47,7 @@ export const useDatabaseFunctionCreateMutation = ({
{
async onSuccess(data, variables, context) {
const { projectRef } = variables
await queryClient.invalidateQueries(sqlKeys.query(projectRef, ['functions-list']))
await queryClient.invalidateQueries(databaseKeys.databaseFunctions(projectRef))
await onSuccess?.(data, variables, context)
},
async onError(data, variables, context) {

View File

@@ -3,8 +3,8 @@ import { toast } from 'sonner'
import { z } from 'zod'
import pgMeta from '@supabase/pg-meta'
import { databaseKeys } from 'data/database/keys'
import { executeSql } from 'data/sql/execute-sql-query'
import { sqlKeys } from 'data/sql/keys'
import type { ResponseError } from 'types'
import { DatabaseFunction } from './database-functions-query'
@@ -48,7 +48,7 @@ export const useDatabaseFunctionDeleteMutation = ({
{
async onSuccess(data, variables, context) {
const { projectRef } = variables
await queryClient.invalidateQueries(sqlKeys.query(projectRef, ['functions-list']))
await queryClient.invalidateQueries(databaseKeys.databaseFunctions(projectRef))
await onSuccess?.(data, variables, context)
},
async onError(data, variables, context) {

View File

@@ -1,7 +1,7 @@
import pgMeta from '@supabase/pg-meta'
import { UseQueryOptions } from '@tanstack/react-query'
import type { ExecuteSqlData, ExecuteSqlError } from 'data/sql/execute-sql-query'
import { useExecuteSqlQuery } from 'data/sql/execute-sql-query'
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { databaseKeys } from 'data/database/keys'
import { executeSql } from 'data/sql/execute-sql-query'
import type { ResponseError } from 'types'
import { z } from 'zod'
@@ -14,27 +14,38 @@ export type DatabaseFunction = z.infer<typeof pgMeta.functions.pgFunctionZod>
const pgMetaFunctionsList = pgMeta.functions.list()
export type DatabaseFunctionsData = z.infer<typeof pgMetaFunctionsList.zod>
export type DatabaseFunctionsError = ResponseError
export const useDatabaseFunctionsQuery = <
TData extends DatabaseFunctionsData = DatabaseFunctionsData,
>(
export async function getDatabaseFunctions(
{ projectRef, connectionString }: DatabaseFunctionsVariables,
options: UseQueryOptions<ExecuteSqlData, ExecuteSqlError, TData> = {}
) => {
return useExecuteSqlQuery(
signal?: AbortSignal
) {
const { result } = await executeSql(
{
projectRef,
connectionString,
sql: pgMetaFunctionsList.sql,
queryKey: ['functions-list'],
queryKey: ['database-functions'],
},
signal
)
return result as DatabaseFunction[]
}
export type DatabaseFunctionsData = z.infer<typeof pgMetaFunctionsList.zod>
export type DatabaseFunctionsError = ResponseError
export const useDatabaseFunctionsQuery = <TData = DatabaseFunctionsData>(
{ projectRef, connectionString }: DatabaseFunctionsVariables,
{
enabled = true,
...options
}: UseQueryOptions<DatabaseFunctionsData, DatabaseFunctionsError, TData> = {}
) =>
useQuery<DatabaseFunctionsData, DatabaseFunctionsError, TData>(
databaseKeys.databaseFunctions(projectRef),
({ signal }) => getDatabaseFunctions({ projectRef, connectionString }, signal),
{
select(data) {
return (data as any)?.result ?? []
},
enabled: enabled && typeof projectRef !== 'undefined',
...options,
}
)
}

View File

@@ -3,8 +3,8 @@ import { toast } from 'sonner'
import { z } from 'zod'
import pgMeta from '@supabase/pg-meta'
import { databaseKeys } from 'data/database/keys'
import { executeSql } from 'data/sql/execute-sql-query'
import { sqlKeys } from 'data/sql/keys'
import type { ResponseError } from 'types'
import type { DatabaseFunction } from './database-functions-query'
@@ -50,7 +50,7 @@ export const useDatabaseFunctionUpdateMutation = ({
{
async onSuccess(data, variables, context) {
const { projectRef } = variables
await queryClient.invalidateQueries(sqlKeys.query(projectRef, ['functions-list']))
await queryClient.invalidateQueries(databaseKeys.databaseFunctions(projectRef))
await onSuccess?.(data, variables, context)
},
async onError(data, variables, context) {

View File

@@ -1,9 +1,9 @@
import pgMeta from '@supabase/pg-meta'
import { QueryClient, UseQueryOptions } from '@tanstack/react-query'
import { QueryClient, useQuery, UseQueryOptions } from '@tanstack/react-query'
import { z } from 'zod'
import { ExecuteSqlData, ExecuteSqlError, useExecuteSqlQuery } from 'data/sql/execute-sql-query'
import { sqlKeys } from 'data/sql/keys'
import { executeSql, ExecuteSqlError } from 'data/sql/execute-sql-query'
import { databaseRoleKeys } from './keys'
export type DatabaseRolesVariables = {
projectRef?: string
@@ -14,28 +14,34 @@ export type PgRole = z.infer<typeof pgMeta.roles.zod>
const pgMetaRolesList = pgMeta.roles.list()
export async function getDatabaseRoles(
{ projectRef, connectionString }: DatabaseRolesVariables,
signal?: AbortSignal
) {
const { result } = await executeSql(
{ projectRef, connectionString, sql: pgMetaRolesList.sql, queryKey: ['database-roles'] },
signal
)
return result as PgRole[]
}
export type DatabaseRolesData = z.infer<typeof pgMetaRolesList.zod>
export type DatabaseRolesError = ExecuteSqlError
export const useDatabaseRolesQuery = <TData = DatabaseRolesData>(
{ projectRef, connectionString }: DatabaseRolesVariables,
options: UseQueryOptions<ExecuteSqlData, DatabaseRolesError, TData> = {}
{ enabled = true, ...options }: UseQueryOptions<DatabaseRolesData, DatabaseRolesError, TData> = {}
) =>
useExecuteSqlQuery(
useQuery<DatabaseRolesData, DatabaseRolesError, TData>(
databaseRoleKeys.databaseRoles(projectRef),
({ signal }) => getDatabaseRoles({ projectRef, connectionString }, signal),
{
projectRef,
connectionString,
sql: pgMetaRolesList.sql,
queryKey: ['roles', 'list'],
},
{
select(data) {
return data.result
},
enabled: enabled && typeof projectRef !== 'undefined',
...options,
}
)
export function invalidateRolesQuery(client: QueryClient, projectRef: string | undefined) {
return client.invalidateQueries(sqlKeys.query(projectRef, ['roles', 'list']))
return client.invalidateQueries(databaseRoleKeys.databaseRoles(projectRef))
}

View File

@@ -0,0 +1,4 @@
export const databaseRoleKeys = {
databaseRoles: (projectRef: string | undefined) =>
['projects', projectRef, 'database-roles'] as const,
}

View File

@@ -42,7 +42,6 @@ export const useDatabaseHooksQuery = <TData = DatabaseTriggersData>(
databaseTriggerKeys.list(projectRef),
({ signal }) => getDatabaseTriggers({ projectRef, connectionString }, signal),
{
staleTime: 0,
// @ts-ignore
select(data) {
return (data as PostgresTrigger[]).filter(

View File

@@ -1,7 +1,8 @@
import { UseQueryOptions } from '@tanstack/react-query'
import { ExecuteSqlData, ExecuteSqlError, useExecuteSqlQuery } from '../sql/execute-sql-query'
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { executeSql, ExecuteSqlError } from '../sql/execute-sql-query'
import { databaseKeys } from './keys'
export const getDatabaseSizeQuery = () => {
export const getDatabaseSizeSql = () => {
const sql = /* SQL */ `
select sum(pg_database_size(pg_database.datname))::bigint as db_size from pg_database;
`.trim()
@@ -14,23 +15,42 @@ export type DatabaseSizeVariables = {
connectionString?: string
}
export type DatabaseSizeData = { result: { db_size: number }[] }
export type DatabaseSizeError = ExecuteSqlError
export const useDatabaseSizeQuery = <TData extends DatabaseSizeData = DatabaseSizeData>(
export async function getDatabaseSize(
{ projectRef, connectionString }: DatabaseSizeVariables,
options: UseQueryOptions<ExecuteSqlData, DatabaseSizeError, TData> = {}
) => {
return useExecuteSqlQuery(
signal?: AbortSignal
) {
const sql = getDatabaseSizeSql()
const { result } = await executeSql(
{
projectRef,
connectionString,
sql: getDatabaseSizeQuery(),
sql,
queryKey: ['database-size'],
},
signal
)
const dbSize = result?.[0]?.db_size
if (typeof dbSize !== 'number') {
throw new Error('Error fetching dbSize')
}
return dbSize
}
export type DatabaseSizeData = Awaited<ReturnType<typeof getDatabaseSize>>
export type DatabaseSizeError = ExecuteSqlError
export const useDatabaseSizeQuery = <TData = DatabaseSizeData>(
{ projectRef, connectionString }: DatabaseSizeVariables,
{ enabled = true, ...options }: UseQueryOptions<DatabaseSizeData, DatabaseSizeError, TData> = {}
) =>
useQuery<DatabaseSizeData, DatabaseSizeError, TData>(
databaseKeys.databaseSize(projectRef),
({ signal }) => getDatabaseSize({ projectRef, connectionString }, signal),
{
enabled: enabled && typeof projectRef !== 'undefined',
...options,
staleTime: 1000 * 60, // default good for a minute
}
)
}

View File

@@ -1,12 +1,7 @@
import { QueryClient, UseQueryOptions } from '@tanstack/react-query'
import { QueryClient, useQuery, UseQueryOptions } from '@tanstack/react-query'
import { sqlKeys } from 'data/sql/keys'
import {
executeSql,
ExecuteSqlData,
ExecuteSqlError,
useExecuteSqlQuery,
} from '../sql/execute-sql-query'
import { executeSql, ExecuteSqlError } from '../sql/execute-sql-query'
import { databaseKeys } from './keys'
type GetForeignKeyConstraintsVariables = {
schema?: string
@@ -42,7 +37,11 @@ export type ForeignKeyConstraint = {
target_columns: string[]
}
export const getForeignKeyConstraintsQuery = ({ schema }: GetForeignKeyConstraintsVariables) => {
export const getForeignKeyConstraintsSql = ({ schema }: GetForeignKeyConstraintsVariables) => {
if (!schema) {
throw new Error('schema is required')
}
const sql = /* SQL */ `
SELECT
con.oid as id,
@@ -89,7 +88,7 @@ FROM
INNER JOIN pg_namespace fnsp ON fnsp.oid = frel.relnamespace
WHERE
con.contype = 'f'
${schema !== undefined ? `AND nsp.nspname = '${schema}'` : ''};
AND nsp.nspname = '${schema}'
`.trim()
return sql
@@ -100,60 +99,55 @@ export type ForeignKeyConstraintsVariables = GetForeignKeyConstraintsVariables &
connectionString?: string
}
export type ForeignKeyConstraintsData = ForeignKeyConstraint[]
export type ForeignKeyConstraintsError = ExecuteSqlError
function select(
data:
| {
result: any
}
| undefined
export async function getForeignKeyConstraints(
{ projectRef, connectionString, schema }: ForeignKeyConstraintsVariables,
signal?: AbortSignal
) {
return (data?.result ?? []).map((foreignKey: ForeignKeyConstraintRaw) => {
const sql = getForeignKeyConstraintsSql({ schema })
const { result } = await executeSql(
{
projectRef,
connectionString,
sql,
queryKey: ['foreign-key-constraints', schema],
},
signal
)
return (result ?? []).map((foreignKey: ForeignKeyConstraintRaw) => {
return {
...foreignKey,
source_columns: foreignKey.source_columns.replace('{', '').replace('}', '').split(','),
target_columns: foreignKey.target_columns.replace('{', '').replace('}', '').split(','),
}
})
}) as ForeignKeyConstraint[]
}
export const useForeignKeyConstraintsQuery = <
TData extends ForeignKeyConstraintsData = ForeignKeyConstraintsData,
>(
export type ForeignKeyConstraintsData = Awaited<ReturnType<typeof getForeignKeyConstraints>>
export type ForeignKeyConstraintsError = ExecuteSqlError
export const useForeignKeyConstraintsQuery = <TData = ForeignKeyConstraintsData>(
{ projectRef, connectionString, schema }: ForeignKeyConstraintsVariables,
options: UseQueryOptions<ExecuteSqlData, ForeignKeyConstraintsError, TData> = {}
) => {
return useExecuteSqlQuery(
{
enabled = true,
...options
}: UseQueryOptions<ForeignKeyConstraintsData, ForeignKeyConstraintsError, TData> = {}
) =>
useQuery<ForeignKeyConstraintsData, ForeignKeyConstraintsError, TData>(
databaseKeys.foreignKeyConstraints(projectRef, schema),
({ signal }) => getForeignKeyConstraints({ projectRef, connectionString, schema }, signal),
{
projectRef,
connectionString,
sql: getForeignKeyConstraintsQuery({ schema }),
queryKey: ['foreign-key-constraints', schema],
},
{
select,
enabled: enabled && typeof projectRef !== 'undefined' && typeof schema !== 'undefined',
...options,
}
)
}
export function prefetchForeignKeyConstraints(
client: QueryClient,
{ projectRef, connectionString, schema }: ForeignKeyConstraintsVariables
) {
return client
.fetchQuery(sqlKeys.query(projectRef, ['foreign-key-constraints', schema]), ({ signal }) =>
executeSql(
{
projectRef,
connectionString,
sql: getForeignKeyConstraintsQuery({ schema }),
queryKey: ['foreign-key-constraints', schema],
},
signal
)
)
.then((data) => select(data) as ForeignKeyConstraintsData)
return client.fetchQuery(databaseKeys.foreignKeyConstraints(projectRef, schema), ({ signal }) =>
getForeignKeyConstraints({ projectRef, connectionString, schema }, signal)
)
}

View File

@@ -1,17 +1,12 @@
import { UseQueryOptions } from '@tanstack/react-query'
import { ExecuteSqlData, ExecuteSqlError, useExecuteSqlQuery } from '../sql/execute-sql-query'
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { executeSql, ExecuteSqlError } from '../sql/execute-sql-query'
import { databaseKeys } from './keys'
// [Joshen] Future refactor to move to pg-meta once available
export type DatabaseIndex = {
name: string
schema: string
table: string
definition: string
enabled: boolean
type GetIndexesArgs = {
schema?: string
}
export const getIndexesQuery = ({ schema }: { schema: string }) => {
export const getIndexesSql = ({ schema }: GetIndexesArgs) => {
const sql = /* SQL */ `
SELECT schemaname as "schema",
tablename as "table",
@@ -24,29 +19,49 @@ WHERE schemaname = '${schema}';
return sql
}
type getIndexesDefinition = {
export type DatabaseIndex = {
name: string
schema: string
table: string
definition: string
enabled: boolean
}
export type IndexesVariables = getIndexesDefinition & {
export type IndexesVariables = GetIndexesArgs & {
projectRef?: string
connectionString?: string
}
export type IndexesData = { result: DatabaseIndex[] }
export async function getIndexes(
{ schema, projectRef, connectionString }: IndexesVariables,
signal?: AbortSignal
) {
if (!schema) {
throw new Error('schema is required')
}
const sql = getIndexesSql({ schema })
const { result } = await executeSql(
{ projectRef, connectionString, sql, queryKey: ['indexes', schema] },
signal
)
return result as DatabaseIndex[]
}
export type IndexesData = Awaited<ReturnType<typeof getIndexes>>
export type IndexesError = ExecuteSqlError
export const useIndexesQuery = <TData extends IndexesData = IndexesData>(
{ schema, projectRef, connectionString }: IndexesVariables,
options: UseQueryOptions<ExecuteSqlData, IndexesError, TData> = {}
) => {
return useExecuteSqlQuery(
export const useIndexesQuery = <TData = IndexesData>(
{ projectRef, connectionString, schema }: IndexesVariables,
{ enabled = true, ...options }: UseQueryOptions<IndexesData, IndexesError, TData> = {}
) =>
useQuery<IndexesData, IndexesError, TData>(
databaseKeys.indexes(projectRef, schema),
({ signal }) => getIndexes({ projectRef, connectionString, schema }, signal),
{
projectRef,
connectionString,
sql: getIndexesQuery({ schema }),
queryKey: ['indexes', schema],
},
options
enabled: enabled && typeof projectRef !== 'undefined' && typeof schema !== 'undefined',
...options,
}
)
}

View File

@@ -1,6 +1,16 @@
import { SupportedAssistantEntities } from 'components/ui/AIAssistantPanel/AIAssistant.types'
export const databaseKeys = {
schemas: (projectRef: string | undefined) => ['projects', projectRef, 'schemas'] as const,
indexes: (projectRef: string | undefined, schema: string | undefined) =>
['projects', projectRef, 'indexes', schema] as const,
keywords: (projectRef: string | undefined) => ['projects', projectRef, 'keywords'] as const,
migrations: (projectRef: string | undefined) => ['projects', projectRef, 'migrations'] as const,
tableColumns: (
projectRef: string | undefined,
schema: string | undefined,
table: string | undefined
) => ['projects', projectRef, 'table-columns', schema, table] as const,
databaseFunctions: (projectRef: string | undefined) =>
['projects', projectRef, 'database-functions'] as const,
entityDefinition: (projectRef: string | undefined, id?: number) =>
['projects', projectRef, 'entity-definition', id] as const,
entityDefinitions: (projectRef: string | undefined, schemas: string[]) =>
@@ -9,13 +19,20 @@ export const databaseKeys = {
['projects', projectRef, 'table-definition', id] as const,
viewDefinition: (projectRef: string | undefined, id?: number) =>
['projects', projectRef, 'view-definition', id] as const,
backups: (projectRef: string | undefined) => [projectRef, 'database', 'backups'] as const,
backups: (projectRef: string | undefined) =>
['projects', projectRef, 'database', 'backups'] as const,
poolingConfiguration: (projectRef: string | undefined) =>
[projectRef, 'database', 'pooling-configuration'] as const,
['projects', projectRef, 'database', 'pooling-configuration'] as const,
indexesFromQuery: (projectRef: string | undefined, query: string) =>
['projects', projectRef, 'indexes', { query }] as const,
indexAdvisorFromQuery: (projectRef: string | undefined, query: string) =>
['projects', projectRef, 'index-advisor', { query }] as const,
tableConstraints: (projectRef: string | undefined, id?: number) =>
['projects', projectRef, 'table-constraints', id] as const,
foreignKeyConstraints: (projectRef: string | undefined, schema?: string) =>
['projects', projectRef, 'foreign-key-constraints', schema] as const,
databaseSize: (projectRef: string | undefined) =>
['projects', projectRef, 'database-size'] as const,
maxConnections: (projectRef: string | undefined) =>
['projects', projectRef, 'max-connections'] as const,
}

View File

@@ -1,9 +1,8 @@
import { UseQueryOptions } from '@tanstack/react-query'
import { ExecuteSqlData, ExecuteSqlError, useExecuteSqlQuery } from '../sql/execute-sql-query'
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { executeSql, ExecuteSqlError } from '../sql/execute-sql-query'
import { databaseKeys } from './keys'
export type DatabaseKeyword = { word: string }
export const getKeywordsQuery = () => {
export const getKeywordsSql = () => {
const sql = /* SQL */ `
SELECT word FROM pg_get_keywords();
`.trim()
@@ -16,27 +15,32 @@ export type KeywordsVariables = {
connectionString?: string
}
export type KeywordsData = { result: string[] }
export async function getKeywords(
{ projectRef, connectionString }: KeywordsVariables,
signal?: AbortSignal
) {
const sql = getKeywordsSql()
const { result } = await executeSql(
{ projectRef, connectionString, sql, queryKey: ['keywords'] },
signal
)
return result.map((x: { word: string }) => x.word.toLocaleLowerCase()) as string[]
}
export type KeywordsData = Awaited<ReturnType<typeof getKeywords>>
export type KeywordsError = ExecuteSqlError
export const useKeywordsQuery = <TData extends KeywordsData = KeywordsData>(
export const useKeywordsQuery = <TData = KeywordsData>(
{ projectRef, connectionString }: KeywordsVariables,
options: UseQueryOptions<ExecuteSqlData, KeywordsError, TData> = {}
) => {
return useExecuteSqlQuery(
{ enabled = true, ...options }: UseQueryOptions<KeywordsData, KeywordsError, TData> = {}
) =>
useQuery<KeywordsData, KeywordsError, TData>(
databaseKeys.keywords(projectRef),
({ signal }) => getKeywords({ projectRef, connectionString }, signal),
{
projectRef,
connectionString,
sql: getKeywordsQuery(),
queryKey: ['keywords'],
},
{
select: (data) => {
return {
result: data.result.map((x: DatabaseKeyword) => x.word.toLocaleLowerCase()),
} as any
},
enabled: enabled && typeof projectRef !== 'undefined',
...options,
}
)
}

View File

@@ -1,7 +1,8 @@
import { UseQueryOptions } from '@tanstack/react-query'
import { ExecuteSqlData, ExecuteSqlError, useExecuteSqlQuery } from '../sql/execute-sql-query'
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { executeSql, ExecuteSqlError } from '../sql/execute-sql-query'
import { databaseKeys } from './keys'
export const getMaxConnectionsQuery = () => {
export const getMaxConnectionsSql = () => {
const sql = /* SQL */ `show max_connections`
return sql
@@ -14,26 +15,37 @@ export type MaxConnectionsVariables = {
schema?: string
}
export type MaxConnectionsData = { maxConnections: number }
export async function getMaxConnections(
{ projectRef, connectionString }: MaxConnectionsVariables,
signal?: AbortSignal
) {
const sql = getMaxConnectionsSql()
const { result } = await executeSql(
{ projectRef, connectionString, sql, queryKey: ['max-connections'] },
signal
)
const connections = parseInt(result[0].max_connections)
return { maxConnections: connections }
}
export type MaxConnectionsData = Awaited<ReturnType<typeof getMaxConnections>>
export type MaxConnectionsError = ExecuteSqlError
export const useMaxConnectionsQuery = <TData extends MaxConnectionsData = MaxConnectionsData>(
export const useMaxConnectionsQuery = <TData = MaxConnectionsData>(
{ projectRef, connectionString }: MaxConnectionsVariables,
options: UseQueryOptions<ExecuteSqlData, MaxConnectionsError, TData> = {}
) => {
return useExecuteSqlQuery<TData>(
{
enabled = true,
...options
}: UseQueryOptions<MaxConnectionsData, MaxConnectionsError, TData> = {}
) =>
useQuery<MaxConnectionsData, MaxConnectionsError, TData>(
databaseKeys.maxConnections(projectRef),
({ signal }) => getMaxConnections({ projectRef, connectionString }, signal),
{
projectRef,
connectionString,
sql: getMaxConnectionsQuery(),
queryKey: ['max-connections'],
},
{
select: (data: { result: { max_connections: string }[] }) => {
const connections = parseInt(data.result[0].max_connections)
return { maxConnections: connections } as any
},
enabled: enabled && typeof projectRef !== 'undefined',
...options,
}
)
}

View File

@@ -1,5 +1,6 @@
import { UseQueryOptions } from '@tanstack/react-query'
import { ExecuteSqlData, ExecuteSqlError, useExecuteSqlQuery } from '../sql/execute-sql-query'
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { executeSql, ExecuteSqlError } from '../sql/execute-sql-query'
import { databaseKeys } from './keys'
export type DatabaseMigration = {
version: string
@@ -7,7 +8,7 @@ export type DatabaseMigration = {
statements?: string[]
}
export const getMigrationsQuery = () => {
export const getMigrationsSql = () => {
const sql = /* SQL */ `
select
*
@@ -23,29 +24,44 @@ export type MigrationsVariables = {
connectionString?: string
}
export type MigrationsData = { result: DatabaseMigration[] }
export async function getMigrations(
{ projectRef, connectionString }: MigrationsVariables,
signal?: AbortSignal
) {
const sql = getMigrationsSql()
try {
const { result } = await executeSql(
{ projectRef, connectionString, sql, queryKey: ['migrations'] },
signal
)
return result as DatabaseMigration[]
} catch (error) {
if (
(error as ExecuteSqlError).message.includes(
'relation "supabase_migrations.schema_migrations" does not exist'
)
) {
return []
}
throw error
}
}
export type MigrationsData = Awaited<ReturnType<typeof getMigrations>>
export type MigrationsError = ExecuteSqlError
export const useMigrationsQuery = <TData extends MigrationsData = MigrationsData>(
export const useMigrationsQuery = <TData = MigrationsData>(
{ projectRef, connectionString }: MigrationsVariables,
options: UseQueryOptions<ExecuteSqlData, MigrationsError, TData> = {}
) => {
return useExecuteSqlQuery(
{ enabled = true, ...options }: UseQueryOptions<MigrationsData, MigrationsError, TData> = {}
) =>
useQuery<MigrationsData, MigrationsError, TData>(
databaseKeys.migrations(projectRef),
({ signal }) => getMigrations({ projectRef, connectionString }, signal),
{
projectRef,
connectionString,
sql: getMigrationsQuery(),
queryKey: ['migrations'],
handleError: (error) => {
if (
error.message.includes('relation "supabase_migrations.schema_migrations" does not exist')
) {
return { result: [] }
} else {
throw error
}
},
},
options
enabled: enabled && typeof projectRef !== 'undefined',
...options,
}
)
}

View File

@@ -1,14 +1,9 @@
import pgMeta from '@supabase/pg-meta'
import { QueryClient, UseQueryOptions } from '@tanstack/react-query'
import { QueryClient, useQuery, UseQueryOptions } from '@tanstack/react-query'
import { z } from 'zod'
import {
executeSql,
ExecuteSqlData,
ExecuteSqlError,
useExecuteSqlQuery,
} from 'data/sql/execute-sql-query'
import { sqlKeys } from 'data/sql/keys'
import { executeSql, ExecuteSqlError } from 'data/sql/execute-sql-query'
import { databaseKeys } from './keys'
export type SchemasVariables = {
projectRef?: string
@@ -22,42 +17,45 @@ const pgMetaSchemasList = pgMeta.schemas.list()
export type SchemasData = z.infer<typeof pgMetaSchemasList.zod>
export type SchemasError = ExecuteSqlError
export const useSchemasQuery = <TData = SchemasData>(
export async function getSchemas(
{ projectRef, connectionString }: SchemasVariables,
options: UseQueryOptions<ExecuteSqlData, SchemasError, TData> = {}
) =>
useExecuteSqlQuery(
signal?: AbortSignal
) {
const { result } = await executeSql(
{
projectRef,
connectionString,
sql: pgMetaSchemasList.sql,
queryKey: ['schemas', 'list'],
queryKey: ['schemas'],
},
signal
)
return result
}
export const useSchemasQuery = <TData = SchemasData>(
{ projectRef, connectionString }: SchemasVariables,
{ enabled = true, ...options }: UseQueryOptions<SchemasData, SchemasError, TData> = {}
) =>
useQuery<SchemasData, SchemasError, TData>(
databaseKeys.schemas(projectRef),
({ signal }) => getSchemas({ projectRef, connectionString }, signal),
{
select(data) {
return data.result
},
enabled: enabled && typeof projectRef !== 'undefined',
...options,
}
)
export function invalidateSchemasQuery(client: QueryClient, projectRef: string | undefined) {
return client.invalidateQueries(sqlKeys.query(projectRef, ['schemas', 'list']))
return client.invalidateQueries(databaseKeys.schemas(projectRef))
}
export function prefetchSchemas(
client: QueryClient,
{ projectRef, connectionString }: SchemasVariables
) {
return client.fetchQuery(sqlKeys.query(projectRef, ['schemas', 'list']), ({ signal }) =>
executeSql(
{
projectRef,
connectionString,
sql: pgMetaSchemasList.sql,
queryKey: ['schemas', 'list'],
},
signal
)
return client.fetchQuery(databaseKeys.schemas(projectRef), ({ signal }) =>
getSchemas({ projectRef, connectionString }, signal)
)
}

View File

@@ -1,5 +1,6 @@
import { UseQueryOptions } from '@tanstack/react-query'
import { ExecuteSqlData, ExecuteSqlError, useExecuteSqlQuery } from '../sql/execute-sql-query'
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { executeSql, ExecuteSqlError } from '../sql/execute-sql-query'
import { databaseKeys } from './keys'
export type TableColumn = {
schemaname: string
@@ -9,7 +10,7 @@ export type TableColumn = {
columns: any[]
}
export const getTableColumnsQuery = (table?: string, schema?: string) => {
export const getTableColumnsSql = ({ table, schema }: { table?: string; schema?: string }) => {
const conditions = []
if (table) {
conditions.push(`tablename = '${table}'`)
@@ -91,20 +92,32 @@ export type TableColumnsVariables = {
schema?: string
}
export type TableColumnsData = { result: TableColumn[] }
export async function getTableColumns(
{ projectRef, connectionString, table, schema }: TableColumnsVariables,
signal?: AbortSignal
) {
const sql = getTableColumnsSql({ table, schema })
const { result } = await executeSql(
{ projectRef, connectionString, sql, queryKey: ['table-columns', schema, table] },
signal
)
return result as TableColumn[]
}
export type TableColumnsData = Awaited<ReturnType<typeof getTableColumns>>
export type TableColumnsError = ExecuteSqlError
export const useTableColumnsQuery = <TData extends TableColumnsData = TableColumnsData>(
{ projectRef, connectionString, table, schema }: TableColumnsVariables,
options: UseQueryOptions<ExecuteSqlData, TableColumnsError, TData> = {}
) => {
return useExecuteSqlQuery(
export const useTableColumnsQuery = <TData = TableColumnsData>(
{ projectRef, connectionString, schema, table }: TableColumnsVariables,
{ enabled = true, ...options }: UseQueryOptions<TableColumnsData, TableColumnsError, TData> = {}
) =>
useQuery<TableColumnsData, TableColumnsError, TData>(
databaseKeys.tableColumns(projectRef, schema, table),
({ signal }) => getTableColumns({ projectRef, connectionString, schema, table }, signal),
{
projectRef,
connectionString,
sql: getTableColumnsQuery(table, schema),
queryKey: ['table-columns', schema, table],
},
options
enabled: enabled && typeof projectRef !== 'undefined',
...options,
}
)
}

View File

@@ -105,7 +105,7 @@ export async function getEntityTypes(
projectRef,
connectionString,
sql,
queryKey: ['public', 'entity-types'],
queryKey: ['entity-types', ...schemas, page],
},
signal
)
@@ -149,7 +149,6 @@ export const useEntityTypesQuery = <TData = EntityTypesData>(
),
{
enabled: enabled && typeof projectRef !== 'undefined',
staleTime: 0,
getNextPageParam(lastPage, pages) {
const page = pages.length
const currentTotalCount = page * limit

View File

@@ -22,7 +22,7 @@ export async function getEnumeratedTypes(
if (connectionString) headers.set('x-connection-encrypted', connectionString)
const { data, error } = await get('/platform/pg-meta/{ref}/types', {
// @ts-ignore: We don't need to pass included included_schemas / excluded_schemas in query params
// @ts-expect-error: We don't need to pass included included_schemas / excluded_schemas in query params
params: {
header: { 'x-connection-encrypted': connectionString! },
path: { ref: projectRef },
@@ -50,7 +50,6 @@ export const useEnumeratedTypesQuery = <TData = EnumeratedTypesData>(
({ signal }) => getEnumeratedTypes({ projectRef, connectionString }, signal),
{
enabled: enabled && typeof projectRef !== 'undefined',
staleTime: 0,
...options,
}
)

View File

@@ -6,12 +6,13 @@ import {
WrapperMeta,
} from 'components/interfaces/Database/Wrappers/Wrappers.types'
import { entityTypeKeys } from 'data/entity-types/keys'
import { foreignTableKeys } from 'data/foreign-tables/keys'
import { pgSodiumKeys } from 'data/pg-sodium-keys/keys'
import { executeSql } from 'data/sql/execute-sql-query'
import { sqlKeys } from 'data/sql/keys'
import { wrapWithTransaction } from 'data/sql/utils/transaction'
import { vaultSecretsKeys } from 'data/vault/keys'
import type { ResponseError } from 'types'
import { fdwKeys } from './keys'
export type FDWCreateVariables = {
projectRef?: string
@@ -162,10 +163,11 @@ export const useFDWCreateMutation = ({
const { projectRef } = variables
await Promise.all([
queryClient.invalidateQueries(fdwKeys.list(projectRef), { refetchType: 'all' }),
queryClient.invalidateQueries(entityTypeKeys.list(projectRef)),
queryClient.invalidateQueries(sqlKeys.query(projectRef, ['fdws']), { refetchType: 'all' }),
queryClient.invalidateQueries(foreignTableKeys.list(projectRef)),
queryClient.invalidateQueries(pgSodiumKeys.list(projectRef)),
queryClient.invalidateQueries(sqlKeys.query(projectRef, vaultSecretsKeys.list(projectRef))),
queryClient.invalidateQueries(vaultSecretsKeys.list(projectRef)),
])
await onSuccess?.(data, variables, context)

View File

@@ -3,13 +3,14 @@ import { toast } from 'sonner'
import type { WrapperMeta } from 'components/interfaces/Database/Wrappers/Wrappers.types'
import { entityTypeKeys } from 'data/entity-types/keys'
import { foreignTableKeys } from 'data/foreign-tables/keys'
import { pgSodiumKeys } from 'data/pg-sodium-keys/keys'
import { executeSql } from 'data/sql/execute-sql-query'
import { sqlKeys } from 'data/sql/keys'
import { wrapWithTransaction } from 'data/sql/utils/transaction'
import { vaultSecretsKeys } from 'data/vault/keys'
import type { ResponseError } from 'types'
import { FDW } from './fdws-query'
import { fdwKeys } from './keys'
export type FDWDeleteVariables = {
projectRef: string
@@ -73,10 +74,11 @@ export const useFDWDeleteMutation = ({
const { projectRef } = variables
await Promise.all([
queryClient.invalidateQueries(fdwKeys.list(projectRef), { refetchType: 'all' }),
queryClient.invalidateQueries(entityTypeKeys.list(projectRef)),
queryClient.invalidateQueries(sqlKeys.query(projectRef, ['fdws'])),
queryClient.invalidateQueries(foreignTableKeys.list(projectRef)),
queryClient.invalidateQueries(pgSodiumKeys.list(projectRef)),
queryClient.invalidateQueries(sqlKeys.query(projectRef, vaultSecretsKeys.list(projectRef))),
queryClient.invalidateQueries(vaultSecretsKeys.list(projectRef)),
])
await onSuccess?.(data, variables, context)

View File

@@ -3,15 +3,16 @@ import { toast } from 'sonner'
import type { WrapperMeta } from 'components/interfaces/Database/Wrappers/Wrappers.types'
import { entityTypeKeys } from 'data/entity-types/keys'
import { foreignTableKeys } from 'data/foreign-tables/keys'
import { pgSodiumKeys } from 'data/pg-sodium-keys/keys'
import { executeSql } from 'data/sql/execute-sql-query'
import { sqlKeys } from 'data/sql/keys'
import { wrapWithTransaction } from 'data/sql/utils/transaction'
import { vaultSecretsKeys } from 'data/vault/keys'
import type { ResponseError } from 'types'
import { getCreateFDWSql } from './fdw-create-mutation'
import { getDeleteFDWSql } from './fdw-delete-mutation'
import { FDW } from './fdws-query'
import { fdwKeys } from './keys'
export type FDWUpdateVariables = {
projectRef?: string
@@ -72,10 +73,11 @@ export const useFDWUpdateMutation = ({
const { projectRef } = variables
await Promise.all([
queryClient.invalidateQueries(fdwKeys.list(projectRef), { refetchType: 'all' }),
queryClient.invalidateQueries(entityTypeKeys.list(projectRef)),
queryClient.invalidateQueries(sqlKeys.query(projectRef, ['fdws'])),
queryClient.invalidateQueries(foreignTableKeys.list(projectRef)),
queryClient.invalidateQueries(pgSodiumKeys.list(projectRef)),
queryClient.invalidateQueries(sqlKeys.query(projectRef, vaultSecretsKeys.list(projectRef))),
queryClient.invalidateQueries(vaultSecretsKeys.list(projectRef)),
])
await onSuccess?.(data, variables, context)

View File

@@ -1,5 +1,6 @@
import { UseQueryOptions } from '@tanstack/react-query'
import { ExecuteSqlData, ExecuteSqlError, useExecuteSqlQuery } from '../sql/execute-sql-query'
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { executeSql, ExecuteSqlError } from '../sql/execute-sql-query'
import { fdwKeys } from './keys'
export const getFDWsSql = () => {
const sql = /* SQL */ `
@@ -62,28 +63,37 @@ export type FDW = {
tables: FDWTable[]
}
export type FDWsResponse = {
result: FDW[]
}
export type FDWsVariables = {
projectRef?: string
connectionString?: string
}
export type FDWsData = FDWsResponse
export async function getFDWs(
{ projectRef, connectionString }: FDWsVariables,
signal?: AbortSignal
) {
const sql = getFDWsSql()
const { result } = await executeSql(
{ projectRef, connectionString, sql, queryKey: ['fdws'] },
signal
)
return result as FDW[]
}
export type FDWsData = Awaited<ReturnType<typeof getFDWs>>
export type FDWsError = ExecuteSqlError
export const useFDWsQuery = <TData extends FDWsData = FDWsData>(
export const useFDWsQuery = <TData = FDWsData>(
{ projectRef, connectionString }: FDWsVariables,
options: UseQueryOptions<ExecuteSqlData, FDWsError, TData> = {}
{ enabled = true, ...options }: UseQueryOptions<FDWsData, FDWsError, TData> = {}
) =>
useExecuteSqlQuery(
useQuery<FDWsData, FDWsError, TData>(
fdwKeys.list(projectRef),
({ signal }) => getFDWs({ projectRef, connectionString }, signal),
{
projectRef,
connectionString,
sql: getFDWsSql(),
queryKey: ['fdws'],
},
options
enabled: enabled && typeof projectRef !== 'undefined',
...options,
}
)

View File

@@ -0,0 +1,3 @@
export const fdwKeys = {
list: (projectRef: string | undefined) => ['projects', projectRef, 'fdws'] as const,
}

View File

@@ -49,7 +49,6 @@ export const useForeignTablesQuery = <TData = ForeignTablesData>(
({ signal }) => getForeignTables({ projectRef, connectionString, schema }, signal),
{
enabled: enabled && typeof projectRef !== 'undefined',
staleTime: 0,
...options,
}
)

View File

@@ -54,6 +54,8 @@ export const useMaterializedViewsQuery = <TData = MaterializedViewsData>(
({ signal }) => getMaterializedViews({ projectRef, connectionString, schema }, signal),
{
enabled: enabled && typeof projectRef !== 'undefined',
// We're using a staleTime of 0 here because the only way to create a
// materialized view is via SQL, which we don't know about
staleTime: 0,
...options,
}

View File

@@ -4,4 +4,6 @@ export const replicaKeys = {
['project', projectRef, 'replicas-statuses'] as const,
loadBalancers: (projectRef: string | undefined) =>
['project', projectRef, 'load-balancers'] as const,
replicaLag: (projectRef: string | undefined, id: string) =>
['project', projectRef, 'replica-lag', id] as const,
}

View File

@@ -1,7 +1,8 @@
import { UseQueryOptions } from '@tanstack/react-query'
import { ExecuteSqlData, ExecuteSqlError, useExecuteSqlQuery } from '../sql/execute-sql-query'
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { executeSql, ExecuteSqlError } from '../sql/execute-sql-query'
import { replicaKeys } from './keys'
export const replicationLagQuery = () => {
export const replicationLagSql = () => {
const sql = /* SQL */ `
select
case
@@ -20,24 +21,34 @@ export type ReplicationLagVariables = {
connectionString?: string
}
export type ReplicationLagData = number
export async function getReplicationLag(
{ projectRef, connectionString, id }: ReplicationLagVariables,
signal?: AbortSignal
) {
const sql = replicationLagSql()
const { result } = await executeSql(
{ projectRef, connectionString, sql, queryKey: ['replica-lag', id] },
signal
)
return Number((result[0] ?? null)?.physical_replica_lag_second ?? 0)
}
export type ReplicationLagData = Awaited<ReturnType<typeof getReplicationLag>>
export type ReplicationLagError = ExecuteSqlError
export const useReplicationLagQuery = <TData extends ReplicationLagData = ReplicationLagData>(
export const useReplicationLagQuery = <TData = ReplicationLagData>(
{ projectRef, connectionString, id }: ReplicationLagVariables,
{ enabled = true, ...options }: UseQueryOptions<ExecuteSqlData, ReplicationLagError, TData> = {}
{
enabled = true,
...options
}: UseQueryOptions<ReplicationLagData, ReplicationLagError, TData> = {}
) =>
useExecuteSqlQuery(
useQuery<ReplicationLagData, ReplicationLagError, TData>(
replicaKeys.replicaLag(projectRef, id),
({ signal }) => getReplicationLag({ projectRef, connectionString, id }, signal),
{
projectRef,
connectionString,
sql: replicationLagQuery(),
queryKey: ['replica-lag', id],
},
{
select(data) {
return Number((data.result[0] ?? null)?.physical_replica_lag_second ?? 0) as TData
},
enabled: enabled && typeof projectRef !== 'undefined' && typeof id !== 'undefined',
...options,
}

View File

@@ -33,7 +33,7 @@ export const useQueryAbortMutation = ({
{
async onSuccess(data, variables, context) {
const { projectRef } = variables
await queryClient.invalidateQueries(sqlKeys.query(projectRef, ['ongoing-queries']))
await queryClient.invalidateQueries(sqlKeys.ongoingQueries(projectRef))
await onSuccess?.(data, variables, context)
},
async onError(data, variables, context) {

View File

@@ -55,7 +55,7 @@ export async function executeSql(
params: {
header: { 'x-connection-encrypted': connectionString ?? '' },
path: { ref: projectRef },
// @ts-ignore: This is just a client side thing to identify queries better
// @ts-expect-error: This is just a client side thing to identify queries better
query: {
key:
queryKey?.filter((seg) => typeof seg === 'string' || typeof seg === 'number').join('-') ??
@@ -64,7 +64,7 @@ export async function executeSql(
},
body: { query: sql },
headers: Object.fromEntries(headers),
} as any) // Needed to fix generated api types for now
})
if (error) {
if (
@@ -114,6 +114,9 @@ export async function executeSql(
export type ExecuteSqlData = Awaited<ReturnType<typeof executeSql>>
export type ExecuteSqlError = ResponseError
/**
* @deprecated Use the regular useQuery with a function that calls executeSql() instead
*/
export const useExecuteSqlQuery = <TData = ExecuteSqlData>(
{
projectRef,

View File

@@ -3,4 +3,6 @@ import type { QueryKey } from '@tanstack/react-query'
export const sqlKeys = {
query: (projectRef: string | undefined, queryKey: QueryKey) =>
['projects', projectRef, 'query', ...queryKey] as const,
ongoingQueries: (projectRef: string | undefined) =>
['projects', projectRef, 'ongoing-queries'] as const,
}

View File

@@ -1,5 +1,6 @@
import { UseQueryOptions } from '@tanstack/react-query'
import { ExecuteSqlData, ExecuteSqlError, useExecuteSqlQuery } from '../sql/execute-sql-query'
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { executeSql, ExecuteSqlError } from '../sql/execute-sql-query'
import { sqlKeys } from './keys'
type OngoingQuery = {
pid: number
@@ -7,7 +8,7 @@ type OngoingQuery = {
query_start: string
}
export const getOngoingQueries = () => {
export const getOngoingQueriesSql = () => {
const sql = /* SQL */ `
select pid, query, query_start from pg_stat_activity where state = 'active' and datname = 'postgres';
`.trim()
@@ -20,27 +21,35 @@ export type OngoingQueriesVariables = {
connectionString?: string
}
export type OngoingQueriesData = OngoingQuery[]
export async function getOngoingQueries(
{ projectRef, connectionString }: OngoingQueriesVariables,
signal?: AbortSignal
) {
const sql = getOngoingQueriesSql().trim()
const { result } = await executeSql(
{ projectRef, connectionString, sql, queryKey: ['ongoing-queries'] },
signal
)
return (result ?? []).filter((x: OngoingQuery) => !x.query.startsWith(sql)) as OngoingQuery[]
}
export type OngoingQueriesData = Awaited<ReturnType<typeof getOngoingQueries>>
export type OngoingQueriesError = ExecuteSqlError
export const useOngoingQueriesQuery = <TData extends OngoingQueriesData = OngoingQueriesData>(
export const useOngoingQueriesQuery = <TData = OngoingQueriesData>(
{ projectRef, connectionString }: OngoingQueriesVariables,
options: UseQueryOptions<ExecuteSqlData, OngoingQueriesError, TData> = {}
) => {
return useExecuteSqlQuery(
{
projectRef,
connectionString,
sql: getOngoingQueries(),
queryKey: ['ongoing-queries'],
},
{
enabled = true,
...options
}: UseQueryOptions<OngoingQueriesData, OngoingQueriesError, TData> = {}
) =>
useQuery<OngoingQueriesData, OngoingQueriesError, TData>(
sqlKeys.ongoingQueries(projectRef),
({ signal }) => getOngoingQueries({ projectRef, connectionString }, signal),
{
enabled: enabled && typeof projectRef !== 'undefined',
...options,
select(data) {
return (data?.result ?? []).filter(
(x: OngoingQuery) => !x.query.startsWith(getOngoingQueries())
)
},
}
)
}

View File

@@ -4,9 +4,9 @@ type TableRowKeyArgs = Omit<GetTableRowsArgs, 'table'> & { table?: { id?: number
export const tableRowKeys = {
tableRows: (projectRef?: string, { table, ...args }: TableRowKeyArgs = {}) =>
[projectRef, 'table-rows', table?.id, 'rows', args] as const,
['projects', projectRef, 'table-rows', table?.id, 'rows', args] as const,
tableRowsCount: (projectRef?: string, { table, ...args }: TableRowKeyArgs = {}) =>
[projectRef, 'table-rows', table?.id, 'count', args] as const,
['projects', projectRef, 'table-rows', table?.id, 'count', args] as const,
tableRowsAndCount: (projectRef?: string, tableId?: number) =>
[projectRef, 'table-rows', tableId] as const,
['projects', projectRef, 'table-rows', tableId] as const,
}

View File

@@ -72,7 +72,7 @@ export const useTablesQuery = <TData = TablesData>(
return useQuery<TablesData, TablesError, TData>(
tableKeys.list(projectRef, schema, includeColumns),
({ signal }) => getTables({ projectRef, connectionString, schema, includeColumns }, signal),
{ enabled: enabled && typeof projectRef !== 'undefined', staleTime: 0, ...options }
{ enabled: enabled && typeof projectRef !== 'undefined', ...options }
)
}

View File

@@ -3,7 +3,6 @@ import { toast } from 'sonner'
import { Query } from 'components/grid/query/Query'
import { executeSql } from 'data/sql/execute-sql-query'
import { sqlKeys } from 'data/sql/keys'
import type { ResponseError, VaultSecret } from 'types'
import { vaultSecretsKeys } from './keys'
@@ -44,9 +43,7 @@ export const useVaultSecretCreateMutation = ({
{
async onSuccess(data, variables, context) {
const { projectRef } = variables
await queryClient.invalidateQueries(
sqlKeys.query(projectRef, vaultSecretsKeys.list(projectRef))
)
await queryClient.invalidateQueries(vaultSecretsKeys.list(projectRef))
await onSuccess?.(data, variables, context)
},
async onError(data, variables, context) {

View File

@@ -3,7 +3,6 @@ import { toast } from 'sonner'
import { Query } from 'components/grid/query/Query'
import { executeSql } from 'data/sql/execute-sql-query'
import { sqlKeys } from 'data/sql/keys'
import type { ResponseError } from 'types'
import { vaultSecretsKeys } from './keys'
@@ -40,9 +39,7 @@ export const useVaultSecretDeleteMutation = ({
{
async onSuccess(data, variables, context) {
const { projectRef } = variables
await queryClient.invalidateQueries(
sqlKeys.query(projectRef, vaultSecretsKeys.list(projectRef))
)
await queryClient.invalidateQueries(vaultSecretsKeys.list(projectRef))
await onSuccess?.(data, variables, context)
},
async onError(data, variables, context) {

View File

@@ -3,7 +3,6 @@ import { toast } from 'sonner'
import { Query } from 'components/grid/query/Query'
import { executeSql } from 'data/sql/execute-sql-query'
import { sqlKeys } from 'data/sql/keys'
import type { ResponseError, VaultSecret } from 'types'
import { vaultSecretsKeys } from './keys'
@@ -48,9 +47,7 @@ export const useVaultSecretUpdateMutation = ({
const { id, projectRef } = variables
await Promise.all([
queryClient.removeQueries(vaultSecretsKeys.getDecryptedValue(projectRef, id)),
queryClient.invalidateQueries(
sqlKeys.query(projectRef, vaultSecretsKeys.list(projectRef))
),
queryClient.invalidateQueries(vaultSecretsKeys.list(projectRef)),
])
await onSuccess?.(data, variables, context)
},

View File

@@ -1,10 +1,10 @@
import { UseQueryOptions } from '@tanstack/react-query'
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { Query } from 'components/grid/query/Query'
import type { VaultSecret } from 'types'
import { ExecuteSqlData, ExecuteSqlError, useExecuteSqlQuery } from '../sql/execute-sql-query'
import { executeSql, ExecuteSqlError } from '../sql/execute-sql-query'
import { vaultSecretsKeys } from './keys'
export const getVaultSecretsQuery = () => {
export const getVaultSecretsSql = () => {
const sql = new Query()
.from('secrets', 'vault')
.select('id,name,description,secret,key_id,created_at,updated_at')
@@ -18,26 +18,32 @@ export type VaultSecretsVariables = {
connectionString?: string
}
export type VaultSecretsData = VaultSecret[]
export async function getVaultSecrets(
{ projectRef, connectionString }: VaultSecretsVariables,
signal?: AbortSignal
) {
const sql = getVaultSecretsSql()
const { result } = await executeSql(
{ projectRef, connectionString, sql, queryKey: ['vault-secrets'] },
signal
)
return result as VaultSecret[]
}
export type VaultSecretsData = Awaited<ReturnType<typeof getVaultSecrets>>
export type VaultSecretsError = ExecuteSqlError
export const useVaultSecretsQuery = <TData extends VaultSecretsData = VaultSecretsData>(
export const useVaultSecretsQuery = <TData = VaultSecretsData>(
{ projectRef, connectionString }: VaultSecretsVariables,
{ enabled = true, ...options }: UseQueryOptions<ExecuteSqlData, VaultSecretsError, TData> = {}
) => {
return useExecuteSqlQuery(
{ enabled = true, ...options }: UseQueryOptions<VaultSecretsData, VaultSecretsError, TData> = {}
) =>
useQuery<VaultSecretsData, VaultSecretsError, TData>(
vaultSecretsKeys.list(projectRef),
({ signal }) => getVaultSecrets({ projectRef, connectionString }, signal),
{
projectRef,
connectionString,
sql: getVaultSecretsQuery(),
queryKey: vaultSecretsKeys.list(projectRef),
},
{
select(data) {
return data.result
},
enabled: enabled && typeof projectRef !== 'undefined',
...options,
}
)
}

View File

@@ -48,6 +48,8 @@ export const useViewsQuery = <TData = ViewsData>(
({ signal }) => getViews({ projectRef, connectionString, schema }, signal),
{
enabled: enabled && typeof projectRef !== 'undefined',
// We're using a staleTime of 0 here because the only way to create a
// view is via SQL, which we don't know about
staleTime: 0,
...options,
}

View File

@@ -22,12 +22,12 @@ import { useProjectDiskResizeMutation } from 'data/config/project-disk-resize-mu
import { useDatabaseSizeQuery } from 'data/database/database-size-query'
import { useDatabaseReport } from 'data/reports/database-report-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { useFlag } from 'hooks/ui/useFlag'
import { TIME_PERIODS_INFRA } from 'lib/constants/metrics'
import { formatBytes } from 'lib/helpers'
import { useDatabaseSelectorStateSnapshot } from 'state/database-selector'
import type { NextPageWithLayout } from 'types'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { useFlag } from 'hooks/ui/useFlag'
const DatabaseReport: NextPageWithLayout = () => {
return (
@@ -59,7 +59,7 @@ const DatabaseUsage = () => {
projectRef: project?.ref,
connectionString: project?.connectionString,
})
const databaseSizeBytes = data?.result[0].db_size ?? 0
const databaseSizeBytes = data ?? 0
const currentDiskSize = project?.volumeSizeGb ?? 0
const [showIncreaseDiskSizeModal, setshowIncreaseDiskSizeModal] = useState(false)

View File

@@ -112,9 +112,9 @@ const SqlEditor: NextPageWithLayout = () => {
if (pgInfoRef.current === null) {
pgInfoRef.current = {}
}
pgInfoRef.current.tableColumns = tableColumns?.result
pgInfoRef.current.tableColumns = tableColumns
pgInfoRef.current.schemas = schemas
pgInfoRef.current.keywords = keywords?.result
pgInfoRef.current.keywords = keywords
pgInfoRef.current.functions = functions
}

View File

@@ -9,7 +9,7 @@ const dismissToast = async (page: Page) => {
test.describe('Table Editor page', () => {
test.beforeEach(async ({ page }) => {
const tableResponsePromise = page.waitForResponse(
'http://localhost:8082/api/pg-meta/default/query?key=public-entity-types',
'http://localhost:8082/api/pg-meta/default/query?key=entity-types-public-0',
{ timeout: 0 }
)
await page.goto('/project/default/editor')
@@ -108,7 +108,7 @@ test.describe('Table Editor page', () => {
test('should check the auth schema', async ({ page }) => {
const tableResponsePromise = page.waitForResponse(
'http://localhost:8082/api/pg-meta/default/query?key=public-entity-types',
'http://localhost:8082/api/pg-meta/default/query?key=entity-types-public-0',
{ timeout: 0 }
)