feat: showplan digest
No fixtures/real.sqlplan available (no SQL Server reachable from this machine); the real-plan test skips and says so. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
1c3f222f9d
commit
4fefd0f6da
@@ -0,0 +1,78 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
const { digestPlan, PlanError, digestForModel } = await import("../lib/digest.ts");
|
||||
|
||||
export const PLAN = `<?xml version="1.0" encoding="utf-16"?>
|
||||
<ShowPlanXML xmlns="http://schemas.microsoft.com/sqlserver/2004/07/showplan" Version="1.6" Build="16.0.4085.2">
|
||||
<BatchSequence><Batch><Statements>
|
||||
<StmtSimple StatementId="1" StatementSubtreeCost="4.871" StatementOptmLevel="FULL"
|
||||
StatementText="SELECT o.Id FROM Sales.Orders o WHERE o.CustomerId = @c1 AND o.Total > 500">
|
||||
<StatementOptmEarlyAbortReason>GoodEnoughPlanFound</StatementOptmEarlyAbortReason>
|
||||
<QueryBlock QueryHash="0x1A" QueryPlanHash="0x2B">
|
||||
<RelOp NodeId="0" PhysicalOp="Clustered Index Scan" LogicalOp="Clustered Index Scan" EstimateRows="1000"
|
||||
EstimateIO="41.6" EstimateCPU="39.0" EstimatedTotalSubtreeCost="4.10" Parallel="false">
|
||||
<RunTimeInformation><RunTimeCountersPerThread Thread="0" ActualRows="1243500" ActualExecs="1"
|
||||
ActualElapsedms="2411" ActualCPUms="2210"/></RunTimeInformation>
|
||||
<IndexScan Database="[wide]" Schema="[Sales]" Table="[Orders]"/>
|
||||
<Warnings><NoJoinPredicate/><SpillToTempDb SpillLevel="2"/>
|
||||
<WaitTime WaitType="PAGEIOLATCH_SH" WaitTime="812"/></Warnings>
|
||||
</RelOp>
|
||||
<RelOp NodeId="1" PhysicalOp="Index Seek" LogicalOp="Index Seek" EstimateRows="3"
|
||||
EstimateIO="0.7" EstimateCPU="0.02" EstimatedTotalSubtreeCost="0.77" Parallel="true">
|
||||
<RunTimeInformation><RunTimeCountersPerThread Thread="0" ActualRows="4" ActualExecs="2"/></RunTimeInformation>
|
||||
<IndexScan Database="[wide]" Schema="[Sales]" Table="[Customer]"/>
|
||||
</RelOp>
|
||||
<MissingIndexes><MissingIndexGroup Impact="94.17">
|
||||
<MissingIndex Database="[wide]" Schema="[Sales]" Table="[Orders]">
|
||||
<ColumnGroup Usage="EQUALITY"><Column Name="[CustomerId]"/></ColumnGroup>
|
||||
<ColumnGroup Usage="INEQUALITY"><Column Name="[Total]"/></ColumnGroup>
|
||||
<IncludedColumns><Column Name="[Id]"/></IncludedColumns>
|
||||
</MissingIndex></MissingIndexGroup></MissingIndexes>
|
||||
<NonParallelPlanReason>Could Not Generate Valid Parallel Plan</NonParallelPlanReason>
|
||||
</QueryBlock>
|
||||
</StmtSimple>
|
||||
</Statements></Batch></BatchSequence>
|
||||
</ShowPlanXML>`;
|
||||
|
||||
test("digests what matters in a showplan", () => {
|
||||
const d = digestPlan(PLAN);
|
||||
assert.match(d.sql, /o\.Total > 500/, "XML entities decoded");
|
||||
assert.equal(d.subtreeCost, 4.871);
|
||||
assert.equal(d.earlyAbort, "GoodEnoughPlanFound");
|
||||
assert.equal(d.nonParallelReason, "Could Not Generate Valid Parallel Plan");
|
||||
assert.deepEqual(d.tables, ["wide.Sales.Orders", "wide.Sales.Customer"]);
|
||||
assert.equal(d.topOperators[0].op, "Clustered Index Scan");
|
||||
assert.equal(d.topOperators[0].spillLevels, 2);
|
||||
assert.equal(d.topOperators[0].actualRows, 1243500);
|
||||
assert.equal(d.topOperators[0].parallel, false);
|
||||
assert.deepEqual(d.missingIndexes[0], { table: "wide.Sales.Orders", equality: ["CustomerId"],
|
||||
inequality: ["Total"], included: ["Id"], impact: 94.17 });
|
||||
assert.ok(d.warnings.includes("NoJoinPredicate"));
|
||||
assert.ok(d.warnings.some((w) => w.startsWith("SpillToTempDb")));
|
||||
assert.deepEqual(d.waits, [{ type: "PAGEIOLATCH_SH", ms: 812 }]);
|
||||
assert.ok(d.rowGuessErrors[0].ratio > 100);
|
||||
assert.equal(d.bytes, PLAN.length);
|
||||
});
|
||||
test("a BOM-prefixed plan still digests — SSMS writes .sqlplan as UTF-16 with a BOM", () => {
|
||||
assert.equal(digestPlan("\uFEFF" + PLAN).subtreeCost, 4.871);
|
||||
});
|
||||
test("rejects input that is not a plan", () => {
|
||||
for (const bad of ["", "SELECT 1", "<html></html>", "<ShowPlanXML><trunc"])
|
||||
assert.throws(() => digestPlan(bad), PlanError);
|
||||
});
|
||||
test("digestForModel stays bounded on a fat plan", () => {
|
||||
const big = `<ShowPlanXML><Batch><Statements><StmtSimple StatementText="q">` +
|
||||
Array.from({ length: 400 }, (_, i) =>
|
||||
`<RelOp NodeId="${i}" LogicalOp="Scan" EstimateRows="1" EstimatedTotalSubtreeCost="${i}"/>`).join("") +
|
||||
`</StmtSimple></Statements></Batch></Batch></ShowPlanXML>`;
|
||||
const v = digestForModel(digestPlan(big)) as { topOperators: unknown[]; tables: unknown[] };
|
||||
assert.ok(v.topOperators.length <= 12);
|
||||
});
|
||||
test("a real .sqlplan digests", () => {
|
||||
let xml: string;
|
||||
try { xml = readFileSync("fixtures/real.sqlplan", "utf8"); } catch { return console.log("skip: no fixtures/real.sqlplan"); }
|
||||
const d = digestPlan(xml);
|
||||
assert.ok(d.topOperators.length > 0, "no operators found — check attribute spellings");
|
||||
assert.ok(d.sql.length > 0);
|
||||
});
|
||||
Reference in New Issue
Block a user