fix(datasources): MySQL browser race + slow row counts
When switching source engine, the sample effect fired with the previous engine selected object (e.g. public.sales_orders against MySQL), surfacing "Table hr.sales_orders doesn't exist". Track which engine the selected object belongs to and only sample when it matches the active engine, plus guard against stale responses overwriting newer ones. Row-count for the browser used an exact count(*) which full-scanned huge tables (~30s on 24M rows). Use planner/statistics estimates and only run a time-bounded exact count for small (<=50k) tables.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import {
|
||||
Activity,
|
||||
ChevronRight,
|
||||
@@ -88,6 +88,8 @@ export function DataSourcesView({ focusEngine, onPulse }: Props) {
|
||||
const [catalog, setCatalog] = useState<CatalogResponse | null>(null)
|
||||
const [catalogLoading, setCatalogLoading] = useState(false)
|
||||
const [selectedObject, setSelectedObject] = useState<CatalogObject | null>(null)
|
||||
const [objEngine, setObjEngine] = useState<SourceEngine | null>(null)
|
||||
const sampleReq = useRef(0)
|
||||
const [sample, setSample] = useState<SampleResponse | null>(null)
|
||||
const [sampleLoading, setSampleLoading] = useState(false)
|
||||
const [page, setPage] = useState(1)
|
||||
@@ -114,6 +116,7 @@ export function DataSourcesView({ focusEngine, onPulse }: Props) {
|
||||
setCatalogLoading(true)
|
||||
setCatalog(null)
|
||||
setSelectedObject(null)
|
||||
setObjEngine(null)
|
||||
setSample(null)
|
||||
try {
|
||||
const r = await fetch(`/api/sql/catalog/${engine}`)
|
||||
@@ -121,7 +124,7 @@ export function DataSourcesView({ focusEngine, onPulse }: Props) {
|
||||
const j: CatalogResponse = await r.json()
|
||||
setCatalog(j)
|
||||
const first = j.objects?.find((o) => o.type === 'table' || o.type === 'collection' || o.type === 'node_label')
|
||||
if (first) setSelectedObject(first)
|
||||
if (first) { setSelectedObject(first); setObjEngine(engine) }
|
||||
}
|
||||
} catch { /* */ } finally {
|
||||
setCatalogLoading(false)
|
||||
@@ -129,6 +132,7 @@ export function DataSourcesView({ focusEngine, onPulse }: Props) {
|
||||
}, [])
|
||||
|
||||
const loadSample = useCallback(async (engine: SourceEngine, obj: CatalogObject, pg = page, ps = pageSize) => {
|
||||
const myId = ++sampleReq.current
|
||||
setSampleLoading(true)
|
||||
setSample(null)
|
||||
const offset = (pg - 1) * ps
|
||||
@@ -137,11 +141,13 @@ export function DataSourcesView({ focusEngine, onPulse }: Props) {
|
||||
`/api/sql/sample/${engine}?object=${encodeURIComponent(obj.fqn)}&limit=${ps}&offset=${offset}`,
|
||||
)
|
||||
const j = await r.json()
|
||||
if (myId !== sampleReq.current) return
|
||||
setSample(j)
|
||||
} catch {
|
||||
if (myId !== sampleReq.current) return
|
||||
setSample({ ok: false, error: 'Sample API unavailable' })
|
||||
} finally {
|
||||
setSampleLoading(false)
|
||||
if (myId === sampleReq.current) setSampleLoading(false)
|
||||
}
|
||||
}, [page, pageSize])
|
||||
|
||||
@@ -155,8 +161,8 @@ export function DataSourcesView({ focusEngine, onPulse }: Props) {
|
||||
}, [selectedObject?.fqn, active])
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedObject && subTab === 'browser') loadSample(active, selectedObject, page, pageSize)
|
||||
}, [selectedObject, active, subTab, page, pageSize, loadSample])
|
||||
if (selectedObject && objEngine === active && subTab === 'browser') loadSample(active, selectedObject, page, pageSize)
|
||||
}, [selectedObject, objEngine, active, subTab, page, pageSize, loadSample])
|
||||
|
||||
const refreshAll = () => {
|
||||
loadHealth()
|
||||
@@ -276,7 +282,7 @@ export function DataSourcesView({ focusEngine, onPulse }: Props) {
|
||||
<button
|
||||
key={obj.fqn}
|
||||
type="button"
|
||||
onClick={() => setSelectedObject(obj)}
|
||||
onClick={() => { setSelectedObject(obj); setObjEngine(active) }}
|
||||
className={cn(
|
||||
'mb-0.5 flex w-full items-center gap-1.5 rounded px-2 py-1.5 text-left text-[10px] transition-colors',
|
||||
selectedObject?.fqn === obj.fqn ? subTabActive : 'hover:bg-surface-overlay',
|
||||
|
||||
Reference in New Issue
Block a user