vitess.io/vitess@v0.16.2/go/vt/vttest/local_cluster.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 vttest
    18  
    19  import (
    20  	"bufio"
    21  	"bytes"
    22  	"context"
    23  	"encoding/json"
    24  	"fmt"
    25  	"os"
    26  	"os/exec"
    27  	"path"
    28  	"path/filepath"
    29  	"strings"
    30  	"time"
    31  	"unicode"
    32  
    33  	"vitess.io/vitess/go/vt/sidecardb"
    34  
    35  	"google.golang.org/protobuf/encoding/protojson"
    36  	"google.golang.org/protobuf/encoding/prototext"
    37  	"google.golang.org/protobuf/proto"
    38  
    39  	"vitess.io/vitess/go/mysql"
    40  	"vitess.io/vitess/go/sqltypes"
    41  	"vitess.io/vitess/go/vt/log"
    42  	"vitess.io/vitess/go/vt/proto/logutil"
    43  	"vitess.io/vitess/go/vt/vtctl/vtctlclient"
    44  
    45  	vschemapb "vitess.io/vitess/go/vt/proto/vschema"
    46  	vttestpb "vitess.io/vitess/go/vt/proto/vttest"
    47  
    48  	// we need to import the grpcvtctlclient library so the gRPC
    49  	// vtctl client is registered and can be used.
    50  	_ "vitess.io/vitess/go/vt/vtctl/grpcvtctlclient"
    51  )
    52  
    53  // Config are the settings used to configure the self-contained Vitess cluster.
    54  // The LocalCluster struct embeds Config so it's possible to either initialize
    55  // a LocalCluster with the given settings, or set the settings directly after
    56  // initialization.
    57  // All settings must be set before LocalCluster.Setup() is called.
    58  type Config struct {
    59  	// Topology defines the fake cluster's topology. This field is mandatory.
    60  	// See: vt/proto/vttest.VTTestTopology
    61  	Topology *vttestpb.VTTestTopology
    62  
    63  	// Seed can be set with a SeedConfig struct to enable
    64  	// auto-initialization of the database cluster with random data.
    65  	// If nil, no random initialization will be performed.
    66  	// See: SeedConfig
    67  	Seed *SeedConfig
    68  
    69  	// SchemaDir is the directory for schema files. Within this dir,
    70  	// there should be a subdir for each keyspace. Within each keyspace
    71  	// dir, each file is executed as SQL after the database is created on
    72  	// each shard.
    73  	// If the directory contains a `vschema.json`` file, it will be used
    74  	// as the VSchema for the V3 API
    75  	SchemaDir string
    76  
    77  	// DefaultSchemaDir is the default directory for initial schema files.
    78  	// If no schema is found in SchemaDir, default to this location.
    79  	DefaultSchemaDir string
    80  
    81  	// DataDir is the directory where the data files will be placed.
    82  	// If no directory is specified a random directory will be used
    83  	// under VTDATAROOT.
    84  	DataDir string
    85  
    86  	// Charset is the default charset used by MySQL
    87  	Charset string
    88  
    89  	// PlannerVersion is the planner version to use for the vtgate.
    90  	// Choose between V3, Gen4, Gen4Greedy and Gen4Fallback
    91  	PlannerVersion string
    92  
    93  	// ExtraMyCnf are the extra .CNF files to be added to the MySQL config
    94  	ExtraMyCnf []string
    95  
    96  	// OnlyMySQL can be set so only MySQL is initialized as part of the
    97  	// local cluster configuration. The rest of the Vitess components will
    98  	// not be started.
    99  	OnlyMySQL bool
   100  
   101  	// PersistentMode can be set so that MySQL data directory is not cleaned up
   102  	// when LocalCluster.TearDown() is called. This is useful for running
   103  	// vttestserver as a database container in local developer environments. Note
   104  	// that db and vschema migration files (-schema_dir option) and seeding of
   105  	// random data (-initialize_with_random_data option) will only run during
   106  	// cluster startup if the data directory does not already exist.
   107  	PersistentMode bool
   108  
   109  	// MySQL protocol bind address.
   110  	// vtcombo will bind to this address when exposing the mysql protocol socket
   111  	MySQLBindHost string
   112  	// SnapshotFile is the path to the MySQL Snapshot that will be used to
   113  	// initialize the mysqld instance in the cluster. Note that some environments
   114  	// do not suppport initialization through snapshot files.
   115  	SnapshotFile string
   116  
   117  	// Enable system settings to be changed per session at the database connection level
   118  	EnableSystemSettings bool
   119  
   120  	// TransactionMode is SINGLE, MULTI or TWOPC
   121  	TransactionMode string
   122  
   123  	TransactionTimeout float64
   124  
   125  	// The host name to use for the table otherwise it will be resolved from the local hostname
   126  	TabletHostName string
   127  
   128  	// Whether to enable/disable workflow manager
   129  	InitWorkflowManager bool
   130  
   131  	// Authorize vschema ddl operations to a list of users
   132  	VSchemaDDLAuthorizedUsers string
   133  
   134  	// How to handle foreign key constraint in CREATE/ALTER TABLE.  Valid values are "allow", "disallow"
   135  	ForeignKeyMode string
   136  
   137  	// Allow users to submit, view, and control Online DDL
   138  	EnableOnlineDDL bool
   139  
   140  	// Allow users to submit direct DDL statements
   141  	EnableDirectDDL bool
   142  
   143  	// Allow users to start a local cluster using a remote topo server
   144  	ExternalTopoImplementation string
   145  
   146  	ExternalTopoGlobalServerAddress string
   147  
   148  	ExternalTopoGlobalRoot string
   149  
   150  	VtgateTabletRefreshInterval time.Duration
   151  }
   152  
   153  // InitSchemas is a shortcut for tests that just want to setup a single
   154  // keyspace with a single SQL file, and/or a vschema.
   155  // It creates a temporary directory, and puts the schema/vschema in there.
   156  // It then sets the right value for cfg.SchemaDir.
   157  // At the end of the test, the caller should os.RemoveAll(cfg.SchemaDir).
   158  func (cfg *Config) InitSchemas(keyspace, schema string, vschema *vschemapb.Keyspace) error {
   159  	if cfg.SchemaDir != "" {
   160  		return fmt.Errorf("SchemaDir is already set to %v", cfg.SchemaDir)
   161  	}
   162  
   163  	// Create a base temporary directory.
   164  	tempSchemaDir, err := os.MkdirTemp("", "vttest")
   165  	if err != nil {
   166  		return err
   167  	}
   168  
   169  	// Write the schema if set.
   170  	if schema != "" {
   171  		ksDir := path.Join(tempSchemaDir, keyspace)
   172  		err = os.Mkdir(ksDir, os.ModeDir|0775)
   173  		if err != nil {
   174  			return err
   175  		}
   176  		fileName := path.Join(ksDir, "schema.sql")
   177  		err = os.WriteFile(fileName, []byte(schema), 0666)
   178  		if err != nil {
   179  			return err
   180  		}
   181  	}
   182  
   183  	// Write in the vschema if set.
   184  	if vschema != nil {
   185  		vschemaFilePath := path.Join(tempSchemaDir, keyspace, "vschema.json")
   186  		vschemaJSON, err := json.Marshal(vschema)
   187  		if err != nil {
   188  			return err
   189  		}
   190  		if err := os.WriteFile(vschemaFilePath, vschemaJSON, 0644); err != nil {
   191  			return err
   192  		}
   193  	}
   194  	cfg.SchemaDir = tempSchemaDir
   195  	return nil
   196  }
   197  
   198  // DbName returns the default name for a database in this cluster.
   199  // If OnlyMySQL is set, this will be the name of the single database
   200  // created in MySQL. Otherwise, this will be blank.
   201  func (cfg *Config) DbName() string {
   202  	ns := cfg.Topology.GetKeyspaces()
   203  	if len(ns) > 0 && cfg.OnlyMySQL {
   204  		return ns[0].Name
   205  	}
   206  	return ""
   207  }
   208  
   209  // TopoData is a struct representing a test topology.
   210  //
   211  // It implements pflag.Value and can be used as a destination command-line via
   212  // pflag.Var or pflag.VarP.
   213  type TopoData struct {
   214  	vtTestTopology *vttestpb.VTTestTopology
   215  	unmarshal      func(b []byte, m proto.Message) error
   216  }
   217  
   218  // String is part of the pflag.Value interface.
   219  func (td *TopoData) String() string {
   220  	return prototext.Format(td.vtTestTopology)
   221  }
   222  
   223  // Set is part of the pflag.Value interface.
   224  func (td *TopoData) Set(value string) error {
   225  	return td.unmarshal([]byte(value), td.vtTestTopology)
   226  }
   227  
   228  // Type is part of the pflag.Value interface.
   229  func (td *TopoData) Type() string { return "vttest.TopoData" }
   230  
   231  // TextTopoData returns a test TopoData that unmarshals using
   232  // prototext.Unmarshal.
   233  func TextTopoData(tpb *vttestpb.VTTestTopology) *TopoData {
   234  	return &TopoData{
   235  		vtTestTopology: tpb,
   236  		unmarshal:      prototext.Unmarshal,
   237  	}
   238  }
   239  
   240  // JSONTopoData returns a test TopoData that unmarshals using
   241  // protojson.Unmarshal.
   242  func JSONTopoData(tpb *vttestpb.VTTestTopology) *TopoData {
   243  	return &TopoData{
   244  		vtTestTopology: tpb,
   245  		unmarshal:      protojson.Unmarshal,
   246  	}
   247  }
   248  
   249  // LocalCluster controls a local Vitess setup for testing, containing
   250  // a MySQL instance and one or more vtgate-equivalent access points.
   251  // To use, simply create a new LocalCluster instance and either pass in
   252  // the desired Config, or manually set each field on the struct itself.
   253  // Once the struct is configured, call LocalCluster.Setup() to instantiate
   254  // the cluster.
   255  // See: Config for configuration settings on the cluster
   256  type LocalCluster struct {
   257  	Config
   258  
   259  	// Env is the Environment which will be used for unning this local cluster.
   260  	// It can be set by the user before calling Setup(). If not set, Setup() will
   261  	// use the NewDefaultEnv callback to instantiate an environment with the system
   262  	// default settings
   263  	Env Environment
   264  
   265  	mysql MySQLManager
   266  	topo  TopoManager
   267  	vt    *VtProcess
   268  }
   269  
   270  // MySQLConnParams returns a mysql.ConnParams struct that can be used
   271  // to connect directly to the mysqld service in the self-contained cluster
   272  // This connection should be used for debug/introspection purposes; normal
   273  // cluster access should be performed through the vtgate port.
   274  func (db *LocalCluster) MySQLConnParams() mysql.ConnParams {
   275  	connParams := db.mysql.Params(db.DbName())
   276  	connParams.Charset = db.Config.Charset
   277  	return connParams
   278  }
   279  
   280  // MySQLAppDebugConnParams returns a mysql.ConnParams struct that can be used
   281  // to connect directly to the mysqld service in the self-contained cluster,
   282  // using the appdebug user. It's valid only if you used MySQLOnly option.
   283  func (db *LocalCluster) MySQLAppDebugConnParams() mysql.ConnParams {
   284  	connParams := db.MySQLConnParams()
   285  	connParams.Uname = "vt_appdebug"
   286  	return connParams
   287  }
   288  
   289  // Setup brings up the self-contained Vitess cluster by spinning up
   290  // MySQL and Vitess instances. The spawned processes will be running
   291  // until the TearDown() method is called.
   292  // Please ensure to `defer db.TearDown()` after calling this method
   293  func (db *LocalCluster) Setup() error {
   294  	var err error
   295  
   296  	if db.Env == nil {
   297  		log.Info("No environment in cluster settings. Creating default...")
   298  		db.Env, err = NewDefaultEnv()
   299  		if err != nil {
   300  			return err
   301  		}
   302  	}
   303  
   304  	log.Infof("LocalCluster environment: %+v", db.Env)
   305  
   306  	// Set up topo manager if we are using a remote topo server
   307  	if db.ExternalTopoImplementation != "" {
   308  		db.topo = db.Env.TopoManager(db.ExternalTopoImplementation, db.ExternalTopoGlobalServerAddress, db.ExternalTopoGlobalRoot, db.Topology)
   309  		log.Infof("Initializing Topo Manager: %+v", db.topo)
   310  		if err := db.topo.Setup(); err != nil {
   311  			log.Errorf("Failed to set up Topo Manager: %v", err)
   312  			return err
   313  		}
   314  	}
   315  
   316  	db.mysql, err = db.Env.MySQLManager(db.ExtraMyCnf, db.SnapshotFile)
   317  	if err != nil {
   318  		return err
   319  	}
   320  
   321  	initializing := true
   322  	if db.PersistentMode && dirExist(db.mysql.TabletDir()) {
   323  		initializing = false
   324  	}
   325  
   326  	if initializing {
   327  		log.Infof("Initializing MySQL Manager (%T)...", db.mysql)
   328  		if err := db.mysql.Setup(); err != nil {
   329  			log.Errorf("Mysqlctl failed to start: %s", err)
   330  			if err, ok := err.(*exec.ExitError); ok {
   331  				log.Errorf("stderr: %s", err.Stderr)
   332  			}
   333  			return err
   334  		}
   335  
   336  		if err := db.createDatabases(); err != nil {
   337  			return err
   338  		}
   339  	} else {
   340  		log.Infof("Starting MySQL Manager (%T)...", db.mysql)
   341  		if err := db.mysql.Start(); err != nil {
   342  			log.Errorf("Mysqlctl failed to start: %s", err)
   343  			if err, ok := err.(*exec.ExitError); ok {
   344  				log.Errorf("stderr: %s", err.Stderr)
   345  			}
   346  			return err
   347  		}
   348  	}
   349  
   350  	mycfg, _ := json.Marshal(db.mysql.Params(""))
   351  	log.Infof("MySQL up: %s", mycfg)
   352  
   353  	if !db.OnlyMySQL {
   354  		log.Infof("Starting vtcombo...")
   355  		db.vt, _ = VtcomboProcess(db.Env, &db.Config, db.mysql)
   356  		if err := db.vt.WaitStart(); err != nil {
   357  			return err
   358  		}
   359  		log.Infof("vtcombo up: %s", db.vt.Address())
   360  	}
   361  
   362  	if initializing {
   363  		log.Info("Mysql data directory does not exist. Initializing cluster with database and vschema migrations...")
   364  		// Load schema will apply db and vschema migrations. Running after vtcombo starts to be able to apply vschema migrations
   365  		if err := db.loadSchema(true); err != nil {
   366  			return err
   367  		}
   368  
   369  		if db.Seed != nil {
   370  			log.Info("Populating database with random data...")
   371  			if err := db.populateWithRandomData(); err != nil {
   372  				return err
   373  			}
   374  		}
   375  	} else {
   376  		log.Info("Mysql data directory exists in persistent mode. Will only execute vschema migrations during startup")
   377  		if err := db.loadSchema(false); err != nil {
   378  			return err
   379  		}
   380  	}
   381  
   382  	return nil
   383  }
   384  
   385  // TearDown shuts down all the processes in the local cluster
   386  // and cleans up any temporary on-disk data.
   387  // If an error is returned, some of the running processes may not
   388  // have been shut down cleanly and may need manual cleanup.
   389  func (db *LocalCluster) TearDown() error {
   390  	var errors []string
   391  
   392  	if db.vt != nil {
   393  		if err := db.vt.WaitTerminate(); err != nil {
   394  			errors = append(errors, fmt.Sprintf("vtprocess: %s", err))
   395  		}
   396  	}
   397  
   398  	if err := db.mysql.TearDown(); err != nil {
   399  		errors = append(errors, fmt.Sprintf("mysql: %s", err))
   400  
   401  		log.Errorf("failed to shutdown MySQL: %s", err)
   402  		if err, ok := err.(*exec.ExitError); ok {
   403  			log.Errorf("stderr: %s", err.Stderr)
   404  		}
   405  	}
   406  
   407  	if !db.PersistentMode {
   408  		if err := db.Env.TearDown(); err != nil {
   409  			errors = append(errors, fmt.Sprintf("environment: %s", err))
   410  		}
   411  	}
   412  
   413  	if len(errors) > 0 {
   414  		return fmt.Errorf("failed to teardown LocalCluster:\n%s",
   415  			strings.Join(errors, "\n"))
   416  	}
   417  
   418  	return nil
   419  }
   420  
   421  func (db *LocalCluster) shardNames(keyspace *vttestpb.Keyspace) (names []string) {
   422  	for _, spb := range keyspace.Shards {
   423  		dbname := spb.DbNameOverride
   424  		if dbname == "" {
   425  			dbname = fmt.Sprintf("vt_%s_%s", keyspace.Name, spb.Name)
   426  		}
   427  		names = append(names, dbname)
   428  	}
   429  	return
   430  }
   431  
   432  func isDir(path string) bool {
   433  	info, err := os.Stat(path)
   434  	return err == nil && info.IsDir()
   435  }
   436  
   437  // loadSchema applies sql and vschema migrations respectively for each keyspace in the topology
   438  func (db *LocalCluster) loadSchema(shouldRunDatabaseMigrations bool) error {
   439  	if db.SchemaDir == "" {
   440  		return nil
   441  	}
   442  
   443  	log.Info("Loading custom schema...")
   444  
   445  	if !isDir(db.SchemaDir) {
   446  		return fmt.Errorf("LoadSchema(): SchemaDir does not exist")
   447  	}
   448  
   449  	for _, kpb := range db.Topology.Keyspaces {
   450  		if kpb.ServedFrom != "" {
   451  			// redirected keyspaces have no underlying database
   452  			continue
   453  		}
   454  
   455  		keyspace := kpb.Name
   456  		keyspaceDir := path.Join(db.SchemaDir, keyspace)
   457  
   458  		schemaDir := keyspaceDir
   459  		if !isDir(schemaDir) {
   460  			schemaDir = db.DefaultSchemaDir
   461  			if schemaDir == "" || !isDir(schemaDir) {
   462  				return fmt.Errorf("LoadSchema: schema dir for ks `%s` does not exist (%s)", keyspace, schemaDir)
   463  			}
   464  		}
   465  
   466  		glob, _ := filepath.Glob(path.Join(schemaDir, "*.sql"))
   467  		for _, filepath := range glob {
   468  			cmds, err := LoadSQLFile(filepath, schemaDir)
   469  			if err != nil {
   470  				return err
   471  			}
   472  
   473  			// One single vschema migration per file
   474  			if !db.OnlyMySQL && len(cmds) == 1 && strings.HasPrefix(strings.ToUpper(cmds[0]), "ALTER VSCHEMA") {
   475  				if err = db.applyVschema(keyspace, cmds[0]); err != nil {
   476  					return err
   477  				}
   478  				continue
   479  			}
   480  
   481  			if !shouldRunDatabaseMigrations {
   482  				continue
   483  			}
   484  
   485  			for _, dbname := range db.shardNames(kpb) {
   486  				if err := db.Execute(cmds, dbname); err != nil {
   487  					return err
   488  				}
   489  			}
   490  		}
   491  
   492  		if !db.OnlyMySQL {
   493  			if err := db.reloadSchemaKeyspace(keyspace); err != nil {
   494  				return err
   495  			}
   496  		}
   497  	}
   498  
   499  	return nil
   500  }
   501  
   502  func (db *LocalCluster) createVTSchema() error {
   503  	var sidecardbExec sidecardb.Exec = func(ctx context.Context, query string, maxRows int, useDB bool) (*sqltypes.Result, error) {
   504  		if useDB {
   505  			if err := db.Execute([]string{sidecardb.UseSidecarDatabaseQuery}, ""); err != nil {
   506  				return nil, err
   507  			}
   508  		}
   509  		return db.ExecuteFetch(query, "")
   510  	}
   511  
   512  	if err := sidecardb.Init(context.Background(), sidecardbExec); err != nil {
   513  		return err
   514  	}
   515  	return nil
   516  }
   517  func (db *LocalCluster) createDatabases() error {
   518  	log.Info("Creating databases in cluster...")
   519  
   520  	// The tablets created in vttest do not follow the same tablet init process, so we need to explicitly create
   521  	// the sidecar database tables
   522  	if err := db.createVTSchema(); err != nil {
   523  		return err
   524  	}
   525  
   526  	var sql []string
   527  	for _, kpb := range db.Topology.Keyspaces {
   528  		if kpb.ServedFrom != "" {
   529  			continue
   530  		}
   531  		for _, dbname := range db.shardNames(kpb) {
   532  			sql = append(sql, fmt.Sprintf("create database `%s`", dbname))
   533  		}
   534  	}
   535  	return db.Execute(sql, "")
   536  }
   537  
   538  // Execute runs a series of SQL statements on the MySQL instance backing
   539  // this local cluster. This is provided for debug/introspection purposes;
   540  // normal cluster access should be performed through the Vitess GRPC interface.
   541  func (db *LocalCluster) Execute(sql []string, dbname string) error {
   542  	params := db.mysql.Params(dbname)
   543  	conn, err := mysql.Connect(context.Background(), &params)
   544  	if err != nil {
   545  		return err
   546  	}
   547  	defer conn.Close()
   548  
   549  	_, err = conn.ExecuteFetch("START TRANSACTION", 0, false)
   550  	if err != nil {
   551  		return err
   552  	}
   553  
   554  	for _, cmd := range sql {
   555  		log.Infof("Execute(%s): \"%s\"", dbname, cmd)
   556  		_, err := conn.ExecuteFetch(cmd, -1, false)
   557  		if err != nil {
   558  			return err
   559  		}
   560  	}
   561  
   562  	_, err = conn.ExecuteFetch("COMMIT", 0, false)
   563  	return err
   564  }
   565  
   566  // ExecuteFetch runs a SQL statement on the MySQL instance backing
   567  // this local cluster and returns the result.
   568  func (db *LocalCluster) ExecuteFetch(sql string, dbname string) (*sqltypes.Result, error) {
   569  	params := db.mysql.Params(dbname)
   570  	conn, err := mysql.Connect(context.Background(), &params)
   571  	if err != nil {
   572  		return nil, err
   573  	}
   574  	defer conn.Close()
   575  
   576  	log.Infof("ExecuteFetch(%s): \"%s\"", dbname, sql)
   577  	rs, err := conn.ExecuteFetch(sql, -1, true)
   578  	return rs, err
   579  }
   580  
   581  // Query runs a  SQL query on the MySQL instance backing this local cluster and returns
   582  // its result. This is provided for debug/introspection purposes;
   583  // normal cluster access should be performed through the Vitess GRPC interface.
   584  func (db *LocalCluster) Query(sql, dbname string, limit int) (*sqltypes.Result, error) {
   585  	params := db.mysql.Params(dbname)
   586  	conn, err := mysql.Connect(context.Background(), &params)
   587  	if err != nil {
   588  		return nil, err
   589  	}
   590  	defer conn.Close()
   591  
   592  	return conn.ExecuteFetch(sql, limit, false)
   593  }
   594  
   595  // JSONConfig returns a key/value object with the configuration
   596  // settings for the local cluster. It should be serialized with
   597  // `json.Marshal`
   598  func (db *LocalCluster) JSONConfig() any {
   599  	if db.OnlyMySQL {
   600  		return db.mysql.Params("")
   601  	}
   602  
   603  	config := map[string]any{
   604  		"port":               db.vt.Port,
   605  		"socket":             db.mysql.UnixSocket(),
   606  		"vtcombo_mysql_port": db.Env.PortForProtocol("vtcombo_mysql_port", ""),
   607  		"mysql":              db.Env.PortForProtocol("mysql", ""),
   608  	}
   609  
   610  	if grpc := db.vt.PortGrpc; grpc != 0 {
   611  		config["grpc_port"] = grpc
   612  	}
   613  
   614  	return config
   615  }
   616  
   617  // GrpcPort returns the grpc port used by vtcombo
   618  func (db *LocalCluster) GrpcPort() int {
   619  	return db.vt.PortGrpc
   620  }
   621  
   622  func (db *LocalCluster) applyVschema(keyspace string, migration string) error {
   623  	server := fmt.Sprintf("localhost:%v", db.vt.PortGrpc)
   624  	args := []string{"ApplyVSchema", "--sql", migration, keyspace}
   625  	fmt.Printf("Applying vschema %v", args)
   626  	err := vtctlclient.RunCommandAndWait(context.Background(), server, args, func(e *logutil.Event) {
   627  		log.Info(e)
   628  	})
   629  
   630  	return err
   631  }
   632  
   633  func (db *LocalCluster) reloadSchemaKeyspace(keyspace string) error {
   634  	server := fmt.Sprintf("localhost:%v", db.vt.PortGrpc)
   635  	args := []string{"ReloadSchemaKeyspace", "--include_primary=true", keyspace}
   636  	fmt.Printf("Reloading keyspace schema %v", args)
   637  
   638  	err := vtctlclient.RunCommandAndWait(context.Background(), server, args, func(e *logutil.Event) {
   639  		log.Info(e)
   640  	})
   641  
   642  	return err
   643  }
   644  
   645  func dirExist(dir string) bool {
   646  	exist := true
   647  	if _, err := os.Stat(dir); os.IsNotExist(err) {
   648  		exist = false
   649  	}
   650  	return exist
   651  }
   652  
   653  // LoadSQLFile loads a parses a .sql file from disk, removing all the
   654  // different comments that mysql/mysqldump inserts in these, and returning
   655  // each individual SQL statement as its own string.
   656  // If sourceroot is set, that directory will be used when resolving `source `
   657  // statements in the SQL file.
   658  func LoadSQLFile(filename, sourceroot string) ([]string, error) {
   659  	var (
   660  		cmd  bytes.Buffer
   661  		sql  []string
   662  		inSQ bool
   663  		inDQ bool
   664  	)
   665  
   666  	file, err := os.Open(filename)
   667  	if err != nil {
   668  		return nil, err
   669  	}
   670  	defer file.Close()
   671  
   672  	scanner := bufio.NewScanner(file)
   673  	for scanner.Scan() {
   674  		line := scanner.Text()
   675  		line = strings.TrimRightFunc(line, unicode.IsSpace)
   676  
   677  		if !inSQ && !inDQ && strings.HasPrefix(line, "--") {
   678  			continue
   679  		}
   680  
   681  		var i, next int
   682  		for {
   683  			i = next
   684  			if i >= len(line) {
   685  				break
   686  			}
   687  
   688  			next = i + 1
   689  
   690  			if line[i] == '\\' {
   691  				next = i + 2
   692  			} else if line[i] == '\'' && !inDQ {
   693  				inSQ = !inSQ
   694  			} else if line[i] == '"' && !inSQ {
   695  				inDQ = !inDQ
   696  			} else if !inSQ && !inDQ {
   697  				if line[i] == '#' || strings.HasPrefix(line[i:], "-- ") {
   698  					line = line[:i]
   699  					break
   700  				}
   701  				if line[i] == ';' {
   702  					cmd.WriteString(line[:i])
   703  					sql = append(sql, cmd.String())
   704  					cmd.Reset()
   705  
   706  					line = line[i+1:]
   707  					next = 0
   708  				}
   709  			}
   710  		}
   711  
   712  		if strings.TrimSpace(line) != "" {
   713  			if sourceroot != "" && cmd.Len() == 0 && strings.HasPrefix(line, "source ") {
   714  				srcfile := path.Join(sourceroot, line[7:])
   715  				sql2, err := LoadSQLFile(srcfile, sourceroot)
   716  				if err != nil {
   717  					return nil, err
   718  				}
   719  				sql = append(sql, sql2...)
   720  			} else {
   721  				cmd.WriteString(line)
   722  				cmd.WriteByte('\n')
   723  			}
   724  		}
   725  	}
   726  
   727  	if cmd.Len() != 0 {
   728  		sql = append(sql, cmd.String())
   729  	}
   730  
   731  	if err := scanner.Err(); err != nil {
   732  		return nil, err
   733  	}
   734  
   735  	return sql, nil
   736  }