Clean-room SQL Server showplan XML parser in C++17.
Parses .sqlplan / .queryplan documents (Microsoft's public showplan schema) into strongly typed C++ trees. Pure data. No rendering, layout, or analysis. Bring your own UI / heuristics.
1.0. Stable API.
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
ctest --test-dir build --output-on-failureadd_subdirectory(extern/libshowplan) # or FetchContent / submodule
target_link_libraries(myapp PRIVATE showplan::showplan)#include <showplan/showplan.hpp>
try {
auto plan = showplan::parse_file("query.sqlplan");
for (const auto& stmt : plan.statements) {
// walk stmt.root->children recursively
}
} catch (const showplan::ParseError& e) {
// bad XML, not a ShowPlanXML document, or I/O error
}StatementId,StatementText,StatementType,StatementSubTreeCostcompile_time_ms,compile_cpu_ms,compile_memory_kb,cached_plan_size_kbfrom<QueryPlan>optm_early_abort_reason(TimeOut/MemoryLimitExceeded/GoodEnoughPlanFound/ empty)optm_level(TRIVIAL/FULL)retrieved_from_cache(defaulttruewhen the attribute is absent)parent_object_id:sys.objects.object_idof the module the statement was emitted from. 0 = ad-hoc outer batch; non-zero values resolve toschema.nameviasys.objectsin your client. Drives the "at: <object>, Nest Level: N" call-chain header used by GUI tools.
PhysicalOp,LogicalOp,parallel,is_lookup(Key/RID Lookup)- Estimates:
est_rows,est_rows_per_exec,est_executions,est_row_size_bytes,est_io_cost,est_cpu_cost,est_op_cost,est_subtree_cost execution_mode:Row/Batch(Estimated or runtime variant)est_rows_to_be_read: optimizer-projected SCAN rows (distinct fromest_rowswhich is rows RETURNED)partitioned(RelOp attribute),ordered(IndexScan attribute)seek_predicates: pre-formatted human-readable strings extracted from<SeekPredicates>. Equality prefixes render as"<col> = <expr>"; pairedStartRange+EndRangecollapse into"<col>: <start> - <end>"(saves ~50% of lines on range-partition seeks); standalone bounds keep their operator (>,<,>=,<=).predicate: residual filter expressionoutput_list: fully-qualified column referenceswarnings: per-operator<Warnings>childrenObjectreferences →database/schema/table/index_name/index_kind/storage/alias
- Per thread:
actual_rows,actual_rows_read,actual_executions,actual_logical_reads,actual_physical_reads,actual_read_aheads,actual_lob_logical_reads,actual_cpu_ms,actual_elapsed_ms,actual_rebinds,actual_rewinds,actual_batches - Aggregated onto the
PlanNode:act_rows,act_rows_read,act_executions,act_logical_reads,act_physical_reads,act_cpu_ms,act_elapsed_ms(max across threads, not sum),act_rebinds,act_rewinds,act_batches has_runtimeflag distinguishes actual plans from estimated
MissingIndexeswith generatedCREATE INDEXDDLWarnings(plan-level and per-operator)ParameterList(compile-time and runtime values)OptimizerStatsUsage/StatisticsInfopredominant_database(plan)helper: most-referenced database across all RelOps
parse_file() routes through std::filesystem::u8path so UTF-8 paths with characters outside the system ACP (Cyrillic, CJK, accented filenames) open correctly on Windows.
MIT. Vendors pugixml (MIT) under third_party/pugixml/.