@tabularis/explain 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -53,13 +53,13 @@ SQLite `EXPLAIN QUERY PLAN` rows:
53
53
  import { parseExplain, parseExplainFor, parseRawExplain } from "@tabularis/explain";
54
54
 
55
55
  // Sniff the format of a pasted blob or an uploaded file:
56
- const plan = parseExplain(raw);
56
+ const plan = await parseExplain(raw);
57
57
 
58
58
  // Or name the engine when the caller knows it:
59
- const hinted = parseExplainFor(raw, "mysql");
59
+ const hinted = await parseExplainFor(raw, "mysql");
60
60
 
61
61
  // Or parse the raw payload a host driver handed over:
62
- const fromDriver = parseRawExplain({ engine, format, payload, original_query });
62
+ const fromDriver = await parseRawExplain({ engine, format, payload, original_query });
63
63
  ```
64
64
 
65
65
  A host obtains the bytes however it likes — a driver, a pasted textarea, an
@@ -86,6 +86,9 @@ registerExplainParser({
86
86
  ```
87
87
 
88
88
  Import the parser package before calling `parseRawExplain` or source detection.
89
+ These helpers are asynchronous because plugin parsers run in isolated guest
90
+ frames. Await `detectFormat`, `detectFormatFor`, `parseExplain`,
91
+ `parseExplainFor`, `parseRawExplain`, and `resolveExplainOutput`.
89
92
  Registering the same format again replaces it, which supports plugin upgrades;
90
93
  call `unregisterExplainParser(format)` when unloading it.
91
94
 
@@ -79,7 +79,7 @@ interface ExplainMetrics {
79
79
  totalExclusiveTimeMs: number;
80
80
  }
81
81
  /** Metric a bar chart or heat map can be driven by. */
82
- type ExplainMetricKind = "time" | "rows" | "cost" | "buffers";
82
+ type ExplainMetricKind = 'time' | 'rows' | 'cost' | 'buffers';
83
83
  declare const EXPLAIN_METRIC_KINDS: readonly ExplainMetricKind[];
84
84
  /** Compute exclusive/total metrics for every node in `plan`. */
85
85
  declare function computeExplainMetrics(plan: ExplainPlan): ExplainMetrics;
@@ -100,8 +100,8 @@ declare function getAvailableMetricKinds(metrics: ExplainMetrics): ExplainMetric
100
100
  */
101
101
  declare function getDefaultMetricKind(metrics: ExplainMetrics): ExplainMetricKind | null;
102
102
 
103
- type ExplainDiagnosticKind = "hotspot" | "over-estimate" | "under-estimate" | "disk-sort" | "filter-loss" | "large-seq-scan" | "heap-fetches" | "workers-underused" | "high-loops" | "cache-miss" | "never-executed";
104
- type ExplainDiagnosticSeverity = "info" | "warning" | "critical";
103
+ type ExplainDiagnosticKind = 'hotspot' | 'over-estimate' | 'under-estimate' | 'disk-sort' | 'filter-loss' | 'large-seq-scan' | 'heap-fetches' | 'workers-underused' | 'high-loops' | 'cache-miss' | 'never-executed';
104
+ type ExplainDiagnosticSeverity = 'info' | 'warning' | 'critical';
105
105
  interface ExplainDiagnostic {
106
106
  kind: ExplainDiagnosticKind;
107
107
  severity: ExplainDiagnosticSeverity;
@@ -154,4 +154,4 @@ declare function getWorstSeverity(diagnostics: ExplainDiagnostic[]): ExplainDiag
154
154
  /** How many diagnostics of each severity the whole plan produced. */
155
155
  declare function countDiagnosticsBySeverity(diagnostics: Map<string, ExplainDiagnostic[]>): Record<ExplainDiagnosticSeverity, number>;
156
156
 
157
- export { type ExplainPlan as E, type ExplainNode as a, type ExplainMetrics as b, EXPLAIN_DIAGNOSTIC_THRESHOLDS as c, EXPLAIN_METRIC_KINDS as d, type ExplainDiagnostic as e, type ExplainDiagnosticKind as f, type ExplainDiagnosticSeverity as g, type ExplainMetricKind as h, type ExplainNodeMetrics as i, computeExplainMetrics as j, countDiagnosticsBySeverity as k, getAvailableMetricKinds as l, getDefaultMetricKind as m, getMetricMax as n, getMetricValue as o, getNodeDiagnostics as p, getNodeMetrics as q, getPlanDiagnostics as r, getWorstSeverity as s, isMetricAvailable as t };
157
+ export { type ExplainPlan as E, type ExplainNode as a, type ExplainMetrics as b, type ExplainNodeMetrics as c, type ExplainDiagnostic as d, EXPLAIN_DIAGNOSTIC_THRESHOLDS as e, EXPLAIN_METRIC_KINDS as f, type ExplainDiagnosticKind as g, type ExplainDiagnosticSeverity as h, type ExplainMetricKind as i, computeExplainMetrics as j, countDiagnosticsBySeverity as k, getAvailableMetricKinds as l, getDefaultMetricKind as m, getMetricMax as n, getMetricValue as o, getNodeDiagnostics as p, getNodeMetrics as q, getPlanDiagnostics as r, getWorstSeverity as s, isMetricAvailable as t };
package/dist/flow.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Node, Edge } from '@xyflow/react';
2
- import { a as ExplainNode, i as ExplainNodeMetrics, e as ExplainDiagnostic, E as ExplainPlan, b as ExplainMetrics } from './diagnostics-DPSgsjFk.js';
2
+ import { a as ExplainNode, c as ExplainNodeMetrics, d as ExplainDiagnostic, E as ExplainPlan, b as ExplainMetrics } from './diagnostics-DYKMtJZS.js';
3
3
 
4
4
  /**
5
5
  * ReactFlow adapter: turns a plan tree into positioned nodes and edges.
package/dist/flow.js CHANGED
@@ -74,16 +74,10 @@ function computeExplainMetrics(plan) {
74
74
  let maxExclusiveBuffers = 0;
75
75
  let totalExclusiveTimeMs = 0;
76
76
  for (const metrics of order) {
77
- maxExclusiveTimeMs = Math.max(
78
- maxExclusiveTimeMs,
79
- metrics.exclusiveTimeMs ?? 0
80
- );
77
+ maxExclusiveTimeMs = Math.max(maxExclusiveTimeMs, metrics.exclusiveTimeMs ?? 0);
81
78
  maxExclusiveCost = Math.max(maxExclusiveCost, metrics.exclusiveCost ?? 0);
82
79
  maxTotalRows = Math.max(maxTotalRows, metrics.totalRows ?? 0);
83
- maxExclusiveBuffers = Math.max(
84
- maxExclusiveBuffers,
85
- metrics.exclusiveBuffers ?? 0
86
- );
80
+ maxExclusiveBuffers = Math.max(maxExclusiveBuffers, metrics.exclusiveBuffers ?? 0);
87
81
  totalExclusiveTimeMs += metrics.exclusiveTimeMs ?? 0;
88
82
  }
89
83
  if (totalExclusiveTimeMs > 0) {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { E as ExplainPlan, a as ExplainNode, b as ExplainMetrics } from './diagnostics-DPSgsjFk.js';
2
- export { c as EXPLAIN_DIAGNOSTIC_THRESHOLDS, d as EXPLAIN_METRIC_KINDS, e as ExplainDiagnostic, f as ExplainDiagnosticKind, g as ExplainDiagnosticSeverity, h as ExplainMetricKind, i as ExplainNodeMetrics, j as computeExplainMetrics, k as countDiagnosticsBySeverity, l as getAvailableMetricKinds, m as getDefaultMetricKind, n as getMetricMax, o as getMetricValue, p as getNodeDiagnostics, q as getNodeMetrics, r as getPlanDiagnostics, s as getWorstSeverity, t as isMetricAvailable } from './diagnostics-DPSgsjFk.js';
1
+ import { E as ExplainPlan, a as ExplainNode, b as ExplainMetrics, c as ExplainNodeMetrics, d as ExplainDiagnostic } from './diagnostics-DYKMtJZS.js';
2
+ export { e as EXPLAIN_DIAGNOSTIC_THRESHOLDS, f as EXPLAIN_METRIC_KINDS, g as ExplainDiagnosticKind, h as ExplainDiagnosticSeverity, i as ExplainMetricKind, j as computeExplainMetrics, k as countDiagnosticsBySeverity, l as getAvailableMetricKinds, m as getDefaultMetricKind, n as getMetricMax, o as getMetricValue, p as getNodeDiagnostics, q as getNodeMetrics, r as getPlanDiagnostics, s as getWorstSeverity, t as isMetricAvailable } from './diagnostics-DYKMtJZS.js';
3
+ export { E as EXPLAIN_TABLE_ANALYZE_COLUMNS, a as EXPLAIN_TABLE_COLUMNS, b as ExplainTableColumnId, i as isDefaultExplainTableColumnOrder, m as moveExplainTableColumn, n as normalizeExplainTableColumnOrder, s as shiftExplainTableColumn } from './tableColumns-BWoGh7tK.js';
3
4
 
4
5
  interface RegisteredExplainParser {
5
6
  /** Canonical engine id, for example "sqlserver". */
@@ -9,9 +10,9 @@ interface RegisteredExplainParser {
9
10
  /** Human label for format pickers. */
10
11
  readonly label?: string;
11
12
  /** Parse the raw payload or throw an Error. */
12
- parse(payload: string): ExplainPlan;
13
+ parse(payload: string): ExplainPlan | Promise<ExplainPlan>;
13
14
  /** Cheap, side-effect-free source detection. */
14
- sniff?(payload: string): boolean;
15
+ sniff?(payload: string): boolean | Promise<boolean>;
15
16
  }
16
17
  /** Register a custom parser or replace the effective parser for its format. */
17
18
  declare function registerExplainParser(parser: RegisteredExplainParser): void;
@@ -34,25 +35,25 @@ declare function listExplainParsers(): readonly RegisteredExplainParser[];
34
35
  */
35
36
 
36
37
  /** Engines whose source detection is built into this package. */
37
- type BuiltinExplainEngine = "postgres" | "mysql" | "sqlite";
38
+ type BuiltinExplainEngine = 'postgres' | 'mysql' | 'sqlite';
38
39
  /** The engine that produced an EXPLAIN payload, when the caller knows it. */
39
40
  type ExplainEngine = BuiltinExplainEngine | (string & {});
40
41
  /** Source formats whose detection is built into this package. */
41
42
  type BuiltinExplainSourceFormat =
42
43
  /** Postgres `EXPLAIN (FORMAT JSON [, ANALYZE, BUFFERS])` output. */
43
- "postgres-json"
44
+ 'postgres-json'
44
45
  /**
45
46
  * Postgres default `EXPLAIN` output — indentation-based tree with
46
47
  * `cost=X..Y rows=N width=W` headers and optional `actual time` blocks.
47
48
  */
48
- | "postgres-text"
49
+ | 'postgres-text'
49
50
  /**
50
51
  * MySQL / MariaDB `EXPLAIN FORMAT=JSON` or `ANALYZE FORMAT=JSON` output —
51
52
  * a document with a `query_block` key.
52
53
  */
53
- | "mysql-json"
54
+ | 'mysql-json'
54
55
  /** MySQL `EXPLAIN ANALYZE` / MariaDB `ANALYZE FORMAT=TEXT` indented tree. */
55
- | "mysql-text";
56
+ | 'mysql-text';
56
57
  /** Supported source format supplied by a built-in or registered parser. */
57
58
  type ExplainSourceFormat = BuiltinExplainSourceFormat | (string & {});
58
59
  /** A parser for one serialised EXPLAIN payload format. */
@@ -74,7 +75,7 @@ declare function explainEngineFromDriverName(name: string): ExplainEngine | null
74
75
  *
75
76
  * Recognises the two Postgres shapes before trying registered custom sniffers.
76
77
  */
77
- declare function detectFormat(raw: string): ExplainSourceFormat;
78
+ declare function detectFormat(raw: string): Promise<ExplainSourceFormat>;
78
79
  /**
79
80
  * Detect the format of a payload, given what the caller knows about its
80
81
  * origin.
@@ -82,15 +83,15 @@ declare function detectFormat(raw: string): ExplainSourceFormat;
82
83
  * Built-in hints retain their historical decisions. Custom parsers are tried
83
84
  * in registry order and only through their side-effect-free sniffers.
84
85
  */
85
- declare function detectFormatFor(raw: string, engine: ExplainEngine | null): ExplainSourceFormat;
86
+ declare function detectFormatFor(raw: string, engine: ExplainEngine | null): Promise<ExplainSourceFormat>;
86
87
  /**
87
88
  * Parse a payload of unknown origin, sniffing the format.
88
89
  *
89
90
  * Equivalent to `parseExplainFor(raw, null)`.
90
91
  */
91
- declare function parseExplain(raw: string): ExplainPlan;
92
+ declare function parseExplain(raw: string): Promise<ExplainPlan>;
92
93
  /** Parse a payload, using the caller's engine hint when there is one. */
93
- declare function parseExplainFor(raw: string, engine: ExplainEngine | null): ExplainPlan;
94
+ declare function parseExplainFor(raw: string, engine: ExplainEngine | null): Promise<ExplainPlan>;
94
95
  /**
95
96
  * Label a plan that came from a named source (a file, an upload) so the UI
96
97
  * can display "From file: …" without needing a separate field.
@@ -218,7 +219,7 @@ declare function parseSqliteEqpRows(entries: SqliteEqpRow[]): ExplainPlan;
218
219
  */
219
220
 
220
221
  /** Wire formats supplied by Tabularis built-in drivers. */
221
- type BuiltinRawExplainFormat = "postgres-json" | "mysql-json" | "mysql-analyze-text" | "mysql-tabular-rows" | "sqlite-eqp-rows";
222
+ type BuiltinRawExplainFormat = 'postgres-json' | 'mysql-json' | 'mysql-analyze-text' | 'mysql-tabular-rows' | 'sqlite-eqp-rows';
222
223
  /** Wire format supplied by a built-in or registered plugin driver. */
223
224
  type RawExplainFormat = BuiltinRawExplainFormat | (string & {});
224
225
  /** Raw EXPLAIN output produced by a built-in driver, parsed on this side. */
@@ -235,16 +236,16 @@ interface RawExplainOutput {
235
236
  * built-in driver, or a plan a plugin driver already parsed.
236
237
  */
237
238
  type ExplainQueryOutput = {
238
- kind: "raw";
239
+ kind: 'raw';
239
240
  raw: RawExplainOutput;
240
241
  } | {
241
- kind: "plan";
242
+ kind: 'plan';
242
243
  plan: ExplainPlan;
243
244
  };
244
245
  /** Parse a driver's raw EXPLAIN payload into a plan. */
245
- declare function parseRawExplain(raw: RawExplainOutput): ExplainPlan;
246
+ declare function parseRawExplain(raw: RawExplainOutput): Promise<ExplainPlan>;
246
247
  /** Normalise either shape of `ExplainQueryOutput` into a plan. */
247
- declare function resolveExplainOutput(output: ExplainQueryOutput): ExplainPlan;
248
+ declare function resolveExplainOutput(output: ExplainQueryOutput): Promise<ExplainPlan>;
248
249
 
249
250
  interface ExplainNodeTypeStat {
250
251
  nodeType: string;
@@ -338,5 +339,142 @@ declare function getRowEstimateRatio(node: ExplainNode): number | null;
338
339
  */
339
340
  declare function getExplainPlanSummary(plan: ExplainPlan, metrics?: ExplainMetrics): ExplainPlanSummary;
340
341
  declare function getExplainDriverLegend(plan: ExplainPlan): string[];
342
+ interface ExplainTableRowData {
343
+ index: number;
344
+ depth: number;
345
+ nodeType: string;
346
+ relation: string | null;
347
+ startupCost: number | null;
348
+ totalCost: number | null;
349
+ planRows: number | null;
350
+ actualRows: number | null;
351
+ /** How many times the node ran (`Actual Loops`); null without ANALYZE data. */
352
+ actualLoops: number | null;
353
+ exclusiveTimeMs: number | null;
354
+ /** See {@link getRowEstimateGap}. */
355
+ estimateGap: number | null;
356
+ filter: string | null;
357
+ }
358
+ /**
359
+ * Flatten an ExplainPlan tree into table rows, in tree pre-order, collapsed
360
+ * branches included. See {@link getExplainTableRow} for the row values.
361
+ */
362
+ declare function getExplainTableRows(plan: ExplainPlan, metrics?: ExplainMetrics): ExplainTableRowData[];
363
+
364
+ type ExplainTranslator = (key: string, defaultValue?: string) => string;
365
+ interface ExplainNodeDetailEntry {
366
+ label: string;
367
+ value: string;
368
+ }
369
+ interface ExplainNodeDetailSection {
370
+ title: string;
371
+ titleKey: string;
372
+ entries: ExplainNodeDetailEntry[];
373
+ }
374
+ interface FormatExplainNodeDetailsOptions {
375
+ hasAnalyzeData?: boolean;
376
+ metrics?: ExplainNodeMetrics | null;
377
+ diagnostics?: ExplainDiagnostic[];
378
+ t?: ExplainTranslator;
379
+ includeSectionHeaders?: boolean;
380
+ }
381
+ /**
382
+ * Build structured label/value sections for an EXPLAIN node matching
383
+ * the display in the node details panel. Omit any metric or field
384
+ * that is absent rather than fabricating placeholder values or zeros.
385
+ */
386
+ declare function getExplainNodeDetailSections(node: ExplainNode, options?: {
387
+ hasAnalyzeData?: boolean;
388
+ metrics?: ExplainNodeMetrics | null;
389
+ diagnostics?: ExplainDiagnostic[];
390
+ t?: ExplainTranslator;
391
+ }): ExplainNodeDetailSection[];
392
+ /**
393
+ * Format the displayed details and metrics of an EXPLAIN node into
394
+ * stable, human-readable plain text suitable for clipboard sharing.
395
+ * Omit unavailable metrics rather than inventing zeros.
396
+ */
397
+ declare function formatExplainNodeDetails(node: ExplainNode, options?: FormatExplainNodeDetailsOptions): string;
398
+
399
+ interface ExplainSnapshotContext {
400
+ connectionId?: string;
401
+ connectionName?: string;
402
+ databaseName?: string;
403
+ schema?: string;
404
+ engine: string;
405
+ version?: string;
406
+ }
407
+ interface ExplainSnapshot {
408
+ id: string;
409
+ name: string;
410
+ createdAt: string;
411
+ sql: string;
412
+ context: ExplainSnapshotContext;
413
+ hasAnalyzeData: boolean;
414
+ planningTimeMs: number | null;
415
+ executionTimeMs: number | null;
416
+ totalCost: number | null;
417
+ rawOutput: string | null;
418
+ plan: ExplainPlan;
419
+ isPinned?: boolean;
420
+ }
421
+ interface NodeMetricDelta {
422
+ baselineValue: number | null;
423
+ currentValue: number | null;
424
+ delta: number | null;
425
+ ratio: number | null;
426
+ }
427
+ interface NodeComparison {
428
+ baselineNodeId: string | null;
429
+ currentNodeId: string | null;
430
+ operatorChanged: boolean;
431
+ baselineNodeType: string | null;
432
+ currentNodeType: string | null;
433
+ relation: string | null;
434
+ status: 'matched' | 'added' | 'removed';
435
+ costDelta: NodeMetricDelta;
436
+ timeDelta: NodeMetricDelta;
437
+ rowsDelta: NodeMetricDelta;
438
+ isAmbiguous?: boolean;
439
+ }
440
+ interface PlanComparisonResult {
441
+ isCompatible: boolean;
442
+ incompatibilityReason?: string;
443
+ hasAnalyzeMismatch: boolean;
444
+ baselineSnapshotId: string;
445
+ currentSnapshotId: string;
446
+ summary: {
447
+ totalCostDelta: NodeMetricDelta;
448
+ executionTimeDelta: NodeMetricDelta;
449
+ planningTimeDelta: NodeMetricDelta;
450
+ nodesAdded: number;
451
+ nodesRemoved: number;
452
+ operatorsChanged: number;
453
+ };
454
+ nodeComparisons: NodeComparison[];
455
+ }
456
+ /**
457
+ * Check engine compatibility between baseline and current plans/snapshots.
458
+ * Costs and units are not comparable across different database engines.
459
+ */
460
+ declare function checkEngineCompatibility(baselineEngine: string, currentEngine: string): {
461
+ compatible: boolean;
462
+ reason?: string;
463
+ };
464
+ /**
465
+ * Calculate absolute and relative delta between two nullable metric numbers.
466
+ */
467
+ declare function calculateMetricDelta(baselineValue: number | null | undefined, currentValue: number | null | undefined): NodeMetricDelta;
468
+ /**
469
+ * Align nodes between baseline and current execution plans using deterministic heuristics:
470
+ * 1. Exact tree path match if node types/relations match.
471
+ * 2. Relation and operator matching within relative subtree depth.
472
+ * 3. Identifies added and removed nodes without fabricating metrics.
473
+ */
474
+ declare function alignPlanNodes(baselineRoot: ExplainNode | null, currentRoot: ExplainNode | null, baselineMetrics?: Map<string, ExplainNodeMetrics>, currentMetrics?: Map<string, ExplainNodeMetrics>): NodeComparison[];
475
+ /**
476
+ * Compare two plan snapshots and produce a comprehensive comparison result.
477
+ */
478
+ declare function comparePlanSnapshots(baseline: ExplainSnapshot, current: ExplainSnapshot, baselineMetrics?: Map<string, ExplainNodeMetrics>, currentMetrics?: Map<string, ExplainNodeMetrics>): PlanComparisonResult;
341
479
 
342
- export { type BuiltinExplainEngine, type BuiltinExplainSourceFormat, type BuiltinRawExplainFormat, type ExplainEngine, type ExplainIndexStat, type ExplainMetricNode, ExplainMetrics, ExplainNode, type ExplainNodeTypeStat, ExplainPlan, type ExplainPlanStats, type ExplainPlanSummary, type ExplainQueryOutput, type ExplainRelationStat, type ExplainSourceFormat, type ExplainSourceParser, type MysqlTabularRow, type NodeCostStyle, NodeIdAllocator, type RawExplainFormat, type RawExplainOutput, type RegisteredExplainParser, type SqliteEqpRow, buildSqliteTree, detectFormat, detectFormatFor, explainEngineFromDriverName, findExplainNode, flattenExplainNodes, formatCost, formatRatio, formatRows, formatTime, getExplainDriverLegend, getExplainParser, getExplainPlanStats, getExplainPlanSummary, getHeatBarClass, getMaxCost, getMaxTime, getNodeCostStyle, getRowEstimateRatio, hasAnalyzeDataRecursive, listExplainParsers, parseExplain, parseExplainFor, parseMysqlJson, parseMysqlTabularRows, parseMysqlText, parsePostgresJson, parsePostgresText, parseRawExplain, parseSqliteEqpRows, registerExplainParser, resolveExplainOutput, unregisterExplainParser, withSourceLabel };
480
+ export { type BuiltinExplainEngine, type BuiltinExplainSourceFormat, type BuiltinRawExplainFormat, ExplainDiagnostic, type ExplainEngine, type ExplainIndexStat, type ExplainMetricNode, ExplainMetrics, ExplainNode, type ExplainNodeDetailEntry, type ExplainNodeDetailSection, ExplainNodeMetrics, type ExplainNodeTypeStat, ExplainPlan, type ExplainPlanStats, type ExplainPlanSummary, type ExplainQueryOutput, type ExplainRelationStat, type ExplainSnapshot, type ExplainSnapshotContext, type ExplainSourceFormat, type ExplainSourceParser, type ExplainTableRowData, type FormatExplainNodeDetailsOptions, type MysqlTabularRow, type NodeComparison, type NodeCostStyle, NodeIdAllocator, type NodeMetricDelta, type PlanComparisonResult, type RawExplainFormat, type RawExplainOutput, type RegisteredExplainParser, type SqliteEqpRow, alignPlanNodes, buildSqliteTree, calculateMetricDelta, checkEngineCompatibility, comparePlanSnapshots, detectFormat, detectFormatFor, explainEngineFromDriverName, findExplainNode, flattenExplainNodes, formatCost, formatExplainNodeDetails, formatRatio, formatRows, formatTime, getExplainDriverLegend, getExplainNodeDetailSections, getExplainParser, getExplainPlanStats, getExplainPlanSummary, getExplainTableRows, getHeatBarClass, getMaxCost, getMaxTime, getNodeCostStyle, getRowEstimateRatio, hasAnalyzeDataRecursive, listExplainParsers, parseExplain, parseExplainFor, parseMysqlJson, parseMysqlTabularRows, parseMysqlText, parsePostgresJson, parsePostgresText, parseRawExplain, parseSqliteEqpRows, registerExplainParser, resolveExplainOutput, unregisterExplainParser, withSourceLabel };