github.com/whtcorpsinc/MilevaDB-Prod@v0.0.0-20211104133533-f57f4be3b597/causetstore/petri/acyclic/causet/embedded/planbuilder_test.go (about)

     1  // Copyright 2020 WHTCORPS INC, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package embedded
    15  
    16  import (
    17  	"context"
    18  	"fmt"
    19  	"reflect"
    20  	"strings"
    21  	"unsafe"
    22  	_ "unsafe" // required by go:linkname
    23  
    24  	"github.com/whtcorpsinc/BerolinaSQL"
    25  	"github.com/whtcorpsinc/BerolinaSQL/allegrosql"
    26  	"github.com/whtcorpsinc/BerolinaSQL/ast"
    27  	"github.com/whtcorpsinc/BerolinaSQL/perceptron"
    28  	. "github.com/whtcorpsinc/check"
    29  	"github.com/whtcorpsinc/errors"
    30  	"github.com/whtcorpsinc/milevadb/causet/property"
    31  	"github.com/whtcorpsinc/milevadb/causet/soliton"
    32  	"github.com/whtcorpsinc/milevadb/ekv"
    33  	"github.com/whtcorpsinc/milevadb/memex"
    34  	"github.com/whtcorpsinc/milevadb/memex/aggregation"
    35  	"github.com/whtcorpsinc/milevadb/soliton/chunk"
    36  	"github.com/whtcorpsinc/milevadb/soliton/hint"
    37  	"github.com/whtcorpsinc/milevadb/soliton/mock"
    38  	"github.com/whtcorpsinc/milevadb/statistics"
    39  	"github.com/whtcorpsinc/milevadb/types"
    40  )
    41  
    42  var _ = Suite(&testCausetBuilderSuite{})
    43  
    44  func (s *testCausetBuilderSuite) SetUpSuite(c *C) {
    45  }
    46  
    47  type testCausetBuilderSuite struct {
    48  }
    49  
    50  func (s *testCausetBuilderSuite) TestShow(c *C) {
    51  	node := &ast.ShowStmt{}
    52  	tps := []ast.ShowStmtType{
    53  		ast.ShowEngines,
    54  		ast.ShowDatabases,
    55  		ast.ShowBlocks,
    56  		ast.ShowBlockStatus,
    57  		ast.ShowDeferredCausets,
    58  		ast.ShowWarnings,
    59  		ast.ShowCharset,
    60  		ast.ShowVariables,
    61  		ast.ShowStatus,
    62  		ast.ShowDefCauslation,
    63  		ast.ShowCreateBlock,
    64  		ast.ShowCreateUser,
    65  		ast.ShowGrants,
    66  		ast.ShowTriggers,
    67  		ast.ShowProcedureStatus,
    68  		ast.ShowIndex,
    69  		ast.ShowProcessList,
    70  		ast.ShowCreateDatabase,
    71  		ast.ShowEvents,
    72  		ast.ShowMasterStatus,
    73  		ast.ShowBackups,
    74  		ast.ShowRestores,
    75  	}
    76  	for _, tp := range tps {
    77  		node.Tp = tp
    78  		schemaReplicant, _ := buildShowSchema(node, false, false)
    79  		for _, col := range schemaReplicant.DeferredCausets {
    80  			c.Assert(col.RetType.Flen, Greater, 0)
    81  		}
    82  	}
    83  }
    84  
    85  func (s *testCausetBuilderSuite) TestGetPathByIndexName(c *C) {
    86  	tblInfo := &perceptron.BlockInfo{
    87  		Indices:    make([]*perceptron.IndexInfo, 0),
    88  		PKIsHandle: true,
    89  	}
    90  
    91  	accessPath := []*soliton.AccessPath{
    92  		{IsIntHandlePath: true},
    93  		{Index: &perceptron.IndexInfo{Name: perceptron.NewCIStr("idx")}},
    94  	}
    95  
    96  	path := getPathByIndexName(accessPath, perceptron.NewCIStr("idx"), tblInfo)
    97  	c.Assert(path, NotNil)
    98  	c.Assert(path, Equals, accessPath[1])
    99  
   100  	path = getPathByIndexName(accessPath, perceptron.NewCIStr("primary"), tblInfo)
   101  	c.Assert(path, NotNil)
   102  	c.Assert(path, Equals, accessPath[0])
   103  
   104  	path = getPathByIndexName(accessPath, perceptron.NewCIStr("not exists"), tblInfo)
   105  	c.Assert(path, IsNil)
   106  
   107  	tblInfo = &perceptron.BlockInfo{
   108  		Indices:    make([]*perceptron.IndexInfo, 0),
   109  		PKIsHandle: false,
   110  	}
   111  
   112  	path = getPathByIndexName(accessPath, perceptron.NewCIStr("primary"), tblInfo)
   113  	c.Assert(path, IsNil)
   114  }
   115  
   116  func (s *testCausetBuilderSuite) TestRewriterPool(c *C) {
   117  	builder := NewCausetBuilder(MockContext(), nil, &hint.BlockHintProcessor{})
   118  
   119  	// Make sure CausetBuilder.getExpressionRewriter() provides clean rewriter from pool.
   120  	// First, pick one rewriter from the pool and make it dirty.
   121  	builder.rewriterCounter++
   122  	dirtyRewriter := builder.getExpressionRewriter(context.TODO(), nil)
   123  	dirtyRewriter.asScalar = true
   124  	dirtyRewriter.aggrMap = make(map[*ast.AggregateFuncExpr]int)
   125  	dirtyRewriter.preprocess = func(ast.Node) ast.Node { return nil }
   126  	dirtyRewriter.insertCauset = &Insert{}
   127  	dirtyRewriter.disableFoldCounter = 1
   128  	dirtyRewriter.ctxStack = make([]memex.Expression, 2)
   129  	dirtyRewriter.ctxNameStk = make([]*types.FieldName, 2)
   130  	builder.rewriterCounter--
   131  	// Then, pick again and check if it's cleaned up.
   132  	builder.rewriterCounter++
   133  	cleanRewriter := builder.getExpressionRewriter(context.TODO(), nil)
   134  	c.Assert(cleanRewriter, Equals, dirtyRewriter) // Rewriter should be reused.
   135  	c.Assert(cleanRewriter.asScalar, Equals, false)
   136  	c.Assert(cleanRewriter.aggrMap, IsNil)
   137  	c.Assert(cleanRewriter.preprocess, IsNil)
   138  	c.Assert(cleanRewriter.insertCauset, IsNil)
   139  	c.Assert(cleanRewriter.disableFoldCounter, Equals, 0)
   140  	c.Assert(len(cleanRewriter.ctxStack), Equals, 0)
   141  	builder.rewriterCounter--
   142  }
   143  
   144  func (s *testCausetBuilderSuite) TestDisableFold(c *C) {
   145  	// Functions like BENCHMARK() shall not be folded into result 0,
   146  	// but normal outer function with constant args should be folded.
   147  	// Types of memex and first layer of args will be validated.
   148  	cases := []struct {
   149  		ALLEGROALLEGROSQL string
   150  		Expected          memex.Expression
   151  		Args              []memex.Expression
   152  	}{
   153  		{`select sin(length("abc"))`, &memex.Constant{}, nil},
   154  		{`select benchmark(3, sin(123))`, &memex.ScalarFunction{}, []memex.Expression{
   155  			&memex.Constant{},
   156  			&memex.ScalarFunction{},
   157  		}},
   158  		{`select pow(length("abc"), benchmark(3, sin(123)))`, &memex.ScalarFunction{}, []memex.Expression{
   159  			&memex.Constant{},
   160  			&memex.ScalarFunction{},
   161  		}},
   162  	}
   163  
   164  	ctx := MockContext()
   165  	for _, t := range cases {
   166  		st, err := BerolinaSQL.New().ParseOneStmt(t.ALLEGROALLEGROSQL, "", "")
   167  		c.Assert(err, IsNil)
   168  		stmt := st.(*ast.SelectStmt)
   169  		expr := stmt.Fields.Fields[0].Expr
   170  
   171  		builder := NewCausetBuilder(ctx, nil, &hint.BlockHintProcessor{})
   172  		builder.rewriterCounter++
   173  		rewriter := builder.getExpressionRewriter(context.TODO(), nil)
   174  		c.Assert(rewriter, NotNil)
   175  		c.Assert(rewriter.disableFoldCounter, Equals, 0)
   176  		rewritenExpression, _, err := builder.rewriteExprNode(rewriter, expr, true)
   177  		c.Assert(err, IsNil)
   178  		c.Assert(rewriter.disableFoldCounter, Equals, 0) // Make sure the counter is reduced to 0 in the end.
   179  		builder.rewriterCounter--
   180  
   181  		c.Assert(rewritenExpression, FitsTypeOf, t.Expected)
   182  		for i, expectedArg := range t.Args {
   183  			rewritenArg := memex.GetFuncArg(rewritenExpression, i)
   184  			c.Assert(rewritenArg, FitsTypeOf, expectedArg)
   185  		}
   186  	}
   187  }
   188  
   189  func (s *testCausetBuilderSuite) TestDeepClone(c *C) {
   190  	tp := types.NewFieldType(allegrosql.TypeLonglong)
   191  	expr := &memex.DeferredCauset{RetType: tp}
   192  	byItems := []*soliton.ByItems{{Expr: expr}}
   193  	sort1 := &PhysicalSort{ByItems: byItems}
   194  	sort2 := &PhysicalSort{ByItems: byItems}
   195  	checkDeepClone := func(p1, p2 PhysicalCauset) error {
   196  		whiteList := []string{"*property.StatsInfo", "*stochastikctx.Context", "*mock.Context"}
   197  		return checkDeepClonedCore(reflect.ValueOf(p1), reflect.ValueOf(p2), typeName(reflect.TypeOf(p1)), whiteList, nil)
   198  	}
   199  	c.Assert(checkDeepClone(sort1, sort2), ErrorMatches, "invalid slice pointers, path PhysicalSort.ByItems")
   200  
   201  	byItems2 := []*soliton.ByItems{{Expr: expr}}
   202  	sort2.ByItems = byItems2
   203  	c.Assert(checkDeepClone(sort1, sort2), ErrorMatches, "same pointer, path PhysicalSort.ByItems.*Expression")
   204  
   205  	expr2 := &memex.DeferredCauset{RetType: tp}
   206  	byItems2[0].Expr = expr2
   207  	c.Assert(checkDeepClone(sort1, sort2), ErrorMatches, "same pointer, path PhysicalSort.ByItems.*Expression.FieldType")
   208  
   209  	expr2.RetType = types.NewFieldType(allegrosql.TypeString)
   210  	c.Assert(checkDeepClone(sort1, sort2), ErrorMatches, "different values, path PhysicalSort.ByItems.*Expression.FieldType.uint8")
   211  
   212  	expr2.RetType = types.NewFieldType(allegrosql.TypeLonglong)
   213  	c.Assert(checkDeepClone(sort1, sort2), IsNil)
   214  }
   215  
   216  func (s *testCausetBuilderSuite) TestPhysicalCausetClone(c *C) {
   217  	ctx := mock.NewContext()
   218  	col, cst := &memex.DeferredCauset{RetType: types.NewFieldType(allegrosql.TypeString)}, &memex.Constant{RetType: types.NewFieldType(allegrosql.TypeLonglong)}
   219  	stats := &property.StatsInfo{RowCount: 1000}
   220  	schemaReplicant := memex.NewSchema(col)
   221  	tblInfo := &perceptron.BlockInfo{}
   222  	idxInfo := &perceptron.IndexInfo{}
   223  	hist := &statistics.Histogram{Bounds: chunk.New(nil, 0, 0)}
   224  	aggDesc1, err := aggregation.NewAggFuncDesc(ctx, ast.AggFuncAvg, []memex.Expression{col}, false)
   225  	c.Assert(err, IsNil)
   226  	aggDesc2, err := aggregation.NewAggFuncDesc(ctx, ast.AggFuncCount, []memex.Expression{cst}, true)
   227  	c.Assert(err, IsNil)
   228  	aggDescs := []*aggregation.AggFuncDesc{aggDesc1, aggDesc2}
   229  
   230  	// causet scan
   231  	blockScan := &PhysicalBlockScan{
   232  		AccessCondition: []memex.Expression{col, cst},
   233  		Block:           tblInfo,
   234  		PkDefCauss:      []*memex.DeferredCauset{col},
   235  		Hist:            hist,
   236  	}
   237  	blockScan = blockScan.Init(ctx, 0)
   238  	blockScan.SetSchema(schemaReplicant)
   239  	c.Assert(checkPhysicalCausetClone(blockScan), IsNil)
   240  
   241  	// causet reader
   242  	blockReader := &PhysicalBlockReader{
   243  		blockCauset:  blockScan,
   244  		BlockCausets: []PhysicalCauset{blockScan},
   245  		StoreType:    ekv.TiFlash,
   246  	}
   247  	blockReader = blockReader.Init(ctx, 0)
   248  	c.Assert(checkPhysicalCausetClone(blockReader), IsNil)
   249  
   250  	// index scan
   251  	indexScan := &PhysicalIndexScan{
   252  		AccessCondition:  []memex.Expression{col, cst},
   253  		Block:            tblInfo,
   254  		Index:            idxInfo,
   255  		Hist:             hist,
   256  		dataSourceSchema: schemaReplicant,
   257  	}
   258  	indexScan = indexScan.Init(ctx, 0)
   259  	indexScan.SetSchema(schemaReplicant)
   260  	c.Assert(checkPhysicalCausetClone(indexScan), IsNil)
   261  
   262  	// index reader
   263  	indexReader := &PhysicalIndexReader{
   264  		indexCauset:           indexScan,
   265  		IndexCausets:          []PhysicalCauset{indexScan},
   266  		OutputDeferredCausets: []*memex.DeferredCauset{col, col},
   267  	}
   268  	indexReader = indexReader.Init(ctx, 0)
   269  	c.Assert(checkPhysicalCausetClone(indexReader), IsNil)
   270  
   271  	// index lookup
   272  	indexLookup := &PhysicalIndexLookUpReader{
   273  		IndexCausets:       []PhysicalCauset{indexReader},
   274  		indexCauset:        indexScan,
   275  		BlockCausets:       []PhysicalCauset{blockReader},
   276  		blockCauset:        blockScan,
   277  		ExtraHandleDefCaus: col,
   278  		PushedLimit:        &PushedDownLimit{1, 2},
   279  	}
   280  	indexLookup = indexLookup.Init(ctx, 0)
   281  	c.Assert(checkPhysicalCausetClone(indexLookup), IsNil)
   282  
   283  	// selection
   284  	sel := &PhysicalSelection{Conditions: []memex.Expression{col, cst}}
   285  	sel = sel.Init(ctx, stats, 0)
   286  	c.Assert(checkPhysicalCausetClone(sel), IsNil)
   287  
   288  	// projection
   289  	proj := &PhysicalProjection{Exprs: []memex.Expression{col, cst}}
   290  	proj = proj.Init(ctx, stats, 0)
   291  	c.Assert(checkPhysicalCausetClone(proj), IsNil)
   292  
   293  	// limit
   294  	lim := &PhysicalLimit{Count: 1, Offset: 2}
   295  	lim = lim.Init(ctx, stats, 0)
   296  	c.Assert(checkPhysicalCausetClone(lim), IsNil)
   297  
   298  	// sort
   299  	byItems := []*soliton.ByItems{{Expr: col}, {Expr: cst}}
   300  	sort := &PhysicalSort{ByItems: byItems}
   301  	sort = sort.Init(ctx, stats, 0)
   302  	c.Assert(checkPhysicalCausetClone(sort), IsNil)
   303  
   304  	// topN
   305  	topN := &PhysicalTopN{ByItems: byItems, Offset: 2333, Count: 2333}
   306  	topN = topN.Init(ctx, stats, 0)
   307  	c.Assert(checkPhysicalCausetClone(topN), IsNil)
   308  
   309  	// stream agg
   310  	streamAgg := &PhysicalStreamAgg{basePhysicalAgg{
   311  		AggFuncs:     aggDescs,
   312  		GroupByItems: []memex.Expression{col, cst},
   313  	}}
   314  	streamAgg = streamAgg.initForStream(ctx, stats, 0)
   315  	streamAgg.SetSchema(schemaReplicant)
   316  	c.Assert(checkPhysicalCausetClone(streamAgg), IsNil)
   317  
   318  	// hash agg
   319  	hashAgg := &PhysicalHashAgg{basePhysicalAgg{
   320  		AggFuncs:     aggDescs,
   321  		GroupByItems: []memex.Expression{col, cst},
   322  	}}
   323  	hashAgg = hashAgg.initForHash(ctx, stats, 0)
   324  	hashAgg.SetSchema(schemaReplicant)
   325  	c.Assert(checkPhysicalCausetClone(hashAgg), IsNil)
   326  
   327  	// hash join
   328  	hashJoin := &PhysicalHashJoin{
   329  		Concurrency:     4,
   330  		UseOuterToBuild: true,
   331  	}
   332  	hashJoin = hashJoin.Init(ctx, stats, 0)
   333  	hashJoin.SetSchema(schemaReplicant)
   334  	c.Assert(checkPhysicalCausetClone(hashJoin), IsNil)
   335  
   336  	// merge join
   337  	mergeJoin := &PhysicalMergeJoin{
   338  		CompareFuncs: []memex.CompareFunc{memex.CompareInt},
   339  		Desc:         true,
   340  	}
   341  	mergeJoin = mergeJoin.Init(ctx, stats, 0)
   342  	mergeJoin.SetSchema(schemaReplicant)
   343  	c.Assert(checkPhysicalCausetClone(mergeJoin), IsNil)
   344  }
   345  
   346  //go:linkname valueInterface reflect.valueInterface
   347  func valueInterface(v reflect.Value, safe bool) interface{}
   348  
   349  func typeName(t reflect.Type) string {
   350  	path := t.String()
   351  	tmp := strings.Split(path, ".")
   352  	return tmp[len(tmp)-1]
   353  }
   354  
   355  func checkPhysicalCausetClone(p PhysicalCauset) error {
   356  	cloned, err := p.Clone()
   357  	if err != nil {
   358  		return err
   359  	}
   360  	whiteList := []string{"*property.StatsInfo", "*stochastikctx.Context", "*mock.Context", "*types.FieldType"}
   361  	return checkDeepClonedCore(reflect.ValueOf(p), reflect.ValueOf(cloned), typeName(reflect.TypeOf(p)), whiteList, nil)
   362  }
   363  
   364  // checkDeepClonedCore is used to check if v2 is deep cloned from v1.
   365  // It's modified from reflect.deepValueEqual. We cannot use reflect.DeepEqual here since they have different
   366  // logic, for example, if two pointers point the same address, they will pass the DeepEqual check while failing in the DeepClone check.
   367  func checkDeepClonedCore(v1, v2 reflect.Value, path string, whiteList []string, visited map[visit]bool) error {
   368  	if !v1.IsValid() || !v2.IsValid() {
   369  		if v1.IsValid() != v2.IsValid() {
   370  			return errors.Errorf("invalid")
   371  		}
   372  		return nil
   373  	}
   374  	if v1.Type() != v2.Type() {
   375  		return errors.Errorf("different type %v, %v, path %v", v1.Type(), v2.Type(), path)
   376  	}
   377  
   378  	if visited == nil {
   379  		visited = make(map[visit]bool)
   380  	}
   381  	hard := func(k reflect.HoTT) bool {
   382  		switch k {
   383  		case reflect.Map, reflect.Slice, reflect.Ptr, reflect.Interface:
   384  			return true
   385  		}
   386  		return false
   387  	}
   388  	if v1.CanAddr() && v2.CanAddr() && hard(v1.HoTT()) {
   389  		addr1 := unsafe.Pointer(v1.UnsafeAddr())
   390  		addr2 := unsafe.Pointer(v2.UnsafeAddr())
   391  		if uintptr(addr1) > uintptr(addr2) {
   392  			addr1, addr2 = addr2, addr1
   393  		}
   394  		typ := v1.Type()
   395  		v := visit{addr1, addr2, typ}
   396  		if visited[v] {
   397  			return nil
   398  		}
   399  		visited[v] = true
   400  	}
   401  
   402  	switch v1.HoTT() {
   403  	case reflect.Array:
   404  		for i := 0; i < v1.Len(); i++ {
   405  			if err := checkDeepClonedCore(v1.Index(i), v2.Index(i), fmt.Sprintf("%v[%v]", path, i), whiteList, visited); err != nil {
   406  				return err
   407  			}
   408  		}
   409  	case reflect.Slice:
   410  		if (v1.IsNil() && v2.IsNil()) || (v1.Len() == 0 && v2.Len() == 0) {
   411  			return nil
   412  		}
   413  		if v1.Len() != v2.Len() {
   414  			return errors.Errorf("different slice lengths, len %v, %v, path %v", v1.Len(), v2.Len(), path)
   415  		}
   416  		if v1.IsNil() != v2.IsNil() {
   417  			if v1.Len() == 0 && v2.Len() == 0 {
   418  				return nil // nil and an empty slice are accepted
   419  			}
   420  			return errors.Errorf("different slices nil %v, %v, path %v", v1.IsNil(), v2.IsNil(), path)
   421  		}
   422  		if v1.Pointer() == v2.Pointer() {
   423  			return errors.Errorf("invalid slice pointers, path %v", path)
   424  		}
   425  		for i := 0; i < v1.Len(); i++ {
   426  			if err := checkDeepClonedCore(v1.Index(i), v2.Index(i), fmt.Sprintf("%v[%v]", path, i), whiteList, visited); err != nil {
   427  				return err
   428  			}
   429  		}
   430  	case reflect.Interface:
   431  		if v1.IsNil() && v2.IsNil() {
   432  			return nil
   433  		}
   434  		if v1.IsNil() != v2.IsNil() {
   435  			return errors.Errorf("invalid interfaces, path %v", path)
   436  		}
   437  		return checkDeepClonedCore(v1.Elem(), v2.Elem(), path, whiteList, visited)
   438  	case reflect.Ptr:
   439  		if v1.IsNil() && v2.IsNil() {
   440  			return nil
   441  		}
   442  		if v1.Pointer() == v2.Pointer() {
   443  			typeName := v1.Type().String()
   444  			inWhiteList := false
   445  			for _, whiteName := range whiteList {
   446  				if whiteName == typeName {
   447  					inWhiteList = true
   448  					break
   449  				}
   450  			}
   451  			if inWhiteList {
   452  				return nil
   453  			}
   454  			return errors.Errorf("same pointer, path %v", path)
   455  		}
   456  		return checkDeepClonedCore(v1.Elem(), v2.Elem(), path, whiteList, visited)
   457  	case reflect.Struct:
   458  		for i, n := 0, v1.NumField(); i < n; i++ {
   459  			if err := checkDeepClonedCore(v1.Field(i), v2.Field(i), fmt.Sprintf("%v.%v", path, typeName(v1.Field(i).Type())), whiteList, visited); err != nil {
   460  				return err
   461  			}
   462  		}
   463  	case reflect.Map:
   464  		if (v1.IsNil() && v2.IsNil()) || (v1.Len() == 0 && v2.Len() == 0) {
   465  			return nil
   466  		}
   467  		if v1.IsNil() != v2.IsNil() || v1.Len() != v2.Len() {
   468  			return errors.Errorf("different maps nil: %v, %v, len: %v, %v, path: %v", v1.IsNil(), v2.IsNil(), v1.Len(), v2.Len(), path)
   469  		}
   470  		if v1.Pointer() == v2.Pointer() {
   471  			return errors.Errorf("invalid map pointers, path %v", path)
   472  		}
   473  		if len(v1.MapKeys()) != len(v2.MapKeys()) {
   474  			return errors.Errorf("invalid map")
   475  		}
   476  		for _, k := range v1.MapKeys() {
   477  			val1 := v1.MapIndex(k)
   478  			val2 := v2.MapIndex(k)
   479  			if !val1.IsValid() || !val2.IsValid() {
   480  				if err := checkDeepClonedCore(val1, val2, fmt.Sprintf("%v[%v]", path, typeName(k.Type())), whiteList, visited); err != nil {
   481  					return err
   482  				}
   483  			}
   484  		}
   485  	case reflect.Func:
   486  		if v1.IsNil() != v2.IsNil() {
   487  			return errors.Errorf("invalid functions, path %v", path)
   488  		}
   489  		return nil // assume that these functions are stateless
   490  	default:
   491  		if valueInterface(v1, false) != valueInterface(v2, false) {
   492  			return errors.Errorf("different values, path %v", path)
   493  		}
   494  	}
   495  	return nil
   496  }
   497  
   498  type visit struct {
   499  	a1  unsafe.Pointer
   500  	a2  unsafe.Pointer
   501  	typ reflect.Type
   502  }