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 }