fix: updated explain to not crash on non text formats (#42076)

updated explain to not crash on non text formats
This commit is contained in:
Ali Waseem authored and GitHub committed 2026-01-22 12:26:45 -07:00
1 parent d27bb74258
commit f4409d4ac5
3 files changed
+60 -1

No files matched your search

@@ -133,6 +133,12 @@ export function isExplainQuery(rows: readonly unknown[]): boolean {
return 'QUERY PLAN' in firstRow && Object.keys(firstRow).length === 1
}
export function isTextFormatExplain(rows: readonly unknown[]): boolean {
if (!isExplainQuery(rows)) return false
const firstRow = rows[0] as Record<string, unknown>
return typeof firstRow['QUERY PLAN'] === 'string'
}
export function isExplainSql(sql: string): boolean {
return /^\s*explain\b/i.test(sql)
}
@@ -4,7 +4,10 @@ import { useState } from 'react'
import CopyButton from 'components/ui/CopyButton'
import { ExplainVisualizer } from 'components/interfaces/ExplainVisualizer/ExplainVisualizer'
import { ExplainHeader } from 'components/interfaces/ExplainVisualizer/ExplainVisualizer.Header'
import { isExplainQuery } from 'components/interfaces/ExplainVisualizer/ExplainVisualizer.utils'
import {
isExplainQuery,
isTextFormatExplain,
} from 'components/interfaces/ExplainVisualizer/ExplainVisualizer.utils'
import { useSqlEditorV2StateSnapshot } from 'state/sql-editor-v2'
import { Tooltip, TooltipContent, TooltipTrigger } from 'ui'
import Results from './Results'
@@ -78,6 +81,7 @@ export function UtilityTabExplain({ id, isExecuting }: UtilityTabExplainProps) {
}
const isValidExplain = isExplainQuery(explainResult.rows)
const isTextFormat = isTextFormatExplain(explainResult.rows)
if (!isValidExplain) {
return (
@@ -89,6 +93,20 @@ export function UtilityTabExplain({ id, isExecuting }: UtilityTabExplainProps) {
)
}
// Handle non-TEXT formats (JSON, YAML, XML) - show raw output only
if (!isTextFormat) {
return (
<div className="h-full flex flex-col pb-9">
<div className="px-4 py-3 bg-surface-100 border-b border-default flex items-center gap-2">
<span className="text-sm text-foreground-light">
Visual execution plan is only available for TEXT format. Showing raw output.
</span>
</div>
<Results rows={explainResult.rows} />
</div>
)
}
const toggleMode = () => setMode(mode === 'visual' ? 'raw' : 'visual')
return (
@@ -3,6 +3,7 @@ import {
splitSqlStatements,
isExplainQuery,
isExplainSql,
isTextFormatExplain,
formatNodeDuration,
} from 'components/interfaces/ExplainVisualizer/ExplainVisualizer.utils'
@@ -12,6 +13,12 @@ describe('isExplainQuery', () => {
expect(isExplainQuery(rows)).toBe(true)
})
test('returns true for JSON format EXPLAIN result rows', () => {
// JSON format returns an array/object in the QUERY PLAN column
const rows = [{ 'QUERY PLAN': [{ Plan: { 'Node Type': 'Seq Scan' } }] }]
expect(isExplainQuery(rows)).toBe(true)
})
test('returns false for empty array', () => {
expect(isExplainQuery([])).toBe(false)
})
@@ -22,6 +29,34 @@ describe('isExplainQuery', () => {
})
})
describe('isTextFormatExplain', () => {
test('returns true for TEXT format EXPLAIN result rows', () => {
const rows = [
{ 'QUERY PLAN': 'Seq Scan on users (cost=0.00..10.50 rows=100 width=36)' },
{ 'QUERY PLAN': ' Filter: (active = true)' },
]
expect(isTextFormatExplain(rows)).toBe(true)
})
test('returns false for JSON format EXPLAIN result rows', () => {
const rows = [
{ 'QUERY PLAN': [{ Plan: { 'Node Type': 'Seq Scan', 'Relation Name': 'users' } }] },
]
expect(isTextFormatExplain(rows)).toBe(false)
})
test('returns true for YAML format EXPLAIN result rows (returned as string)', () => {
const rows = [
{
'QUERY PLAN':
'- Plan: \n Node Type: "Seq Scan"\n Parallel Aware: false\n Async Capable: false\n Relation Name: "orders"\n Alias: "orders"\n Startup Cost: 0.00\n Total Cost: 97.00\n Plan Rows: 5000\n Plan Width: 41',
},
]
expect(isTextFormatExplain(rows)).toBe(true)
})
})
describe('isExplainSql', () => {
test('returns true for EXPLAIN queries', () => {
expect(isExplainSql('EXPLAIN SELECT * FROM users')).toBe(true)