Files
tidb/statistics/selectivity_test.go

150 lines
4.9 KiB
Go

// Copyright 2017 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 statistics_test
import (
"math"
"testing"
"time"
"github.com/pingcap/tidb/domain"
"github.com/pingcap/tidb/parser/model"
"github.com/pingcap/tidb/parser/mysql"
"github.com/pingcap/tidb/sessionctx/stmtctx"
"github.com/pingcap/tidb/statistics"
"github.com/pingcap/tidb/testkit"
"github.com/pingcap/tidb/types"
"github.com/pingcap/tidb/util/codec"
"github.com/pingcap/tidb/util/ranger"
"github.com/stretchr/testify/require"
)
const eps = 1e-9
// generateIntDatum will generate a datum slice, every dimension is begin from 0, end with num - 1.
// If dimension is x, num is y, the total number of datum is y^x. And This slice is sorted.
func generateIntDatum(dimension, num int) ([]types.Datum, error) {
length := int(math.Pow(float64(num), float64(dimension)))
ret := make([]types.Datum, length)
if dimension == 1 {
for i := 0; i < num; i++ {
ret[i] = types.NewIntDatum(int64(i))
}
} else {
sc := &stmtctx.StatementContext{TimeZone: time.Local}
// In this way, we can guarantee the datum is in order.
for i := 0; i < length; i++ {
data := make([]types.Datum, dimension)
j := i
for k := 0; k < dimension; k++ {
data[dimension-k-1].SetInt64(int64(j % num))
j = j / num
}
bytes, err := codec.EncodeKey(sc, nil, data...)
if err != nil {
return nil, err
}
ret[i].SetBytes(bytes)
}
}
return ret, nil
}
// mockStatsHistogram will create a statistics.Histogram, of which the data is uniform distribution.
func mockStatsHistogram(id int64, values []types.Datum, repeat int64, tp *types.FieldType) *statistics.Histogram {
ndv := len(values)
histogram := statistics.NewHistogram(id, int64(ndv), 0, 0, tp, ndv, 0)
for i := 0; i < ndv; i++ {
histogram.AppendBucket(&values[i], &values[i], repeat*int64(i+1), repeat)
}
return histogram
}
func mockStatsTable(tbl *model.TableInfo, rowCount int64) *statistics.Table {
histColl := statistics.HistColl{
PhysicalID: tbl.ID,
HavePhysicalID: true,
Count: rowCount,
Columns: make(map[int64]*statistics.Column, len(tbl.Columns)),
Indices: make(map[int64]*statistics.Index, len(tbl.Indices)),
}
statsTbl := &statistics.Table{
HistColl: histColl,
}
return statsTbl
}
func prepareSelectivity(testKit *testkit.TestKit, dom *domain.Domain) (*statistics.Table, error) {
testKit.MustExec("use test")
testKit.MustExec("drop table if exists t")
testKit.MustExec("create table t(a int primary key, b int, c int, d int, e int, index idx_cd(c, d), index idx_de(d, e))")
is := dom.InfoSchema()
tb, err := is.TableByName(model.NewCIStr("test"), model.NewCIStr("t"))
if err != nil {
return nil, err
}
tbl := tb.Meta()
// mock the statistic table
statsTbl := mockStatsTable(tbl, 540)
// Set the value of columns' histogram.
colValues, err := generateIntDatum(1, 54)
if err != nil {
return nil, err
}
for i := 1; i <= 5; i++ {
statsTbl.Columns[int64(i)] = &statistics.Column{Histogram: *mockStatsHistogram(int64(i), colValues, 10, types.NewFieldType(mysql.TypeLonglong)), Info: tbl.Columns[i-1]}
}
// Set the value of two indices' histograms.
idxValues, err := generateIntDatum(2, 3)
if err != nil {
return nil, err
}
tp := types.NewFieldType(mysql.TypeBlob)
statsTbl.Indices[1] = &statistics.Index{Histogram: *mockStatsHistogram(1, idxValues, 60, tp), Info: tbl.Indices[0]}
statsTbl.Indices[2] = &statistics.Index{Histogram: *mockStatsHistogram(2, idxValues, 60, tp), Info: tbl.Indices[1]}
return statsTbl, nil
}
func getRange(start, end int64) []*ranger.Range {
ran := &ranger.Range{
LowVal: []types.Datum{types.NewIntDatum(start)},
HighVal: []types.Datum{types.NewIntDatum(end)},
}
return []*ranger.Range{ran}
}
func TestSelectivityGreedyAlgo(t *testing.T) {
t.Parallel()
nodes := make([]*statistics.StatsNode, 3)
nodes[0] = statistics.MockStatsNode(1, 3, 2)
nodes[1] = statistics.MockStatsNode(2, 5, 2)
nodes[2] = statistics.MockStatsNode(3, 9, 2)
// Sets should not overlap on mask, so only nodes[0] is chosen.
usedSets := statistics.GetUsableSetsByGreedy(nodes)
require.Equal(t, 1, len(usedSets))
require.Equal(t, int64(1), usedSets[0].ID)
nodes[0], nodes[1] = nodes[1], nodes[0]
// Sets chosen should be stable, so the returned node is still the one with ID 1.
usedSets = statistics.GetUsableSetsByGreedy(nodes)
require.Equal(t, 1, len(usedSets))
require.Equal(t, int64(1), usedSets[0].ID)
}