Repository navigation
Plan analysis: skip Row Estimate Mismatch on unexecuted operators, skip engine functions in the TVF rule (#4522, #4525) - #4542
Merged
Conversation
…'s RED runs on dev (#4525)
erikdarlingdata
marked this pull request as ready for review
September 28, 2026 02:20
This was referenced Sep 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4522
Fixes #4525
Part of #4511
Why
Two of the plan analyzer's rules fired when they shouldn't have.
Rule 5 (Row Estimate Mismatch) checked only that an operator had actual stats and a nonzero row estimate. An operator in a branch that never ran (one side of a concatenation, an outer join's unreached inner side) returns 0 rows because it never executed — that zero says nothing about whether the estimate was right. The rule warned anyway.
Rule 23 (Table-Valued Function) fired on every operator with
LogicalOp == "Table-valued function", including the engine's own functions:STRING_SPLIT,OPENJSON,GENERATE_SERIES, and every DMV/DMF. Its advice (rewrite as an inline function, or stage rows in a#temptable) is for user-written multi-statement TVFs, which have no statistics. The engine's functions aren't user code and the advice doesn't apply to them.What changes
PlanAnalyzer.csrule 5: addsnode.ActualExecutions > 0to the condition, and removes the now-unreachableActualExecutions > 0 ? ActualExecutions : 1fallback (the divisor is always the real execution count once the new guard runs first).PlanModels.cs: addsPlanNode.SchemaName, alongside the existingDatabaseName.ShowPlanParser.cs: setsSchemaNamefrom the parsedObjectelement, next to whereDatabaseNameis already set.PlanAnalyzer.csrule 23: skips an operator whoseObjecthas neither a database nor a schema — how the engine's own functions appear in the plan XML. A user-written function always has both.This mirrors erikdarlingdata/PerformanceStudio@cc18844 and erikdarlingdata/PerformanceStudio@de1cac6 (#4522) and erikdarlingdata/PerformanceStudio#584 (#4525), adapted only in that PerformanceMonitor has no
AnalyzerConfig, so there's nocfg.IsRuleDisabled(...)guard to preserve.Test plan
New tests:
Darling.Tests.PlanSync4522Tests,Darling.Tests.PlanSync4525Tests. Both build a synthetic actual-plan XML (dbo.t,[db]) and run it throughShowPlanParser.Parse+PlanAnalyzer.Analyze— the product's own parse-then-analyze path, not a hand-builtPlanNode.PlanSync4522Tests.Rule05_OperatorThatNeverExecuted_IsNotAnEstimateMismatch: runtime RED on dev (Assert.False() Failure: Expected: False, Actual: True), and a still-warns mirror.PlanSync4525Tests: the two rule-23 facts (an engineSTRING_SPLIT→ no warning;[db].[dbo]user function → still warns) use only existing members. The engine fact is a runtime RED on dev (Assert.DoesNotContain() Failure: Filter matched in collection, the TVF warning present).Parser_SetsSchemaName_NextToDatabaseNameasserts the newPlanNode.SchemaNamemember, so it is a compile-only RED on dev.node.ActualExecutions > 0from rule 5 fails the rule-5 fact; dropping!isEngineFunctionfrom rule 23 fails the engine fact.ShowPlanParserCondAndMultiplePlanTests, theActualPlan*classes,QueryModificationDetectorTests,ReproScriptBuilderHardeningTests,DarlingAnalysisPipelineTests,DarlingMcpPlanToolsSurfaceAndSqlTests,McpPlanAnalysisEnvelopeTests,SerialLoopStoreSizeSourceTests,TsqlConventionGuardTests,DocCommentHygieneTests):Total: 234, Failed: 0. The live-Postgres plan-tool class runs in CI.Lite.Testsbuilds; its plan classes run on Windows only.CHANGELOG
SECTION: Fixed
ENTRY: - Plan analysis no longer flags an unexecuted operator as a row estimate mismatch, and stops recommending a rewrite for the engine's own table-valued functions (
STRING_SPLIT,OPENJSON,GENERATE_SERIES, DMVs/DMFs) ([#4542]) - Rule 5 previously warned about a 0-row estimate mismatch on any operator whose branch never ran; it now requiresActualExecutions > 0. Rule 23 previously warned on every table-valued function operator; it now skips one whose object has neither a database nor a schema, which is how the engine's own functions appear.REF: [#4542]: #4542