Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport v16: schema.Reload(): ignore column reading errors for views only, error for tables #13442 #13456

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go/mysql/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,7 @@ const (
ERIllegalValueForType = 1367
ERDataTooLong = 1406
ErrWrongValueForType = 1411
ERNoSuchUser = 1449
ERForbidSchemaChange = 1450
ERWrongValue = 1525
ERDataOutOfRange = 1690
Expand Down
16 changes: 14 additions & 2 deletions go/vt/mysqlctl/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ import (

var autoIncr = regexp.MustCompile(` AUTO_INCREMENT=\d+`)

type EmptyColumnsErr struct {
dbName, tableName, query string
}

func (e EmptyColumnsErr) Error() string {
return fmt.Sprintf("unable to get columns for table %s.%s using query %s", e.dbName, e.tableName, e.query)
}

// executeSchemaCommands executes some SQL commands, using the mysql
// command line tool. It uses the dba connection parameters, with credentials.
func (mysqld *Mysqld) executeSchemaCommands(sql string) error {
Expand Down Expand Up @@ -287,6 +295,10 @@ const (
GetFieldsQuery = "SELECT %s FROM %s WHERE 1 != 1"
)

// GetColumnsList returns the column names for a given table/view, using a query generating function.
// Returned values:
// - selectColumns: a string of comma delimited qualified names to be used in a SELECT query. e.g. "`id`, `name`, `val`"
// - err: error
func GetColumnsList(dbName, tableName string, exec func(string, int, bool) (*sqltypes.Result, error)) (string, error) {
var dbName2 string
if dbName == "" {
Expand All @@ -300,8 +312,8 @@ func GetColumnsList(dbName, tableName string, exec func(string, int, bool) (*sql
return "", err
}
if qr == nil || len(qr.Rows) == 0 {
err = fmt.Errorf("unable to get columns for table %s.%s using query %s", dbName, tableName, query)
log.Errorf("%s", fmt.Errorf("unable to get columns for table %s.%s using query %s", dbName, tableName, query))
err := &EmptyColumnsErr{dbName: dbName, tableName: tableName, query: query}
log.Error(err.Error())
return "", err
}
selectColumns := ""
Expand Down
26 changes: 25 additions & 1 deletion go/vt/vttablet/tabletserver/schema/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,17 @@ import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"sync"
"time"

"vitess.io/vitess/go/sqltypes"
"vitess.io/vitess/go/vt/concurrency"
"vitess.io/vitess/go/vt/mysqlctl"
"vitess.io/vitess/go/vt/mysqlctl/tmutils"
"vitess.io/vitess/go/vt/sidecardb"

"vitess.io/vitess/go/stats"
Expand Down Expand Up @@ -412,6 +417,7 @@ func (se *Engine) reload(ctx context.Context, includeStats bool) error {
return err
}

rec := concurrency.AllErrorRecorder{}
// curTables keeps track of tables in the new snapshot so we can detect what was dropped.
curTables := map[string]bool{"dual": true}
// changedTables keeps track of tables that have changed so we can reload their pk info.
Expand Down Expand Up @@ -452,9 +458,24 @@ func (se *Engine) reload(ctx context.Context, includeStats bool) error {
}

log.V(2).Infof("Reading schema for table: %s", tableName)
tableType := row[1].String()
table, err := LoadTable(conn, se.cp.DBName(), tableName, row[3].ToString())
if err != nil {
log.Warningf("Failed reading schema for the table: %s, error: %v", tableName, err)
isView := strings.Contains(tableType, tmutils.TableView)
var emptyColumnsError mysqlctl.EmptyColumnsErr
if errors.As(err, &emptyColumnsError) && isView {
log.Warningf("Failed reading schema for the table: %s, error: %v", tableName, err)
continue
}
sqlErr, isSQLErr := mysql.NewSQLErrorFromError(err).(*mysql.SQLError)
if isSQLErr && sqlErr != nil && sqlErr.Number() == mysql.ERNoSuchUser && isView {
// A VIEW that has an invalid DEFINER, leading to:
// ERROR 1449 (HY000): The user specified as a definer (...) does not exist
log.Warningf("Failed reading schema for the table: %s, error: %v", tableName, err)
continue
}
// Non recoverable error:
rec.RecordError(vterrors.Wrapf(err, "in Engine.reload(), reading table %s", tableName))
continue
}
if includeStats {
Expand All @@ -469,6 +490,9 @@ func (se *Engine) reload(ctx context.Context, includeStats bool) error {
created = append(created, tableName)
}
}
if rec.HasErrors() {
return rec.Error()
}

// Compute and handle dropped tables.
var dropped []string
Expand Down
8 changes: 3 additions & 5 deletions go/vt/vttablet/tabletserver/schema/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -444,18 +444,16 @@ func TestOpenFailedDueToLoadTableErr(t *testing.T) {
db.AddQueryPattern(fmt.Sprintf(mysql.GetColumnNamesQueryPatternForTable, "test_view"),
sqltypes.MakeTestResult(sqltypes.MakeTestFields("column_name", "varchar"), ""))
// rejecting the impossible query
db.AddRejectedQuery("SELECT * FROM `fakesqldb`.`test_view` WHERE 1 != 1", errors.New("The user specified as a definer ('root'@'%') does not exist (errno 1449) (sqlstate HY000)"))
db.AddRejectedQuery("SELECT * FROM `fakesqldb`.`test_view` WHERE 1 != 1", mysql.NewSQLErrorFromError(errors.New("The user specified as a definer ('root'@'%') does not exist (errno 1449) (sqlstate HY000)")))

AddFakeInnoDBReadRowsResult(db, 0)
se := newEngine(10, 1*time.Second, 1*time.Second, db)
err := se.Open()
// failed load should not return any error, instead should be logged.
require.NoError(t, err)
// failed load should return an error because of test_table
assert.ErrorContains(t, err, "Row count exceeded")

logs := tl.GetAllLogs()
logOutput := strings.Join(logs, ":::")
assert.Contains(t, logOutput, "WARNING:Failed reading schema for the table: test_table")
assert.Contains(t, logOutput, "Row count exceeded")
assert.Contains(t, logOutput, "WARNING:Failed reading schema for the table: test_view")
assert.Contains(t, logOutput, "The user specified as a definer ('root'@'%') does not exist (errno 1449) (sqlstate HY000)")
}
Expand Down
Loading