diff --git a/pkg/planner/core/generator/hash64_equals/hash64_equals_generator.go b/pkg/planner/core/generator/hash64_equals/hash64_equals_generator.go index b3d22ec531..824ad9ba44 100644 --- a/pkg/planner/core/generator/hash64_equals/hash64_equals_generator.go +++ b/pkg/planner/core/generator/hash64_equals/hash64_equals_generator.go @@ -39,8 +39,8 @@ func GenHash64Equals4LogicalOps() ([]byte, error) { var structures = []any{logicalop.LogicalJoin{}, logicalop.LogicalAggregation{}, logicalop.LogicalApply{}, logicalop.LogicalExpand{}, logicalop.LogicalLimit{}, logicalop.LogicalMaxOneRow{}, logicalop.DataSource{}, logicalop.LogicalMemTable{}, logicalop.LogicalUnionAll{}, logicalop.LogicalPartitionUnionAll{}, logicalop.LogicalProjection{}, - logicalop.LogicalSelection{}, logicalop.LogicalShow{}, logicalop.LogicalShowDDLJobs{}, logicalop.LogicalSort{}, - logicalop.LogicalTableDual{}, logicalop.LogicalTopN{}, logicalop.LogicalUnionScan{}, logicalop.LogicalWindow{}, + logicalop.LogicalSelection{}, logicalop.LogicalSequence{}, logicalop.LogicalShow{}, logicalop.LogicalShowDDLJobs{}, + logicalop.LogicalSort{}, logicalop.LogicalTableDual{}, logicalop.LogicalTopN{}, logicalop.LogicalUnionScan{}, logicalop.LogicalWindow{}, } c := new(cc) c.write(codeGenHash64EqualsPrefix) @@ -139,6 +139,8 @@ func logicalOpName2PlanCodecString(name string) string { return "plancodec.TypeProj" case "LogicalSelection": return "plancodec.TypeSel" + case "LogicalSequence": + return "plancodec.TypeSequence" case "LogicalShow": return "plancodec.TypeShow" case "LogicalShowDDLJobs": diff --git a/pkg/planner/core/operator/logicalop/base_logical_plan.go b/pkg/planner/core/operator/logicalop/base_logical_plan.go index 4e533b4f0e..2c67390667 100644 --- a/pkg/planner/core/operator/logicalop/base_logical_plan.go +++ b/pkg/planner/core/operator/logicalop/base_logical_plan.go @@ -56,13 +56,21 @@ type BaseLogicalPlan struct { // Hash64 implements HashEquals.<0th> interface. func (p *BaseLogicalPlan) Hash64(h base2.Hasher) { - intest.Assert(false, "Hash64 should not be called directly") + _, ok1 := p.self.(*LogicalSequence) + _, ok2 := p.self.(*LogicalMaxOneRow) + if !ok1 && !ok2 { + intest.Assert(false, "Hash64 should not be called directly") + } h.HashInt(p.ID()) } // Equals implements HashEquals.<1st> interface. func (p *BaseLogicalPlan) Equals(other any) bool { - intest.Assert(false, "Equals should not be called directly") + _, ok1 := p.self.(*LogicalSequence) + _, ok2 := p.self.(*LogicalMaxOneRow) + if !ok1 && !ok2 { + intest.Assert(false, "Equals should not be called directly") + } if other == nil { return false } diff --git a/pkg/planner/core/operator/logicalop/hash64_equals_generated.go b/pkg/planner/core/operator/logicalop/hash64_equals_generated.go index 99444d0450..74056cc19b 100644 --- a/pkg/planner/core/operator/logicalop/hash64_equals_generated.go +++ b/pkg/planner/core/operator/logicalop/hash64_equals_generated.go @@ -452,6 +452,7 @@ func (op *LogicalLimit) Equals(other any) bool { // Hash64 implements the Hash64Equals interface. func (op *LogicalMaxOneRow) Hash64(h base.Hasher) { h.HashString(plancodec.TypeMaxOneRow) + op.BaseLogicalPlan.Hash64(h) } // Equals implements the Hash64Equals interface, only receive *LogicalMaxOneRow pointer. @@ -466,7 +467,9 @@ func (op *LogicalMaxOneRow) Equals(other any) bool { if op2 == nil { return false } - _ = op2 + if !op.BaseLogicalPlan.Equals(&op2.BaseLogicalPlan) { + return false + } return true } @@ -725,6 +728,30 @@ func (op *LogicalSelection) Equals(other any) bool { return true } +// Hash64 implements the Hash64Equals interface. +func (op *LogicalSequence) Hash64(h base.Hasher) { + h.HashString(plancodec.TypeSequence) + op.BaseLogicalPlan.Hash64(h) +} + +// Equals implements the Hash64Equals interface, only receive *LogicalSequence pointer. +func (op *LogicalSequence) Equals(other any) bool { + op2, ok := other.(*LogicalSequence) + if !ok { + return false + } + if op == nil { + return op2 == nil + } + if op2 == nil { + return false + } + if !op.BaseLogicalPlan.Equals(&op2.BaseLogicalPlan) { + return false + } + return true +} + // Hash64 implements the Hash64Equals interface. func (op *LogicalShow) Hash64(h base.Hasher) { h.HashString(plancodec.TypeShow) diff --git a/pkg/planner/core/operator/logicalop/logical_max_one_row.go b/pkg/planner/core/operator/logicalop/logical_max_one_row.go index 200a8ac517..686d461da0 100644 --- a/pkg/planner/core/operator/logicalop/logical_max_one_row.go +++ b/pkg/planner/core/operator/logicalop/logical_max_one_row.go @@ -26,7 +26,8 @@ import ( // LogicalMaxOneRow checks if a query returns no more than one row. type LogicalMaxOneRow struct { - BaseLogicalPlan + // logical max one row, doesn't have any other attribute to distinguish, use plan id inside. + BaseLogicalPlan `hash64-equals:"true"` } // Init initializes LogicalMaxOneRow. diff --git a/pkg/planner/core/operator/logicalop/logical_sequence.go b/pkg/planner/core/operator/logicalop/logical_sequence.go index 4ea69496b9..9effb8712e 100644 --- a/pkg/planner/core/operator/logicalop/logical_sequence.go +++ b/pkg/planner/core/operator/logicalop/logical_sequence.go @@ -34,7 +34,8 @@ import ( // // We use this property to do complex optimizations for CTEs. type LogicalSequence struct { - BaseLogicalPlan + // logical sequence doesn't have any other attribute to distinguish, use plan id inside. + BaseLogicalPlan `hash64-equals:"true"` } // Init initializes LogicalSequence diff --git a/pkg/planner/core/operator/logicalop/logicalop_test/BUILD.bazel b/pkg/planner/core/operator/logicalop/logicalop_test/BUILD.bazel index e2d506019d..d2ffdc9066 100644 --- a/pkg/planner/core/operator/logicalop/logicalop_test/BUILD.bazel +++ b/pkg/planner/core/operator/logicalop/logicalop_test/BUILD.bazel @@ -8,7 +8,7 @@ go_test( "logical_mem_table_predicate_extractor_test.go", ], flaky = True, - shard_count = 27, + shard_count = 28, deps = [ "//pkg/domain", "//pkg/expression", diff --git a/pkg/planner/core/operator/logicalop/logicalop_test/hash64_equals_test.go b/pkg/planner/core/operator/logicalop/logicalop_test/hash64_equals_test.go index 8e56fd9bfb..dfd4e81244 100644 --- a/pkg/planner/core/operator/logicalop/logicalop_test/hash64_equals_test.go +++ b/pkg/planner/core/operator/logicalop/logicalop_test/hash64_equals_test.go @@ -33,6 +33,26 @@ import ( "github.com/stretchr/testify/require" ) +func TestLogicalSequence(t *testing.T) { + ctx := mock.NewContext() + m1 := logicalop.LogicalSequence{}.Init(ctx, 1) + m2 := logicalop.LogicalSequence{}.Init(ctx, 1) + // since logical max one row doesn't have any elements, they are always indicate + // that they are equal. + hasher1 := base.NewHashEqualer() + hasher2 := base.NewHashEqualer() + m1.Hash64(hasher1) + m2.Hash64(hasher2) + require.NotEqual(t, hasher1.Sum64(), hasher2.Sum64()) + require.False(t, m1.Equals(m2)) + + m2.SetID(m1.ID()) + hasher2.Reset() + m2.Hash64(hasher2) + require.Equal(t, hasher1.Sum64(), hasher2.Sum64()) + require.True(t, m1.Equals(m2)) +} + func TestLogicalSelectionHash64Equals(t *testing.T) { col1 := &expression.Column{ ID: 1, @@ -278,14 +298,21 @@ func TestLogicalSchemaProducerHash64Equals(t *testing.T) { } func TestLogicalMaxOneRowHash64Equals(t *testing.T) { - m1 := &logicalop.LogicalMaxOneRow{} - m2 := &logicalop.LogicalMaxOneRow{} + ctx := mock.NewContext() + m1 := logicalop.LogicalMaxOneRow{}.Init(ctx, 1) + m2 := logicalop.LogicalMaxOneRow{}.Init(ctx, 1) // since logical max one row doesn't have any elements, they are always indicate // that they are equal. hasher1 := base.NewHashEqualer() hasher2 := base.NewHashEqualer() m1.Hash64(hasher1) m2.Hash64(hasher2) + require.NotEqual(t, hasher1.Sum64(), hasher2.Sum64()) + require.False(t, m1.Equals(m2)) + + m2.SetID(m1.ID()) + hasher2.Reset() + m2.Hash64(hasher2) require.Equal(t, hasher1.Sum64(), hasher2.Sum64()) require.True(t, m1.Equals(m2)) }