Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

planner: integrate hashEqual interface into LogicalPlan and expression.Expression. #55652

Merged
merged 20 commits into from
Aug 27, 2024
4 changes: 1 addition & 3 deletions pkg/planner/cascades/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ go_library(
name = "cascades",
srcs = [
"enforcer_rules.go",
"hash_equaler.go",
"implementation_rules.go",
"optimize.go",
"stringer.go",
Expand Down Expand Up @@ -42,7 +41,6 @@ go_test(
timeout = "short",
srcs = [
"enforcer_rules_test.go",
"hash_equaler_test.go",
"main_test.go",
"optimize_test.go",
"stringer_test.go",
Expand All @@ -51,7 +49,7 @@ go_test(
data = glob(["testdata/**"]),
embed = [":cascades"],
flaky = True,
shard_count = 28,
shard_count = 25,
deps = [
"//pkg/domain",
"//pkg/expression",
Expand Down
24 changes: 24 additions & 0 deletions pkg/planner/cascades/base/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")

go_library(
name = "base",
srcs = [
"base.go",
"hash_equaler.go",
],
importpath = "github.com/pingcap/tidb/pkg/planner/cascades/base",
visibility = ["//visibility:public"],
)

go_test(
name = "base_test",
timeout = "short",
srcs = [
"base_test.go",
"hash_equaler_test.go",
],
embed = [":base"],
flaky = True,
shard_count = 3,
deps = ["@com_github_stretchr_testify//require"],
)
37 changes: 37 additions & 0 deletions pkg/planner/cascades/base/base.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2024 PingCAP, Inc.
//
// 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 base

// Hash64 is the interface for hashcode.
// It is used to calculate the lossy digest of an object to return uint64
// rather than compacted bytes from cascaded operators bottom-up.
type Hash64 interface {
// Hash64 returns the uint64 digest of an object.
Hash64(h Hasher)
}

// Equals is the interface for equality check.
// When we need to compare two objects when countering hash conflicts, we can
// use this interface to check whether they are equal.
type Equals interface {
// Equals checks whether two base objects are equal.
Equals(any) bool
}

// HashEquals is the interface for hash64 and equality check.
type HashEquals interface {
Hash64
Equals
}
87 changes: 87 additions & 0 deletions pkg/planner/cascades/base/base_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright 2024 PingCAP, Inc.
AilinKid marked this conversation as resolved.
Show resolved Hide resolved
//
// 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 base

import (
"testing"
)

type testcase struct {
a int
b int
c string
}

type TestEquals[T any] interface {
EqualsT(other T) bool
}

type TestEqualAny interface {
EqualsAny(other any) bool
}

type TestSuperEquals interface {
TestEquals[TestSuperEquals]
}

func (tc *testcase) EqualsT(other *testcase) bool {
return tc.a == other.a && tc.b == other.b && tc.c == other.c
}

//func (tc *testcase) EqualsT(other TestSuperEquals) bool {
// tc1, ok := other.(*testcase)
// if !ok {
// return false
// }
// return tc.a == tc1.a && tc.b == tc1.b && tc.c == tc1.c
//}

func (tc *testcase) EqualsAny(other any) bool {
tc1, ok := other.(*testcase)
if !ok {
return false
}
return tc.a == tc1.a && tc.b == tc1.b && tc.c == tc1.c
}

// goos: linux
// goarch: amd64
// pkg: github.com/pingcap/tidb/pkg/planner/cascades/memo
// cpu: 12th Gen Intel(R) Core(TM) i7-12700KF
// BenchmarkEqualsT
// BenchmarkEqualsT-20 1000000000 0.8981 ns/op
func BenchmarkEqualsT(b *testing.B) {
tc1 := &testcase{a: 1, b: 2, c: "3"}
var tc2 TestEquals[*testcase] = &testcase{a: 1, b: 2, c: "3"}
for i := 0; i < b.N; i++ {
if tc3, ok := tc2.(*testcase); ok {
tc1.EqualsT(tc3)
}
}
}

// goos: linux
// goarch: amd64
// pkg: github.com/pingcap/tidb/pkg/planner/cascades/memo
// cpu: 12th Gen Intel(R) Core(TM) i7-12700KF
// BenchmarkEqualsAny
// BenchmarkEqualsAny-20 1000000000 0.4837 ns/op
func BenchmarkEqualsAny(b *testing.B) {
tc1 := &testcase{a: 1, b: 2, c: "3"}
var tc2 TestEqualAny = &testcase{a: 1, b: 2, c: "3"}
for i := 0; i < b.N; i++ {
tc1.EqualsAny(tc2)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package cascades
package base

import (
"math"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package cascades
package base

import (
"testing"
Expand Down
1 change: 1 addition & 0 deletions pkg/planner/core/base/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ go_library(
deps = [
"//pkg/expression",
"//pkg/kv",
"//pkg/planner/cascades/base",
"//pkg/planner/context",
"//pkg/planner/funcdep",
"//pkg/planner/property",
Expand Down
2 changes: 2 additions & 0 deletions pkg/planner/core/base/plan_base.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (

"github.com/pingcap/tidb/pkg/expression"
"github.com/pingcap/tidb/pkg/kv"
"github.com/pingcap/tidb/pkg/planner/cascades/base"
"github.com/pingcap/tidb/pkg/planner/context"
fd "github.com/pingcap/tidb/pkg/planner/funcdep"
"github.com/pingcap/tidb/pkg/planner/property"
Expand Down Expand Up @@ -192,6 +193,7 @@ func (c *PlanCounterTp) IsForce() bool {
// We can do a lot of logical optimizations to it, like predicate push-down and column pruning.
type LogicalPlan interface {
Plan
base.HashEquals

// HashCode encodes a LogicalPlan to fast compare whether a LogicalPlan equals to another.
// We use a strict encode method here which ensures there is no conflict.
Expand Down
2 changes: 2 additions & 0 deletions pkg/planner/core/operator/logicalop/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ go_library(
"//pkg/parser/model",
"//pkg/parser/mysql",
"//pkg/planner/cardinality",
"//pkg/planner/cascades/base",
"//pkg/planner/core/base",
"//pkg/planner/core/constraint",
"//pkg/planner/core/cost",
Expand All @@ -58,6 +59,7 @@ go_library(
"//pkg/types",
"//pkg/util/dbterror/plannererrors",
"//pkg/util/hint",
"//pkg/util/intest",
"//pkg/util/intset",
"//pkg/util/plancodec",
"//pkg/util/size",
Expand Down
23 changes: 23 additions & 0 deletions pkg/planner/core/operator/logicalop/base_logical_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package logicalop
import (
"github.com/pingcap/tidb/pkg/expression"
"github.com/pingcap/tidb/pkg/kv"
base2 "github.com/pingcap/tidb/pkg/planner/cascades/base"
"github.com/pingcap/tidb/pkg/planner/core/base"
"github.com/pingcap/tidb/pkg/planner/core/operator/baseimpl"
fd "github.com/pingcap/tidb/pkg/planner/funcdep"
Expand All @@ -26,6 +27,7 @@ import (
"github.com/pingcap/tidb/pkg/planner/util/utilfuncp"
"github.com/pingcap/tidb/pkg/types"
"github.com/pingcap/tidb/pkg/util/dbterror/plannererrors"
"github.com/pingcap/tidb/pkg/util/intest"
"github.com/pingcap/tidb/pkg/util/tracing"
)

Expand All @@ -50,6 +52,27 @@ type BaseLogicalPlan struct {
fdSet *fd.FDSet
}

// *************************** implementation of HashEquals interface ***************************

// Hash64 implements HashEquals.<0th> interface.
func (p *BaseLogicalPlan) Hash64(h base2.Hasher) {
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")
if other == nil {
return false
}
olp, ok := other.(*BaseLogicalPlan)
if !ok {
return false
}
return p.ID() == olp.ID()
}

// *************************** implementation of base Plan interface ***************************

// ExplainInfo implements Plan interface.
Expand Down
50 changes: 48 additions & 2 deletions pkg/planner/core/operator/logicalop/logical_projection.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/pingcap/tidb/pkg/expression"
"github.com/pingcap/tidb/pkg/parser/mysql"
"github.com/pingcap/tidb/pkg/planner/cardinality"
base2 "github.com/pingcap/tidb/pkg/planner/cascades/base"
"github.com/pingcap/tidb/pkg/planner/core/base"
ruleutil "github.com/pingcap/tidb/pkg/planner/core/rule/util"
fd "github.com/pingcap/tidb/pkg/planner/funcdep"
Expand Down Expand Up @@ -55,7 +56,52 @@ func (p LogicalProjection) Init(ctx base.PlanContext, qbOffset int) *LogicalProj
return &p
}

// *************************** start implementation of Plan interface ***************************
// *************************** start implementation of HashEquals interface ****************************

// Hash64 implements the base.Hash64.<0th> interface.
func (p *LogicalProjection) Hash64(h base2.Hasher) {
// todo: LogicalSchemaProducer should implement HashEquals interface, otherwise, its self elements
// like schema and names are lost.
p.LogicalSchemaProducer.Hash64(h)
// todo: if we change the logicalProjection's Expr definition as:Exprs []memo.ScalarOperator[any],
// we should use like below:
// for _, one := range p.Exprs {
// one.Hash64(one)
// }
// otherwise, we would use the belowing code.
//for _, one := range p.Exprs {
// one.Hash64(h)
//}
h.HashBool(p.CalculateNoDelay)
h.HashBool(p.Proj4Expand)
}

// Equals implements the base.HashEquals.<1st> interface.
func (p *LogicalProjection) Equals(other any) bool {
if other == nil {
return false
}
proj, ok := other.(*LogicalProjection)
if !ok {
return false
}
// todo: LogicalSchemaProducer should implement HashEquals interface, otherwise, its self elements
// like schema and names are lost.
if !p.LogicalSchemaProducer.Equals(&proj.LogicalSchemaProducer) {
return false
}
//for i, one := range p.Exprs {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, next pull request will introduce hashEqual to expression.Expression, which will enable the comment line here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

// if !one.(memo.ScalarOperator[any]).Equals(other.Exprs[i]) {
// return false
// }
//}
if p.CalculateNoDelay != proj.CalculateNoDelay {
return false
}
return p.Proj4Expand == proj.Proj4Expand
}

// *************************** start implementation of Plan interface **********************************

// ExplainInfo implements Plan interface.
func (p *LogicalProjection) ExplainInfo() string {
Expand All @@ -71,7 +117,7 @@ func (p *LogicalProjection) ReplaceExprColumns(replace map[string]*expression.Co
}
}

// *************************** end implementation of Plan interface ***************************
// *************************** end implementation of Plan interface ************************************

// *************************** start implementation of logicalPlan interface ***************************

Expand Down