github.com/dolthub/go-mysql-server@v0.18.0/sql/planbuilder/analyze.go (about) 1 // Copyright 2023 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 planbuilder 16 17 import ( 18 "encoding/json" 19 "fmt" 20 "strings" 21 22 ast "github.com/dolthub/vitess/go/vt/sqlparser" 23 24 "github.com/dolthub/go-mysql-server/sql" 25 "github.com/dolthub/go-mysql-server/sql/expression" 26 "github.com/dolthub/go-mysql-server/sql/plan" 27 "github.com/dolthub/go-mysql-server/sql/stats" 28 ) 29 30 func (b *Builder) buildAnalyze(inScope *scope, n *ast.Analyze, query string) (outScope *scope) { 31 defaultDb := b.ctx.GetCurrentDatabase() 32 33 if n.Action == "" { 34 return b.buildAnalyzeTables(inScope, n, query) 35 } 36 37 // table and columns 38 if len(n.Tables) != 1 { 39 err := fmt.Errorf("ANALYZE %s expected 1 table name, found %d", n.Action, len(n.Tables)) 40 b.handleErr(err) 41 } 42 43 dbName := strings.ToLower(n.Tables[0].Qualifier.String()) 44 if dbName == "" { 45 if defaultDb == "" { 46 err := sql.ErrNoDatabaseSelected.New() 47 b.handleErr(err) 48 } 49 dbName = defaultDb 50 } 51 tableName := strings.ToLower(n.Tables[0].Name.String()) 52 53 tableScope, ok := b.buildTablescan(inScope, dbName, tableName, nil) 54 if !ok { 55 err := sql.ErrTableNotFound.New(tableName) 56 b.handleErr(err) 57 } 58 _, ok = tableScope.node.(*plan.ResolvedTable) 59 if !ok { 60 err := fmt.Errorf("can only update statistics for base tables, found %s: %s", tableName, tableScope.node) 61 b.handleErr(err) 62 } 63 64 columns := make([]string, len(n.Columns)) 65 types := make([]sql.Type, len(n.Columns)) 66 for i, c := range n.Columns { 67 col, ok := tableScope.resolveColumn(dbName, tableName, c.Lowered(), false, false) 68 if !ok { 69 err := sql.ErrTableColumnNotFound.New(tableName, c.Lowered()) 70 b.handleErr(err) 71 } 72 columns[i] = col.col 73 types[i] = col.typ 74 } 75 76 switch n.Action { 77 case ast.UpdateStr: 78 sch := tableScope.node.Schema() 79 return b.buildAnalyzeUpdate(inScope, n, dbName, tableName, sch, columns, types) 80 case ast.DropStr: 81 outScope = inScope.push() 82 outScope.node = plan.NewDropHistogram(dbName, tableName, columns).WithProvider(b.cat) 83 default: 84 err := fmt.Errorf("invalid ANALYZE action: %s, expected UPDATE or DROP", n.Action) 85 b.handleErr(err) 86 } 87 return 88 } 89 90 func (b *Builder) buildAnalyzeTables(inScope *scope, n *ast.Analyze, query string) (outScope *scope) { 91 outScope = inScope.push() 92 defaultDb := b.ctx.GetCurrentDatabase() 93 tables := make([]sql.Table, len(n.Tables)) 94 for i, table := range n.Tables { 95 dbName := table.Qualifier.String() 96 if dbName == "" { 97 if defaultDb == "" { 98 err := sql.ErrNoDatabaseSelected.New() 99 b.handleErr(err) 100 } 101 dbName = defaultDb 102 } 103 tableName := strings.ToLower(table.Name.String()) 104 tableScope, ok := b.buildTablescan(inScope, dbName, tableName, nil) 105 if !ok { 106 err := sql.ErrTableNotFound.New(tableName) 107 b.handleErr(err) 108 } 109 rt, ok := tableScope.node.(*plan.ResolvedTable) 110 if !ok { 111 err := fmt.Errorf("can only update statistics for base tables, found %s: %s", tableName, tableScope.node) 112 b.handleErr(err) 113 } 114 115 tables[i] = rt.Table 116 } 117 analyze := plan.NewAnalyze(tables) 118 outScope.node = analyze.WithDb(defaultDb).WithStats(b.cat) 119 return 120 } 121 122 func (b *Builder) buildAnalyzeUpdate(inScope *scope, n *ast.Analyze, dbName, tableName string, sch sql.Schema, columns []string, types []sql.Type) (outScope *scope) { 123 outScope = inScope.push() 124 statistic := new(stats.Statistic) 125 using := b.buildScalar(inScope, n.Using) 126 if l, ok := using.(*expression.Literal); ok { 127 if typ, ok := l.Type().(sql.StringType); ok { 128 val, _, err := typ.Convert(l.Value()) 129 if err != nil { 130 b.handleErr(err) 131 } 132 if str, ok := val.(string); ok { 133 err := json.Unmarshal([]byte(str), statistic) 134 if err != nil { 135 err = ErrFailedToParseStats.New(err.Error(), str) 136 b.handleErr(err) 137 } 138 } 139 140 } 141 } 142 if statistic == nil { 143 err := fmt.Errorf("no statistics found for update") 144 b.handleErr(err) 145 } 146 indexName := statistic.Qual.Idx 147 if indexName == "" { 148 indexName = "primary" 149 } 150 statistic.SetQualifier(sql.NewStatQualifier(dbName, tableName, strings.ToLower(indexName))) 151 statistic.SetColumns(columns) 152 statistic.SetTypes(types) 153 154 statCols := sql.NewFastIntSet() 155 for _, c := range columns { 156 i := sch.IndexOfColName(c) 157 statCols.Add(i + 1) 158 } 159 allCols := sql.NewFastIntSet() 160 allCols.AddRange(1, len(sch)+1) 161 statColset := sql.NewColSetFromIntSet(statCols) 162 allColset := sql.NewColSetFromIntSet(allCols) 163 // TODO find if underlying index has strict/lax key 164 fds := sql.NewTablescanFDs(allColset, nil, nil, allColset) 165 updatedStat := statistic.WithColSet(statColset).WithFuncDeps(fds) 166 updatedStat = stats.UpdateCounts(updatedStat) 167 168 outScope.node = plan.NewUpdateHistogram(dbName, tableName, indexName, columns, updatedStat).WithProvider(b.cat) 169 return outScope 170 }