mirror of
https://github.com/ruvnet/RuVector.git
synced 2026-05-24 22:15:18 +00:00
Major additions: - Complete Next.js studio application with 1600+ components - Docker support (Dockerfile.combined, docker-compose.yml) - GCP deployment documentation and benchmarks - SQL benchmark scripts for performance testing - Sentry integration for monitoring - Comprehensive test suite and mocks Studio features: - Dashboard and admin interfaces - Data visualization components - Authentication and user management - API integration with RuVector backend - Static data and public assets 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
159 lines
4.9 KiB
TypeScript
159 lines
4.9 KiB
TypeScript
import { Query } from '@supabase/pg-meta/src/query'
|
|
import {
|
|
COUNT_ESTIMATE_SQL,
|
|
THRESHOLD_COUNT,
|
|
} from '@supabase/pg-meta/src/sql/studio/get-count-estimate'
|
|
import { QueryClient, useQuery, useQueryClient } from '@tanstack/react-query'
|
|
|
|
import { parseSupaTable } from 'components/grid/SupabaseGrid.utils'
|
|
import type { Filter, SupaTable } from 'components/grid/types'
|
|
import { prefetchTableEditor } from 'data/table-editor/table-editor-query'
|
|
import { RoleImpersonationState, wrapWithRoleImpersonation } from 'lib/role-impersonation'
|
|
import { isRoleImpersonationEnabled } from 'state/role-impersonation-state'
|
|
import { executeSql, ExecuteSqlError } from '../sql/execute-sql-query'
|
|
import { tableRowKeys } from './keys'
|
|
import { formatFilterValue } from './utils'
|
|
import { UseCustomQueryOptions } from 'types'
|
|
|
|
type GetTableRowsCountArgs = {
|
|
table?: SupaTable
|
|
filters?: Filter[]
|
|
enforceExactCount?: boolean
|
|
}
|
|
|
|
export const getTableRowsCountSql = ({
|
|
table,
|
|
filters = [],
|
|
enforceExactCount = false,
|
|
}: GetTableRowsCountArgs) => {
|
|
if (!table) return ``
|
|
|
|
if (enforceExactCount) {
|
|
const query = new Query()
|
|
let queryChains = query.from(table.name, table.schema ?? undefined).count()
|
|
filters
|
|
.filter((x) => x.value && x.value !== '')
|
|
.forEach((x) => {
|
|
const value = formatFilterValue(table, x)
|
|
queryChains = queryChains.filter(x.column, x.operator, value)
|
|
})
|
|
return `select (${queryChains.toSql().slice(0, -1)}), false as is_estimate;`
|
|
} else {
|
|
const selectQuery = new Query()
|
|
let selectQueryChains = selectQuery.from(table.name, table.schema ?? undefined).select('*')
|
|
filters
|
|
.filter((x) => x.value && x.value != '')
|
|
.forEach((x) => {
|
|
const value = formatFilterValue(table, x)
|
|
selectQueryChains = selectQueryChains.filter(x.column, x.operator, value)
|
|
})
|
|
const selectBaseSql = selectQueryChains.toSql()
|
|
|
|
const countQuery = new Query()
|
|
let countQueryChains = countQuery.from(table.name, table.schema ?? undefined).count()
|
|
filters
|
|
.filter((x) => x.value && x.value != '')
|
|
.forEach((x) => {
|
|
const value = formatFilterValue(table, x)
|
|
countQueryChains = countQueryChains.filter(x.column, x.operator, value)
|
|
})
|
|
const countBaseSql = countQueryChains.toSql().slice(0, -1)
|
|
|
|
const sql = `
|
|
${COUNT_ESTIMATE_SQL}
|
|
|
|
with approximation as (
|
|
select reltuples as estimate
|
|
from pg_class
|
|
where oid = ${table.id}
|
|
)
|
|
select
|
|
case
|
|
when estimate = -1 then (select pg_temp.count_estimate('${selectBaseSql.replaceAll("'", "''")}'))
|
|
when estimate > ${THRESHOLD_COUNT} then ${filters.length > 0 ? `pg_temp.count_estimate('${selectBaseSql.replaceAll("'", "''")}')` : 'estimate'}
|
|
else (${countBaseSql})
|
|
end as count,
|
|
estimate = -1 or estimate > ${THRESHOLD_COUNT} as is_estimate
|
|
from approximation;
|
|
`.trim()
|
|
|
|
return sql
|
|
}
|
|
}
|
|
|
|
export type TableRowsCount = {
|
|
count?: number
|
|
is_estimate?: boolean
|
|
}
|
|
|
|
export type TableRowsCountVariables = Omit<GetTableRowsCountArgs, 'table'> & {
|
|
queryClient: QueryClient
|
|
tableId?: number
|
|
roleImpersonationState?: RoleImpersonationState
|
|
projectRef?: string
|
|
connectionString?: string | null
|
|
}
|
|
|
|
export type TableRowsCountData = TableRowsCount
|
|
export type TableRowsCountError = ExecuteSqlError
|
|
|
|
export async function getTableRowsCount(
|
|
{
|
|
queryClient,
|
|
projectRef,
|
|
connectionString,
|
|
tableId,
|
|
filters,
|
|
roleImpersonationState,
|
|
enforceExactCount,
|
|
}: TableRowsCountVariables,
|
|
signal?: AbortSignal
|
|
) {
|
|
const entity = await prefetchTableEditor(queryClient, {
|
|
projectRef,
|
|
connectionString,
|
|
id: tableId,
|
|
})
|
|
if (!entity) {
|
|
throw new Error('Table not found')
|
|
}
|
|
|
|
const table = parseSupaTable(entity)
|
|
|
|
const sql = wrapWithRoleImpersonation(
|
|
getTableRowsCountSql({ table, filters, enforceExactCount }),
|
|
roleImpersonationState
|
|
)
|
|
const { result } = await executeSql(
|
|
{
|
|
projectRef,
|
|
connectionString,
|
|
sql,
|
|
queryKey: ['table-rows-count', table.id],
|
|
isRoleImpersonationEnabled: isRoleImpersonationEnabled(roleImpersonationState?.role),
|
|
},
|
|
signal
|
|
)
|
|
|
|
return {
|
|
count: result?.[0]?.count,
|
|
is_estimate: result?.[0]?.is_estimate ?? false,
|
|
} as TableRowsCount
|
|
}
|
|
|
|
export const useTableRowsCountQuery = <TData = TableRowsCountData>(
|
|
{ projectRef, connectionString, tableId, ...args }: Omit<TableRowsCountVariables, 'queryClient'>,
|
|
{
|
|
enabled = true,
|
|
...options
|
|
}: UseCustomQueryOptions<TableRowsCountData, TableRowsCountError, TData> = {}
|
|
) => {
|
|
const queryClient = useQueryClient()
|
|
return useQuery<TableRowsCountData, TableRowsCountError, TData>({
|
|
queryKey: tableRowKeys.tableRowsCount(projectRef, { table: { id: tableId }, ...args }),
|
|
queryFn: ({ signal }) =>
|
|
getTableRowsCount({ queryClient, projectRef, connectionString, tableId, ...args }, signal),
|
|
enabled: enabled && typeof projectRef !== 'undefined' && typeof tableId !== 'undefined',
|
|
...options,
|
|
})
|
|
}
|