planner: fix max-one-row will hash equals to each other because of no elements inside. (#57797)

ref pingcap/tidb#51664
This commit is contained in:
Arenatlx
2024-11-29 14:00:05 +08:00
committed by GitHub
parent cbf34b01b5
commit f6ff4126dd
7 changed files with 76 additions and 10 deletions

View File

@ -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":

View File

@ -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
}

View File

@ -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)

View File

@ -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.

View File

@ -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

View File

@ -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",

View File

@ -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))
}