From e961a37c49de66b405ca384f128a61675576b5bf Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Wed, 17 Dec 2025 21:17:05 +0000 Subject: [PATCH] Vibe code initial implementation for supporting multiple `DISTINCT` aggregations. Signed-off-by: Arthur Schreiber --- .../queries/aggregation/aggregation_test.go | 8 +- go/vt/vtgate/engine/aggregations.go | 144 ++++++++++++++---- go/vt/vtgate/engine/aggregations_test.go | 128 ++++++++++++++++ .../planbuilder/operator_transformers.go | 2 + .../operators/aggregation_pushing.go | 84 ++++++---- .../planbuilder/operators/aggregator.go | 9 +- go/vt/vtgate/planbuilder/operators/phases.go | 8 +- .../planbuilder/operators/queryprojection.go | 5 + .../testdata/unsupported_cases.json | 10 -- 9 files changed, 327 insertions(+), 71 deletions(-) diff --git a/go/test/endtoend/vtgate/queries/aggregation/aggregation_test.go b/go/test/endtoend/vtgate/queries/aggregation/aggregation_test.go index 6549aabfb47..a677355779f 100644 --- a/go/test/endtoend/vtgate/queries/aggregation/aggregation_test.go +++ b/go/test/endtoend/vtgate/queries/aggregation/aggregation_test.go @@ -666,15 +666,15 @@ func TestDistinctAggregation(t *testing.T) { expectedErr string minVersion int }{{ - query: `SELECT COUNT(DISTINCT value), SUM(DISTINCT shardkey) FROM t1`, - expectedErr: "VT12001: unsupported: only one DISTINCT aggregation is allowed in a SELECT: sum(distinct shardkey) (errno 1235) (sqlstate 42000)", + // Multiple distinct aggregations with different columns - now supported via hash-based distinct + query: `SELECT COUNT(DISTINCT value), SUM(DISTINCT shardkey) FROM t1`, }, { query: `SELECT a.t1_id, SUM(DISTINCT b.shardkey) FROM t1 a, t1 b group by a.t1_id`, }, { query: `SELECT a.value, SUM(DISTINCT b.shardkey) FROM t1 a, t1 b group by a.value`, }, { - query: `SELECT count(distinct a.value), SUM(DISTINCT b.t1_id) FROM t1 a, t1 b`, - expectedErr: "VT12001: unsupported: only one DISTINCT aggregation is allowed in a SELECT: sum(distinct b.t1_id) (errno 1235) (sqlstate 42000)", + // Multiple distinct aggregations in join - now supported via hash-based distinct + query: `SELECT count(distinct a.value), SUM(DISTINCT b.t1_id) FROM t1 a, t1 b`, }, { query: `SELECT a.value, SUM(DISTINCT b.t1_id), min(DISTINCT a.t1_id) FROM t1 a, t1 b group by a.value`, }, { diff --git a/go/vt/vtgate/engine/aggregations.go b/go/vt/vtgate/engine/aggregations.go index f003d647b72..c71d5a39a8a 100644 --- a/go/vt/vtgate/engine/aggregations.go +++ b/go/vt/vtgate/engine/aggregations.go @@ -29,6 +29,7 @@ import ( "vitess.io/vitess/go/vt/vterrors" "vitess.io/vitess/go/vt/vtgate/engine/opcode" "vitess.io/vitess/go/vt/vtgate/evalengine" + "vitess.io/vitess/go/vt/vthash" ) // AggregateParams specify the parameters for each aggregation. @@ -56,6 +57,11 @@ type AggregateParams struct { OrigOpcode opcode.AggregateOpcode CollationEnv *collations.Environment + + // UseHashDistinct indicates this aggregation should use hash-based + // distinct tracking instead of sort-based. This is used when multiple + // distinct aggregations with different expressions exist in a query. + UseHashDistinct bool } // NewAggregateParam creates a new aggregate param @@ -128,6 +134,14 @@ type aggregator interface { reset() } +// distinctTracker is an interface for tracking distinct values. +// It can be implemented by sort-based (aggregatorDistinct) or +// hash-based (aggregatorDistinctHash) trackers. +type distinctTracker interface { + shouldReturn(row []sqltypes.Value) (bool, error) + reset() +} + type aggregatorDistinct struct { column int last sqltypes.Value @@ -160,18 +174,69 @@ func (a *aggregatorDistinct) reset() { a.last = sqltypes.NULL } +// aggregatorDistinctHash is a hash-based distinct tracker that uses hash sets +// to track seen values. Unlike aggregatorDistinct which requires sorted data, +// this can handle arbitrary input order. It's used when multiple distinct +// aggregations with different expressions exist in a single query. +type aggregatorDistinctHash struct { + column int + wsColumn int // weight string column for collation support + seen map[vthash.Hash]struct{} + hasher vthash.Hasher + coll collations.ID + collationEnv *collations.Environment + typ querypb.Type + sqlmode evalengine.SQLMode + values *evalengine.EnumSetValues +} + +func (a *aggregatorDistinctHash) shouldReturn(row []sqltypes.Value) (bool, error) { + val := row[a.column] + if val.IsNull() { + // NULL values are never counted as distinct duplicates + return false, nil + } + + a.hasher.Reset() + err := evalengine.NullsafeHashcode128(&a.hasher, val, a.coll, a.typ, a.sqlmode, a.values) + if err != nil { + // Fallback to weight string if available + if a.wsColumn >= 0 { + val = row[a.wsColumn] + a.hasher.Reset() + err = evalengine.NullsafeHashcode128(&a.hasher, val, collations.Unknown, sqltypes.VarBinary, a.sqlmode, nil) + } + if err != nil { + return false, err + } + } + + hash := a.hasher.Sum128() + if _, found := a.seen[hash]; found { + return true, nil // Already seen, skip + } + a.seen[hash] = struct{}{} + return false, nil +} + +func (a *aggregatorDistinctHash) reset() { + a.seen = make(map[vthash.Hash]struct{}) +} + type aggregatorCount struct { from int n int64 - distinct aggregatorDistinct + distinct distinctTracker } func (a *aggregatorCount) add(row []sqltypes.Value) error { if row[a.from].IsNull() { return nil } - if ret, err := a.distinct.shouldReturn(row); ret { - return err + if a.distinct != nil { + if ret, err := a.distinct.shouldReturn(row); ret { + return err + } } a.n++ return nil @@ -183,7 +248,9 @@ func (a *aggregatorCount) finish(*evalengine.ExpressionEnv, collations.ID) (sqlt func (a *aggregatorCount) reset() { a.n = 0 - a.distinct.reset() + if a.distinct != nil { + a.distinct.reset() + } } type aggregatorCountStar struct { @@ -235,15 +302,17 @@ func (a *aggregatorMinMax) reset() { type aggregatorSum struct { from int sum evalengine.Sum - distinct aggregatorDistinct + distinct distinctTracker } func (a *aggregatorSum) add(row []sqltypes.Value) error { if row[a.from].IsNull() { return nil } - if ret, err := a.distinct.shouldReturn(row); ret { - return err + if a.distinct != nil { + if ret, err := a.distinct.shouldReturn(row); ret { + return err + } } return a.sum.Add(row[a.from]) } @@ -254,7 +323,9 @@ func (a *aggregatorSum) finish(*evalengine.ExpressionEnv, collations.ID) (sqltyp func (a *aggregatorSum) reset() { a.sum.Reset() - a.distinct.reset() + if a.distinct != nil { + a.distinct.reset() + } } type aggregatorScalar struct { @@ -411,6 +482,35 @@ func isComparable(typ sqltypes.Type) bool { return false } +// createDistinctTracker creates the appropriate distinct tracker based on the aggregation parameters. +// If UseHashDistinct is true, it creates a hash-based tracker that can handle arbitrary input order. +// Otherwise, it creates a sort-based tracker that requires sorted input. +func createDistinctTracker(aggr *AggregateParams, distinctCol int, wsCol int, sourceType querypb.Type) distinctTracker { + if distinctCol < 0 { + return nil // No distinct tracking needed + } + + if aggr.UseHashDistinct { + return &aggregatorDistinctHash{ + column: distinctCol, + wsColumn: wsCol, + seen: make(map[vthash.Hash]struct{}), + hasher: vthash.New(), + coll: aggr.Type.Collation(), + collationEnv: aggr.CollationEnv, + typ: sourceType, + values: aggr.Type.Values(), + } + } + + return &aggregatorDistinct{ + column: distinctCol, + coll: aggr.Type.Collation(), + collationEnv: aggr.CollationEnv, + values: aggr.Type.Values(), + } +} + func newAggregation(fields []*querypb.Field, aggregates []*AggregateParams, env *evalengine.ExpressionEnv, collation collations.ID) (*aggregationState, []*querypb.Field, error) { fields = slice.Map(fields, func(from *querypb.Field) *querypb.Field { return from.CloneVT() }) @@ -423,12 +523,14 @@ func newAggregation(fields []*querypb.Field, aggregates []*AggregateParams, env targetType := aggr.typ(sourceType, env, collation) var ag aggregator - var distinct = -1 + var distinctCol = -1 + var wsCol = -1 if aggr.Opcode.IsDistinct() { - distinct = aggr.KeyCol + distinctCol = aggr.KeyCol + wsCol = aggr.WCol if aggr.WAssigned() && !isComparable(sourceType) { - distinct = aggr.WCol + distinctCol = aggr.WCol } } @@ -444,13 +546,8 @@ func newAggregation(fields []*querypb.Field, aggregates []*AggregateParams, env case opcode.AggregateCount, opcode.AggregateCountDistinct: ag = &aggregatorCount{ - from: aggr.Col, - distinct: aggregatorDistinct{ - column: distinct, - coll: aggr.Type.Collation(), - collationEnv: aggr.CollationEnv, - values: aggr.Type.Values(), - }, + from: aggr.Col, + distinct: createDistinctTracker(aggr, distinctCol, wsCol, sourceType), } case opcode.AggregateSum, opcode.AggregateSumDistinct: @@ -463,14 +560,9 @@ func newAggregation(fields []*querypb.Field, aggregates []*AggregateParams, env } ag = &aggregatorSum{ - from: aggr.Col, - sum: sum, - distinct: aggregatorDistinct{ - column: distinct, - coll: aggr.Type.Collation(), - collationEnv: aggr.CollationEnv, - values: aggr.Type.Values(), - }, + from: aggr.Col, + sum: sum, + distinct: createDistinctTracker(aggr, distinctCol, wsCol, sourceType), } case opcode.AggregateMin: diff --git a/go/vt/vtgate/engine/aggregations_test.go b/go/vt/vtgate/engine/aggregations_test.go index 9facf3d4f6f..4c123bd702d 100644 --- a/go/vt/vtgate/engine/aggregations_test.go +++ b/go/vt/vtgate/engine/aggregations_test.go @@ -179,3 +179,131 @@ func BenchmarkScalarAggregate(b *testing.B) { }) } } + +// TestHashBasedDistinct verifies that hash-based distinct tracking correctly +// identifies duplicate values even when data is not sorted. +func TestHashBasedDistinct(t *testing.T) { + // Test data: unsorted values with duplicates + // Values: 1, 3, 2, 1, 3, 4 -> distinct count should be 4 (1, 2, 3, 4) + fields := sqltypes.MakeTestFields("col", "int64") + results := []*sqltypes.Result{{ + Fields: fields, + Rows: [][]sqltypes.Value{ + {sqltypes.NewInt64(1)}, + {sqltypes.NewInt64(3)}, + {sqltypes.NewInt64(2)}, + {sqltypes.NewInt64(1)}, // duplicate + {sqltypes.NewInt64(3)}, // duplicate + {sqltypes.NewInt64(4)}, + }, + }} + + fp := &fakePrimitive{ + allResultsInOneCall: true, + results: results, + } + + // Test hash-based distinct count + oa := &ScalarAggregate{ + Aggregates: []*AggregateParams{ + { + Opcode: AggregateCountDistinct, + Col: 0, + KeyCol: 0, + WCol: -1, + UseHashDistinct: true, // Use hash-based distinct tracking + }, + }, + Input: fp, + } + + result, err := oa.TryExecute(context.Background(), &noopVCursor{}, nil, true) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if len(result.Rows) != 1 { + t.Fatalf("expected 1 row, got %d", len(result.Rows)) + } + + // Should count 4 distinct values + val, err := result.Rows[0][0].ToInt64() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if val != 4 { + t.Errorf("expected distinct count of 4, got %d", val) + } +} + +// TestMultipleHashBasedDistinct verifies that multiple hash-based distinct +// aggregations can work independently on different columns. +func TestMultipleHashBasedDistinct(t *testing.T) { + // Test data with two integer columns for simpler hash behavior + // col1: 1, 1, 2, 2, 3 -> 3 distinct values + // col2: 10, 20, 10, 30, 40 -> 4 distinct values + fields := sqltypes.MakeTestFields("col1|col2", "int64|int64") + results := []*sqltypes.Result{{ + Fields: fields, + Rows: [][]sqltypes.Value{ + {sqltypes.NewInt64(1), sqltypes.NewInt64(10)}, + {sqltypes.NewInt64(1), sqltypes.NewInt64(20)}, + {sqltypes.NewInt64(2), sqltypes.NewInt64(10)}, + {sqltypes.NewInt64(2), sqltypes.NewInt64(30)}, + {sqltypes.NewInt64(3), sqltypes.NewInt64(40)}, + }, + }} + + fp := &fakePrimitive{ + allResultsInOneCall: true, + results: results, + } + + // Test two hash-based distinct counts on different columns + oa := &ScalarAggregate{ + Aggregates: []*AggregateParams{ + { + Opcode: AggregateCountDistinct, + Col: 0, + KeyCol: 0, + WCol: -1, + UseHashDistinct: true, + }, + { + Opcode: AggregateCountDistinct, + Col: 1, + KeyCol: 1, + WCol: -1, + UseHashDistinct: true, + }, + }, + Input: fp, + } + + result, err := oa.TryExecute(context.Background(), &noopVCursor{}, nil, true) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if len(result.Rows) != 1 { + t.Fatalf("expected 1 row, got %d", len(result.Rows)) + } + + // First column should have 3 distinct values + val1, err := result.Rows[0][0].ToInt64() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if val1 != 3 { + t.Errorf("expected first distinct count of 3, got %d", val1) + } + + // Second column should have 4 distinct values + val2, err := result.Rows[0][1].ToInt64() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if val2 != 4 { + t.Errorf("expected second distinct count of 4, got %d", val2) + } +} diff --git a/go/vt/vtgate/planbuilder/operator_transformers.go b/go/vt/vtgate/planbuilder/operator_transformers.go index 0366c984d6a..31fa8d899de 100644 --- a/go/vt/vtgate/planbuilder/operator_transformers.go +++ b/go/vt/vtgate/planbuilder/operator_transformers.go @@ -369,6 +369,8 @@ func transformAggregator(ctx *plancontext.PlanningContext, op *operators.Aggrega aggrParam.OrigOpcode = aggr.OriginalOpCode aggrParam.WCol = aggr.WSOffset aggrParam.Type = aggr.GetTypeCollation(ctx) + // Pass hash-based distinct flag from planner to engine + aggrParam.UseHashDistinct = aggr.UseHashDistinct || op.UseHashDistinct aggregates = append(aggregates, aggrParam) } diff --git a/go/vt/vtgate/planbuilder/operators/aggregation_pushing.go b/go/vt/vtgate/planbuilder/operators/aggregation_pushing.go index 1ca27be3bb7..470b52acc05 100644 --- a/go/vt/vtgate/planbuilder/operators/aggregation_pushing.go +++ b/go/vt/vtgate/planbuilder/operators/aggregation_pushing.go @@ -169,7 +169,14 @@ func pushAggregationThroughRoute( // pushAggregations splits aggregations between the original aggregator and the one we are pushing down func pushAggregations(ctx *plancontext.PlanningContext, aggregator *Aggregator, aggrBelowRoute *Aggregator) { - canPushDistinctAggr, distinctExprs := checkIfWeCanPush(ctx, aggregator) + canPushDistinctAggr, distinctExprs, hasMultipleDifferentDistinct := checkIfWeCanPush(ctx, aggregator) + + // If we have multiple distinct aggregations with different expressions and can't push them, + // we need to use hash-based distinct tracking + useHashDistinct := !canPushDistinctAggr && hasMultipleDifferentDistinct + if useHashDistinct { + aggregator.UseHashDistinct = true + } distinctAggrGroupByAdded := false @@ -181,35 +188,45 @@ func pushAggregations(ctx *plancontext.PlanningContext, aggregator *Aggregator, continue } - if len(distinctExprs) != 1 { + // Mark this aggregation for hash-based distinct if we're using that approach + if useHashDistinct { + aggregator.Aggregations[i].UseHashDistinct = true + } + + args := aggr.Func.GetArgs() + if len(args) != 1 { errDistinctAggrWithMultiExpr(aggr.Func) } - // We handle a distinct aggregation by turning it into a group by and - // doing the aggregating on the vtgate level instead - aeDistinctExpr := aeWrap(distinctExprs[0]) + // We handle a distinct aggregation by adding the distinct expression as a column + // For hash-based distinct, we don't need to add GROUP BY, just ensure the column is available + aeDistinctExpr := aeWrap(args[0]) aggrBelowRoute.Columns[aggr.ColOffset] = aeDistinctExpr - // We handle a distinct aggregation by turning it into a group by and - // doing the aggregating on the vtgate level instead - // Adding to group by can be done only once even though there are multiple distinct aggregation with same expression. - if !distinctAggrGroupByAdded { - groupBy := NewGroupBy(distinctExprs[0]) + // For sort-based distinct (single distinct expression), we add GROUP BY + // For hash-based distinct (multiple different expressions), we skip GROUP BY + if !useHashDistinct && !distinctAggrGroupByAdded { + groupBy := NewGroupBy(args[0]) groupBy.ColOffset = aggr.ColOffset aggrBelowRoute.Grouping = append(aggrBelowRoute.Grouping, groupBy) distinctAggrGroupByAdded = true } } - if !canPushDistinctAggr { + // For sort-based distinct, store the single distinct expression for ordering + if !canPushDistinctAggr && !useHashDistinct && len(distinctExprs) > 0 { aggregator.DistinctExpr = distinctExprs[0] } } -func checkIfWeCanPush(ctx *plancontext.PlanningContext, aggregator *Aggregator) (bool, []sqlparser.Expr) { - canPush := true - var distinctExprs []sqlparser.Expr - var differentExpr *sqlparser.AliasedExpr +// checkIfWeCanPush checks if distinct aggregations can be pushed down to MySQL. +// Returns: +// - canPush: true if all distinct aggregations have unique vindexes and can be pushed +// - distinctExprs: the expressions from the first distinct aggregation (used for single-distinct case) +// - hasMultipleDifferentDistinct: true if there are multiple distinct aggregations with different expressions +func checkIfWeCanPush(ctx *plancontext.PlanningContext, aggregator *Aggregator) (canPush bool, distinctExprs []sqlparser.Expr, hasMultipleDifferentDistinct bool) { + canPush = true + hasMultipleDifferentDistinct = false for _, aggr := range aggregator.Aggregations { if !aggr.Distinct { @@ -229,20 +246,21 @@ func checkIfWeCanPush(ctx *plancontext.PlanningContext, aggregator *Aggregator) } if len(distinctExprs) == 0 { distinctExprs = args - } - for idx, expr := range distinctExprs { - if !ctx.SemTable.EqualsExpr(expr, args[idx]) { - differentExpr = aggr.Original - break + } else { + // Check if this distinct aggregation has different expressions + for idx, expr := range distinctExprs { + if idx >= len(args) || !ctx.SemTable.EqualsExpr(expr, args[idx]) { + hasMultipleDifferentDistinct = true + break + } + } + if len(args) != len(distinctExprs) { + hasMultipleDifferentDistinct = true } } } - if !canPush && differentExpr != nil { - panic(vterrors.VT12001("only one DISTINCT aggregation is allowed in a SELECT: " + sqlparser.String(differentExpr))) - } - - return canPush, distinctExprs + return canPush, distinctExprs, hasMultipleDifferentDistinct } func pushAggregationThroughFilter( @@ -541,15 +559,25 @@ func splitAggrColumnsToLeftAndRight( outerJoin: leftJoin, } - canPushDistinctAggr, distinctExprs := checkIfWeCanPush(ctx, aggregator) + canPushDistinctAggr, distinctExprs, hasMultipleDifferentDistinct := checkIfWeCanPush(ctx, aggregator) // Distinctable aggregation cannot be pushed down in the join. // We keep node of the distinct aggregation expression to be used later for ordering. if !canPushDistinctAggr { - if len(distinctExprs) != 1 { + if hasMultipleDifferentDistinct { + // For multiple different distinct expressions, we use hash-based tracking + aggregator.UseHashDistinct = true + for i := range aggregator.Aggregations { + if aggregator.Aggregations[i].Distinct { + aggregator.Aggregations[i].UseHashDistinct = true + } + } + } else if len(distinctExprs) == 1 { + // For single distinct expression, use sort-based (existing behavior) + aggregator.DistinctExpr = distinctExprs[0] + } else { errDistinctAggrWithMultiExpr(nil) } - aggregator.DistinctExpr = distinctExprs[0] return nil, errAbortAggrPushing } diff --git a/go/vt/vtgate/planbuilder/operators/aggregator.go b/go/vt/vtgate/planbuilder/operators/aggregator.go index fe3e06d70e0..c1be24bcd3a 100644 --- a/go/vt/vtgate/planbuilder/operators/aggregator.go +++ b/go/vt/vtgate/planbuilder/operators/aggregator.go @@ -43,9 +43,16 @@ type ( // We support a single distinct aggregation per aggregator. It is stored here. // When planning the ordering that the OrderedAggregate will require, - // this needs to be the last ORDER BY expression + // this needs to be the last ORDER BY expression. + // Deprecated: Use UseHashDistinct for multiple distinct support. DistinctExpr sqlparser.Expr + // UseHashDistinct indicates that distinct aggregations should use hash-based + // tracking instead of sort-based. This is set when multiple distinct aggregations + // with different expressions exist in the query. When true, DistinctExpr is ignored + // and no sorting by distinct expressions is required. + UseHashDistinct bool + // Pushed will be set to true once this aggregation has been pushed deeper in the tree Pushed bool offsetPlanned bool diff --git a/go/vt/vtgate/planbuilder/operators/phases.go b/go/vt/vtgate/planbuilder/operators/phases.go index eb6c42b8724..8206406e4a5 100644 --- a/go/vt/vtgate/planbuilder/operators/phases.go +++ b/go/vt/vtgate/planbuilder/operators/phases.go @@ -241,7 +241,9 @@ func addOrderingFor(aggrOp *Aggregator) { orderBys := slice.Map(aggrOp.Grouping, func(from GroupBy) OrderBy { return from.AsOrderBy() }) - if aggrOp.DistinctExpr != nil { + // Only add ordering for DistinctExpr if we're using sort-based distinct tracking. + // Hash-based distinct (UseHashDistinct=true) doesn't require ordering by the distinct expression. + if aggrOp.DistinctExpr != nil && !aggrOp.UseHashDistinct { orderBys = append(orderBys, OrderBy{ Inner: &sqlparser.Order{ Expr: aggrOp.DistinctExpr, @@ -256,7 +258,9 @@ func needsOrdering(ctx *plancontext.PlanningContext, in *Aggregator) bool { requiredOrder := slice.Map(in.Grouping, func(from GroupBy) sqlparser.Expr { return from.Inner }) - if in.DistinctExpr != nil { + // Only require ordering for DistinctExpr if we're using sort-based distinct tracking. + // Hash-based distinct (UseHashDistinct=true) doesn't require ordering by the distinct expression. + if in.DistinctExpr != nil && !in.UseHashDistinct { requiredOrder = append(requiredOrder, in.DistinctExpr) } if len(requiredOrder) == 0 { diff --git a/go/vt/vtgate/planbuilder/operators/queryprojection.go b/go/vt/vtgate/planbuilder/operators/queryprojection.go index 6fe5f7f8641..6e0914d2474 100644 --- a/go/vt/vtgate/planbuilder/operators/queryprojection.go +++ b/go/vt/vtgate/planbuilder/operators/queryprojection.go @@ -79,6 +79,11 @@ type ( Distinct bool // Whether the aggregation function is DISTINCT + // UseHashDistinct indicates this aggregation should use hash-based distinct tracking + // instead of sort-based. Set when multiple distinct aggregations with different + // expressions exist in the query. + UseHashDistinct bool + // Offsets pointing to columns within the same aggregator ColOffset int // Offset for the column being aggregated WSOffset int // Offset for the weight string of the column diff --git a/go/vt/vtgate/planbuilder/testdata/unsupported_cases.json b/go/vt/vtgate/planbuilder/testdata/unsupported_cases.json index 4986e7897d6..43af88c4ecb 100644 --- a/go/vt/vtgate/planbuilder/testdata/unsupported_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/unsupported_cases.json @@ -314,11 +314,6 @@ "query": "select 1 from music union (select id from user union select name from unsharded)", "plan": "VT12001: unsupported: nesting of UNIONs on the right-hand side" }, - { - "comment": "Cannot have more than one aggr(distinct...", - "query": "select count(distinct a), count(distinct b) from user", - "plan": "VT12001: unsupported: only one DISTINCT aggregation is allowed in a SELECT: count(distinct b)" - }, { "comment": "subqueries not supported in the join condition of outer joins", "query": "select unsharded_a.col from unsharded_a left join unsharded_b on unsharded_a.col IN (select col from user)", @@ -369,11 +364,6 @@ "query": "select count(distinct user_id, name) from user", "plan": "VT12001: unsupported: distinct aggregation function with multiple expressions 'count(distinct user_id, `name`)'" }, - { - "comment": "count and sum distinct on different columns", - "query": "SELECT COUNT(DISTINCT col), SUM(DISTINCT id) FROM user", - "plan": "VT12001: unsupported: only one DISTINCT aggregation is allowed in a SELECT: sum(distinct id)" - }, { "comment": "Over clause isn't supported in sharded cases", "query": "SELECT val, CUME_DIST() OVER w, ROW_NUMBER() OVER w, DENSE_RANK() OVER w, PERCENT_RANK() OVER w, RANK() OVER w AS 'cd' FROM user",