@@ -78,6 +78,9 @@ func (builder *QueryBuilder) flattenSubquery(nodeID int32, subquery *plan.Subque
7878
7979 subID := subquery .NodeId
8080 subCtx := builder .ctxByNode [subID ]
81+ var scalarMatch * plan.Expr
82+ var scalarOuterResult * plan.Expr
83+ var scalarExistential bool
8184
8285 // Strip unnecessary subqueries which have no FROM clause
8386 subNode := builder .qry .Nodes [subID ]
@@ -122,6 +125,11 @@ func (builder *QueryBuilder) flattenSubquery(nodeID int32, subquery *plan.Subque
122125 }
123126 }
124127
128+ if subquery .Typ == plan .SubqueryRef_SCALAR {
129+ subID , scalarMatch , scalarOuterResult , scalarExistential =
130+ builder .normalizeDirectCorrelatedScalarProjection (subID , subCtx )
131+ }
132+
125133 subID , preds , err := builder .pullupCorrelatedPredicates (subID , subCtx )
126134 if err != nil {
127135 return 0 , nil , err
@@ -149,11 +157,36 @@ func (builder *QueryBuilder) flattenSubquery(nodeID int32, subquery *plan.Subque
149157 switch subquery .Typ {
150158 case plan .SubqueryRef_SCALAR :
151159 var rewrite bool
160+
152161 // Uncorrelated subquery
153162 if len (joinPreds ) > 0 && builder .findAggrCount (subCtx .aggregates ) {
154163 rewrite = true
155164 }
156165
166+ if scalarExistential {
167+ if len (joinPreds ) == 0 {
168+ joinPreds = append (joinPreds , constTrue )
169+ }
170+ var retExpr * plan.Expr
171+ nodeID , retExpr , err = builder .insertMarkJoin (nodeID , subID , joinPreds , nil , false , ctx )
172+ if err != nil {
173+ return 0 , nil , err
174+ }
175+ if len (filterPreds ) > 0 {
176+ nodeID = builder .appendNode (& plan.Node {
177+ NodeType : plan .Node_FILTER ,
178+ Children : []int32 {nodeID },
179+ FilterList : filterPreds ,
180+ }, ctx )
181+ }
182+ retExpr , err = BindFuncExprImplByPlanExpr (builder .GetContext (), "case" , []* plan.Expr {
183+ retExpr ,
184+ scalarOuterResult ,
185+ makePlan2NullConstExprWithType (),
186+ })
187+ return nodeID , retExpr , err
188+ }
189+
157190 joinType := plan .Node_SINGLE
158191 if subCtx .hasSingleRow {
159192 joinType = plan .Node_LEFT
@@ -175,14 +208,27 @@ func (builder *QueryBuilder) flattenSubquery(nodeID int32, subquery *plan.Subque
175208 }, ctx )
176209 }
177210
178- retExpr := & plan.Expr {
179- Typ : subCtx .results [0 ].Typ ,
180- Expr : & plan.Expr_Col {
181- Col : & plan.ColRef {
182- RelPos : subCtx .topTag (),
183- ColPos : 0 ,
211+ retExpr := scalarMatch
212+ if retExpr == nil {
213+ retExpr = & plan.Expr {
214+ Typ : subCtx .results [0 ].Typ ,
215+ Expr : & plan.Expr_Col {
216+ Col : & plan.ColRef {
217+ RelPos : subCtx .topTag (),
218+ ColPos : 0 ,
219+ },
184220 },
185- },
221+ }
222+ }
223+ if scalarOuterResult != nil {
224+ retExpr , err = BindFuncExprImplByPlanExpr (builder .GetContext (), "case" , []* plan.Expr {
225+ retExpr ,
226+ scalarOuterResult ,
227+ makePlan2NullConstExprWithType (),
228+ })
229+ if err != nil {
230+ return 0 , nil , err
231+ }
186232 }
187233 if rewrite {
188234 argsType := make ([]types.Type , 1 )
@@ -317,6 +363,85 @@ func (builder *QueryBuilder) flattenSubquery(nodeID int32, subquery *plan.Subque
317363 }
318364}
319365
366+ // normalizeDirectCorrelatedScalarProjection handles a scalar subquery whose
367+ // only result is a direct reference to the outer row. Rim projections, sorting,
368+ // DISTINCT, and literal positive LIMITs do not change that value. Removing them
369+ // before pulling up predicates keeps join-key references on the real projection
370+ // instead of leaving correlated expressions in executable wrapper nodes.
371+ func (builder * QueryBuilder ) normalizeDirectCorrelatedScalarProjection (
372+ subID int32 ,
373+ ctx * BindContext ,
374+ ) (int32 , * plan.Expr , * plan.Expr , bool ) {
375+ if len (ctx .results ) != 1 || len (ctx .projects ) == 0 {
376+ return subID , nil , nil , false
377+ }
378+
379+ projectCorr := ctx .projects [0 ].GetCorr ()
380+ if projectCorr == nil || projectCorr .Depth != 1 {
381+ return subID , nil , nil , false
382+ }
383+ if ! builder .casePreservesType (ctx .projects [0 ]) {
384+ return subID , nil , nil , false
385+ }
386+
387+ nodeID := subID
388+ existential := false
389+ for {
390+ node := builder .qry .Nodes [nodeID ]
391+ if node .Offset != nil || node .RankOption != nil {
392+ return subID , nil , nil , false
393+ }
394+ if node .Limit != nil {
395+ limit , ok := getLiteralUint64 (node .Limit )
396+ if ! ok || limit == 0 {
397+ return subID , nil , nil , false
398+ }
399+ if limit == 1 {
400+ existential = true
401+ }
402+ }
403+
404+ if node .NodeType == plan .Node_PROJECT && len (node .BindingTags ) > 0 && node .BindingTags [0 ] == ctx .projectTag {
405+ if len (node .ProjectList ) == 0 {
406+ return subID , nil , nil , false
407+ }
408+ corr := node .ProjectList [0 ].GetCorr ()
409+ if corr == nil || corr .RelPos != projectCorr .RelPos || corr .ColPos != projectCorr .ColPos || corr .Depth != projectCorr .Depth {
410+ return subID , nil , nil , false
411+ }
412+
413+ outerResult , _ := decreaseDepth (DeepCopyExpr (ctx .projects [0 ]))
414+ marker := DeepCopyExpr (constTrue )
415+ node .ProjectList = []* plan.Expr {marker }
416+ ctx .projects = []* plan.Expr {marker }
417+ node .Limit = nil
418+ return nodeID , GetColExpr (marker .Typ , ctx .projectTag , 0 ), outerResult , existential
419+ }
420+
421+ if len (node .Children ) != 1 {
422+ return subID , nil , nil , false
423+ }
424+ switch node .NodeType {
425+ case plan .Node_PROJECT , plan .Node_SORT :
426+ case plan .Node_DISTINCT :
427+ existential = true
428+ default :
429+ return subID , nil , nil , false
430+ }
431+ nodeID = node .Children [0 ]
432+ }
433+ }
434+
435+ func (builder * QueryBuilder ) casePreservesType (expr * plan.Expr ) bool {
436+ sourceType := makeTypeByPlan2Expr (expr )
437+ caseFn , err := function .GetFunctionByName (builder .GetContext (), "case" , []types.Type {
438+ types .T_bool .ToType (),
439+ sourceType ,
440+ types .T_any .ToType (),
441+ })
442+ return err == nil && caseFn .GetReturnType ().Eq (sourceType )
443+ }
444+
320445func (builder * QueryBuilder ) insertMarkJoin (left , right int32 , joinPreds []* plan.Expr , outerPred * plan.Expr , negate bool , ctx * BindContext ) (nodeID int32 , markExpr * plan.Expr , err error ) {
321446 markTag := builder .genNewBindTag ()
322447
0 commit comments