github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/sqle/statsnoms/iter.go (about) 1 // Copyright 2024 Dolthub, 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 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package statsnoms 16 17 import ( 18 "fmt" 19 "strings" 20 "time" 21 22 "github.com/dolthub/go-mysql-server/sql" 23 "github.com/dolthub/go-mysql-server/sql/planbuilder" 24 "github.com/dolthub/go-mysql-server/sql/stats" 25 "gopkg.in/errgo.v2/errors" 26 27 "github.com/dolthub/dolt/go/libraries/doltcore/schema" 28 "github.com/dolthub/dolt/go/store/hash" 29 "github.com/dolthub/dolt/go/store/prolly" 30 "github.com/dolthub/dolt/go/store/prolly/tree" 31 "github.com/dolthub/dolt/go/store/val" 32 ) 33 34 var ErrIncompatibleVersion = errors.New("client stats version mismatch") 35 36 func NewStatsIter(ctx *sql.Context, m prolly.Map) (*statsIter, error) { 37 iter, err := m.IterAll(ctx) 38 if err != nil { 39 return nil, err 40 } 41 kd, vd := m.Descriptors() 42 keyBuilder := val.NewTupleBuilder(kd) 43 valueBuilder := val.NewTupleBuilder(vd) 44 ns := m.NodeStore() 45 46 return &statsIter{ 47 iter: iter, 48 kb: keyBuilder, 49 vb: valueBuilder, 50 ns: ns, 51 planb: planbuilder.New(ctx, nil, sql.NewMysqlParser()), 52 }, nil 53 } 54 55 // statsIter reads histogram buckets into string-compatible types. 56 // Values that are SQL rows should be converted with statsIter.ParseRow. 57 // todo: make a JSON compatible container for sql.Row w/ types so that we 58 // can eagerly convert to sql.Row without sacrificing string printing. 59 type statsIter struct { 60 iter prolly.MapIter 61 kb, vb *val.TupleBuilder 62 ns tree.NodeStore 63 planb *planbuilder.Builder 64 currentQual string 65 currentTypes []sql.Type 66 } 67 68 var _ sql.RowIter = (*statsIter)(nil) 69 70 func (s *statsIter) Next(ctx *sql.Context) (sql.Row, error) { 71 k, v, err := s.iter.Next(ctx) 72 if err != nil { 73 return nil, err 74 } 75 76 // deserialize K, V 77 version, err := tree.GetField(ctx, s.vb.Desc, 0, v, s.ns) 78 if err != nil { 79 return nil, err 80 } 81 if version != schema.StatsVersion { 82 return nil, fmt.Errorf("%w: write version %d does not match read version %d", ErrIncompatibleVersion, version, schema.StatsVersion) 83 } 84 85 var row sql.Row 86 for i := 0; i < s.kb.Desc.Count(); i++ { 87 f, err := tree.GetField(ctx, s.kb.Desc, i, k, s.ns) 88 if err != nil { 89 return nil, err 90 } 91 row = append(row, f) 92 } 93 94 for i := 0; i < s.vb.Desc.Count(); i++ { 95 f, err := tree.GetField(ctx, s.vb.Desc, i, v, s.ns) 96 if err != nil { 97 return nil, err 98 } 99 row = append(row, f) 100 } 101 102 dbName := row[schema.StatsDbTag].(string) 103 tableName := row[schema.StatsTableTag].(string) 104 indexName := row[schema.StatsIndexTag].(string) 105 position := row[schema.StatsPositionTag].(int64) 106 _ = row[schema.StatsVersionTag] 107 commit := hash.Parse(row[schema.StatsCommitHashTag].(string)) 108 rowCount := row[schema.StatsRowCountTag].(int64) 109 distinctCount := row[schema.StatsDistinctCountTag].(int64) 110 nullCount := row[schema.StatsNullCountTag].(int64) 111 columnsStr := row[schema.StatsColumnsTag].(string) 112 typesStr := row[schema.StatsTypesTag].(string) 113 upperBoundStr := row[schema.StatsUpperBoundTag].(string) 114 upperBoundCnt := row[schema.StatsUpperBoundCntTag].(int64) 115 createdAt := row[schema.StatsCreatedAtTag].(time.Time) 116 117 typs := strings.Split(typesStr, ",") 118 for i, t := range typs { 119 typs[i] = strings.TrimSpace(t) 120 } 121 122 qual := sql.NewStatQualifier(dbName, tableName, indexName) 123 if curQual := qual.String(); !strings.EqualFold(curQual, s.currentQual) { 124 s.currentQual = curQual 125 s.currentTypes, err = stats.ParseTypeStrings(typs) 126 if err != nil { 127 return nil, err 128 } 129 } 130 131 mcvCountsStr := row[schema.StatsMcvCountsTag].(string) 132 133 numMcvs := schema.StatsMcvCountsTag - schema.StatsMcv1Tag 134 mcvs := make([]string, numMcvs) 135 for i, v := range row[schema.StatsMcv1Tag:schema.StatsMcvCountsTag] { 136 if v != nil { 137 mcvs[i] = v.(string) 138 } 139 } 140 141 return sql.Row{ 142 dbName, 143 tableName, 144 indexName, 145 int(position), 146 version, 147 commit.String(), 148 uint64(rowCount), 149 uint64(distinctCount), 150 uint64(nullCount), 151 columnsStr, 152 typesStr, 153 upperBoundStr, 154 uint64(upperBoundCnt), 155 createdAt, 156 mcvs[0], mcvs[1], mcvs[2], mcvs[3], 157 mcvCountsStr, 158 }, nil 159 } 160 161 func (s *statsIter) ParseRow(rowStr string) (sql.Row, error) { 162 var row sql.Row 163 for i, v := range strings.Split(rowStr, ",") { 164 val, _, err := s.currentTypes[i].Convert(v) 165 if err != nil { 166 return nil, err 167 } 168 row = append(row, val) 169 } 170 return row, nil 171 } 172 173 func (s *statsIter) Close(context *sql.Context) error { 174 return nil 175 }