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  }