Skip to content

Commit

Permalink
planner: integrate hashEqual interface into LogicalPlan and expressio…
Browse files Browse the repository at this point in the history
…n.Expression. (#55652)

ref #51664
  • Loading branch information
AilinKid authored Aug 27, 2024
1 parent 290dc46 commit 4f85a35
Show file tree
Hide file tree
Showing 11 changed files with 219 additions and 7 deletions.
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
}
79 changes: 79 additions & 0 deletions pkg/planner/cascades/base/base_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// 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

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) 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 {
// 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

0 comments on commit 4f85a35

Please sign in to comment.