@tabularis/explain 0.1.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/LICENSE +201 -0
- package/README.md +104 -0
- package/dist/diagnostics-DPSgsjFk.d.ts +157 -0
- package/dist/flow.d.ts +40 -0
- package/dist/flow.js +171 -0
- package/dist/index.d.ts +325 -0
- package/dist/index.js +1792 -0
- package/dist/react.d.ts +91 -0
- package/dist/react.js +1617 -0
- package/package.json +95 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
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';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Format detection and dispatch for a raw EXPLAIN payload.
|
|
6
|
+
*
|
|
7
|
+
* A caller that knows which engine produced the output says so, and the right
|
|
8
|
+
* parser is used directly. A caller that does not — a dropped file, a pasted
|
|
9
|
+
* blob — omits the hint and the format is sniffed.
|
|
10
|
+
*
|
|
11
|
+
* The host stays responsible for obtaining the bytes; this module only
|
|
12
|
+
* inspects them.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** The engine that produced an EXPLAIN payload, when the caller knows it. */
|
|
16
|
+
type ExplainEngine = "postgres" | "mysql" | "sqlite";
|
|
17
|
+
/** Supported source formats that a host may hand to `parseExplainFor`. */
|
|
18
|
+
type ExplainSourceFormat =
|
|
19
|
+
/** Postgres `EXPLAIN (FORMAT JSON [, ANALYZE, BUFFERS])` output. */
|
|
20
|
+
"postgres-json"
|
|
21
|
+
/**
|
|
22
|
+
* Postgres default `EXPLAIN` output — indentation-based tree with
|
|
23
|
+
* `cost=X..Y rows=N width=W` headers and optional `actual time` blocks.
|
|
24
|
+
*/
|
|
25
|
+
| "postgres-text"
|
|
26
|
+
/**
|
|
27
|
+
* MySQL / MariaDB `EXPLAIN FORMAT=JSON` or `ANALYZE FORMAT=JSON` output —
|
|
28
|
+
* a document with a `query_block` key.
|
|
29
|
+
*/
|
|
30
|
+
| "mysql-json"
|
|
31
|
+
/** MySQL `EXPLAIN ANALYZE` / MariaDB `ANALYZE FORMAT=TEXT` indented tree. */
|
|
32
|
+
| "mysql-text";
|
|
33
|
+
/**
|
|
34
|
+
* A parser for one serialised EXPLAIN payload format.
|
|
35
|
+
*
|
|
36
|
+
* Every engine exposes its formats through this same interface so dispatch,
|
|
37
|
+
* sniffing and hosts handle them uniformly.
|
|
38
|
+
*/
|
|
39
|
+
interface ExplainSourceParser {
|
|
40
|
+
readonly engine: ExplainEngine;
|
|
41
|
+
readonly format: ExplainSourceFormat;
|
|
42
|
+
/** Turn a raw payload into a plan; throws `Error` when the payload does not fit. */
|
|
43
|
+
parse(raw: string): ExplainPlan;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Map a driver identifier — the same string carried in `ExplainPlan.driver` —
|
|
47
|
+
* onto an engine.
|
|
48
|
+
*
|
|
49
|
+
* Matching is case-insensitive, and MariaDB maps onto `"mysql"` because they
|
|
50
|
+
* share every plan format. Returns `null` for an unknown name, so an
|
|
51
|
+
* unrecognised driver degrades to sniffing rather than failing.
|
|
52
|
+
*/
|
|
53
|
+
declare function explainEngineFromDriverName(name: string): ExplainEngine | null;
|
|
54
|
+
/**
|
|
55
|
+
* Detect the format of a payload of unknown origin.
|
|
56
|
+
*
|
|
57
|
+
* Recognises the two Postgres shapes only; pass an engine to
|
|
58
|
+
* `detectFormatFor` to reach the others.
|
|
59
|
+
*/
|
|
60
|
+
declare function detectFormat(raw: string): ExplainSourceFormat;
|
|
61
|
+
/**
|
|
62
|
+
* Detect the format of a payload, given what the caller knows about its
|
|
63
|
+
* origin.
|
|
64
|
+
*
|
|
65
|
+
* With an engine the choice is between that engine's own formats. Without
|
|
66
|
+
* one, behaviour is unchanged from `detectFormat`: JSON is recognised by the
|
|
67
|
+
* leading `[` or `{`, and the text form by a Postgres cost header
|
|
68
|
+
* (`cost=X..Y rows=N width=W`).
|
|
69
|
+
*/
|
|
70
|
+
declare function detectFormatFor(raw: string, engine: ExplainEngine | null): ExplainSourceFormat;
|
|
71
|
+
/**
|
|
72
|
+
* Parse a payload of unknown origin, sniffing the format.
|
|
73
|
+
*
|
|
74
|
+
* Equivalent to `parseExplainFor(raw, null)`.
|
|
75
|
+
*/
|
|
76
|
+
declare function parseExplain(raw: string): ExplainPlan;
|
|
77
|
+
/** Parse a payload, using the caller's engine hint when there is one. */
|
|
78
|
+
declare function parseExplainFor(raw: string, engine: ExplainEngine | null): ExplainPlan;
|
|
79
|
+
/**
|
|
80
|
+
* Label a plan that came from a named source (a file, an upload) so the UI
|
|
81
|
+
* can display "From file: …" without needing a separate field.
|
|
82
|
+
*
|
|
83
|
+
* Takes the display name rather than a path: deriving a basename from a path
|
|
84
|
+
* is the host's job.
|
|
85
|
+
*/
|
|
86
|
+
declare function withSourceLabel(plan: ExplainPlan, name: string): ExplainPlan;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Postgres `EXPLAIN` parsers: `FORMAT JSON` documents and the default
|
|
90
|
+
* indentation-based text output.
|
|
91
|
+
*
|
|
92
|
+
* Pure string -> `ExplainPlan` transformations: no connection, no statement
|
|
93
|
+
* execution, no file system.
|
|
94
|
+
*/
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Parse a Postgres `EXPLAIN (FORMAT JSON)` document into an `ExplainPlan`.
|
|
98
|
+
*
|
|
99
|
+
* Postgres emits a top-level JSON array with one element per explained
|
|
100
|
+
* statement. We honour this by picking the first element; each object carries
|
|
101
|
+
* a `Plan` node plus optional `Planning Time` / `Execution Time` timings.
|
|
102
|
+
*/
|
|
103
|
+
declare function parsePostgresJson(raw: string): ExplainPlan;
|
|
104
|
+
/** Parse a Postgres text EXPLAIN dump into an `ExplainPlan`. */
|
|
105
|
+
declare function parsePostgresText(raw: string): ExplainPlan;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Shared building blocks for every plan parser.
|
|
109
|
+
*
|
|
110
|
+
* Pure value -> `ExplainNode` helpers: no host, no driver, no React.
|
|
111
|
+
*/
|
|
112
|
+
|
|
113
|
+
/** Hands out sequential `node_N` ids so every node in a plan is unique. */
|
|
114
|
+
declare class NodeIdAllocator {
|
|
115
|
+
private counter;
|
|
116
|
+
next(): string;
|
|
117
|
+
}
|
|
118
|
+
/** Check recursively whether any node in the tree carries actual ANALYZE data. */
|
|
119
|
+
declare function hasAnalyzeDataRecursive(node: ExplainNode): boolean;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* MySQL / MariaDB plan parsers.
|
|
123
|
+
*
|
|
124
|
+
* Covers the two shapes these engines emit that carry a full plan tree:
|
|
125
|
+
* `EXPLAIN FORMAT=JSON` (and MariaDB's `ANALYZE FORMAT=JSON`, with its
|
|
126
|
+
* `filesort` / `temporary_table` / `subquery_cache` wrappers), MariaDB's
|
|
127
|
+
* `ANALYZE FORMAT=TEXT` indented output, plus the tabular `EXPLAIN` rows that
|
|
128
|
+
* the host decodes from the wire and hands over as plain values.
|
|
129
|
+
*/
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Parse a MySQL `EXPLAIN FORMAT=JSON` or MariaDB `ANALYZE FORMAT=JSON`
|
|
133
|
+
* document into a plan.
|
|
134
|
+
*
|
|
135
|
+
* Both shapes hang the tree off a `query_block` key, and MariaDB's analysing
|
|
136
|
+
* variant adds `query_optimization.r_total_time_ms` — picked up as the
|
|
137
|
+
* planning time when present, absent for plain `EXPLAIN FORMAT=JSON`.
|
|
138
|
+
*
|
|
139
|
+
* `original_query` is left empty: only the caller knows the statement.
|
|
140
|
+
*/
|
|
141
|
+
declare function parseMysqlJson(raw: string): ExplainPlan;
|
|
142
|
+
/**
|
|
143
|
+
* Parse a MySQL `EXPLAIN ANALYZE` / MariaDB `ANALYZE FORMAT=TEXT` indented
|
|
144
|
+
* tree into a plan.
|
|
145
|
+
*
|
|
146
|
+
* `original_query` is left empty: only the caller knows the statement.
|
|
147
|
+
*/
|
|
148
|
+
declare function parseMysqlText(raw: string): ExplainPlan;
|
|
149
|
+
/**
|
|
150
|
+
* One decoded row of tabular `EXPLAIN` output, as the host serialises it.
|
|
151
|
+
*
|
|
152
|
+
* The host owns the wire decoding (column lookup, VARBINARY vs VARCHAR); this
|
|
153
|
+
* shape is the boundary between the driver and the parser.
|
|
154
|
+
*/
|
|
155
|
+
interface MysqlTabularRow {
|
|
156
|
+
select_type: string;
|
|
157
|
+
table: string | null;
|
|
158
|
+
access_type: string | null;
|
|
159
|
+
possible_keys: string | null;
|
|
160
|
+
key: string | null;
|
|
161
|
+
rows: number | null;
|
|
162
|
+
filtered: number | null;
|
|
163
|
+
extra: string | null;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Build a plan from tabular `EXPLAIN` rows (MySQL/MariaDB without
|
|
167
|
+
* FORMAT=JSON). The flat rows become children of a synthetic `Query` root.
|
|
168
|
+
*/
|
|
169
|
+
declare function parseMysqlTabularRows(rows: MysqlTabularRow[]): ExplainPlan;
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* SQLite plan parsers.
|
|
173
|
+
*
|
|
174
|
+
* SQLite's `EXPLAIN QUERY PLAN` returns `(id, parent, detail)` triples. The
|
|
175
|
+
* host decodes those rows; everything below turns them into a plan tree and
|
|
176
|
+
* reads the `detail` strings, with no driver in sight.
|
|
177
|
+
*/
|
|
178
|
+
|
|
179
|
+
/** One decoded `EXPLAIN QUERY PLAN` row, as the host serialises it. */
|
|
180
|
+
interface SqliteEqpRow {
|
|
181
|
+
id: number;
|
|
182
|
+
parent: number;
|
|
183
|
+
detail: string;
|
|
184
|
+
}
|
|
185
|
+
declare function buildSqliteTree(entries: SqliteEqpRow[], parentId: number, ids: NodeIdAllocator): ExplainNode;
|
|
186
|
+
/**
|
|
187
|
+
* Build a full plan from decoded `EXPLAIN QUERY PLAN` rows, reconstructing
|
|
188
|
+
* the familiar `id|parent|detail` raw text alongside the tree.
|
|
189
|
+
*/
|
|
190
|
+
declare function parseSqliteEqpRows(entries: SqliteEqpRow[]): ExplainPlan;
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* The boundary between a host driver and the parsers.
|
|
194
|
+
*
|
|
195
|
+
* A built-in driver runs `EXPLAIN`, decides which variant succeeded, and hands
|
|
196
|
+
* over the raw payload untouched — text, JSON, or decoded rows re-serialised
|
|
197
|
+
* as JSON. Everything that *interprets* that payload lives here, so the same
|
|
198
|
+
* parsers back the desktop app and any standalone visualiser fed a pasted or
|
|
199
|
+
* uploaded plan.
|
|
200
|
+
*
|
|
201
|
+
* Plugin drivers know engines this package does not, so they keep returning a
|
|
202
|
+
* fully-parsed plan; `resolveExplainOutput` accepts both shapes.
|
|
203
|
+
*/
|
|
204
|
+
|
|
205
|
+
/** Wire formats a built-in driver can hand over. */
|
|
206
|
+
type RawExplainFormat = "postgres-json" | "mysql-json" | "mysql-analyze-text" | "mysql-tabular-rows" | "sqlite-eqp-rows";
|
|
207
|
+
/** Raw EXPLAIN output produced by a built-in driver, parsed on this side. */
|
|
208
|
+
interface RawExplainOutput {
|
|
209
|
+
/** Driver id of the engine that produced the payload ("postgres", …). */
|
|
210
|
+
engine: string;
|
|
211
|
+
format: RawExplainFormat;
|
|
212
|
+
/** The untouched payload: text, a JSON document, or rows as a JSON array. */
|
|
213
|
+
payload: string;
|
|
214
|
+
original_query: string;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* What the host's `explain_query_plan` command returns: a raw payload from a
|
|
218
|
+
* built-in driver, or a plan a plugin driver already parsed.
|
|
219
|
+
*/
|
|
220
|
+
type ExplainQueryOutput = {
|
|
221
|
+
kind: "raw";
|
|
222
|
+
raw: RawExplainOutput;
|
|
223
|
+
} | {
|
|
224
|
+
kind: "plan";
|
|
225
|
+
plan: ExplainPlan;
|
|
226
|
+
};
|
|
227
|
+
/** Parse a driver's raw EXPLAIN payload into a plan. */
|
|
228
|
+
declare function parseRawExplain(raw: RawExplainOutput): ExplainPlan;
|
|
229
|
+
/** Normalise either shape of `ExplainQueryOutput` into a plan. */
|
|
230
|
+
declare function resolveExplainOutput(output: ExplainQueryOutput): ExplainPlan;
|
|
231
|
+
|
|
232
|
+
interface ExplainNodeTypeStat {
|
|
233
|
+
nodeType: string;
|
|
234
|
+
count: number;
|
|
235
|
+
/** Summed exclusive time, or `null` when no node of this type was timed. */
|
|
236
|
+
exclusiveTimeMs: number | null;
|
|
237
|
+
/** Share of the plan's total exclusive time, 0..1. */
|
|
238
|
+
timeShare: number | null;
|
|
239
|
+
}
|
|
240
|
+
interface ExplainRelationStat {
|
|
241
|
+
relation: string;
|
|
242
|
+
/** How many nodes read this relation. */
|
|
243
|
+
accessCount: number;
|
|
244
|
+
/** Node types that touched it, in first-seen order. */
|
|
245
|
+
nodeTypes: string[];
|
|
246
|
+
exclusiveTimeMs: number | null;
|
|
247
|
+
totalRows: number | null;
|
|
248
|
+
}
|
|
249
|
+
interface ExplainIndexStat {
|
|
250
|
+
indexName: string;
|
|
251
|
+
relation: string | null;
|
|
252
|
+
scanCount: number;
|
|
253
|
+
exclusiveTimeMs: number | null;
|
|
254
|
+
}
|
|
255
|
+
interface ExplainPlanStats {
|
|
256
|
+
nodeCount: number;
|
|
257
|
+
/** Depth of the deepest node, 1 for a single-node plan. */
|
|
258
|
+
maxDepth: number;
|
|
259
|
+
neverExecutedCount: number;
|
|
260
|
+
totalExclusiveTimeMs: number | null;
|
|
261
|
+
nodeTypes: ExplainNodeTypeStat[];
|
|
262
|
+
relations: ExplainRelationStat[];
|
|
263
|
+
indexes: ExplainIndexStat[];
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Plan-wide aggregates: which node types dominate the runtime, which relations
|
|
267
|
+
* are read and how often, and which indexes were used.
|
|
268
|
+
*/
|
|
269
|
+
declare function getExplainPlanStats(plan: ExplainPlan, metrics: ExplainMetrics): ExplainPlanStats;
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Plan analysis primitives: tree traversal, formatters, heat colours and the
|
|
273
|
+
* headline summary. No rendering library and no notion of the statement that
|
|
274
|
+
* produced the plan.
|
|
275
|
+
*/
|
|
276
|
+
|
|
277
|
+
interface NodeCostStyle {
|
|
278
|
+
border: string;
|
|
279
|
+
headerBg: string;
|
|
280
|
+
}
|
|
281
|
+
interface ExplainMetricNode {
|
|
282
|
+
nodeId: string;
|
|
283
|
+
nodeType: string;
|
|
284
|
+
relation: string | null;
|
|
285
|
+
value: number;
|
|
286
|
+
ratio?: number;
|
|
287
|
+
}
|
|
288
|
+
interface ExplainPlanSummary {
|
|
289
|
+
/** Node with the highest cost of its own, children excluded. */
|
|
290
|
+
highestCostNode: ExplainMetricNode | null;
|
|
291
|
+
/** Node that spent the most time in itself, children excluded. */
|
|
292
|
+
slowestNode: ExplainMetricNode | null;
|
|
293
|
+
largestRowMismatchNode: ExplainMetricNode | null;
|
|
294
|
+
sequentialScans: number;
|
|
295
|
+
tempOperations: number;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Heat colour for one node, given the largest value in the plan. Callers pass
|
|
299
|
+
* *exclusive* values: an inclusive one would paint the plan root red in every
|
|
300
|
+
* plan and hide the actual bottleneck.
|
|
301
|
+
*/
|
|
302
|
+
declare function getNodeCostStyle(cost: number, maxCost: number): NodeCostStyle;
|
|
303
|
+
/**
|
|
304
|
+
* Fill colour for a proportional bar, on the same yellow-to-red scale as the
|
|
305
|
+
* graph nodes. Callers pass exclusive values, as for {@link getNodeCostStyle}.
|
|
306
|
+
*/
|
|
307
|
+
declare function getHeatBarClass(value: number, max: number): string;
|
|
308
|
+
declare function formatCost(n: number): string;
|
|
309
|
+
declare function formatTime(ms: number): string;
|
|
310
|
+
declare function formatRows(n: number): string;
|
|
311
|
+
declare function formatRatio(n: number): string;
|
|
312
|
+
declare function getMaxCost(node: ExplainNode): number;
|
|
313
|
+
declare function getMaxTime(node: ExplainNode): number;
|
|
314
|
+
declare function flattenExplainNodes(root: ExplainNode): ExplainNode[];
|
|
315
|
+
declare function findExplainNode(root: ExplainNode, nodeId: string | null): ExplainNode | null;
|
|
316
|
+
declare function getRowEstimateRatio(node: ExplainNode): number | null;
|
|
317
|
+
/**
|
|
318
|
+
* Headline findings for the overview bar. Cost and time are ranked on each
|
|
319
|
+
* node's own contribution, so the winner is the step to look at rather than
|
|
320
|
+
* whichever node happens to sit closest to the plan root.
|
|
321
|
+
*/
|
|
322
|
+
declare function getExplainPlanSummary(plan: ExplainPlan, metrics?: ExplainMetrics): ExplainPlanSummary;
|
|
323
|
+
declare function getExplainDriverLegend(plan: ExplainPlan): string[];
|
|
324
|
+
|
|
325
|
+
export { 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 SqliteEqpRow, buildSqliteTree, detectFormat, detectFormatFor, explainEngineFromDriverName, findExplainNode, flattenExplainNodes, formatCost, formatRatio, formatRows, formatTime, getExplainDriverLegend, getExplainPlanStats, getExplainPlanSummary, getHeatBarClass, getMaxCost, getMaxTime, getNodeCostStyle, getRowEstimateRatio, hasAnalyzeDataRecursive, parseExplain, parseExplainFor, parseMysqlJson, parseMysqlTabularRows, parseMysqlText, parsePostgresJson, parsePostgresText, parseRawExplain, parseSqliteEqpRows, resolveExplainOutput, withSourceLabel };
|