@@ -26,6 +26,7 @@ import {
2626 TableSource
2727} from "../models/Clause" ;
2828import { SelectQueryParser } from "../parsers/SelectQueryParser" ;
29+ import { ValueParser } from "../parsers/ValueParser" ;
2930import { rewriteValueComponentWithColumnResolver } from "../utils/ValueComponentRewriter" ;
3031import {
3132 ParameterConditionPlacementOptimizer ,
@@ -46,6 +47,10 @@ import {
4647 ConditionDeduplicationApplied ,
4748 ConditionDeduplicationOptimizer
4849} from "./ConditionDeduplicationOptimizer" ;
50+ import {
51+ analyzePredicateReachability ,
52+ PredicateReachabilityResult
53+ } from "./PredicateReachabilityAnalyzer" ;
4954import {
5055 collectSupportedOptionalConditionBranches ,
5156 OptionalConditionPruningParameters ,
@@ -147,6 +152,12 @@ export interface ConditionOptimizationSourceFilterProbe {
147152 predicate : string ;
148153 suggestedSql : string ;
149154 reason : string ;
155+ relation ?: "source_filter" | "join_equivalence" ;
156+ targetPredicate ?: string ;
157+ targetScope ?: {
158+ kind : "scope" | "cte" | "table" | "subquery" | "source" ;
159+ name : string ;
160+ } ;
150161}
151162
152163export interface ConditionOptimizationSkippedProbe {
@@ -166,6 +177,16 @@ export interface ConditionOptimizationDiagnostics {
166177 */
167178 probes : readonly ConditionOptimizationSourceFilterProbe [ ] ;
168179 skippedProbes : readonly ConditionOptimizationSkippedProbe [ ] ;
180+ /**
181+ * API output shape review: keep result.sql/result.query as the production-safe
182+ * rewrite output and expose debugSql/debugQuery only for diagnostic pipelines.
183+ *
184+ * Debug-only probe SQL is additive: result.sql/result.query remain the safe-only
185+ * production rewrite output, while debugSql/debugQuery include investigation
186+ * probes that callers can feed into lineage/debug surfaces.
187+ */
188+ debugSql ?: string ;
189+ debugQuery ?: SelectQuery | null ;
169190}
170191
171192export interface ConditionOptimizationResult {
@@ -609,6 +630,121 @@ const collectConditionOptimizationDiagnostics = (
609630 return { probes, skippedProbes } ;
610631} ;
611632
633+ const makeReachabilityProbeSuggestedSql = (
634+ sourceName : string ,
635+ predicate : string | undefined ,
636+ options : SqlComponentFormatOptions
637+ ) : string => {
638+ if ( ! predicate ) {
639+ return `select * from ${ sourceName } ` ;
640+ }
641+
642+ const sourceLocalPredicate = rewriteValueComponentWithColumnResolver (
643+ ValueParser . parse ( predicate ) ,
644+ reference => new ColumnReference ( null , reference . column . name )
645+ ) ;
646+ return sourceLocalPredicate
647+ ? `select * from ${ sourceName } where ${ formatSqlComponent ( sourceLocalPredicate , options ) } `
648+ : `select * from ${ sourceName } ` ;
649+ } ;
650+
651+ const collectJoinEquivalenceProbeDiagnostics = (
652+ reachability : PredicateReachabilityResult ,
653+ options : SqlComponentFormatOptions
654+ ) : Pick < ConditionOptimizationDiagnostics , "probes" | "skippedProbes" > => {
655+ const probes : ConditionOptimizationSourceFilterProbe [ ] = [ ] ;
656+ const skippedProbes : ConditionOptimizationSkippedProbe [ ] = [ ] ;
657+
658+ for ( const predicate of reachability . predicates ) {
659+ for ( const target of predicate . probeTargets ) {
660+ if ( target . relation !== "join_equivalence" ) {
661+ continue ;
662+ }
663+ probes . push ( {
664+ kind : "source_filter_probe" ,
665+ source : target . target . name ,
666+ sourceAlias : target . target . name ,
667+ predicate : target . predicateSql ,
668+ suggestedSql : makeReachabilityProbeSuggestedSql ( target . target . name , target . targetPredicateSql , options ) ,
669+ reason : "predicate inferred through JOIN equivalence for debug probing" ,
670+ relation : "join_equivalence" ,
671+ ...( target . targetPredicateSql ? { targetPredicate : target . targetPredicateSql } : { } ) ,
672+ targetScope : target . target
673+ } ) ;
674+ }
675+
676+ for ( const blocked of predicate . blocked ) {
677+ if ( blocked . relation !== "join_equivalence" ) {
678+ continue ;
679+ }
680+ const [ , ...nameParts ] = blocked . scopeId . split ( ":" ) ;
681+ const source = nameParts . join ( ":" ) || undefined ;
682+ skippedProbes . push ( {
683+ kind : "source_filter_probe_skipped" ,
684+ source,
685+ sourceAlias : source ,
686+ predicate : predicate . predicateSql ,
687+ code : blocked . code ,
688+ reason : blocked . reason
689+ } ) ;
690+ }
691+ }
692+
693+ return { probes, skippedProbes } ;
694+ } ;
695+
696+ const findDebugCte = (
697+ query : SimpleSelectQuery ,
698+ name : string
699+ ) => {
700+ const normalized = normalizeIdentifier ( name ) ;
701+ const matches = ( query . withClause ?. tables ?? [ ] )
702+ . filter ( table => normalizeIdentifier ( table . getSourceAliasName ( ) ) === normalized ) ;
703+ return matches . length === 1 ? matches [ 0 ] ! : null ;
704+ } ;
705+
706+ const buildDebugProbeQuery = (
707+ baseQuery : SelectQuery | null ,
708+ reachability : PredicateReachabilityResult ,
709+ options : SqlComponentFormatOptions
710+ ) : SelectQuery | null => {
711+ if ( ! ( baseQuery instanceof SimpleSelectQuery ) ) {
712+ return null ;
713+ }
714+
715+ const applicableTargets = reachability . predicates . flatMap ( predicate => predicate . probeTargets )
716+ . filter ( target =>
717+ target . relation === "join_equivalence"
718+ && Boolean ( target . targetPredicateSql )
719+ && target . target . kind === "cte"
720+ ) ;
721+ if ( applicableTargets . length === 0 ) {
722+ return null ;
723+ }
724+
725+ const debugQuery = SelectQueryParser . parse ( formatSqlComponent ( baseQuery , options ) ) ;
726+ if ( ! ( debugQuery instanceof SimpleSelectQuery ) ) {
727+ return null ;
728+ }
729+
730+ let applied = false ;
731+ for ( const target of applicableTargets ) {
732+ const cte = findDebugCte ( debugQuery , target . target . name ) ;
733+ if ( ! cte || ! ( cte . query instanceof SimpleSelectQuery ) || ! target . targetPredicateSql ) {
734+ continue ;
735+ }
736+ cte . query . appendWhereRaw ( target . targetPredicateSql ) ;
737+ applied = true ;
738+ }
739+
740+ if ( ! applied ) {
741+ return null ;
742+ }
743+
744+ const dedupePhase = new ConditionDeduplicationOptimizer ( ) . optimize ( debugQuery , options ) ;
745+ return dedupePhase . query ?? debugQuery ;
746+ } ;
747+
612748const isAbsentOptionalValue = (
613749 parameters : OptionalConditionPruningParameters ,
614750 parameterName : string
@@ -1050,6 +1186,54 @@ const makePhaseSummary = (
10501186 errorCount : counts . errorCount
10511187} ) ;
10521188
1189+ const getAppliedConditionSql = ( applied : ConditionOptimizationApplied ) : string | undefined => {
1190+ if ( "predicateSql" in applied ) {
1191+ return applied . predicateSql ;
1192+ }
1193+ if ( "conditionSql" in applied ) {
1194+ return applied . conditionSql ;
1195+ }
1196+ return undefined ;
1197+ } ;
1198+
1199+ const normalizeDiagnosticPredicate = (
1200+ predicate : string ,
1201+ options : SqlComponentFormatOptions
1202+ ) : string => {
1203+ try {
1204+ return formatSqlComponent ( ValueParser . parse ( predicate ) , options )
1205+ . replace ( / \s + / g, " " )
1206+ . trim ( )
1207+ . toLowerCase ( ) ;
1208+ } catch {
1209+ return predicate
1210+ . replace ( / \s + / g, " " )
1211+ . trim ( )
1212+ . toLowerCase ( ) ;
1213+ }
1214+ } ;
1215+
1216+ const filterSkippedProbesAlreadyApplied = (
1217+ skippedProbes : readonly ConditionOptimizationSkippedProbe [ ] ,
1218+ applied : readonly ConditionOptimizationApplied [ ] ,
1219+ options : SqlComponentFormatOptions
1220+ ) : ConditionOptimizationSkippedProbe [ ] => {
1221+ const appliedPredicates = new Set (
1222+ applied
1223+ . map ( item => getAppliedConditionSql ( item ) )
1224+ . filter ( ( predicate ) : predicate is string => Boolean ( predicate ) )
1225+ . map ( predicate => normalizeDiagnosticPredicate ( predicate , options ) )
1226+ ) ;
1227+
1228+ if ( appliedPredicates . size === 0 ) {
1229+ return [ ...skippedProbes ] ;
1230+ }
1231+
1232+ return skippedProbes . filter ( probe => probe . code !== "NESTED_QUERY_UNSUPPORTED" || ! appliedPredicates . has (
1233+ normalizeDiagnosticPredicate ( probe . predicate , options )
1234+ ) ) ;
1235+ } ;
1236+
10531237const runConditionOptimization = (
10541238 input : ConditionOptimizationInput ,
10551239 options : ConditionOptimizationOptions ,
@@ -1061,7 +1245,13 @@ const runConditionOptimization = (
10611245 // Run semantic SSSQL handling before generic placement phases so optional
10621246 // branches remain owned by SSSQL even if later phases grow broader support.
10631247 const sssqlPhase = runSssqlOptionalConditionPhase ( input , { ...options , dryRun } ) ;
1064- const diagnostics = collectConditionOptimizationDiagnostics ( sssqlPhase . query , options ) ;
1248+ const sourceDiagnostics = collectConditionOptimizationDiagnostics ( sssqlPhase . query , options ) ;
1249+ const reachability = sssqlPhase . query
1250+ ? analyzePredicateReachability ( sssqlPhase . query , { ...options , cloneInput : false } )
1251+ : null ;
1252+ const reachabilityDiagnostics = reachability
1253+ ? collectJoinEquivalenceProbeDiagnostics ( reachability , options )
1254+ : { probes : [ ] , skippedProbes : [ ] } ;
10651255 const parameterOptimizer = new ParameterConditionPlacementOptimizer ( ) ;
10661256 const parameterInput = reuseOwnedModel
10671257 ? sssqlPhase . query ?? sssqlPhase . sql
@@ -1103,6 +1293,20 @@ const runConditionOptimization = (
11031293 const finalSql = dedupePhase . applied . length > 0 && dedupePhase . query
11041294 ? formatSqlComponent ( dedupePhase . query , options )
11051295 : staticPhase . sql ;
1296+ const applied = [ ...sssqlPhase . applied , ...parameterApplied , ...staticApplied , ...dedupeApplied ] ;
1297+ const debugQuery = reachability
1298+ ? buildDebugProbeQuery ( dedupePhase . query , reachability , options )
1299+ : null ;
1300+ const debugSql = debugQuery ? formatSqlComponent ( debugQuery , options ) : undefined ;
1301+ const diagnostics : ConditionOptimizationDiagnostics = {
1302+ probes : [ ...sourceDiagnostics . probes , ...reachabilityDiagnostics . probes ] ,
1303+ skippedProbes : filterSkippedProbesAlreadyApplied (
1304+ [ ...sourceDiagnostics . skippedProbes , ...reachabilityDiagnostics . skippedProbes ] ,
1305+ applied ,
1306+ options
1307+ ) ,
1308+ ...( debugSql ? { debugSql, debugQuery } : { } )
1309+ } ;
11061310
11071311 return {
11081312 ok : errors . length === 0 ,
@@ -1134,7 +1338,7 @@ const runConditionOptimization = (
11341338 errorCount : 0
11351339 } )
11361340 ] ,
1137- applied : [ ... sssqlPhase . applied , ... parameterApplied , ... staticApplied , ... dedupeApplied ] ,
1341+ applied,
11381342 skipped : [ ...sssqlPhase . skipped , ...parameterSkipped , ...staticSkipped ] ,
11391343 warnings,
11401344 errors,
0 commit comments