vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletserver/schema/load_table.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package schema 18 19 import ( 20 "context" 21 "fmt" 22 "strconv" 23 "strings" 24 "time" 25 26 "vitess.io/vitess/go/mysql/collations" 27 "vitess.io/vitess/go/sqltypes" 28 "vitess.io/vitess/go/vt/mysqlctl" 29 querypb "vitess.io/vitess/go/vt/proto/query" 30 "vitess.io/vitess/go/vt/sqlparser" 31 "vitess.io/vitess/go/vt/vtgate/evalengine" 32 "vitess.io/vitess/go/vt/vttablet/tabletserver/connpool" 33 ) 34 35 // LoadTable creates a Table from the schema info in the database. 36 func LoadTable(conn *connpool.DBConn, databaseName, tableName string, comment string) (*Table, error) { 37 ta := NewTable(tableName) 38 sqlTableName := sqlparser.String(ta.Name) 39 if err := fetchColumns(ta, conn, databaseName, sqlTableName); err != nil { 40 return nil, err 41 } 42 switch { 43 case strings.Contains(comment, "vitess_sequence"): 44 ta.Type = Sequence 45 ta.SequenceInfo = &SequenceInfo{} 46 case strings.Contains(comment, "vitess_message"): 47 if err := loadMessageInfo(ta, comment); err != nil { 48 return nil, err 49 } 50 ta.Type = Message 51 } 52 return ta, nil 53 } 54 55 func fetchColumns(ta *Table, conn *connpool.DBConn, databaseName, sqlTableName string) error { 56 ctx := context.Background() 57 exec := func(query string, maxRows int, wantFields bool) (*sqltypes.Result, error) { 58 return conn.Exec(ctx, query, maxRows, wantFields) 59 } 60 fields, _, err := mysqlctl.GetColumns(databaseName, sqlTableName, exec) 61 if err != nil { 62 return err 63 } 64 ta.Fields = fields 65 return nil 66 } 67 68 func loadMessageInfo(ta *Table, comment string) error { 69 ta.MessageInfo = &MessageInfo{} 70 // Extract keyvalues. 71 keyvals := make(map[string]string) 72 inputs := strings.Split(comment, ",") 73 for _, input := range inputs { 74 kv := strings.Split(input, "=") 75 if len(kv) != 2 { 76 continue 77 } 78 keyvals[kv[0]] = kv[1] 79 } 80 81 var err error 82 if ta.MessageInfo.AckWaitDuration, err = getDuration(keyvals, "vt_ack_wait"); err != nil { 83 return err 84 } 85 if ta.MessageInfo.PurgeAfterDuration, err = getDuration(keyvals, "vt_purge_after"); err != nil { 86 return err 87 } 88 if ta.MessageInfo.BatchSize, err = getNum(keyvals, "vt_batch_size"); err != nil { 89 return err 90 } 91 if ta.MessageInfo.CacheSize, err = getNum(keyvals, "vt_cache_size"); err != nil { 92 return err 93 } 94 if ta.MessageInfo.PollInterval, err = getDuration(keyvals, "vt_poller_interval"); err != nil { 95 return err 96 } 97 98 // errors are ignored because these fields are optional and 0 is the default value 99 ta.MessageInfo.MinBackoff, _ = getDuration(keyvals, "vt_min_backoff") 100 // the original default minimum backoff was based on ack wait timeout, so this preserves that 101 if ta.MessageInfo.MinBackoff == 0 { 102 ta.MessageInfo.MinBackoff = ta.MessageInfo.AckWaitDuration 103 } 104 105 ta.MessageInfo.MaxBackoff, _ = getDuration(keyvals, "vt_max_backoff") 106 107 // these columns are required for message manager to function properly, but only 108 // id is required to be streamed to subscribers 109 requiredCols := []string{ 110 "id", 111 "priority", 112 "time_next", 113 "epoch", 114 "time_acked", 115 } 116 117 // by default, these columns are loaded for the message manager, but not sent to subscribers 118 // via stream * from msg_tbl 119 hiddenCols := map[string]struct{}{ 120 "priority": {}, 121 "time_next": {}, 122 "epoch": {}, 123 "time_acked": {}, 124 } 125 126 // make sure required columns exist in the table schema 127 for _, col := range requiredCols { 128 num := ta.FindColumn(sqlparser.NewIdentifierCI(col)) 129 if num == -1 { 130 return fmt.Errorf("%s missing from message table: %s", col, ta.Name.String()) 131 } 132 } 133 134 // check to see if the user has specified columns to stream to subscribers 135 specifiedCols := parseMessageCols(keyvals, "vt_message_cols") 136 137 if len(specifiedCols) > 0 { 138 // make sure that all the specified columns exist in the table schema 139 for _, col := range specifiedCols { 140 num := ta.FindColumn(sqlparser.NewIdentifierCI(col)) 141 if num == -1 { 142 return fmt.Errorf("%s missing from message table: %s", col, ta.Name.String()) 143 } 144 } 145 146 // the original implementation in message_manager assumes id is the first column, as originally users 147 // could not restrict columns. As the PK, id is required, and by requiring it as the first column, 148 // we avoid the need to change the implementation. 149 if specifiedCols[0] != "id" { 150 return fmt.Errorf("vt_message_cols must begin with id: %s", ta.Name.String()) 151 } 152 ta.MessageInfo.Fields = getSpecifiedMessageFields(ta.Fields, specifiedCols) 153 } else { 154 ta.MessageInfo.Fields = getDefaultMessageFields(ta.Fields, hiddenCols) 155 } 156 157 return nil 158 } 159 160 func getDuration(in map[string]string, key string) (time.Duration, error) { 161 sv := in[key] 162 if sv == "" { 163 return 0, fmt.Errorf("attribute %s not specified for message table", key) 164 } 165 v, err := strconv.ParseFloat(sv, 64) 166 if err != nil { 167 return 0, err 168 } 169 return time.Duration(v * 1e9), nil 170 } 171 172 func getNum(in map[string]string, key string) (int, error) { 173 sv := in[key] 174 if sv == "" { 175 return 0, fmt.Errorf("attribute %s not specified for message table", key) 176 } 177 v, err := strconv.Atoi(sv) 178 if err != nil { 179 return 0, err 180 } 181 return v, nil 182 } 183 184 // parseMessageCols parses the vt_message_cols attribute. It doesn't error out if the attribute is not specified 185 // because the default behavior is to stream all columns to subscribers, and if done incorrectly, later checks 186 // to see if the columns exist in the table schema will fail. 187 func parseMessageCols(in map[string]string, key string) []string { 188 sv := in[key] 189 cols := strings.Split(sv, "|") 190 if len(cols) == 1 && strings.TrimSpace(cols[0]) == "" { 191 return nil 192 } 193 return cols 194 } 195 196 func getDefaultMessageFields(tableFields []*querypb.Field, hiddenCols map[string]struct{}) []*querypb.Field { 197 fields := make([]*querypb.Field, 0, len(tableFields)) 198 // Load user-defined columns. Any "unrecognized" column is user-defined. 199 for _, field := range tableFields { 200 if _, ok := hiddenCols[strings.ToLower(field.Name)]; ok { 201 continue 202 } 203 204 fields = append(fields, field) 205 } 206 return fields 207 } 208 209 // we have already validated that all the specified columns exist in the table schema, so we don't need to 210 // check again and possibly return an error here. 211 func getSpecifiedMessageFields(tableFields []*querypb.Field, specifiedCols []string) []*querypb.Field { 212 fields := make([]*querypb.Field, 0, len(specifiedCols)) 213 for _, col := range specifiedCols { 214 for _, field := range tableFields { 215 if res, _ := evalengine.NullsafeCompare(sqltypes.NewVarChar(field.Name), sqltypes.NewVarChar(strings.TrimSpace(col)), collations.Default()); res == 0 { 216 fields = append(fields, field) 217 break 218 } 219 } 220 } 221 return fields 222 }