vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletserver/schema/engine.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  	"bytes"
    21  	"context"
    22  	"encoding/json"
    23  	"fmt"
    24  	"net/http"
    25  	"sync"
    26  	"time"
    27  
    28  	"vitess.io/vitess/go/sqltypes"
    29  	"vitess.io/vitess/go/vt/sidecardb"
    30  
    31  	"vitess.io/vitess/go/stats"
    32  	"vitess.io/vitess/go/vt/dbconnpool"
    33  	"vitess.io/vitess/go/vt/schema"
    34  	"vitess.io/vitess/go/vt/servenv"
    35  	"vitess.io/vitess/go/vt/vtgate/evalengine"
    36  
    37  	"vitess.io/vitess/go/acl"
    38  	"vitess.io/vitess/go/mysql"
    39  	"vitess.io/vitess/go/timer"
    40  	"vitess.io/vitess/go/vt/concurrency"
    41  	"vitess.io/vitess/go/vt/dbconfigs"
    42  	"vitess.io/vitess/go/vt/log"
    43  	"vitess.io/vitess/go/vt/sqlparser"
    44  	"vitess.io/vitess/go/vt/vterrors"
    45  	"vitess.io/vitess/go/vt/vttablet/tabletserver/connpool"
    46  	"vitess.io/vitess/go/vt/vttablet/tabletserver/tabletenv"
    47  
    48  	binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata"
    49  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    50  	vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
    51  )
    52  
    53  const maxTableCount = 10000
    54  
    55  type notifier func(full map[string]*Table, created, altered, dropped []string)
    56  
    57  // Engine stores the schema info and performs operations that
    58  // keep itself up-to-date.
    59  type Engine struct {
    60  	env tabletenv.Env
    61  	cp  dbconfigs.Connector
    62  
    63  	// mu protects the following fields.
    64  	mu         sync.Mutex
    65  	isOpen     bool
    66  	tables     map[string]*Table
    67  	lastChange int64
    68  	reloadTime time.Duration
    69  	//the position at which the schema was last loaded. it is only used in conjunction with ReloadAt
    70  	reloadAtPos mysql.Position
    71  	notifierMu  sync.Mutex
    72  	notifiers   map[string]notifier
    73  
    74  	// SkipMetaCheck skips the metadata about the database and table information
    75  	SkipMetaCheck bool
    76  
    77  	historian *historian
    78  
    79  	conns *connpool.Pool
    80  	ticks *timer.Timer
    81  
    82  	// dbCreationFailed is for preventing log spam.
    83  	dbCreationFailed bool
    84  
    85  	tableFileSizeGauge      *stats.GaugesWithSingleLabel
    86  	tableAllocatedSizeGauge *stats.GaugesWithSingleLabel
    87  	innoDbReadRowsCounter   *stats.Counter
    88  	SchemaReloadTimings     *servenv.TimingsWrapper
    89  }
    90  
    91  // NewEngine creates a new Engine.
    92  func NewEngine(env tabletenv.Env) *Engine {
    93  	reloadTime := env.Config().SchemaReloadIntervalSeconds.Get()
    94  	se := &Engine{
    95  		env: env,
    96  		// We need three connections: one for the reloader, one for
    97  		// the historian, and one for the tracker.
    98  		conns: connpool.NewPool(env, "", tabletenv.ConnPoolConfig{
    99  			Size:               3,
   100  			IdleTimeoutSeconds: env.Config().OltpReadPool.IdleTimeoutSeconds,
   101  		}),
   102  		ticks:      timer.NewTimer(reloadTime),
   103  		reloadTime: reloadTime,
   104  	}
   105  	_ = env.Exporter().NewGaugeDurationFunc("SchemaReloadTime", "vttablet keeps table schemas in its own memory and periodically refreshes it from MySQL. This config controls the reload time.", se.ticks.Interval)
   106  	se.tableFileSizeGauge = env.Exporter().NewGaugesWithSingleLabel("TableFileSize", "tracks table file size", "Table")
   107  	se.tableAllocatedSizeGauge = env.Exporter().NewGaugesWithSingleLabel("TableAllocatedSize", "tracks table allocated size", "Table")
   108  	se.innoDbReadRowsCounter = env.Exporter().NewCounter("InnodbRowsRead", "number of rows read by mysql")
   109  	se.SchemaReloadTimings = env.Exporter().NewTimings("SchemaReload", "time taken to reload the schema", "type")
   110  
   111  	env.Exporter().HandleFunc("/debug/schema", se.handleDebugSchema)
   112  	env.Exporter().HandleFunc("/schemaz", func(w http.ResponseWriter, r *http.Request) {
   113  		// Ensure schema engine is Open. If vttablet came up in a non_serving role,
   114  		// the schema engine may not have been initialized.
   115  		err := se.Open()
   116  		if err != nil {
   117  			w.Write([]byte(err.Error()))
   118  			return
   119  		}
   120  
   121  		schemazHandler(se.GetSchema(), w, r)
   122  	})
   123  	se.historian = newHistorian(env.Config().TrackSchemaVersions, se.conns)
   124  	return se
   125  }
   126  
   127  // InitDBConfig must be called before Open.
   128  func (se *Engine) InitDBConfig(cp dbconfigs.Connector) {
   129  	se.cp = cp
   130  }
   131  
   132  // syncSidecarDB is called either the first time a primary starts, or on subsequent loads, to possibly upgrade to a
   133  // new Vitess version. This is the only entry point into the sidecardb module to get the _vt database to the desired
   134  // schema for the running Vitess version.
   135  // There is some extra logging in here which can be removed in a future version (>v16) once the new schema init
   136  // functionality is stable.
   137  func (se *Engine) syncSidecarDB(ctx context.Context, conn *dbconnpool.DBConnection) error {
   138  	log.Infof("In syncSidecarDB")
   139  	defer func(start time.Time) {
   140  		log.Infof("syncSidecarDB took %d ms", time.Since(start).Milliseconds())
   141  	}(time.Now())
   142  
   143  	var exec sidecardb.Exec = func(ctx context.Context, query string, maxRows int, useDB bool) (*sqltypes.Result, error) {
   144  		if useDB {
   145  			_, err := conn.ExecuteFetch(sidecardb.UseSidecarDatabaseQuery, maxRows, false)
   146  			if err != nil {
   147  				return nil, err
   148  			}
   149  		}
   150  		return conn.ExecuteFetch(query, maxRows, true)
   151  	}
   152  	if err := sidecardb.Init(ctx, exec); err != nil {
   153  		log.Errorf("Error in sidecardb.Init: %+v", err)
   154  		if se.env.Config().DB.HasGlobalSettings() {
   155  			log.Warning("Ignoring sidecardb.Init error for unmanaged tablets")
   156  			return nil
   157  		}
   158  		log.Errorf("syncSidecarDB error %+v", err)
   159  		return err
   160  	}
   161  	log.Infof("syncSidecarDB done")
   162  	return nil
   163  }
   164  
   165  // EnsureConnectionAndDB ensures that we can connect to mysql.
   166  // If tablet type is primary and there is no db, then the database is created.
   167  // This function can be called before opening the Engine.
   168  func (se *Engine) EnsureConnectionAndDB(tabletType topodatapb.TabletType) error {
   169  	ctx := tabletenv.LocalContext()
   170  	// We use AllPrivs since syncSidecarDB() might need to upgrade the schema
   171  	conn, err := dbconnpool.NewDBConnection(ctx, se.env.Config().DB.AllPrivsWithDB())
   172  	if err == nil {
   173  		se.dbCreationFailed = false
   174  		// upgrade _vt if required, for a tablet with an existing database
   175  		if tabletType == topodatapb.TabletType_PRIMARY {
   176  			if err := se.syncSidecarDB(ctx, conn); err != nil {
   177  				conn.Close()
   178  				return err
   179  			}
   180  		}
   181  		conn.Close()
   182  		return nil
   183  	}
   184  	if tabletType != topodatapb.TabletType_PRIMARY {
   185  		return err
   186  	}
   187  	if merr, isSQLErr := err.(*mysql.SQLError); !isSQLErr || merr.Num != mysql.ERBadDb {
   188  		return err
   189  	}
   190  
   191  	// We are primary and db is not found. Let's create it.
   192  	// We use allprivs instead of DBA because we want db create to fail if we're read-only.
   193  	conn, err = dbconnpool.NewDBConnection(ctx, se.env.Config().DB.AllPrivsConnector())
   194  	if err != nil {
   195  		return err
   196  	}
   197  	defer conn.Close()
   198  
   199  	dbname := se.env.Config().DB.DBName
   200  	_, err = conn.ExecuteFetch(fmt.Sprintf("create database if not exists `%s`", dbname), 1, false)
   201  	if err != nil {
   202  		if !se.dbCreationFailed {
   203  			// This is the first failure.
   204  			log.Errorf("db creation failed for %v: %v, will keep retrying", dbname, err)
   205  			se.dbCreationFailed = true
   206  		}
   207  		return err
   208  	}
   209  
   210  	log.Infof("db %v created", dbname)
   211  	se.dbCreationFailed = false
   212  	// creates sidecar schema, the first time the database is created
   213  	if err := se.syncSidecarDB(ctx, conn); err != nil {
   214  		return err
   215  	}
   216  	return nil
   217  }
   218  
   219  // Open initializes the Engine. Calling Open on an already
   220  // open engine is a no-op.
   221  func (se *Engine) Open() error {
   222  	se.mu.Lock()
   223  	defer se.mu.Unlock()
   224  	if se.isOpen {
   225  		return nil
   226  	}
   227  	log.Info("Schema Engine: opening")
   228  
   229  	ctx := tabletenv.LocalContext()
   230  
   231  	// The function we're in is supposed to be idempotent, but this conns.Open()
   232  	// call is not itself idempotent. Therefore, if we return for any reason
   233  	// without marking ourselves as open, we need to call conns.Close() so the
   234  	// pools aren't leaked the next time we call Open().
   235  	se.conns.Open(se.cp, se.cp, se.cp)
   236  	defer func() {
   237  		if !se.isOpen {
   238  			se.conns.Close()
   239  		}
   240  	}()
   241  
   242  	se.tables = map[string]*Table{
   243  		"dual": NewTable("dual"),
   244  	}
   245  	se.notifiers = make(map[string]notifier)
   246  
   247  	if err := se.reload(ctx, true); err != nil {
   248  		return err
   249  	}
   250  	if !se.SkipMetaCheck {
   251  		if err := se.historian.Open(); err != nil {
   252  			return err
   253  		}
   254  	}
   255  
   256  	se.ticks.Start(func() {
   257  		if err := se.Reload(ctx); err != nil {
   258  			log.Errorf("periodic schema reload failed: %v", err)
   259  		}
   260  	})
   261  
   262  	se.isOpen = true
   263  	return nil
   264  }
   265  
   266  // IsOpen checks if engine is open
   267  func (se *Engine) IsOpen() bool {
   268  	se.mu.Lock()
   269  	defer se.mu.Unlock()
   270  	return se.isOpen
   271  }
   272  
   273  // Close shuts down Engine and is idempotent.
   274  // It can be re-opened after Close.
   275  func (se *Engine) Close() {
   276  	se.mu.Lock()
   277  	if !se.isOpen {
   278  		se.mu.Unlock()
   279  		return
   280  	}
   281  
   282  	se.closeLocked()
   283  	log.Info("Schema Engine: closed")
   284  }
   285  
   286  // closeLocked closes the schema engine. It is meant to be called after locking the mutex of the schema engine.
   287  // It also unlocks the engine when it returns.
   288  func (se *Engine) closeLocked() {
   289  	// Close the Timer in a separate go routine because
   290  	// there might be a tick after we have acquired the lock above
   291  	// but before closing the timer, in which case Stop function will wait for the
   292  	// configured function to complete running and that function (ReloadAt) will block
   293  	// on the lock we have already acquired
   294  	wg := sync.WaitGroup{}
   295  	wg.Add(1)
   296  	go func() {
   297  		se.ticks.Stop()
   298  		wg.Done()
   299  	}()
   300  	se.historian.Close()
   301  	se.conns.Close()
   302  
   303  	se.tables = make(map[string]*Table)
   304  	se.lastChange = 0
   305  	se.notifiers = make(map[string]notifier)
   306  	se.isOpen = false
   307  
   308  	// Unlock the mutex. If there is a tick blocked on this lock,
   309  	// then it will run and we wait for the Stop function to finish its execution
   310  	se.mu.Unlock()
   311  	wg.Wait()
   312  }
   313  
   314  // MakeNonPrimary clears the sequence caches to make sure that
   315  // they don't get accidentally reused after losing primaryship.
   316  func (se *Engine) MakeNonPrimary() {
   317  	// This function is tested through endtoend test.
   318  	se.mu.Lock()
   319  	defer se.mu.Unlock()
   320  	for _, t := range se.tables {
   321  		if t.SequenceInfo != nil {
   322  			t.SequenceInfo.Lock()
   323  			t.SequenceInfo.NextVal = 0
   324  			t.SequenceInfo.LastVal = 0
   325  			t.SequenceInfo.Unlock()
   326  		}
   327  	}
   328  }
   329  
   330  // EnableHistorian forces tracking to be on or off.
   331  // Only used for testing.
   332  func (se *Engine) EnableHistorian(enabled bool) error {
   333  	return se.historian.Enable(enabled)
   334  }
   335  
   336  // Reload reloads the schema info from the db.
   337  // Any tables that have changed since the last load are updated.
   338  // The includeStats argument controls whether table size statistics should be
   339  // emitted, as they can be expensive to calculate for a large number of tables
   340  func (se *Engine) Reload(ctx context.Context) error {
   341  	return se.ReloadAt(ctx, mysql.Position{})
   342  }
   343  
   344  // ReloadAt reloads the schema info from the db.
   345  // Any tables that have changed since the last load are updated.
   346  // It maintains the position at which the schema was reloaded and if the same position is provided
   347  // (say by multiple vstreams) it returns the cached schema. In case of a newer or empty pos it always reloads the schema
   348  func (se *Engine) ReloadAt(ctx context.Context, pos mysql.Position) error {
   349  	return se.ReloadAtEx(ctx, pos, true)
   350  }
   351  
   352  // ReloadAtEx reloads the schema info from the db.
   353  // Any tables that have changed since the last load are updated.
   354  // It maintains the position at which the schema was reloaded and if the same position is provided
   355  // (say by multiple vstreams) it returns the cached schema. In case of a newer or empty pos it always reloads the schema
   356  // The includeStats argument controls whether table size statistics should be
   357  // emitted, as they can be expensive to calculate for a large number of tables
   358  func (se *Engine) ReloadAtEx(ctx context.Context, pos mysql.Position, includeStats bool) error {
   359  	se.mu.Lock()
   360  	defer se.mu.Unlock()
   361  	if !se.isOpen {
   362  		log.Warning("Schema reload called for an engine that is not yet open")
   363  		return nil
   364  	}
   365  	if !pos.IsZero() && se.reloadAtPos.AtLeast(pos) {
   366  		log.V(2).Infof("ReloadAtEx: found cached schema at %s", mysql.EncodePosition(pos))
   367  		return nil
   368  	}
   369  	if err := se.reload(ctx, includeStats); err != nil {
   370  		return err
   371  	}
   372  	se.reloadAtPos = pos
   373  	return nil
   374  }
   375  
   376  // reload reloads the schema. It can also be used to initialize it.
   377  func (se *Engine) reload(ctx context.Context, includeStats bool) error {
   378  	start := time.Now()
   379  	defer func() {
   380  		se.env.LogError()
   381  		se.SchemaReloadTimings.Record("SchemaReload", start)
   382  	}()
   383  
   384  	conn, err := se.conns.Get(ctx, nil)
   385  	if err != nil {
   386  		return err
   387  	}
   388  	defer conn.Recycle()
   389  
   390  	// curTime will be saved into lastChange after schema is loaded.
   391  	curTime, err := se.mysqlTime(ctx, conn)
   392  	if err != nil {
   393  		return err
   394  	}
   395  	// if this flag is set, then we don't need table meta information
   396  	if se.SkipMetaCheck {
   397  		return nil
   398  	}
   399  
   400  	var showTablesQuery string
   401  	if includeStats {
   402  		showTablesQuery = conn.BaseShowTablesWithSizes()
   403  	} else {
   404  		showTablesQuery = conn.BaseShowTables()
   405  	}
   406  	tableData, err := conn.Exec(ctx, showTablesQuery, maxTableCount, false)
   407  	if err != nil {
   408  		return vterrors.Wrapf(err, "in Engine.reload(), reading tables")
   409  	}
   410  
   411  	err = se.updateInnoDBRowsRead(ctx, conn)
   412  	if err != nil {
   413  		return err
   414  	}
   415  
   416  	rec := concurrency.AllErrorRecorder{}
   417  	// curTables keeps track of tables in the new snapshot so we can detect what was dropped.
   418  	curTables := map[string]bool{"dual": true}
   419  	// changedTables keeps track of tables that have changed so we can reload their pk info.
   420  	changedTables := make(map[string]*Table)
   421  	// created and altered contain the names of created and altered tables for broadcast.
   422  	var created, altered []string
   423  	for _, row := range tableData.Rows {
   424  		tableName := row[0].ToString()
   425  		curTables[tableName] = true
   426  		createTime, _ := evalengine.ToInt64(row[2])
   427  		var fileSize, allocatedSize uint64
   428  
   429  		if includeStats {
   430  			fileSize, _ = evalengine.ToUint64(row[4])
   431  			allocatedSize, _ = evalengine.ToUint64(row[5])
   432  			// publish the size metrics
   433  			se.tableFileSizeGauge.Set(tableName, int64(fileSize))
   434  			se.tableAllocatedSizeGauge.Set(tableName, int64(allocatedSize))
   435  		}
   436  
   437  		// Table schemas are cached by tabletserver. For each table we cache `information_schema.tables.create_time` (`tbl.CreateTime`).
   438  		// We also record the last time the schema was loaded (`se.lastChange`). Both are in seconds. We reload a table only when:
   439  		//   1. A table's underlying mysql metadata has changed: `se.lastChange >= createTime`. This can happen if a table was directly altered.
   440  		//      Note that we also reload if `se.lastChange == createTime` since it is possible, especially in unit tests,
   441  		//      that a table might be changed multiple times within the same second.
   442  		//
   443  		//   2. A table was swapped in by Online DDL: `createTime != tbl.CreateTime`. When an Online DDL migration is completed the temporary table is
   444  		//      renamed to the table being altered. `se.lastChange` is updated every time the schema is reloaded (default: 30m).
   445  		//      Online DDL can take hours. So it is possible that the `create_time` of the temporary table is before se.lastChange. Hence,
   446  		//      #1 will not identify the renamed table as a changed one.
   447  		tbl, isInTablesMap := se.tables[tableName]
   448  		if isInTablesMap && createTime == tbl.CreateTime && createTime < se.lastChange {
   449  			if includeStats {
   450  				tbl.FileSize = fileSize
   451  				tbl.AllocatedSize = allocatedSize
   452  			}
   453  			continue
   454  		}
   455  
   456  		log.V(2).Infof("Reading schema for table: %s", tableName)
   457  		table, err := LoadTable(conn, se.cp.DBName(), tableName, row[3].ToString())
   458  		if err != nil {
   459  			rec.RecordError(vterrors.Wrapf(err, "in Engine.reload(), reading table %s", tableName))
   460  			continue
   461  		}
   462  		if includeStats {
   463  			table.FileSize = fileSize
   464  			table.AllocatedSize = allocatedSize
   465  		}
   466  		table.CreateTime = createTime
   467  		changedTables[tableName] = table
   468  		if isInTablesMap {
   469  			altered = append(altered, tableName)
   470  		} else {
   471  			created = append(created, tableName)
   472  		}
   473  	}
   474  	if rec.HasErrors() {
   475  		return rec.Error()
   476  	}
   477  
   478  	// Compute and handle dropped tables.
   479  	var dropped []string
   480  	for tableName := range se.tables {
   481  		if !curTables[tableName] {
   482  			dropped = append(dropped, tableName)
   483  			delete(se.tables, tableName)
   484  			// We can't actually delete the label from the stats, but we can set it to 0.
   485  			// Many monitoring tools will drop zero-valued metrics.
   486  			se.tableFileSizeGauge.Reset(tableName)
   487  			se.tableAllocatedSizeGauge.Reset(tableName)
   488  		}
   489  	}
   490  
   491  	// Populate PKColumns for changed tables.
   492  	if err := se.populatePrimaryKeys(ctx, conn, changedTables); err != nil {
   493  		return err
   494  	}
   495  
   496  	// Update se.tables
   497  	for k, t := range changedTables {
   498  		se.tables[k] = t
   499  	}
   500  	se.lastChange = curTime
   501  	if len(created) > 0 || len(altered) > 0 || len(dropped) > 0 {
   502  		log.Infof("schema engine created %v, altered %v, dropped %v", created, altered, dropped)
   503  	}
   504  	se.broadcast(created, altered, dropped)
   505  	return nil
   506  }
   507  
   508  func (se *Engine) updateInnoDBRowsRead(ctx context.Context, conn *connpool.DBConn) error {
   509  	readRowsData, err := conn.Exec(ctx, mysql.ShowRowsRead, 10, false)
   510  	if err != nil {
   511  		return err
   512  	}
   513  
   514  	if len(readRowsData.Rows) == 1 && len(readRowsData.Rows[0]) == 2 {
   515  		value, err := evalengine.ToInt64(readRowsData.Rows[0][1])
   516  		if err != nil {
   517  			return err
   518  		}
   519  
   520  		se.innoDbReadRowsCounter.Set(value)
   521  	} else {
   522  		log.Warningf("got strange results from 'show status': %v", readRowsData.Rows)
   523  	}
   524  	return nil
   525  }
   526  
   527  func (se *Engine) mysqlTime(ctx context.Context, conn *connpool.DBConn) (int64, error) {
   528  	// Keep `SELECT UNIX_TIMESTAMP` is in uppercase because binlog server queries are case sensitive and expect it to be so.
   529  	tm, err := conn.Exec(ctx, "SELECT UNIX_TIMESTAMP()", 1, false)
   530  	if err != nil {
   531  		return 0, vterrors.Errorf(vtrpcpb.Code_UNKNOWN, "could not get MySQL time: %v", err)
   532  	}
   533  	if len(tm.Rows) != 1 || len(tm.Rows[0]) != 1 || tm.Rows[0][0].IsNull() {
   534  		return 0, vterrors.Errorf(vtrpcpb.Code_UNKNOWN, "unexpected result for MySQL time: %+v", tm.Rows)
   535  	}
   536  	t, err := evalengine.ToInt64(tm.Rows[0][0])
   537  	if err != nil {
   538  		return 0, vterrors.Errorf(vtrpcpb.Code_UNKNOWN, "could not parse time %v: %v", tm, err)
   539  	}
   540  	return t, nil
   541  }
   542  
   543  // populatePrimaryKeys populates the PKColumns for the specified tables.
   544  func (se *Engine) populatePrimaryKeys(ctx context.Context, conn *connpool.DBConn, tables map[string]*Table) error {
   545  	pkData, err := conn.Exec(ctx, mysql.BaseShowPrimary, maxTableCount, false)
   546  	if err != nil {
   547  		return vterrors.Errorf(vtrpcpb.Code_UNKNOWN, "could not get table primary key info: %v", err)
   548  	}
   549  	for _, row := range pkData.Rows {
   550  		tableName := row[0].ToString()
   551  		table, ok := tables[tableName]
   552  		if !ok {
   553  			continue
   554  		}
   555  		colName := row[1].ToString()
   556  		index := table.FindColumn(sqlparser.NewIdentifierCI(colName))
   557  		if index < 0 {
   558  			return vterrors.Errorf(vtrpcpb.Code_INTERNAL, "column %v is listed as primary key, but not present in table %v", colName, tableName)
   559  		}
   560  		table.PKColumns = append(table.PKColumns, index)
   561  	}
   562  	return nil
   563  }
   564  
   565  // RegisterVersionEvent is called by the vstream when it encounters a version event (an insert into _vt.schema_tracking)
   566  // It triggers the historian to load the newer rows from the database to update its cache
   567  func (se *Engine) RegisterVersionEvent() error {
   568  	return se.historian.RegisterVersionEvent()
   569  }
   570  
   571  // GetTableForPos returns a best-effort schema for a specific gtid
   572  func (se *Engine) GetTableForPos(tableName sqlparser.IdentifierCS, gtid string) (*binlogdatapb.MinimalTable, error) {
   573  	mt, err := se.historian.GetTableForPos(tableName, gtid)
   574  	if err != nil {
   575  		log.Infof("GetTableForPos returned error: %s", err.Error())
   576  		return nil, err
   577  	}
   578  	if mt != nil {
   579  		return mt, nil
   580  	}
   581  	se.mu.Lock()
   582  	defer se.mu.Unlock()
   583  	tableNameStr := tableName.String()
   584  	st, ok := se.tables[tableNameStr]
   585  	if !ok {
   586  		if schema.IsInternalOperationTableName(tableNameStr) {
   587  			log.Infof("internal table %v found in vttablet schema: skipping for GTID search", tableNameStr)
   588  		} else {
   589  			log.Infof("table %v not found in vttablet schema, current tables: %v", tableNameStr, se.tables)
   590  			return nil, fmt.Errorf("table %v not found in vttablet schema", tableNameStr)
   591  		}
   592  	}
   593  	return newMinimalTable(st), nil
   594  }
   595  
   596  // RegisterNotifier registers the function for schema change notification.
   597  // It also causes an immediate notification to the caller. The notified
   598  // function must not change the map or its contents. The only exception
   599  // is the sequence table where the values can be changed using the lock.
   600  func (se *Engine) RegisterNotifier(name string, f notifier) {
   601  	if !se.isOpen {
   602  		return
   603  	}
   604  
   605  	se.notifierMu.Lock()
   606  	defer se.notifierMu.Unlock()
   607  
   608  	se.notifiers[name] = f
   609  	var created []string
   610  	for tableName := range se.tables {
   611  		created = append(created, tableName)
   612  	}
   613  	f(se.tables, created, nil, nil)
   614  }
   615  
   616  // UnregisterNotifier unregisters the notifier function.
   617  func (se *Engine) UnregisterNotifier(name string) {
   618  	if !se.isOpen {
   619  		log.Infof("schema Engine is not open")
   620  		return
   621  	}
   622  
   623  	log.Infof("schema Engine - acquiring notifierMu lock")
   624  	se.notifierMu.Lock()
   625  	log.Infof("schema Engine - acquired notifierMu lock")
   626  	defer se.notifierMu.Unlock()
   627  
   628  	delete(se.notifiers, name)
   629  	log.Infof("schema Engine - finished UnregisterNotifier")
   630  }
   631  
   632  // broadcast must be called while holding a lock on se.mu.
   633  func (se *Engine) broadcast(created, altered, dropped []string) {
   634  	if !se.isOpen {
   635  		return
   636  	}
   637  
   638  	se.notifierMu.Lock()
   639  	defer se.notifierMu.Unlock()
   640  	s := make(map[string]*Table, len(se.tables))
   641  	for k, v := range se.tables {
   642  		s[k] = v
   643  	}
   644  	for _, f := range se.notifiers {
   645  		f(s, created, altered, dropped)
   646  	}
   647  }
   648  
   649  // GetTable returns the info for a table.
   650  func (se *Engine) GetTable(tableName sqlparser.IdentifierCS) *Table {
   651  	se.mu.Lock()
   652  	defer se.mu.Unlock()
   653  	return se.tables[tableName.String()]
   654  }
   655  
   656  // GetSchema returns the current schema. The Tables are a
   657  // shared data structure and must be treated as read-only.
   658  func (se *Engine) GetSchema() map[string]*Table {
   659  	se.mu.Lock()
   660  	defer se.mu.Unlock()
   661  	tables := make(map[string]*Table, len(se.tables))
   662  	for k, v := range se.tables {
   663  		tables[k] = v
   664  	}
   665  	return tables
   666  }
   667  
   668  // GetConnection returns a connection from the pool
   669  func (se *Engine) GetConnection(ctx context.Context) (*connpool.DBConn, error) {
   670  	return se.conns.Get(ctx, nil)
   671  }
   672  
   673  func (se *Engine) handleDebugSchema(response http.ResponseWriter, request *http.Request) {
   674  	if err := acl.CheckAccessHTTP(request, acl.DEBUGGING); err != nil {
   675  		acl.SendError(response, err)
   676  		return
   677  	}
   678  	se.handleHTTPSchema(response)
   679  }
   680  
   681  func (se *Engine) handleHTTPSchema(response http.ResponseWriter) {
   682  	// Ensure schema engine is Open. If vttablet came up in a non_serving role,
   683  	// the schema engine may not have been initialized.
   684  	err := se.Open()
   685  	if err != nil {
   686  		response.Write([]byte(err.Error()))
   687  		return
   688  	}
   689  
   690  	response.Header().Set("Content-Type", "application/json; charset=utf-8")
   691  	b, err := json.MarshalIndent(se.GetSchema(), "", " ")
   692  	if err != nil {
   693  		response.Write([]byte(err.Error()))
   694  		return
   695  	}
   696  	buf := bytes.NewBuffer(nil)
   697  	json.HTMLEscape(buf, b)
   698  	response.Write(buf.Bytes())
   699  }
   700  
   701  // Test methods. Do not use in non-test code.
   702  
   703  // NewEngineForTests creates a new engine, that can't query the
   704  // database, and will not send notifications. It starts opened, and
   705  // doesn't reload.  Use SetTableForTests to set table schema.
   706  func NewEngineForTests() *Engine {
   707  	se := &Engine{
   708  		isOpen:    true,
   709  		tables:    make(map[string]*Table),
   710  		historian: newHistorian(false, nil),
   711  	}
   712  	return se
   713  }
   714  
   715  // SetTableForTests puts a Table in the map directly.
   716  func (se *Engine) SetTableForTests(table *Table) {
   717  	se.mu.Lock()
   718  	defer se.mu.Unlock()
   719  	se.tables[table.Name.String()] = table
   720  }