diff --git a/README.md b/README.md
index 3b19bf0..8401ffb 100644
--- a/README.md
+++ b/README.md
@@ -3,9 +3,9 @@
[](https://discord.com/invite/K2hmhfHRSt)
A free online EXPLAIN plan visualizer: paste the EXPLAIN output of
-a **PostgreSQL**, **MySQL/MariaDB** or **SQLite** query and explore it as an
-interactive graph, diagram, table and statistics view — with automatic
-performance findings.
+a **PostgreSQL**, **MySQL/MariaDB**, **SQLite** or **SQL Server** query and
+explore it as an interactive graph, diagram, table and statistics view — with
+automatic performance findings.
Everything runs in the browser. No query is ever executed and nothing is
uploaded to a server.
@@ -24,14 +24,17 @@ Tabularis dark theme.
| PostgreSQL | `EXPLAIN (FORMAT JSON)`, plain `EXPLAIN` / `EXPLAIN ANALYZE` text |
| MySQL / MariaDB | `EXPLAIN FORMAT=JSON`, `EXPLAIN ANALYZE` / `ANALYZE FORMAT=TEXT` trees |
| SQLite | `EXPLAIN QUERY PLAN` — the sqlite3 shell tree (`|--` / `` `-- ``) or raw `id\|parent\|…\|detail` rows |
+| SQL Server | `SHOWPLAN_XML` estimated plans and `STATISTICS XML` actual plans (`ShowPlanXML` documents) |
The engine can be selected explicitly or auto-detected from the pasted text.
+SQL Server's `ShowPlanXML` root element makes its format unambiguous.
## Stack
- [Vite](https://vite.dev) + [React 19](https://react.dev) + TypeScript
- [Tailwind CSS 4](https://tailwindcss.com) with the Tabularis colour tokens
- [`@tabularis/explain`](https://www.npmjs.com/package/@tabularis/explain) for parsing, analysis and the plan views
+- [`@tabularis/explain-sqlserver`](https://www.npmjs.com/package/@tabularis/explain-sqlserver) for SQL Server SHOWPLAN XML
- [Vitest](https://vitest.dev) + Testing Library for tests
## Development
diff --git a/package.json b/package.json
index dc22b97..d4f726d 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "explain-plan",
"private": true,
"version": "0.1.0",
- "description": "Paste a PostgreSQL, MySQL/MariaDB or SQLite EXPLAIN output and visualise the query plan.",
+ "description": "Paste a PostgreSQL, MySQL/MariaDB, SQLite or SQL Server EXPLAIN output and visualise the query plan.",
"type": "module",
"scripts": {
"dev": "vite",
@@ -13,7 +13,8 @@
"test:watch": "vitest"
},
"dependencies": {
- "@tabularis/explain": "^0.1.0",
+ "@tabularis/explain": "^0.2.0",
+ "@tabularis/explain-sqlserver": "^1.0.0-beta.1",
"@xyflow/react": "^12.10.2",
"clsx": "^2.1.1",
"dagre": "^0.8.5",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ecd1a59..512dc05 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -9,8 +9,11 @@ importers:
.:
dependencies:
'@tabularis/explain':
- specifier: ^0.1.0
- version: 0.1.0(@xyflow/react@12.11.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(clsx@2.1.1)(dagre@0.8.5)(lucide-react@0.563.0(react@19.2.8))(react-i18next@16.6.6(i18next@25.10.10(typescript@5.9.3))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@5.9.3))(react@19.2.8)
+ specifier: ^0.2.0
+ version: 0.2.0(@xyflow/react@12.11.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(clsx@2.1.1)(dagre@0.8.5)(lucide-react@0.563.0(react@19.2.8))(react-i18next@16.6.6(i18next@25.10.10(typescript@5.9.3))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@5.9.3))(react@19.2.8)
+ '@tabularis/explain-sqlserver':
+ specifier: ^1.0.0-beta.1
+ version: 1.0.0-beta.1(@tabularis/explain@0.2.0(@xyflow/react@12.11.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(clsx@2.1.1)(dagre@0.8.5)(lucide-react@0.563.0(react@19.2.8))(react-i18next@16.6.6(i18next@25.10.10(typescript@5.9.3))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@5.9.3))(react@19.2.8))
'@xyflow/react':
specifier: ^12.10.2
version: 12.11.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
@@ -521,8 +524,14 @@ packages:
cpu: [x64]
os: [win32]
- '@tabularis/explain@0.1.0':
- resolution: {integrity: sha512-yOEInJYbKr07nxCNaRgp4Yv9+rWGf3yF3/MVQfe9yZCBwiX0AR7RGtPSHoY/ki/WAzqXEErMMkpcxnoMnM+6Bg==}
+ '@tabularis/explain-sqlserver@1.0.0-beta.1':
+ resolution: {integrity: sha512-sJJRH/Di4MJRjIXvoh5YHSDunLM+36gyWsuFPRSStFaV0YcnZPvHjoIHsJMSbmdoagYkaOKfaTqw5b+FP/iP8Q==}
+ engines: {node: '>=20'}
+ peerDependencies:
+ '@tabularis/explain': ^0.2.0
+
+ '@tabularis/explain@0.2.0':
+ resolution: {integrity: sha512-oV7tkkMJ4UrH7J1ZaqjhOnLP/yFsxp0SAsNOas8ITDBYTz2LLIq18apMGxePq16QpuaKSqaDrwJL+3Tv8irO7Q==}
peerDependencies:
'@xyflow/react': ^12.0.0
clsx: ^2.0.0
@@ -1794,7 +1803,11 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.62.2':
optional: true
- '@tabularis/explain@0.1.0(@xyflow/react@12.11.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(clsx@2.1.1)(dagre@0.8.5)(lucide-react@0.563.0(react@19.2.8))(react-i18next@16.6.6(i18next@25.10.10(typescript@5.9.3))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@5.9.3))(react@19.2.8)':
+ '@tabularis/explain-sqlserver@1.0.0-beta.1(@tabularis/explain@0.2.0(@xyflow/react@12.11.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(clsx@2.1.1)(dagre@0.8.5)(lucide-react@0.563.0(react@19.2.8))(react-i18next@16.6.6(i18next@25.10.10(typescript@5.9.3))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@5.9.3))(react@19.2.8))':
+ dependencies:
+ '@tabularis/explain': 0.2.0(@xyflow/react@12.11.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(clsx@2.1.1)(dagre@0.8.5)(lucide-react@0.563.0(react@19.2.8))(react-i18next@16.6.6(i18next@25.10.10(typescript@5.9.3))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@5.9.3))(react@19.2.8)
+
+ '@tabularis/explain@0.2.0(@xyflow/react@12.11.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(clsx@2.1.1)(dagre@0.8.5)(lucide-react@0.563.0(react@19.2.8))(react-i18next@16.6.6(i18next@25.10.10(typescript@5.9.3))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@5.9.3))(react@19.2.8)':
optionalDependencies:
'@xyflow/react': 12.11.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
clsx: 2.1.1
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index e21751e..18e7078 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -1,4 +1,5 @@
allowBuilds:
esbuild: false
minimumReleaseAgeExclude:
- - '@tabularis/explain@0.1.0'
+ - '@tabularis/explain@0.2.0'
+ - '@tabularis/explain-sqlserver@1.0.0-beta.1'
diff --git a/seo.config.json b/seo.config.json
index 54d624e..6ed87c9 100644
--- a/seo.config.json
+++ b/seo.config.json
@@ -1,12 +1,14 @@
{
- "title": "Explain Plan — Visualize PostgreSQL, MySQL & SQLite query plans",
- "description": "Free online EXPLAIN plan visualizer. Paste the EXPLAIN output of a PostgreSQL, MySQL, MariaDB or SQLite query and explore it as an interactive graph, diagram, table and statistics — with automatic performance findings. Nothing leaves your browser.",
+ "title": "Explain Plan — PostgreSQL, MySQL, SQLite & SQL Server viewer",
+ "description": "Free online EXPLAIN plan and SHOWPLAN XML viewer. Paste a PostgreSQL, MySQL, MariaDB, SQLite or SQL Server execution plan and explore its graph, diagram, table and statistics. Nothing leaves your browser.",
"keywords": [
"explain plan",
"query plan visualizer",
"postgres explain analyze",
"mysql explain format=json",
"sqlite explain query plan",
+ "SQL Server execution plan",
+ "SHOWPLAN XML viewer",
"sql performance",
"query optimization",
"tabularis"
diff --git a/src/App.test.tsx b/src/App.test.tsx
index d7a2532..8ebb7df 100644
--- a/src/App.test.tsx
+++ b/src/App.test.tsx
@@ -23,6 +23,9 @@ describe("App", () => {
screen.getByRole("option", { name: "MySQL / MariaDB" }),
).toBeInTheDocument();
expect(screen.getByRole("option", { name: "SQLite" })).toBeInTheDocument();
+ expect(
+ screen.getByRole("option", { name: "SQL Server" }),
+ ).toBeInTheDocument();
expect(
screen.getByRole("link", { name: "Tabularis" }),
).toHaveAttribute("href", "https://tabularis.dev");
@@ -53,6 +56,32 @@ describe("App", () => {
expect(screen.getByText("Raw Output")).toBeInTheDocument();
});
+ it("renders every plan view for a SQL Server runtime sample", async () => {
+ const user = userEvent.setup();
+ const { container } = render(
- Paste the EXPLAIN output of a PostgreSQL, MySQL/MariaDB or SQLite - query and explore it as an interactive graph, diagram, table and - statistics. Everything runs in your browser — no query is + Paste the EXPLAIN output of a PostgreSQL, MySQL/MariaDB, SQLite + or SQL Server query and explore it as an interactive graph, + diagram, table and statistics. Everything runs in your browser — + no query is executed and nothing is uploaded.