diff --git a/go/vt/vtgate/planbuilder/operators/aggregation_pushing.go b/go/vt/vtgate/planbuilder/operators/aggregation_pushing.go index 75ecd5287c0..5cdf28e5165 100644 --- a/go/vt/vtgate/planbuilder/operators/aggregation_pushing.go +++ b/go/vt/vtgate/planbuilder/operators/aggregation_pushing.go @@ -32,18 +32,31 @@ func tryPushingDownAggregator(ctx *plancontext.PlanningContext, aggregator *Aggr if aggregator.Pushed { return aggregator, rewrite.SameTree, nil } - aggregator.Pushed = true switch src := aggregator.Source.(type) { case *Route: + // if we have a single sharded route, we can push it down output, applyResult, err = pushDownAggregationThroughRoute(ctx, aggregator, src) case *ApplyJoin: - output, applyResult, err = pushDownAggregationThroughJoin(ctx, aggregator, src) + if ctx.DelegateAggregation { + output, applyResult, err = pushDownAggregationThroughJoin(ctx, aggregator, src) + } case *Filter: - output, applyResult, err = pushDownAggregationThroughFilter(ctx, aggregator, src) + if ctx.DelegateAggregation { + output, applyResult, err = pushDownAggregationThroughFilter(ctx, aggregator, src) + } default: return aggregator, rewrite.SameTree, nil } + if err != nil { + return nil, nil, err + } + + if output == nil { + return aggregator, rewrite.SameTree, nil + } + + aggregator.Pushed = true if applyResult != rewrite.SameTree && aggregator.Original { aggregator.aggregateTheAggregates() } @@ -74,6 +87,10 @@ func pushDownAggregationThroughRoute( return rewrite.Swap(aggregator, route, "push down aggregation under route - remove original") } + if !ctx.DelegateAggregation { + return nil, nil, nil + } + // Create a new aggregator to be placed below the route. aggrBelowRoute := aggregator.Clone([]ops.Operator{route.Source}).(*Aggregator) aggrBelowRoute.Pushed = false @@ -247,6 +264,10 @@ func pushDownAggregationThroughJoin(ctx *plancontext.PlanningContext, rootAggr * joinColumns, output, err := splitAggrColumnsToLeftAndRight(ctx, rootAggr, join, lhs, rhs) if err != nil { + // if we get this error, we just abort the splitting and fall back on simpler ways of solving the same query + if err == errAbortAggrPushing { + return nil, nil, nil + } return nil, nil, err } @@ -276,6 +297,8 @@ func pushDownAggregationThroughJoin(ctx *plancontext.PlanningContext, rootAggr * return rootAggr, rewrite.NewTree("push Aggregation under join", rootAggr), nil } +var errAbortAggrPushing = fmt.Errorf("abort aggregation pushing") + func addColumnsFromLHSInJoinPredicates(ctx *plancontext.PlanningContext, rootAggr *Aggregator, join *ApplyJoin, lhs *joinPusher) error { for _, pred := range join.JoinPredicates { for _, expr := range pred.LHSExprs { @@ -323,8 +346,17 @@ func splitGroupingToLeftAndRight(ctx *plancontext.PlanningContext, rootAggr *Agg Original: aeWrap(groupBy.Inner), RHSExpr: expr, }) + case deps.IsSolvedBy(lhs.tableID.Merge(rhs.tableID)): + jc, err := BreakExpressionInLHSandRHS(ctx, groupBy.SimplifiedExpr, lhs.tableID) + if err != nil { + return nil, err + } + for _, lhsExpr := range jc.LHSExprs { + lhs.addGrouping(ctx, NewGroupBy(lhsExpr, lhsExpr, aeWrap(lhsExpr))) + } + rhs.addGrouping(ctx, NewGroupBy(jc.RHSExpr, jc.RHSExpr, aeWrap(jc.RHSExpr))) default: - return nil, vterrors.VT12001("grouping on columns from different sources") + return nil, vterrors.VT13001(fmt.Sprintf("grouping with bad dependencies %s", groupBy.SimplifiedExpr)) } } return groupingJCs, nil @@ -464,7 +496,7 @@ func (ab *aggBuilder) handlePushThroughAggregation(ctx *plancontext.PlanningCont case deps.IsSolvedBy(ab.rhs.tableID): ab.pushThroughRight(aggr) default: - return vterrors.VT12001("aggregation on columns from different sources: " + sqlparser.String(aggr.Original.Expr)) + return errAbortAggrPushing } return nil } @@ -493,7 +525,7 @@ func (ab *aggBuilder) handleAggrWithCountStarMultiplier(ctx *plancontext.Plannin rhsAE = aggr.Original default: - return errHorizonNotPlanned() + return errAbortAggrPushing } ab.buildProjectionForAggr(lhsAE, rhsAE, aggr) diff --git a/go/vt/vtgate/planbuilder/operators/aggregator.go b/go/vt/vtgate/planbuilder/operators/aggregator.go index 5da0c10c9fe..b7403fb46dd 100644 --- a/go/vt/vtgate/planbuilder/operators/aggregator.go +++ b/go/vt/vtgate/planbuilder/operators/aggregator.go @@ -167,6 +167,8 @@ func (a *Aggregator) AddColumn(ctx *plancontext.PlanningContext, expr *sqlparser func (a *Aggregator) GetColumns() ([]*sqlparser.AliasedExpr, error) { // we update the incoming columns, so we know about any new columns that have been added + // in the optimization phase, other operators could be pushed down resulting in additional columns for aggregator. + // Aggregator should be made aware of these to truncate them in final result. columns, err := a.Source.GetColumns() if err != nil { return nil, err @@ -207,6 +209,10 @@ func (a *Aggregator) GetOrdering() ([]ops.OrderBy, error) { } func (a *Aggregator) planOffsets(ctx *plancontext.PlanningContext) error { + if !a.Pushed { + return a.planOffsetsNotPushed(ctx) + } + addColumn := func(aliasedExpr *sqlparser.AliasedExpr, addToGroupBy bool) (int, error) { newSrc, offset, err := a.Source.AddColumn(ctx, aliasedExpr, true, addToGroupBy) if err != nil { @@ -220,13 +226,6 @@ func (a *Aggregator) planOffsets(ctx *plancontext.PlanningContext) error { return offset, nil } - if !a.Pushed { - err := a.planOffsetsNotPushed(ctx) - if err != nil { - return err - } - } - for idx, gb := range a.Grouping { if gb.ColOffset == -1 { offset, err := addColumn(aeWrap(gb.Inner), false) diff --git a/go/vt/vtgate/planbuilder/operators/filter.go b/go/vt/vtgate/planbuilder/operators/filter.go index 21c6986d4bf..d8dbd8be9de 100644 --- a/go/vt/vtgate/planbuilder/operators/filter.go +++ b/go/vt/vtgate/planbuilder/operators/filter.go @@ -19,6 +19,8 @@ package operators import ( "strings" + "golang.org/x/exp/slices" + vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" "vitess.io/vitess/go/vt/sqlparser" "vitess.io/vitess/go/vt/vterrors" @@ -46,11 +48,10 @@ func newFilter(op ops.Operator, expr sqlparser.Expr) ops.Operator { // Clone implements the Operator interface func (f *Filter) Clone(inputs []ops.Operator) ops.Operator { - predicatesClone := make([]sqlparser.Expr, len(f.Predicates)) - copy(predicatesClone, f.Predicates) return &Filter{ - Source: inputs[0], - Predicates: predicatesClone, + Source: inputs[0], + Predicates: slices.Clone(f.Predicates), + FinalPredicate: f.FinalPredicate, } } diff --git a/go/vt/vtgate/planbuilder/operators/horizon_expanding.go b/go/vt/vtgate/planbuilder/operators/horizon_expanding.go index c93318825bf..ae730277a54 100644 --- a/go/vt/vtgate/planbuilder/operators/horizon_expanding.go +++ b/go/vt/vtgate/planbuilder/operators/horizon_expanding.go @@ -1,9 +1,12 @@ /* Copyright 2023 The Vitess Authors. + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -29,10 +32,6 @@ func expandHorizon(ctx *plancontext.PlanningContext, horizon horizonLike) (ops.O return nil, nil, errHorizonNotPlanned() } - if sel.Having != nil { - return nil, nil, errHorizonNotPlanned() - } - op, err := createProjectionFromSelect(ctx, horizon) if err != nil { return nil, nil, err @@ -50,6 +49,14 @@ func expandHorizon(ctx *plancontext.PlanningContext, horizon horizonLike) (ops.O } } + if sel.Having != nil { + op = &Filter{ + Source: op, + Predicates: sqlparser.SplitAndExpression(nil, sel.Having.Expr), + FinalPredicate: nil, + } + } + if len(qp.OrderExprs) > 0 { op = &Ordering{ Source: op, diff --git a/go/vt/vtgate/planbuilder/operators/horizon_planning.go b/go/vt/vtgate/planbuilder/operators/horizon_planning.go index 6547d343759..3fbff7a586e 100644 --- a/go/vt/vtgate/planbuilder/operators/horizon_planning.go +++ b/go/vt/vtgate/planbuilder/operators/horizon_planning.go @@ -17,9 +17,9 @@ limitations under the License. package operators import ( + "fmt" "io" - "vitess.io/vitess/go/slices2" "vitess.io/vitess/go/vt/sqlparser" "vitess.io/vitess/go/vt/vterrors" "vitess.io/vitess/go/vt/vtgate/planbuilder/operators/ops" @@ -90,29 +90,6 @@ func tryHorizonPlanning(ctx *plancontext.PlanningContext, root ops.Operator) (ou return } -// Phase defines the different planning phases to go through to produce an optimized plan for the input query. -type Phase struct { - Name string - // preOptimizeAction is the action to be taken before calling plan optimization operation. - preOptimizeAction func(ctx *plancontext.PlanningContext, op ops.Operator) (ops.Operator, error) -} - -// getPhases returns the phases the planner will go through. -// It's used to control so rewriters collaborate correctly -func getPhases() []Phase { - return []Phase{{ - // Initial optimization - Name: "initial horizon planning optimization phase", - }, { - // Adding Ordering Op - Any aggregation that is performed in the VTGate needs the input to be ordered - // Adding Group by - This is needed if the grouping is performed on a join with a join condition then - // aggregation happening at route needs a group by to ensure only matching rows returns - // the aggregations otherwise returns no result. - Name: "add ORDER BY to aggregations above the route and add GROUP BY to aggregations on the RHS of join", - preOptimizeAction: addOrderBysAndGroupBysForAggregations, - }} -} - // planHorizons is the process of figuring out how to perform the operations in the Horizon // If we can push it under a route - done. // If we can't, we will instead expand the Horizon into @@ -122,18 +99,22 @@ func planHorizons(ctx *plancontext.PlanningContext, root ops.Operator) (op ops.O op = root for _, phase := range phases { - if phase.preOptimizeAction != nil { - op, err = phase.preOptimizeAction(ctx, op) + if phase.action != nil { + op, err = phase.action(ctx, op) if err != nil { return nil, err } } + if rewrite.DebugOperatorTree { + fmt.Printf("PHASE: %s\n", phase.Name) + } op, err = optimizeHorizonPlanning(ctx, op) if err != nil { return nil, err } } - return op, nil + + return addGroupByOnRHSOfJoin(op) } func optimizeHorizonPlanning(ctx *plancontext.PlanningContext, root ops.Operator) (ops.Operator, error) { @@ -170,251 +151,35 @@ func optimizeHorizonPlanning(ctx *plancontext.PlanningContext, root ops.Operator return newOp, nil } -func tryPushingDownFilter(ctx *plancontext.PlanningContext, in *Filter) (ops.Operator, *rewrite.ApplyResult, error) { - switch src := in.Source.(type) { - case *Projection: - return pushFilterUnderProjection(ctx, in, src) - case *Route: - return rewrite.Swap(in, src, "push filter into Route") - } - - return in, rewrite.SameTree, nil -} - -func pushFilterUnderProjection(ctx *plancontext.PlanningContext, filter *Filter, projection *Projection) (ops.Operator, *rewrite.ApplyResult, error) { - for _, p := range filter.Predicates { - cantPushDown := false - _ = sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) { - if !fetchByOffset(node) { - return true, nil - } - - if projection.needsEvaluation(ctx, node.(sqlparser.Expr)) { - cantPushDown = true - return false, io.EOF - } - - return true, nil - }, p) - - if cantPushDown { - return filter, rewrite.SameTree, nil +func pushOrExpandHorizon(ctx *plancontext.PlanningContext, in horizonLike) (ops.Operator, *rewrite.ApplyResult, error) { + if derived, ok := in.(*Derived); ok { + if len(derived.ColumnAliases) > 0 { + return nil, nil, errHorizonNotPlanned() } } - return rewrite.Swap(filter, projection, "push filter under projection") - -} -func tryPushingDownDistinct(in *Distinct) (ops.Operator, *rewrite.ApplyResult, error) { - if in.Pushed { - return in, rewrite.SameTree, nil - } - switch src := in.Source.(type) { - case *Route: - if src.IsSingleShard() { - return rewrite.Swap(in, src, "push distinct under route") - } - case *Distinct: - return src, rewrite.NewTree("removed double distinct", src), nil - case *Aggregator: - return in, rewrite.SameTree, nil - } - - cols, err := in.Source.GetColumns() - if err != nil { - return nil, nil, err - } - - aggr := &Aggregator{ - Source: in.Source, - QP: in.QP, - Original: true, - } - - for _, col := range cols { - aggr.addColumnWithoutPushing(col, true) + rb, isRoute := in.src().(*Route) + if isRoute && rb.IsSingleShard() { + return rewrite.Swap(in, rb, "push horizon into route") } - return aggr, rewrite.NewTree("replace distinct with aggregator", in), nil -} - -// addOrderBysForAggregations runs after we have run horizonPlanning until the op tree stops changing -// this means that we have pushed aggregations and other ops as far down as they'll go -// addOrderBysForAggregations will find Aggregators that have not been pushed under routes and -// add the necessary Ordering operators for them -func addOrderBysAndGroupBysForAggregations(ctx *plancontext.PlanningContext, root ops.Operator) (ops.Operator, error) { - visitor := func(in ops.Operator, _ semantics.TableSet, isRoot bool) (ops.Operator, *rewrite.ApplyResult, error) { - switch op := in.(type) { - case *ApplyJoin: - return addLiteralGroupingToRHS(op) - case *Aggregator: - return addOrderingForAggregation(ctx, op) - default: - return in, rewrite.SameTree, nil - } + sel, isSel := in.selectStatement().(*sqlparser.Select) + if !isSel { + return nil, nil, errHorizonNotPlanned() } - return rewrite.TopDown(root, TableID, visitor, stopAtRoute) -} - -func addLiteralGroupingToRHS(in *ApplyJoin) (ops.Operator, *rewrite.ApplyResult, error) { - _ = rewrite.Visit(in.RHS, func(op ops.Operator) error { - aggr, isAggr := op.(*Aggregator) - if !isAggr { - return nil - } - if len(aggr.Grouping) == 0 { - gb := sqlparser.NewIntLiteral(".0") - aggr.Grouping = append(aggr.Grouping, NewGroupBy(gb, gb, aeWrap(gb))) - } - return nil - }) - return in, rewrite.NewTree("added grouping to the RHS", in), nil -} - -func addOrderingForAggregation(ctx *plancontext.PlanningContext, in *Aggregator) (ops.Operator, *rewrite.ApplyResult, error) { - requireOrdering, err := needsOrdering(in, ctx) + qp, err := in.getQP(ctx) if err != nil { return nil, nil, err } - if !requireOrdering { - return in, rewrite.SameTree, nil - } - in.Source = &Ordering{ - Source: in.Source, - Order: slices2.Map(in.Grouping, func(from GroupBy) ops.OrderBy { - return from.AsOrderBy() - }), - } - return in, rewrite.NewTree("added ordering before aggregation", in), nil -} - -func needsOrdering(in *Aggregator, ctx *plancontext.PlanningContext) (bool, error) { - if len(in.Grouping) == 0 { - return false, nil - } - srcOrdering, err := in.Source.GetOrdering() - if err != nil { - return false, err - } - if len(srcOrdering) < len(in.Grouping) { - return true, nil - } - for idx, gb := range in.Grouping { - if !ctx.SemTable.EqualsExprWithDeps(srcOrdering[idx].SimplifiedExpr, gb.SimplifiedExpr) { - return true, nil - } - } - return false, nil -} -func tryPushingDownOrdering(ctx *plancontext.PlanningContext, in *Ordering) (ops.Operator, *rewrite.ApplyResult, error) { - switch src := in.Source.(type) { - case *Route: - return rewrite.Swap(in, src, "push ordering under route") - case *ApplyJoin: - if canPushLeft(ctx, src, in.Order) { - // ApplyJoin is stable in regard to the columns coming from the LHS, - // so if all the ordering columns come from the LHS, we can push down the Ordering there - src.LHS, in.Source = in, src.LHS - return src, rewrite.NewTree("push down ordering on the LHS of a join", in), nil - } - case *Ordering: - // we'll just remove the order underneath. The top order replaces whatever was incoming - in.Source = src.Source - return in, rewrite.NewTree("remove double ordering", src), nil - case *Projection: - // we can move ordering under a projection if it's not introducing a column we're sorting by - for _, by := range in.Order { - if !fetchByOffset(by.SimplifiedExpr) { - return in, rewrite.SameTree, nil - } - } - return rewrite.Swap(in, src, "push ordering under projection") - case *Aggregator: - if !(src.QP.AlignGroupByAndOrderBy(ctx) || overlaps(ctx, in.Order, src.Grouping)) { - return in, rewrite.SameTree, nil - } - - return pushOrderingUnderAggr(ctx, in, src) - - } - return in, rewrite.SameTree, nil -} - -func overlaps(ctx *plancontext.PlanningContext, order []ops.OrderBy, grouping []GroupBy) bool { -ordering: - for _, orderBy := range order { - for _, groupBy := range grouping { - if ctx.SemTable.EqualsExprWithDeps(orderBy.SimplifiedExpr, groupBy.SimplifiedExpr) { - continue ordering - } - } - return false - } - - return true -} - -func pushOrderingUnderAggr(ctx *plancontext.PlanningContext, order *Ordering, aggregator *Aggregator) (ops.Operator, *rewrite.ApplyResult, error) { - // Step 1: Align the GROUP BY and ORDER BY. - // Reorder the GROUP BY columns to match the ORDER BY columns. - // Since the GB clause is a set, we can reorder these columns freely. - var newGrouping []GroupBy - used := make([]bool, len(aggregator.Grouping)) - for _, orderExpr := range order.Order { - for grpIdx, by := range aggregator.Grouping { - if !used[grpIdx] && ctx.SemTable.EqualsExprWithDeps(by.SimplifiedExpr, orderExpr.SimplifiedExpr) { - newGrouping = append(newGrouping, by) - used[grpIdx] = true - } - } - } - - // Step 2: Add any missing columns from the ORDER BY. - // The ORDER BY column is not a set, but we can add more elements - // to the end without changing the semantics of the query. - if len(newGrouping) != len(aggregator.Grouping) { - // we are missing some groupings. We need to add them both to the new groupings list, but also to the ORDER BY - for i, added := range used { - if !added { - groupBy := aggregator.Grouping[i] - newGrouping = append(newGrouping, groupBy) - order.Order = append(order.Order, groupBy.AsOrderBy()) - } - } - } + needsOrdering := len(qp.OrderExprs) > 0 + canPushDown := isRoute && sel.Having == nil && !needsOrdering && !qp.NeedsAggregation() && !sel.Distinct && sel.Limit == nil - aggregator.Grouping = newGrouping - aggrSource, isOrdering := aggregator.Source.(*Ordering) - if isOrdering { - // Transform the query plan tree: - // From: Ordering(1) To: Aggregation - // | | - // Aggregation Ordering(1) - // | | - // Ordering(2) - // | - // - // - // Remove Ordering(2) from the plan tree, as it's redundant - // after pushing down the higher ordering. - order.Source = aggrSource.Source - aggrSource.Source = nil // removing from plan tree - aggregator.Source = order - return aggregator, rewrite.NewTree("push ordering under aggregation, removing extra ordering", aggregator), nil + if canPushDown { + return rewrite.Swap(in, rb, "push horizon into route") } - return rewrite.Swap(order, aggregator, "push ordering under aggregation") -} -func canPushLeft(ctx *plancontext.PlanningContext, aj *ApplyJoin, order []ops.OrderBy) bool { - lhs := TableID(aj.LHS) - for _, order := range order { - deps := ctx.SemTable.DirectDeps(order.Inner.Expr) - if !deps.IsSolvedBy(lhs) { - return false - } - } - return true + return expandHorizon(ctx, in) } func tryPushingDownProjection( @@ -622,11 +387,6 @@ func createProjectionWithTheseColumns( return proj, nil } -func stopAtRoute(operator ops.Operator) rewrite.VisitRule { - _, isRoute := operator.(*Route) - return rewrite.VisitRule(!isRoute) -} - func tryPushingDownLimit(in *Limit) (ops.Operator, *rewrite.ApplyResult, error) { switch src := in.Source.(type) { case *Route: @@ -640,6 +400,14 @@ func tryPushingDownLimit(in *Limit) (ops.Operator, *rewrite.ApplyResult, error) } } +func tryPushingDownLimitInRoute(in *Limit, src *Route) (ops.Operator, *rewrite.ApplyResult, error) { + if src.IsSingleShard() { + return rewrite.Swap(in, src, "limit pushed into single sharded route") + } + + return setUpperLimit(in) +} + func setUpperLimit(in *Limit) (ops.Operator, *rewrite.ApplyResult, error) { if in.Pushed { return in, rewrite.SameTree, nil @@ -673,47 +441,182 @@ func setUpperLimit(in *Limit) (ops.Operator, *rewrite.ApplyResult, error) { return in, rewrite.SameTree, nil } -func tryPushingDownLimitInRoute(in *Limit, src *Route) (ops.Operator, *rewrite.ApplyResult, error) { - if src.IsSingleShard() { - return rewrite.Swap(in, src, "limit pushed into single sharded route") +func tryPushingDownOrdering(ctx *plancontext.PlanningContext, in *Ordering) (ops.Operator, *rewrite.ApplyResult, error) { + switch src := in.Source.(type) { + case *Route: + return rewrite.Swap(in, src, "push ordering under route") + case *ApplyJoin: + if canPushLeft(ctx, src, in.Order) { + // ApplyJoin is stable in regard to the columns coming from the LHS, + // so if all the ordering columns come from the LHS, we can push down the Ordering there + src.LHS, in.Source = in, src.LHS + return src, rewrite.NewTree("push down ordering on the LHS of a join", in), nil + } + case *Ordering: + // we'll just remove the order underneath. The top order replaces whatever was incoming + in.Source = src.Source + return in, rewrite.NewTree("remove double ordering", src), nil + case *Projection: + // we can move ordering under a projection if it's not introducing a column we're sorting by + for _, by := range in.Order { + if !fetchByOffset(by.SimplifiedExpr) { + return in, rewrite.SameTree, nil + } + } + return rewrite.Swap(in, src, "push ordering under projection") + case *Aggregator: + if !src.QP.AlignGroupByAndOrderBy(ctx) && !overlaps(ctx, in.Order, src.Grouping) { + return in, rewrite.SameTree, nil + } + + return pushOrderingUnderAggr(ctx, in, src) + } + return in, rewrite.SameTree, nil +} - return setUpperLimit(in) +func overlaps(ctx *plancontext.PlanningContext, order []ops.OrderBy, grouping []GroupBy) bool { +ordering: + for _, orderBy := range order { + for _, groupBy := range grouping { + if ctx.SemTable.EqualsExprWithDeps(orderBy.SimplifiedExpr, groupBy.SimplifiedExpr) { + continue ordering + } + } + return false + } + + return true } -func pushOrExpandHorizon(ctx *plancontext.PlanningContext, in horizonLike) (ops.Operator, *rewrite.ApplyResult, error) { - if derived, ok := in.(*Derived); ok { - if len(derived.ColumnAliases) > 0 { - return nil, nil, errHorizonNotPlanned() +func pushOrderingUnderAggr(ctx *plancontext.PlanningContext, order *Ordering, aggregator *Aggregator) (ops.Operator, *rewrite.ApplyResult, error) { + // Step 1: Align the GROUP BY and ORDER BY. + // Reorder the GROUP BY columns to match the ORDER BY columns. + // Since the GB clause is a set, we can reorder these columns freely. + var newGrouping []GroupBy + used := make([]bool, len(aggregator.Grouping)) + for _, orderExpr := range order.Order { + for grpIdx, by := range aggregator.Grouping { + if !used[grpIdx] && ctx.SemTable.EqualsExprWithDeps(by.SimplifiedExpr, orderExpr.SimplifiedExpr) { + newGrouping = append(newGrouping, by) + used[grpIdx] = true + } } } - rb, isRoute := in.src().(*Route) - if isRoute && rb.IsSingleShard() { - return rewrite.Swap(in, rb, "push horizon into route") + + // Step 2: Add any missing columns from the ORDER BY. + // The ORDER BY column is not a set, but we can add more elements + // to the end without changing the semantics of the query. + if len(newGrouping) != len(aggregator.Grouping) { + // we are missing some groupings. We need to add them both to the new groupings list, but also to the ORDER BY + for i, added := range used { + if !added { + groupBy := aggregator.Grouping[i] + newGrouping = append(newGrouping, groupBy) + order.Order = append(order.Order, groupBy.AsOrderBy()) + } + } } - sel, isSel := in.selectStatement().(*sqlparser.Select) - if !isSel { - return nil, nil, errHorizonNotPlanned() + aggregator.Grouping = newGrouping + aggrSource, isOrdering := aggregator.Source.(*Ordering) + if isOrdering { + // Transform the query plan tree: + // From: Ordering(1) To: Aggregation + // | | + // Aggregation Ordering(1) + // | | + // Ordering(2) + // | + // + // + // Remove Ordering(2) from the plan tree, as it's redundant + // after pushing down the higher ordering. + order.Source = aggrSource.Source + aggrSource.Source = nil // removing from plan tree + aggregator.Source = order + return aggregator, rewrite.NewTree("push ordering under aggregation, removing extra ordering", aggregator), nil } + return rewrite.Swap(order, aggregator, "push ordering under aggregation") +} - qp, err := in.getQP(ctx) - if err != nil { - return nil, nil, err +func canPushLeft(ctx *plancontext.PlanningContext, aj *ApplyJoin, order []ops.OrderBy) bool { + lhs := TableID(aj.LHS) + for _, order := range order { + deps := ctx.SemTable.DirectDeps(order.Inner.Expr) + if !deps.IsSolvedBy(lhs) { + return false + } + } + return true +} + +func tryPushingDownFilter(ctx *plancontext.PlanningContext, in *Filter) (ops.Operator, *rewrite.ApplyResult, error) { + switch src := in.Source.(type) { + case *Projection: + return pushFilterUnderProjection(ctx, in, src) + case *Route: + return rewrite.Swap(in, src, "push filter into Route") } - needsOrdering := len(qp.OrderExprs) > 0 - canPushDown := isRoute && sel.Having == nil && !needsOrdering && !qp.NeedsAggregation() && !sel.Distinct && sel.Limit == nil + return in, rewrite.SameTree, nil +} - if canPushDown { - return rewrite.Swap(in, rb, "push horizon into route") +func pushFilterUnderProjection(ctx *plancontext.PlanningContext, filter *Filter, projection *Projection) (ops.Operator, *rewrite.ApplyResult, error) { + for _, p := range filter.Predicates { + cantPushDown := false + _ = sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) { + if !fetchByOffset(node) { + return true, nil + } + + if projection.needsEvaluation(ctx, node.(sqlparser.Expr)) { + cantPushDown = true + return false, io.EOF + } + + return true, nil + }, p) + + if cantPushDown { + return filter, rewrite.SameTree, nil + } } + return rewrite.Swap(filter, projection, "push filter under projection") - return expandHorizon(ctx, in) } -func aeWrap(e sqlparser.Expr) *sqlparser.AliasedExpr { - return &sqlparser.AliasedExpr{Expr: e} +func tryPushingDownDistinct(in *Distinct) (ops.Operator, *rewrite.ApplyResult, error) { + if in.Pushed { + return in, rewrite.SameTree, nil + } + switch src := in.Source.(type) { + case *Route: + if src.IsSingleShard() { + return rewrite.Swap(in, src, "push distinct under route") + } + case *Distinct: + return src, rewrite.NewTree("removed double distinct", src), nil + case *Aggregator: + return in, rewrite.SameTree, nil + } + + cols, err := in.Source.GetColumns() + if err != nil { + return nil, nil, err + } + + aggr := &Aggregator{ + Source: in.Source, + QP: in.QP, + Original: true, + } + + for _, col := range cols { + aggr.addColumnWithoutPushing(col, true) + } + + return aggr, rewrite.NewTree("replace distinct with aggregator", in), nil } // makeSureOutputIsCorrect uses the original Horizon to make sure that the output columns line up with what the user asked for @@ -745,3 +648,12 @@ func makeSureOutputIsCorrect(ctx *plancontext.PlanningContext, oldHorizon ops.Op } return proj, nil } + +func stopAtRoute(operator ops.Operator) rewrite.VisitRule { + _, isRoute := operator.(*Route) + return rewrite.VisitRule(!isRoute) +} + +func aeWrap(e sqlparser.Expr) *sqlparser.AliasedExpr { + return &sqlparser.AliasedExpr{Expr: e} +} diff --git a/go/vt/vtgate/planbuilder/operators/offset_planning.go b/go/vt/vtgate/planbuilder/operators/offset_planning.go index 0fb2fbf091d..31c36d609fb 100644 --- a/go/vt/vtgate/planbuilder/operators/offset_planning.go +++ b/go/vt/vtgate/planbuilder/operators/offset_planning.go @@ -19,6 +19,8 @@ package operators import ( "fmt" + "golang.org/x/exp/slices" + "vitess.io/vitess/go/vt/sqlparser" "vitess.io/vitess/go/vt/vterrors" "vitess.io/vitess/go/vt/vtgate/planbuilder/operators/ops" @@ -78,3 +80,131 @@ func planOffsetsOnJoins(ctx *plancontext.PlanningContext, op ops.Operator) error }) return err } + +// useOffsets rewrites an expression to use values from the input +func useOffsets(ctx *plancontext.PlanningContext, expr sqlparser.Expr, op ops.Operator) (sqlparser.Expr, error) { + in := op.Inputs()[0] + columns, err := in.GetColumns() + if err != nil { + return nil, err + } + + var exprOffset *sqlparser.Offset + + found := func(e sqlparser.Expr, offset int) { exprOffset = sqlparser.NewOffset(offset, e) } + + notFound := func(e sqlparser.Expr) error { + _, addToGroupBy := e.(*sqlparser.ColName) + var offset int + in, offset, err = in.AddColumn(ctx, aeWrap(e), true, addToGroupBy) + if err != nil { + return err + } + op.SetInputs([]ops.Operator{in}) + columns, err = in.GetColumns() + if err != nil { + return err + } + exprOffset = sqlparser.NewOffset(offset, e) + return nil + } + + getColumns := func() []*sqlparser.AliasedExpr { return columns } + visitor := getVisitor(ctx, getColumns, found, notFound) + + // The cursor replace is not available while walking `down`, so `up` is used to do the replacement. + up := func(cursor *sqlparser.CopyOnWriteCursor) { + if exprOffset != nil { + cursor.Replace(exprOffset) + exprOffset = nil + } + } + + rewritten := sqlparser.CopyOnRewrite(expr, visitor, up, ctx.SemTable.CopyDependenciesOnSQLNodes) + if err != nil { + return nil, err + } + + return rewritten.(sqlparser.Expr), nil +} + +// addColumnsToInput adds columns needed by an operator to its input. +// This happens only when the filter expression can be retrieved as an offset from the underlying mysql. +func addColumnsToInput(ctx *plancontext.PlanningContext, root ops.Operator) (ops.Operator, error) { + visitor := func(in ops.Operator, _ semantics.TableSet, isRoot bool) (ops.Operator, *rewrite.ApplyResult, error) { + filter, ok := in.(*Filter) + if !ok { + return in, rewrite.SameTree, nil + } + + columns, err := filter.GetColumns() + if err != nil { + return nil, nil, err + } + proj, areOnTopOfProj := filter.Source.(selectExpressions) + if !areOnTopOfProj { + // not much we can do here + return in, rewrite.SameTree, nil + } + addedColumns := false + found := func(expr sqlparser.Expr, i int) {} + notFound := func(e sqlparser.Expr) error { + _, addToGroupBy := e.(*sqlparser.ColName) + proj.addColumnWithoutPushing(aeWrap(e), addToGroupBy) + addedColumns = true + columns, err = proj.GetColumns() + return nil + } + getColumns := func() []*sqlparser.AliasedExpr { + return columns + } + visitor := getVisitor(ctx, getColumns, found, notFound) + + for _, expr := range filter.Predicates { + sqlparser.CopyOnRewrite(expr, visitor, nil, ctx.SemTable.CopyDependenciesOnSQLNodes) + if err != nil { + return nil, nil, err + } + } + if addedColumns { + return in, rewrite.NewTree("added columns because filter needs it", in), nil + } + + return in, rewrite.SameTree, nil + } + + return rewrite.TopDown(root, TableID, visitor, stopAtRoute) +} + +func getVisitor( + ctx *plancontext.PlanningContext, + getColumns func() []*sqlparser.AliasedExpr, + found func(sqlparser.Expr, int), + notFound func(sqlparser.Expr) error, +) func(node, parent sqlparser.SQLNode) bool { + var err error + return func(node, parent sqlparser.SQLNode) bool { + if err != nil { + return false + } + e, ok := node.(sqlparser.Expr) + if !ok { + return true + } + offset := slices.IndexFunc(getColumns(), func(expr *sqlparser.AliasedExpr) bool { + return ctx.SemTable.EqualsExprWithDeps(expr.Expr, e) + }) + + if offset >= 0 { + found(e, offset) + return false + } + + if fetchByOffset(e) { + err = notFound(e) + return false + } + + return true + } +} diff --git a/go/vt/vtgate/planbuilder/operators/phases.go b/go/vt/vtgate/planbuilder/operators/phases.go new file mode 100644 index 00000000000..802d1876fdb --- /dev/null +++ b/go/vt/vtgate/planbuilder/operators/phases.go @@ -0,0 +1,130 @@ +/* +Copyright 2023 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package operators + +import ( + "vitess.io/vitess/go/slices2" + "vitess.io/vitess/go/vt/sqlparser" + "vitess.io/vitess/go/vt/vtgate/planbuilder/operators/ops" + "vitess.io/vitess/go/vt/vtgate/planbuilder/operators/rewrite" + "vitess.io/vitess/go/vt/vtgate/planbuilder/plancontext" + "vitess.io/vitess/go/vt/vtgate/semantics" +) + +// Phase defines the different planning phases to go through to produce an optimized plan for the input query. +type Phase struct { + Name string + // action is the action to be taken before calling plan optimization operation. + action func(ctx *plancontext.PlanningContext, op ops.Operator) (ops.Operator, error) +} + +// getPhases returns the phases the planner will go through. +// It's used to control so rewriters collaborate correctly +func getPhases() []Phase { + return []Phase{{ + // Initial optimization + Name: "initial horizon planning optimization phase", + }, { + // after the initial pushing down of aggregations and filtering, we add columns for the filter ops that + // need it their inputs, and then we start splitting the aggregation + // so parts run on MySQL and parts run on VTGate + Name: "add filter columns to projection or aggregation", + action: func(ctx *plancontext.PlanningContext, op ops.Operator) (ops.Operator, error) { + ctx.DelegateAggregation = true + return addColumnsToInput(ctx, op) + }, + }, { + // addOrderBysForAggregations runs after we have pushed aggregations as far down as they'll go + // addOrderBysForAggregations will find Aggregators that have not been pushed under routes and + // add the necessary Ordering operators for them + Name: "add ORDER BY to aggregations above the route and add GROUP BY to aggregations on the RHS of join", + action: addOrderBysForAggregations, + }} +} + +func addOrderBysForAggregations(ctx *plancontext.PlanningContext, root ops.Operator) (ops.Operator, error) { + visitor := func(in ops.Operator, _ semantics.TableSet, isRoot bool) (ops.Operator, *rewrite.ApplyResult, error) { + aggrOp, ok := in.(*Aggregator) + if !ok { + return in, rewrite.SameTree, nil + } + + requireOrdering, err := needsOrdering(aggrOp, ctx) + if err != nil { + return nil, nil, err + } + if !requireOrdering { + return in, rewrite.SameTree, nil + } + aggrOp.Source = &Ordering{ + Source: aggrOp.Source, + Order: slices2.Map(aggrOp.Grouping, func(from GroupBy) ops.OrderBy { + return from.AsOrderBy() + }), + } + return in, rewrite.NewTree("added ordering before aggregation", in), nil + } + + return rewrite.TopDown(root, TableID, visitor, stopAtRoute) +} + +func needsOrdering(in *Aggregator, ctx *plancontext.PlanningContext) (bool, error) { + if len(in.Grouping) == 0 { + return false, nil + } + srcOrdering, err := in.Source.GetOrdering() + if err != nil { + return false, err + } + if len(srcOrdering) < len(in.Grouping) { + return true, nil + } + for idx, gb := range in.Grouping { + if !ctx.SemTable.EqualsExprWithDeps(srcOrdering[idx].SimplifiedExpr, gb.SimplifiedExpr) { + return true, nil + } + } + return false, nil +} + +func addGroupByOnRHSOfJoin(root ops.Operator) (ops.Operator, error) { + visitor := func(in ops.Operator, _ semantics.TableSet, isRoot bool) (ops.Operator, *rewrite.ApplyResult, error) { + join, ok := in.(*ApplyJoin) + if !ok { + return in, rewrite.SameTree, nil + } + + return addLiteralGroupingToRHS(join) + } + + return rewrite.TopDown(root, TableID, visitor, stopAtRoute) +} + +func addLiteralGroupingToRHS(in *ApplyJoin) (ops.Operator, *rewrite.ApplyResult, error) { + _ = rewrite.Visit(in.RHS, func(op ops.Operator) error { + aggr, isAggr := op.(*Aggregator) + if !isAggr { + return nil + } + if len(aggr.Grouping) == 0 { + gb := sqlparser.NewIntLiteral(".0") + aggr.Grouping = append(aggr.Grouping, NewGroupBy(gb, gb, aeWrap(gb))) + } + return nil + }) + return in, rewrite.SameTree, nil +} diff --git a/go/vt/vtgate/planbuilder/operators/projection.go b/go/vt/vtgate/planbuilder/operators/projection.go index 79e3d837ff6..a969fdc1129 100644 --- a/go/vt/vtgate/planbuilder/operators/projection.go +++ b/go/vt/vtgate/planbuilder/operators/projection.go @@ -268,12 +268,6 @@ func (p *Projection) compactWithRoute(rb *Route) (ops.Operator, *rewrite.ApplyRe return rb, rewrite.SameTree, nil } -func stopAtAggregations(node, _ sqlparser.SQLNode) bool { - _, aggr := node.(sqlparser.AggrFunc) - b := !aggr - return b -} - func (p *Projection) needsEvaluation(ctx *plancontext.PlanningContext, e sqlparser.Expr) bool { offset := slices.IndexFunc(p.Columns, func(expr *sqlparser.AliasedExpr) bool { return ctx.SemTable.EqualsExprWithDeps(expr.Expr, e) @@ -325,63 +319,3 @@ func (p *Projection) planOffsets(ctx *plancontext.PlanningContext) error { return nil } - -func useOffsets(ctx *plancontext.PlanningContext, expr sqlparser.Expr, op ops.Operator) (sqlparser.Expr, error) { - in := op.Inputs()[0] - columns, err := in.GetColumns() - if err != nil { - return nil, err - } - - var exprOffset *sqlparser.Offset - down := func(node, parent sqlparser.SQLNode) bool { - if err != nil { - return false - } - e, ok := node.(sqlparser.Expr) - if !ok { - return true - } - offset := slices.IndexFunc(columns, func(expr *sqlparser.AliasedExpr) bool { - return ctx.SemTable.EqualsExprWithDeps(expr.Expr, e) - }) - if offset >= 0 { - // this expression can be fetched from the input - we can stop here - exprOffset = sqlparser.NewOffset(offset, e) - return false - } - - if fetchByOffset(e) { - // this expression has to be fetched from the input, but we didn't find it in the input. let's add it - _, addToGroupBy := e.(*sqlparser.ColName) - in, offset, err = in.AddColumn(ctx, aeWrap(e), true, addToGroupBy) - if err != nil { - return false - } - op.SetInputs([]ops.Operator{in}) - columns, err = in.GetColumns() - if err != nil { - return false - } - exprOffset = sqlparser.NewOffset(offset, e) - return false - } - - return true - } - - // The cursor replace is not available while walking `down`, so `up` is used to do the replacement. - up := func(cursor *sqlparser.CopyOnWriteCursor) { - if exprOffset != nil { - cursor.Replace(exprOffset) - exprOffset = nil - } - } - - rewritten := sqlparser.CopyOnRewrite(expr, down, up, ctx.SemTable.CopyDependenciesOnSQLNodes) - if err != nil { - return nil, err - } - - return rewritten.(sqlparser.Expr), nil -} diff --git a/go/vt/vtgate/planbuilder/plancontext/planning_context.go b/go/vt/vtgate/planbuilder/plancontext/planning_context.go index e8045ce0b04..85e72a8c6d2 100644 --- a/go/vt/vtgate/planbuilder/plancontext/planning_context.go +++ b/go/vt/vtgate/planbuilder/plancontext/planning_context.go @@ -39,6 +39,10 @@ type PlanningContext struct { // If we during planning have turned this expression into an argument name, // we can continue using the same argument name ReservedArguments map[sqlparser.Expr]string + + // DelegateAggregation tells us when we are allowed to split an aggregation across vtgate and mysql + // We aggregate within a shard, and then at the vtgate level we aggregate the incoming shard aggregates + DelegateAggregation bool } func NewPlanningContext(reservedVars *sqlparser.ReservedVars, semTable *semantics.SemTable, vschema VSchema, version querypb.ExecuteOptions_PlannerVersion) *PlanningContext { diff --git a/go/vt/vtgate/planbuilder/testdata/aggr_cases.json b/go/vt/vtgate/planbuilder/testdata/aggr_cases.json index b31f403e521..fdf43c8266d 100644 --- a/go/vt/vtgate/planbuilder/testdata/aggr_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/aggr_cases.json @@ -1441,9 +1441,9 @@ "Name": "user", "Sharded": true }, - "FieldQuery": "select a, b, c, d, count(*), weight_string(d), weight_string(b), weight_string(a), weight_string(c) from `user` where 1 != 1 group by a, b, c, d, weight_string(d), weight_string(b), weight_string(a), weight_string(c)", + "FieldQuery": "select a, b, c, d, count(*), weight_string(d), weight_string(b), weight_string(a), weight_string(c) from `user` where 1 != 1 group by d, b, a, c, weight_string(d), weight_string(b), weight_string(a), weight_string(c)", "OrderBy": "(3|5) ASC, (1|6) ASC, (0|7) ASC, (2|8) ASC", - "Query": "select a, b, c, d, count(*), weight_string(d), weight_string(b), weight_string(a), weight_string(c) from `user` group by a, b, c, d, weight_string(d), weight_string(b), weight_string(a), weight_string(c) order by d asc, b asc, a asc, c asc", + "Query": "select a, b, c, d, count(*), weight_string(d), weight_string(b), weight_string(a), weight_string(c) from `user` group by d, b, a, c, weight_string(d), weight_string(b), weight_string(a), weight_string(c) order by d asc, b asc, a asc, c asc", "Table": "`user`" } ] @@ -1498,9 +1498,9 @@ "Name": "user", "Sharded": true }, - "FieldQuery": "select a, b, c, d, count(*), weight_string(d), weight_string(b), weight_string(a), weight_string(c) from `user` where 1 != 1 group by c, b, a, d, weight_string(d), weight_string(b), weight_string(a), weight_string(c)", + "FieldQuery": "select a, b, c, d, count(*), weight_string(d), weight_string(b), weight_string(a), weight_string(c) from `user` where 1 != 1 group by d, b, a, c, weight_string(d), weight_string(b), weight_string(a), weight_string(c)", "OrderBy": "(3|5) ASC, (1|6) ASC, (0|7) ASC, (2|8) ASC", - "Query": "select a, b, c, d, count(*), weight_string(d), weight_string(b), weight_string(a), weight_string(c) from `user` group by c, b, a, d, weight_string(d), weight_string(b), weight_string(a), weight_string(c) order by d asc, b asc, a asc, c asc", + "Query": "select a, b, c, d, count(*), weight_string(d), weight_string(b), weight_string(a), weight_string(c) from `user` group by d, b, a, c, weight_string(d), weight_string(b), weight_string(a), weight_string(c) order by d asc, b asc, a asc, c asc", "Table": "`user`" } ] @@ -1555,9 +1555,9 @@ "Name": "user", "Sharded": true }, - "FieldQuery": "select a, b, c, count(*), weight_string(a), weight_string(c), weight_string(b) from `user` where 1 != 1 group by c, b, a, weight_string(a), weight_string(c), weight_string(b)", + "FieldQuery": "select a, b, c, count(*), weight_string(a), weight_string(c), weight_string(b) from `user` where 1 != 1 group by a, c, b, weight_string(a), weight_string(c), weight_string(b)", "OrderBy": "(0|4) DESC, (2|5) DESC, (1|6) ASC", - "Query": "select a, b, c, count(*), weight_string(a), weight_string(c), weight_string(b) from `user` group by c, b, a, weight_string(a), weight_string(c), weight_string(b) order by a desc, c desc, b asc", + "Query": "select a, b, c, count(*), weight_string(a), weight_string(c), weight_string(b) from `user` group by a, c, b, weight_string(a), weight_string(c), weight_string(b) order by a desc, c desc, b asc", "Table": "`user`" } ] @@ -2666,7 +2666,7 @@ "Original": "select count(*) a from user having a = 10", "Instructions": { "OperatorType": "Filter", - "Predicate": ":0 = 10", + "Predicate": "count(*) = 10", "Inputs": [ { "OperatorType": "Aggregate", @@ -2702,7 +2702,7 @@ "Original": "select count(*) a from user having a = '1'", "Instructions": { "OperatorType": "Filter", - "Predicate": ":0 = '1'", + "Predicate": "count(*) = '1'", "Inputs": [ { "OperatorType": "Aggregate", @@ -2738,7 +2738,7 @@ "Original": "select count(*) a from user having a != 10", "Instructions": { "OperatorType": "Filter", - "Predicate": ":0 != 10", + "Predicate": "count(*) != 10", "Inputs": [ { "OperatorType": "Aggregate", @@ -2774,7 +2774,7 @@ "Original": "select count(*) a from user having a != '1'", "Instructions": { "OperatorType": "Filter", - "Predicate": ":0 != '1'", + "Predicate": "count(*) != '1'", "Inputs": [ { "OperatorType": "Aggregate", @@ -2810,7 +2810,7 @@ "Original": "select count(*) a from user having a > 10", "Instructions": { "OperatorType": "Filter", - "Predicate": ":0 > 10", + "Predicate": "count(*) > 10", "Inputs": [ { "OperatorType": "Aggregate", @@ -2846,7 +2846,7 @@ "Original": "select count(*) a from user having a >= 10", "Instructions": { "OperatorType": "Filter", - "Predicate": ":0 >= 10", + "Predicate": "count(*) >= 10", "Inputs": [ { "OperatorType": "Aggregate", @@ -2882,7 +2882,7 @@ "Original": "select count(*) a from user having a < 10", "Instructions": { "OperatorType": "Filter", - "Predicate": ":0 < 10", + "Predicate": "count(*) < 10", "Inputs": [ { "OperatorType": "Aggregate", @@ -2918,7 +2918,7 @@ "Original": "select count(*) a from user having a <= 10", "Instructions": { "OperatorType": "Filter", - "Predicate": ":0 <= 10", + "Predicate": "count(*) <= 10", "Inputs": [ { "OperatorType": "Aggregate", @@ -2954,7 +2954,7 @@ "Original": "select col, count(*) a from user group by col having a <= 10", "Instructions": { "OperatorType": "Filter", - "Predicate": ":1 <= 10", + "Predicate": "count(*) <= 10", "Inputs": [ { "OperatorType": "Aggregate", @@ -2999,7 +2999,7 @@ "Inputs": [ { "OperatorType": "Filter", - "Predicate": ":0 = 1.00", + "Predicate": "count(*) = 1.00", "Inputs": [ { "OperatorType": "Aggregate", @@ -3119,7 +3119,7 @@ "Inputs": [ { "OperatorType": "Filter", - "Predicate": ":1 = 10", + "Predicate": "count(id) = 10", "Inputs": [ { "OperatorType": "Aggregate", @@ -3264,7 +3264,7 @@ "Inputs": [ { "OperatorType": "Filter", - "Predicate": ":1 = 10", + "Predicate": "count(id) = 10", "Inputs": [ { "OperatorType": "Aggregate", @@ -4072,7 +4072,7 @@ "Inputs": [ { "OperatorType": "Filter", - "Predicate": ":1 = 3", + "Predicate": "count(*) = 3", "Inputs": [ { "OperatorType": "Aggregate", @@ -4120,7 +4120,7 @@ "Inputs": [ { "OperatorType": "Filter", - "Predicate": ":1 + :2 = 42", + "Predicate": "sum(foo) + sum(bar) = 42", "Inputs": [ { "OperatorType": "Aggregate", @@ -4168,7 +4168,7 @@ "Inputs": [ { "OperatorType": "Filter", - "Predicate": ":1 + :2 = 42", + "Predicate": "sum(foo) + sum(bar) = 42", "Inputs": [ { "OperatorType": "Aggregate", @@ -4214,7 +4214,7 @@ "Inputs": [ { "OperatorType": "Filter", - "Predicate": ":1 = 3", + "Predicate": "count(*) = 3", "Inputs": [ { "OperatorType": "Aggregate", @@ -4260,7 +4260,7 @@ "Inputs": [ { "OperatorType": "Filter", - "Predicate": ":1 = 3", + "Predicate": "count(u.`name`) = 3", "Inputs": [ { "OperatorType": "Aggregate", @@ -4271,22 +4271,22 @@ { "OperatorType": "Projection", "Expressions": [ - "[COLUMN 0] as id", - "[COLUMN 2] * COALESCE([COLUMN 3], INT64(1)) as count(u.`name`)", - "[COLUMN 1]" + "[COLUMN 2] as id", + "[COLUMN 1] * [COLUMN 0] as count(u.`name`)", + "[COLUMN 3] as weight_string(u.id)" ], "Inputs": [ { "OperatorType": "Sort", "Variant": "Memory", - "OrderBy": "(0|1) ASC", + "OrderBy": "(2|3) ASC", "Inputs": [ { "OperatorType": "Join", "Variant": "Join", - "JoinColumnIndexes": "R:1,R:2,L:1,R:0", + "JoinColumnIndexes": "R:0,L:0,R:1,R:2", "JoinVars": { - "ue_id": 0 + "ue_id": 1 }, "TableName": "user_extra_`user`", "Inputs": [ @@ -4297,8 +4297,8 @@ "Name": "user", "Sharded": true }, - "FieldQuery": "select ue.id, count(*), weight_string(ue.id) from user_extra as ue where 1 != 1 group by ue.id, weight_string(ue.id)", - "Query": "select ue.id, count(*), weight_string(ue.id) from user_extra as ue group by ue.id, weight_string(ue.id)", + "FieldQuery": "select count(*), ue.id from user_extra as ue where 1 != 1 group by ue.id", + "Query": "select count(*), ue.id from user_extra as ue group by ue.id", "Table": "user_extra" }, { @@ -4387,7 +4387,7 @@ "Inputs": [ { "OperatorType": "Filter", - "Predicate": ":1 < 3 and :1 > 5", + "Predicate": "count(*) < 3 and count(*) > 5", "Inputs": [ { "OperatorType": "Aggregate", @@ -4398,22 +4398,22 @@ { "OperatorType": "Projection", "Expressions": [ - "[COLUMN 0] as id", - "[COLUMN 2] * COALESCE([COLUMN 3], INT64(1)) as count(*)", - "[COLUMN 1]" + "[COLUMN 2] as id", + "[COLUMN 0] * [COLUMN 1] as count(*)", + "[COLUMN 3] as weight_string(u.id)" ], "Inputs": [ { "OperatorType": "Sort", "Variant": "Memory", - "OrderBy": "(0|1) ASC", + "OrderBy": "(2|3) ASC", "Inputs": [ { "OperatorType": "Join", "Variant": "Join", - "JoinColumnIndexes": "R:1,R:2,L:1,R:0", + "JoinColumnIndexes": "L:0,R:0,R:1,R:2", "JoinVars": { - "ue_id": 0 + "ue_id": 1 }, "TableName": "user_extra_`user`", "Inputs": [ @@ -4424,8 +4424,8 @@ "Name": "user", "Sharded": true }, - "FieldQuery": "select ue.id, count(*), weight_string(ue.id) from user_extra as ue where 1 != 1 group by ue.id, weight_string(ue.id)", - "Query": "select ue.id, count(*), weight_string(ue.id) from user_extra as ue group by ue.id, weight_string(ue.id)", + "FieldQuery": "select count(*), ue.id from user_extra as ue where 1 != 1 group by ue.id", + "Query": "select count(*), ue.id from user_extra as ue group by ue.id", "Table": "user_extra" }, { @@ -6022,8 +6022,8 @@ { "OperatorType": "Projection", "Expressions": [ - "[COLUMN 2] as col", - "[COLUMN 3] as intcol", + "[COLUMN 3] as col", + "[COLUMN 2] as intcol", "[COLUMN 0] * [COLUMN 1] as count(*)" ], "Inputs": [ @@ -6040,9 +6040,9 @@ "Name": "user", "Sharded": true }, - "FieldQuery": "select count(*), u.col, u.intcol from `user` as u where 1 != 1 group by u.col, u.intcol", - "OrderBy": "2 ASC, 1 ASC", - "Query": "select count(*), u.col, u.intcol from `user` as u group by u.col, u.intcol order by u.intcol asc, u.col asc", + "FieldQuery": "select count(*), u.intcol, u.col from `user` as u where 1 != 1 group by u.intcol, u.col", + "OrderBy": "1 ASC, 2 ASC", + "Query": "select count(*), u.intcol, u.col from `user` as u group by u.intcol, u.col order by u.intcol asc, u.col asc", "Table": "`user`" }, { @@ -6170,40 +6170,45 @@ } }, { - "QueryType": "SELECT", - "Original": "select distinct count(*) from user group by col", - "Instructions": { - "OperatorType": "Distinct", - "Collations": [ - "0" - ], - "ResultColumns": 1, - "Inputs": [ - { - "OperatorType": "Aggregate", - "Variant": "Ordered", - "Aggregates": "sum_count_star(0) AS count(*)", - "GroupBy": "1", - "Inputs": [ - { - "OperatorType": "Route", - "Variant": "Scatter", - "Keyspace": { - "Name": "user", - "Sharded": true - }, - "FieldQuery": "select count(*), col from `user` where 1 != 1 group by col", - "OrderBy": "1 ASC", - "Query": "select count(*), col from `user` group by col order by col asc", - "Table": "`user`" - } - ] - } + "comment": "distinct on top of aggregation", + "query": "select distinct count(*) from user group by col", + "v3-plan": "VT12001: unsupported: in scatter query: GROUP BY column must reference column in SELECT list", + "gen4-plan": { + "QueryType": "SELECT", + "Original": "select distinct count(*) from user group by col", + "Instructions": { + "OperatorType": "Distinct", + "Collations": [ + "0" + ], + "ResultColumns": 1, + "Inputs": [ + { + "OperatorType": "Aggregate", + "Variant": "Ordered", + "Aggregates": "sum_count_star(0) AS count(*)", + "GroupBy": "1", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select count(*), col from `user` where 1 != 1 group by col", + "OrderBy": "1 ASC", + "Query": "select count(*), col from `user` group by col order by col asc", + "Table": "`user`" + } + ] + } + ] + }, + "TablesUsed": [ + "user.user" ] - }, - "TablesUsed": [ - "user.user" - ] + } }, { "comment": "scalar aggregates with min, max, sum distinct and count distinct using collations", @@ -6443,5 +6448,183 @@ "user.user_extra" ] } + }, + { + "comment": "extremum on input from both sides", + "query": "select max(u.foo*ue.bar) from user u join user_extra ue", + "v3-plan": "VT12001: unsupported: cross-shard query with aggregates", + "gen4-plan": { + "QueryType": "SELECT", + "Original": "select max(u.foo*ue.bar) from user u join user_extra ue", + "Instructions": { + "OperatorType": "Aggregate", + "Variant": "Scalar", + "Aggregates": "max(0) AS max(u.foo * ue.bar)", + "Inputs": [ + { + "OperatorType": "Join", + "Variant": "Join", + "JoinColumnIndexes": "R:0", + "JoinVars": { + "u_foo": 0 + }, + "TableName": "`user`_user_extra", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select u.foo from `user` as u where 1 != 1", + "Query": "select u.foo from `user` as u", + "Table": "`user`" + }, + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select :u_foo * ue.bar from user_extra as ue where 1 != 1", + "Query": "select :u_foo * ue.bar from user_extra as ue", + "Table": "user_extra" + } + ] + } + ] + }, + "TablesUsed": [ + "user.user", + "user.user_extra" + ] + } + }, + { + "comment": "aggregate on input from both sides - TODO optimize more", + "query": "select sum(user.foo+user_extra.bar) from user, user_extra", + "v3-plan": "VT12001: unsupported: cross-shard query with aggregates", + "gen4-plan": { + "QueryType": "SELECT", + "Original": "select sum(user.foo+user_extra.bar) from user, user_extra", + "Instructions": { + "OperatorType": "Aggregate", + "Variant": "Scalar", + "Aggregates": "sum(0) AS sum(`user`.foo + user_extra.bar)", + "Inputs": [ + { + "OperatorType": "Join", + "Variant": "Join", + "JoinColumnIndexes": "R:0", + "JoinVars": { + "user_foo": 0 + }, + "TableName": "`user`_user_extra", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select `user`.foo from `user` where 1 != 1", + "Query": "select `user`.foo from `user`", + "Table": "`user`" + }, + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select :user_foo + user_extra.bar from user_extra where 1 != 1", + "Query": "select :user_foo + user_extra.bar from user_extra", + "Table": "user_extra" + } + ] + } + ] + }, + "TablesUsed": [ + "user.user", + "user.user_extra" + ] + } + }, + { + "comment": "grouping column could be coming from multiple sides", + "query": "select count(*) from user, user_extra group by user.id+user_extra.id", + "v3-plan": "VT12001: unsupported: cross-shard query with aggregates", + "gen4-plan": { + "QueryType": "SELECT", + "Original": "select count(*) from user, user_extra group by user.id+user_extra.id", + "Instructions": { + "OperatorType": "Aggregate", + "Variant": "Ordered", + "Aggregates": "sum_count_star(0) AS count(*)", + "GroupBy": "(1|2)", + "ResultColumns": 1, + "Inputs": [ + { + "OperatorType": "Sort", + "Variant": "Memory", + "OrderBy": "(1|2) ASC", + "Inputs": [ + { + "OperatorType": "Projection", + "Expressions": [ + "[COLUMN 0] * [COLUMN 1] as count(*)", + "[COLUMN 2] as `user`.id + user_extra.id", + "[COLUMN 3] as weight_string(`user`.id + user_extra.id)" + ], + "Inputs": [ + { + "OperatorType": "Join", + "Variant": "Join", + "JoinColumnIndexes": "L:0,R:0,R:1,R:2", + "JoinVars": { + "user_id": 1 + }, + "TableName": "`user`_user_extra", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select count(*), `user`.id from `user` where 1 != 1 group by `user`.id", + "Query": "select count(*), `user`.id from `user` group by `user`.id", + "Table": "`user`" + }, + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select count(*), :user_id + user_extra.id, weight_string(:user_id + user_extra.id) from user_extra where 1 != 1 group by :user_id + user_extra.id", + "Query": "select count(*), :user_id + user_extra.id, weight_string(:user_id + user_extra.id) from user_extra group by :user_id + user_extra.id", + "Table": "user_extra" + } + ] + } + ] + } + ] + } + ] + }, + "TablesUsed": [ + "user.user", + "user.user_extra" + ] + } } ] diff --git a/go/vt/vtgate/planbuilder/testdata/filter_cases.json b/go/vt/vtgate/planbuilder/testdata/filter_cases.json index 790b1cdccb3..3fecffc3316 100644 --- a/go/vt/vtgate/planbuilder/testdata/filter_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/filter_cases.json @@ -6598,7 +6598,7 @@ "Inputs": [ { "OperatorType": "Filter", - "Predicate": "repeat(a.textcol1, :1) like 'And%res'", + "Predicate": "repeat(a.textcol1, sum(a.id)) like 'And%res'", "Inputs": [ { "OperatorType": "Aggregate", @@ -6609,16 +6609,16 @@ { "OperatorType": "Projection", "Expressions": [ - "[COLUMN 0] as textcol1", - "[COLUMN 1] * COALESCE([COLUMN 2], INT64(1)) as sum(a.id)" + "[COLUMN 2] as textcol1", + "[COLUMN 0] * [COLUMN 1] as sum(a.id)" ], "Inputs": [ { "OperatorType": "Join", "Variant": "Join", - "JoinColumnIndexes": "L:0,L:1,R:1", + "JoinColumnIndexes": "L:0,R:0,L:1", "JoinVars": { - "a_textcol1": 0 + "a_textcol1": 1 }, "TableName": "`user`_`user`", "Inputs": [ @@ -6629,9 +6629,9 @@ "Name": "user", "Sharded": true }, - "FieldQuery": "select a.textcol1, sum(a.id) from `user` as a where 1 != 1 group by a.textcol1", - "OrderBy": "0 ASC COLLATE latin1_swedish_ci", - "Query": "select a.textcol1, sum(a.id) from `user` as a group by a.textcol1 order by a.textcol1 asc", + "FieldQuery": "select sum(a.id), a.textcol1 from `user` as a where 1 != 1 group by a.textcol1", + "OrderBy": "1 ASC COLLATE latin1_swedish_ci", + "Query": "select sum(a.id), a.textcol1 from `user` as a group by a.textcol1 order by a.textcol1 asc", "Table": "`user`" }, { @@ -6641,8 +6641,8 @@ "Name": "user", "Sharded": true }, - "FieldQuery": "select 1, count(*) from `user` as b where 1 != 1 group by 1", - "Query": "select 1, count(*) from `user` as b where b.textcol2 = :a_textcol1 group by 1", + "FieldQuery": "select count(*) from `user` as b where 1 != 1 group by .0", + "Query": "select count(*) from `user` as b where b.textcol2 = :a_textcol1 group by .0", "Table": "`user`" } ] diff --git a/go/vt/vtgate/planbuilder/testdata/postprocess_cases.json b/go/vt/vtgate/planbuilder/testdata/postprocess_cases.json index 6b80ba8862b..ffeec4c3eaf 100644 --- a/go/vt/vtgate/planbuilder/testdata/postprocess_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/postprocess_cases.json @@ -3102,7 +3102,7 @@ "Original": "select count(*) a from user having a = 0x01", "Instructions": { "OperatorType": "Filter", - "Predicate": ":0 = 0x01", + "Predicate": "count(*) = 0x01", "Inputs": [ { "OperatorType": "Aggregate", @@ -3291,14 +3291,14 @@ "QueryType": "SELECT", "Original": "select a.tcol1 from user a join music b where a.tcol1 = b.tcol2 group by a.tcol1 having repeat(a.tcol1,min(a.id)) like \"A\\%B\" order by a.tcol1", "Instructions": { - "OperatorType": "SimpleProjection", - "Columns": [ - 0 - ], + "OperatorType": "Sort", + "Variant": "Memory", + "OrderBy": "(0|2) ASC", + "ResultColumns": 1, "Inputs": [ { "OperatorType": "Filter", - "Predicate": "repeat(a.tcol1, :1) like 'A\\%B'", + "Predicate": "repeat(a.tcol1, min(a.id)) like 'A\\%B'", "Inputs": [ { "OperatorType": "Aggregate", @@ -3307,46 +3307,36 @@ "GroupBy": "(0|2)", "Inputs": [ { - "OperatorType": "Projection", - "Expressions": [ - "[COLUMN 0] as tcol1", - "[COLUMN 2] as min(a.id)", - "[COLUMN 1]" - ], + "OperatorType": "Join", + "Variant": "Join", + "JoinColumnIndexes": "L:1,L:0,L:2", + "JoinVars": { + "a_tcol1": 1 + }, + "TableName": "`user`_music", "Inputs": [ { - "OperatorType": "Join", - "Variant": "Join", - "JoinColumnIndexes": "L:0,L:2,L:1", - "JoinVars": { - "a_tcol1": 0 + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select min(a.id), a.tcol1, weight_string(a.tcol1) from `user` as a where 1 != 1 group by a.tcol1, weight_string(a.tcol1)", + "OrderBy": "(1|2) ASC", + "Query": "select min(a.id), a.tcol1, weight_string(a.tcol1) from `user` as a group by a.tcol1, weight_string(a.tcol1) order by a.tcol1 asc", + "Table": "`user`" + }, + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true }, - "TableName": "`user`_music", - "Inputs": [ - { - "OperatorType": "Route", - "Variant": "Scatter", - "Keyspace": { - "Name": "user", - "Sharded": true - }, - "FieldQuery": "select a.tcol1, min(a.id), weight_string(a.tcol1) from `user` as a where 1 != 1 group by a.tcol1, weight_string(a.tcol1)", - "OrderBy": "(0|2) ASC", - "Query": "select a.tcol1, min(a.id), weight_string(a.tcol1) from `user` as a group by a.tcol1, weight_string(a.tcol1) order by a.tcol1 asc", - "Table": "`user`" - }, - { - "OperatorType": "Route", - "Variant": "Scatter", - "Keyspace": { - "Name": "user", - "Sharded": true - }, - "FieldQuery": "select 1 from music as b where 1 != 1 group by 1", - "Query": "select 1 from music as b where b.tcol2 = :a_tcol1 group by 1", - "Table": "music" - } - ] + "FieldQuery": "select 1 from music as b where 1 != 1 group by .0", + "Query": "select 1 from music as b where b.tcol2 = :a_tcol1 group by .0", + "Table": "music" } ] } diff --git a/go/vt/vtgate/planbuilder/testdata/tpch_cases.json b/go/vt/vtgate/planbuilder/testdata/tpch_cases.json index 33f3f73d9fb..7ef69afad01 100644 --- a/go/vt/vtgate/planbuilder/testdata/tpch_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/tpch_cases.json @@ -970,82 +970,71 @@ "OperatorType": "Aggregate", "Variant": "Ordered", "Aggregates": "sum(1) AS high_line_count, sum(2) AS low_line_count", - "GroupBy": "(0|3)", + "GroupBy": "0", "ResultColumns": 3, "Inputs": [ { - "OperatorType": "Projection", - "Expressions": [ - "[COLUMN 3] as l_shipmode", - "[COLUMN 0] * [COLUMN 1] as high_line_count", - "[COLUMN 2] * [COLUMN 1] as low_line_count", - "[COLUMN 4] as weight_string(l_shipmode)" - ], + "OperatorType": "Sort", + "Variant": "Memory", + "OrderBy": "(0|3) ASC", "Inputs": [ { - "OperatorType": "Sort", - "Variant": "Memory", - "OrderBy": "(3|4) ASC", + "OperatorType": "Join", + "Variant": "Join", + "JoinColumnIndexes": "R:0,L:0,L:1,R:1", + "JoinVars": { + "o_orderkey": 2 + }, + "TableName": "orders_lineitem", "Inputs": [ { - "OperatorType": "Join", - "Variant": "Join", - "JoinColumnIndexes": "L:0,R:0,L:1,R:1,R:2", - "JoinVars": { - "o_orderkey": 2 + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "main", + "Sharded": true + }, + "FieldQuery": "select case when o_orderpriority = '1-URGENT' or o_orderpriority = '2-HIGH' then 1 else 0 end, case when o_orderpriority != '1-URGENT' and o_orderpriority != '2-HIGH' then 1 else 0 end, o_orderkey from orders where 1 != 1", + "Query": "select case when o_orderpriority = '1-URGENT' or o_orderpriority = '2-HIGH' then 1 else 0 end, case when o_orderpriority != '1-URGENT' and o_orderpriority != '2-HIGH' then 1 else 0 end, o_orderkey from orders", + "Table": "orders" + }, + { + "OperatorType": "VindexLookup", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "main", + "Sharded": true }, - "TableName": "orders_lineitem", + "Values": [ + ":o_orderkey" + ], + "Vindex": "lineitem_map", "Inputs": [ { "OperatorType": "Route", - "Variant": "Scatter", + "Variant": "IN", "Keyspace": { "Name": "main", "Sharded": true }, - "FieldQuery": "select sum(case when o_orderpriority = '1-URGENT' or o_orderpriority = '2-HIGH' then 1 else 0 end) as high_line_count, sum(case when o_orderpriority != '1-URGENT' and o_orderpriority != '2-HIGH' then 1 else 0 end) as low_line_count, o_orderkey from orders where 1 != 1 group by o_orderkey", - "Query": "select sum(case when o_orderpriority = '1-URGENT' or o_orderpriority = '2-HIGH' then 1 else 0 end) as high_line_count, sum(case when o_orderpriority != '1-URGENT' and o_orderpriority != '2-HIGH' then 1 else 0 end) as low_line_count, o_orderkey from orders group by o_orderkey", - "Table": "orders" + "FieldQuery": "select l_orderkey, l_linenumber from lineitem_map where 1 != 1", + "Query": "select l_orderkey, l_linenumber from lineitem_map where l_orderkey in ::__vals", + "Table": "lineitem_map", + "Values": [ + "::l_orderkey" + ], + "Vindex": "md5" }, { - "OperatorType": "VindexLookup", - "Variant": "EqualUnique", + "OperatorType": "Route", + "Variant": "ByDestination", "Keyspace": { "Name": "main", "Sharded": true }, - "Values": [ - ":o_orderkey" - ], - "Vindex": "lineitem_map", - "Inputs": [ - { - "OperatorType": "Route", - "Variant": "IN", - "Keyspace": { - "Name": "main", - "Sharded": true - }, - "FieldQuery": "select l_orderkey, l_linenumber from lineitem_map where 1 != 1", - "Query": "select l_orderkey, l_linenumber from lineitem_map where l_orderkey in ::__vals", - "Table": "lineitem_map", - "Values": [ - "::l_orderkey" - ], - "Vindex": "md5" - }, - { - "OperatorType": "Route", - "Variant": "ByDestination", - "Keyspace": { - "Name": "main", - "Sharded": true - }, - "FieldQuery": "select count(*), l_shipmode, weight_string(l_shipmode) from lineitem where 1 != 1 group by l_shipmode, weight_string(l_shipmode)", - "Query": "select count(*), l_shipmode, weight_string(l_shipmode) from lineitem where l_shipmode in ('MAIL', 'SHIP') and l_commitdate < l_receiptdate and l_shipdate < l_commitdate and l_receiptdate >= date('1994-01-01') and l_receiptdate < date('1994-01-01') + interval '1' year and l_orderkey = :o_orderkey group by l_shipmode, weight_string(l_shipmode)", - "Table": "lineitem" - } - ] + "FieldQuery": "select l_shipmode, weight_string(l_shipmode) from lineitem where 1 != 1", + "Query": "select l_shipmode, weight_string(l_shipmode) from lineitem where l_shipmode in ('MAIL', 'SHIP') and l_commitdate < l_receiptdate and l_shipdate < l_commitdate and l_receiptdate >= date('1994-01-01') and l_receiptdate < date('1994-01-01') + interval '1' year and l_orderkey = :o_orderkey", + "Table": "lineitem" } ] } diff --git a/go/vt/vtgate/planbuilder/testdata/unsupported_cases.json b/go/vt/vtgate/planbuilder/testdata/unsupported_cases.json index 3676c09ead9..0d32c2f5b14 100644 --- a/go/vt/vtgate/planbuilder/testdata/unsupported_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/unsupported_cases.json @@ -466,30 +466,12 @@ "query": "delete from user where x = (@val := 42)", "plan": "VT12001: unsupported: Assignment expression" }, - { - "comment": "grouping column could be coming from multiple sides", - "query": "select count(*) from user, user_extra group by user.id+user_extra.id", - "v3-plan": "VT12001: unsupported: cross-shard query with aggregates", - "gen4-plan": "VT12001: unsupported: grouping on columns from different sources" - }, - { - "comment": "aggregate on input from both sides", - "query": "select sum(user.foo+user_extra.bar) from user, user_extra", - "v3-plan": "VT12001: unsupported: cross-shard query with aggregates", - "gen4-plan": "VT12001: unsupported: aggregation on columns from different sources" - }, { "comment": "combine the output of two aggregations in the final result", "query": "select greatest(sum(user.foo), sum(user_extra.bar)) from user join user_extra on user.col = user_extra.col", "v3-plan": "VT12001: unsupported: cross-shard query with aggregates", "gen4-plan": "VT12001: unsupported: in scatter query: complex aggregate expression" }, - { - "comment": "extremum on input from both sides", - "query": "select max(u.foo*ue.bar) from user u join user_extra ue", - "v3-plan": "VT12001: unsupported: cross-shard query with aggregates", - "gen4-plan": "VT12001: unsupported: aggregation on columns from different sources: max(u.foo * ue.bar)" - }, { "comment": "extremum on input from both sides", "query": "insert into music(user_id, id) select foo, bar from music on duplicate key update id = id+1",