vitess.io/vitess@v0.16.2/go/vt/wrangler/materializer.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 wrangler
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"hash/fnv"
    23  	"math"
    24  	"sort"
    25  	"strings"
    26  	"sync"
    27  	"text/template"
    28  	"time"
    29  
    30  	"google.golang.org/protobuf/encoding/prototext"
    31  	"google.golang.org/protobuf/proto"
    32  
    33  	"vitess.io/vitess/go/json2"
    34  	"vitess.io/vitess/go/sqlescape"
    35  	"vitess.io/vitess/go/sqltypes"
    36  	"vitess.io/vitess/go/vt/binlog/binlogplayer"
    37  	"vitess.io/vitess/go/vt/concurrency"
    38  	"vitess.io/vitess/go/vt/key"
    39  	"vitess.io/vitess/go/vt/log"
    40  	"vitess.io/vitess/go/vt/mysqlctl/tmutils"
    41  	"vitess.io/vitess/go/vt/schema"
    42  	"vitess.io/vitess/go/vt/sqlparser"
    43  	"vitess.io/vitess/go/vt/topo"
    44  	"vitess.io/vitess/go/vt/topotools"
    45  	"vitess.io/vitess/go/vt/vtctl/schematools"
    46  	"vitess.io/vitess/go/vt/vtctl/workflow"
    47  	"vitess.io/vitess/go/vt/vterrors"
    48  	"vitess.io/vitess/go/vt/vtgate/evalengine"
    49  	"vitess.io/vitess/go/vt/vtgate/vindexes"
    50  	"vitess.io/vitess/go/vt/vttablet/tabletmanager/vreplication"
    51  
    52  	binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata"
    53  	querypb "vitess.io/vitess/go/vt/proto/query"
    54  	tabletmanagerdatapb "vitess.io/vitess/go/vt/proto/tabletmanagerdata"
    55  	vschemapb "vitess.io/vitess/go/vt/proto/vschema"
    56  	vtctldatapb "vitess.io/vitess/go/vt/proto/vtctldata"
    57  )
    58  
    59  type materializer struct {
    60  	wr            *Wrangler
    61  	ms            *vtctldatapb.MaterializeSettings
    62  	targetVSchema *vindexes.KeyspaceSchema
    63  	sourceShards  []*topo.ShardInfo
    64  	targetShards  []*topo.ShardInfo
    65  	isPartial     bool
    66  }
    67  
    68  const (
    69  	createDDLAsCopy                = "copy"
    70  	createDDLAsCopyDropConstraint  = "copy:drop_constraint"
    71  	createDDLAsCopyDropForeignKeys = "copy:drop_foreign_keys"
    72  )
    73  
    74  // addTablesToVSchema adds tables to an (unsharded) vschema. Depending on copyAttributes It will also add any sequence info
    75  // that is associated with a table by copying it from the vschema of the source keyspace.
    76  // For a migrate workflow we do not copy attributes since the source keyspace is just a proxy to import data into Vitess
    77  // Todo: For now we only copy sequence but later we may also want to copy other attributes like authoritative column flag and list of columns
    78  func (wr *Wrangler) addTablesToVSchema(ctx context.Context, sourceKeyspace string, targetVSchema *vschemapb.Keyspace, tables []string, copyAttributes bool) error {
    79  	if targetVSchema.Tables == nil {
    80  		targetVSchema.Tables = make(map[string]*vschemapb.Table)
    81  	}
    82  	for _, table := range tables {
    83  		targetVSchema.Tables[table] = &vschemapb.Table{}
    84  	}
    85  
    86  	if copyAttributes { // if source keyspace is provided, copy over the sequence info.
    87  		srcVSchema, err := wr.ts.GetVSchema(ctx, sourceKeyspace)
    88  		if err != nil {
    89  			return err
    90  		}
    91  		for _, table := range tables {
    92  			srcTable, ok := srcVSchema.Tables[table]
    93  			if ok {
    94  				targetVSchema.Tables[table].AutoIncrement = srcTable.AutoIncrement
    95  			}
    96  		}
    97  
    98  	}
    99  	return nil
   100  }
   101  
   102  func shouldInclude(table string, excludes []string) bool {
   103  	// We filter out internal tables elsewhere when processing SchemaDefinition
   104  	// structures built from the GetSchema database related API calls. In this
   105  	// case, however, the table list comes from the user via the -tables flag
   106  	// so we need to filter out internal table names here in case a user has
   107  	// explicitly specified some.
   108  	// This could happen if there's some automated tooling that creates the list of
   109  	// tables to explicitly specify.
   110  	// But given that this should never be done in practice, we ignore the request.
   111  	if schema.IsInternalOperationTableName(table) {
   112  		return false
   113  	}
   114  	for _, t := range excludes {
   115  		if t == table {
   116  			return false
   117  		}
   118  	}
   119  	return true
   120  }
   121  
   122  // MoveTables initiates moving table(s) over to another keyspace
   123  func (wr *Wrangler) MoveTables(ctx context.Context, workflow, sourceKeyspace, targetKeyspace, tableSpecs,
   124  	cell, tabletTypes string, allTables bool, excludeTables string, autoStart, stopAfterCopy bool,
   125  	externalCluster string, dropForeignKeys, deferSecondaryKeys bool, sourceTimeZone, onDDL string, sourceShards []string) error {
   126  	//FIXME validate tableSpecs, allTables, excludeTables
   127  	var tables []string
   128  	var externalTopo *topo.Server
   129  	var err error
   130  
   131  	if externalCluster != "" { // when the source is an external mysql cluster mounted using the Mount command
   132  		externalTopo, err = wr.ts.OpenExternalVitessClusterServer(ctx, externalCluster)
   133  		if err != nil {
   134  			return err
   135  		}
   136  		wr.sourceTs = externalTopo
   137  		log.Infof("Successfully opened external topo: %+v", externalTopo)
   138  	}
   139  
   140  	var vschema *vschemapb.Keyspace
   141  	vschema, err = wr.ts.GetVSchema(ctx, targetKeyspace)
   142  	if err != nil {
   143  		return err
   144  	}
   145  	if vschema == nil {
   146  		return fmt.Errorf("no vschema found for target keyspace %s", targetKeyspace)
   147  	}
   148  	if strings.HasPrefix(tableSpecs, "{") {
   149  		if vschema.Tables == nil {
   150  			vschema.Tables = make(map[string]*vschemapb.Table)
   151  		}
   152  		wrap := fmt.Sprintf(`{"tables": %s}`, tableSpecs)
   153  		ks := &vschemapb.Keyspace{}
   154  		if err := json2.Unmarshal([]byte(wrap), ks); err != nil {
   155  			return err
   156  		}
   157  		for table, vtab := range ks.Tables {
   158  			vschema.Tables[table] = vtab
   159  			tables = append(tables, table)
   160  		}
   161  	} else {
   162  		if len(strings.TrimSpace(tableSpecs)) > 0 {
   163  			tables = strings.Split(tableSpecs, ",")
   164  		}
   165  		ksTables, err := wr.getKeyspaceTables(ctx, sourceKeyspace, wr.sourceTs)
   166  		if err != nil {
   167  			return err
   168  		}
   169  		if len(tables) > 0 {
   170  			err = wr.validateSourceTablesExist(ctx, sourceKeyspace, ksTables, tables)
   171  			if err != nil {
   172  				return err
   173  			}
   174  		} else {
   175  			if allTables {
   176  				tables = ksTables
   177  			} else {
   178  				return fmt.Errorf("no tables to move")
   179  			}
   180  		}
   181  		var excludeTablesList []string
   182  		excludeTables = strings.TrimSpace(excludeTables)
   183  		if excludeTables != "" {
   184  			excludeTablesList = strings.Split(excludeTables, ",")
   185  			err = wr.validateSourceTablesExist(ctx, sourceKeyspace, ksTables, excludeTablesList)
   186  			if err != nil {
   187  				return err
   188  			}
   189  		}
   190  		var tables2 []string
   191  		for _, t := range tables {
   192  			if shouldInclude(t, excludeTablesList) {
   193  				tables2 = append(tables2, t)
   194  			}
   195  		}
   196  		tables = tables2
   197  		if len(tables) == 0 {
   198  			return fmt.Errorf("no tables to move")
   199  		}
   200  		log.Infof("Found tables to move: %s", strings.Join(tables, ","))
   201  
   202  		if !vschema.Sharded {
   203  			if err := wr.addTablesToVSchema(ctx, sourceKeyspace, vschema, tables, externalTopo == nil); err != nil {
   204  				return err
   205  			}
   206  		}
   207  	}
   208  	if externalTopo == nil {
   209  		// Save routing rules before vschema. If we save vschema first, and routing rules
   210  		// fails to save, we may generate duplicate table errors.
   211  		rules, err := topotools.GetRoutingRules(ctx, wr.ts)
   212  		if err != nil {
   213  			return err
   214  		}
   215  		for _, table := range tables {
   216  			toSource := []string{sourceKeyspace + "." + table}
   217  			rules[table] = toSource
   218  			rules[table+"@replica"] = toSource
   219  			rules[table+"@rdonly"] = toSource
   220  			rules[targetKeyspace+"."+table] = toSource
   221  			rules[targetKeyspace+"."+table+"@replica"] = toSource
   222  			rules[targetKeyspace+"."+table+"@rdonly"] = toSource
   223  			rules[targetKeyspace+"."+table] = toSource
   224  			rules[sourceKeyspace+"."+table+"@replica"] = toSource
   225  			rules[sourceKeyspace+"."+table+"@rdonly"] = toSource
   226  		}
   227  		if err := topotools.SaveRoutingRules(ctx, wr.ts, rules); err != nil {
   228  			return err
   229  		}
   230  
   231  		if vschema != nil {
   232  			// We added to the vschema.
   233  			if err := wr.ts.SaveVSchema(ctx, targetKeyspace, vschema); err != nil {
   234  				return err
   235  			}
   236  		}
   237  	}
   238  	if err := wr.ts.RebuildSrvVSchema(ctx, nil); err != nil {
   239  		return err
   240  	}
   241  	ms := &vtctldatapb.MaterializeSettings{
   242  		Workflow:              workflow,
   243  		MaterializationIntent: vtctldatapb.MaterializationIntent_MOVETABLES,
   244  		SourceKeyspace:        sourceKeyspace,
   245  		TargetKeyspace:        targetKeyspace,
   246  		Cell:                  cell,
   247  		TabletTypes:           tabletTypes,
   248  		StopAfterCopy:         stopAfterCopy,
   249  		ExternalCluster:       externalCluster,
   250  		SourceShards:          sourceShards,
   251  		OnDdl:                 onDDL,
   252  		DeferSecondaryKeys:    deferSecondaryKeys,
   253  	}
   254  	if sourceTimeZone != "" {
   255  		ms.SourceTimeZone = sourceTimeZone
   256  		ms.TargetTimeZone = "UTC"
   257  	}
   258  	createDDLMode := createDDLAsCopy
   259  	if dropForeignKeys {
   260  		createDDLMode = createDDLAsCopyDropForeignKeys
   261  	}
   262  
   263  	for _, table := range tables {
   264  		buf := sqlparser.NewTrackedBuffer(nil)
   265  		buf.Myprintf("select * from %v", sqlparser.NewIdentifierCS(table))
   266  		ms.TableSettings = append(ms.TableSettings, &vtctldatapb.TableMaterializeSettings{
   267  			TargetTable:      table,
   268  			SourceExpression: buf.String(),
   269  			CreateDdl:        createDDLMode,
   270  		})
   271  	}
   272  	mz, err := wr.prepareMaterializerStreams(ctx, ms)
   273  	if err != nil {
   274  		return err
   275  	}
   276  
   277  	if sourceTimeZone != "" {
   278  		if err := mz.checkTZConversion(ctx, sourceTimeZone); err != nil {
   279  			return err
   280  		}
   281  	}
   282  
   283  	tabletShards, err := wr.collectTargetStreams(ctx, mz)
   284  	if err != nil {
   285  		return err
   286  	}
   287  
   288  	migrationID, err := getMigrationID(targetKeyspace, tabletShards)
   289  	if err != nil {
   290  		return err
   291  	}
   292  
   293  	if externalCluster == "" {
   294  		exists, tablets, err := wr.checkIfPreviousJournalExists(ctx, mz, migrationID)
   295  		if err != nil {
   296  			return err
   297  		}
   298  		if exists {
   299  			wr.Logger().Errorf("Found a previous journal entry for %d", migrationID)
   300  			msg := fmt.Sprintf("found an entry from a previous run for migration id %d in _vt.resharding_journal of tablets %s,",
   301  				migrationID, strings.Join(tablets, ","))
   302  			msg += fmt.Sprintf("please review and delete it before proceeding and restart the workflow using the Workflow %s.%s start",
   303  				workflow, targetKeyspace)
   304  			return fmt.Errorf(msg)
   305  		}
   306  	}
   307  	if autoStart {
   308  		return mz.startStreams(ctx)
   309  	}
   310  	wr.Logger().Infof("Streams will not be started since -auto_start is set to false")
   311  
   312  	return nil
   313  }
   314  
   315  func (wr *Wrangler) validateSourceTablesExist(ctx context.Context, sourceKeyspace string, ksTables, tables []string) error {
   316  	// validate that tables provided are present in the source keyspace
   317  	var missingTables []string
   318  	for _, table := range tables {
   319  		if schema.IsInternalOperationTableName(table) {
   320  			continue
   321  		}
   322  		found := false
   323  
   324  		for _, ksTable := range ksTables {
   325  			if table == ksTable {
   326  				found = true
   327  				break
   328  			}
   329  		}
   330  		if !found {
   331  			missingTables = append(missingTables, table)
   332  		}
   333  	}
   334  	if len(missingTables) > 0 {
   335  		return fmt.Errorf("table(s) not found in source keyspace %s: %s", sourceKeyspace, strings.Join(missingTables, ","))
   336  	}
   337  	return nil
   338  }
   339  
   340  func (wr *Wrangler) getKeyspaceTables(ctx context.Context, ks string, ts *topo.Server) ([]string, error) {
   341  	shards, err := ts.GetServingShards(ctx, ks)
   342  	if err != nil {
   343  		return nil, err
   344  	}
   345  	if len(shards) == 0 {
   346  		return nil, fmt.Errorf("keyspace %s has no shards", ks)
   347  	}
   348  	primary := shards[0].PrimaryAlias
   349  	if primary == nil {
   350  		return nil, fmt.Errorf("shard does not have a primary: %v", shards[0].ShardName())
   351  	}
   352  	allTables := []string{"/.*/"}
   353  
   354  	ti, err := ts.GetTablet(ctx, primary)
   355  	if err != nil {
   356  		return nil, err
   357  	}
   358  	req := &tabletmanagerdatapb.GetSchemaRequest{Tables: allTables}
   359  	schema, err := wr.tmc.GetSchema(ctx, ti.Tablet, req)
   360  	if err != nil {
   361  		return nil, err
   362  	}
   363  	log.Infof("got table schemas from source primary %v.", primary)
   364  
   365  	var sourceTables []string
   366  	for _, td := range schema.TableDefinitions {
   367  		sourceTables = append(sourceTables, td.Name)
   368  	}
   369  	return sourceTables, nil
   370  }
   371  
   372  func (wr *Wrangler) checkIfPreviousJournalExists(ctx context.Context, mz *materializer, migrationID int64) (bool, []string, error) {
   373  	forAllSources := func(f func(*topo.ShardInfo) error) error {
   374  		var wg sync.WaitGroup
   375  		allErrors := &concurrency.AllErrorRecorder{}
   376  		for _, sourceShard := range mz.sourceShards {
   377  			wg.Add(1)
   378  			go func(sourceShard *topo.ShardInfo) {
   379  				defer wg.Done()
   380  
   381  				if err := f(sourceShard); err != nil {
   382  					allErrors.RecordError(err)
   383  				}
   384  			}(sourceShard)
   385  		}
   386  		wg.Wait()
   387  		return allErrors.AggrError(vterrors.Aggregate)
   388  	}
   389  
   390  	var (
   391  		mu      sync.Mutex
   392  		exists  bool
   393  		tablets []string
   394  		ws      = workflow.NewServer(wr.ts, wr.tmc)
   395  	)
   396  
   397  	err := forAllSources(func(si *topo.ShardInfo) error {
   398  		tablet, err := wr.ts.GetTablet(ctx, si.PrimaryAlias)
   399  		if err != nil {
   400  			return err
   401  		}
   402  		if tablet == nil {
   403  			return nil
   404  		}
   405  		_, exists, err = ws.CheckReshardingJournalExistsOnTablet(ctx, tablet.Tablet, migrationID)
   406  		if err != nil {
   407  			return err
   408  		}
   409  		if exists {
   410  			mu.Lock()
   411  			defer mu.Unlock()
   412  			tablets = append(tablets, tablet.AliasString())
   413  		}
   414  		return nil
   415  	})
   416  	return exists, tablets, err
   417  }
   418  
   419  // CreateLookupVindex creates a lookup vindex and sets up the backfill.
   420  func (wr *Wrangler) CreateLookupVindex(ctx context.Context, keyspace string, specs *vschemapb.Keyspace, cell, tabletTypes string, continueAfterCopyWithOwner bool) error {
   421  	ms, sourceVSchema, targetVSchema, err := wr.prepareCreateLookup(ctx, keyspace, specs, continueAfterCopyWithOwner)
   422  	if err != nil {
   423  		return err
   424  	}
   425  	if err := wr.ts.SaveVSchema(ctx, ms.TargetKeyspace, targetVSchema); err != nil {
   426  		return err
   427  	}
   428  	ms.Cell = cell
   429  	ms.TabletTypes = tabletTypes
   430  	if err := wr.Materialize(ctx, ms); err != nil {
   431  		return err
   432  	}
   433  	if err := wr.ts.SaveVSchema(ctx, keyspace, sourceVSchema); err != nil {
   434  		return err
   435  	}
   436  
   437  	return wr.ts.RebuildSrvVSchema(ctx, nil)
   438  }
   439  
   440  // prepareCreateLookup performs the preparatory steps for creating a lookup vindex.
   441  func (wr *Wrangler) prepareCreateLookup(ctx context.Context, keyspace string, specs *vschemapb.Keyspace, continueAfterCopyWithOwner bool) (ms *vtctldatapb.MaterializeSettings, sourceVSchema, targetVSchema *vschemapb.Keyspace, err error) {
   442  	// Important variables are pulled out here.
   443  	var (
   444  		// lookup vindex info
   445  		vindexName      string
   446  		vindex          *vschemapb.Vindex
   447  		targetKeyspace  string
   448  		targetTableName string
   449  		vindexFromCols  []string
   450  		vindexToCol     string
   451  
   452  		// source table info
   453  		sourceTableName string
   454  		// sourceTable is the supplied table info
   455  		sourceTable *vschemapb.Table
   456  		// sourceVSchemaTable is the table info present in the vschema
   457  		sourceVSchemaTable *vschemapb.Table
   458  		// sourceVindexColumns are computed from the input sourceTable
   459  		sourceVindexColumns []string
   460  
   461  		// target table info
   462  		createDDL        string
   463  		materializeQuery string
   464  	)
   465  
   466  	// Validate input vindex
   467  	if len(specs.Vindexes) != 1 {
   468  		return nil, nil, nil, fmt.Errorf("only one vindex must be specified in the specs: %v", specs.Vindexes)
   469  	}
   470  	for name, vi := range specs.Vindexes {
   471  		vindexName = name
   472  		vindex = vi
   473  	}
   474  	if !strings.Contains(vindex.Type, "lookup") {
   475  		return nil, nil, nil, fmt.Errorf("vindex %s is not a lookup type", vindex.Type)
   476  	}
   477  
   478  	targetKeyspace, targetTableName, err = sqlparser.ParseTable(vindex.Params["table"])
   479  	if err != nil || targetKeyspace == "" {
   480  		return nil, nil, nil, fmt.Errorf("vindex table name must be in the form <keyspace>.<table>. Got: %v", vindex.Params["table"])
   481  	}
   482  
   483  	vindexFromCols = strings.Split(vindex.Params["from"], ",")
   484  	if strings.Contains(vindex.Type, "unique") {
   485  		if len(vindexFromCols) != 1 {
   486  			return nil, nil, nil, fmt.Errorf("unique vindex 'from' should have only one column: %v", vindex)
   487  		}
   488  	} else {
   489  		if len(vindexFromCols) < 2 {
   490  			return nil, nil, nil, fmt.Errorf("non-unique vindex 'from' should have more than one column: %v", vindex)
   491  		}
   492  	}
   493  	vindexToCol = vindex.Params["to"]
   494  	// Make the vindex write_only. If one exists already in the vschema,
   495  	// it will need to match this vindex exactly, including the write_only setting.
   496  	vindex.Params["write_only"] = "true"
   497  	// See if we can create the vindex without errors.
   498  	if _, err := vindexes.CreateVindex(vindex.Type, vindexName, vindex.Params); err != nil {
   499  		return nil, nil, nil, err
   500  	}
   501  
   502  	// Validate input table
   503  	if len(specs.Tables) != 1 {
   504  		return nil, nil, nil, fmt.Errorf("exactly one table must be specified in the specs: %v", specs.Tables)
   505  	}
   506  	// Loop executes once.
   507  	for k, ti := range specs.Tables {
   508  		if len(ti.ColumnVindexes) != 1 {
   509  			return nil, nil, nil, fmt.Errorf("exactly one ColumnVindex must be specified for the table: %v", specs.Tables)
   510  		}
   511  		sourceTableName = k
   512  		sourceTable = ti
   513  	}
   514  
   515  	// Validate input table and vindex consistency
   516  	if sourceTable.ColumnVindexes[0].Name != vindexName {
   517  		return nil, nil, nil, fmt.Errorf("ColumnVindex name must match vindex name: %s vs %s", sourceTable.ColumnVindexes[0].Name, vindexName)
   518  	}
   519  	if vindex.Owner != "" && vindex.Owner != sourceTableName {
   520  		return nil, nil, nil, fmt.Errorf("vindex owner must match table name: %v vs %v", vindex.Owner, sourceTableName)
   521  	}
   522  	if len(sourceTable.ColumnVindexes[0].Columns) != 0 {
   523  		sourceVindexColumns = sourceTable.ColumnVindexes[0].Columns
   524  	} else {
   525  		if sourceTable.ColumnVindexes[0].Column == "" {
   526  			return nil, nil, nil, fmt.Errorf("at least one column must be specified in ColumnVindexes: %v", sourceTable.ColumnVindexes)
   527  		}
   528  		sourceVindexColumns = []string{sourceTable.ColumnVindexes[0].Column}
   529  	}
   530  	if len(sourceVindexColumns) != len(vindexFromCols) {
   531  		return nil, nil, nil, fmt.Errorf("length of table columns differes from length of vindex columns: %v vs %v", sourceVindexColumns, vindexFromCols)
   532  	}
   533  
   534  	// Validate against source vschema
   535  	sourceVSchema, err = wr.ts.GetVSchema(ctx, keyspace)
   536  	if err != nil {
   537  		return nil, nil, nil, err
   538  	}
   539  	if sourceVSchema.Vindexes == nil {
   540  		sourceVSchema.Vindexes = make(map[string]*vschemapb.Vindex)
   541  	}
   542  	// If source and target keyspaces are same, Make vschemas point to the same object.
   543  	if keyspace == targetKeyspace {
   544  		targetVSchema = sourceVSchema
   545  	} else {
   546  		targetVSchema, err = wr.ts.GetVSchema(ctx, targetKeyspace)
   547  		if err != nil {
   548  			return nil, nil, nil, err
   549  		}
   550  	}
   551  	if targetVSchema.Vindexes == nil {
   552  		targetVSchema.Vindexes = make(map[string]*vschemapb.Vindex)
   553  	}
   554  	if targetVSchema.Tables == nil {
   555  		targetVSchema.Tables = make(map[string]*vschemapb.Table)
   556  	}
   557  	if existing, ok := sourceVSchema.Vindexes[vindexName]; ok {
   558  		if !proto.Equal(existing, vindex) {
   559  			return nil, nil, nil, fmt.Errorf("a conflicting vindex named %s already exists in the source vschema", vindexName)
   560  		}
   561  	}
   562  	sourceVSchemaTable = sourceVSchema.Tables[sourceTableName]
   563  	if sourceVSchemaTable == nil {
   564  		if !schema.IsInternalOperationTableName(sourceTableName) {
   565  			return nil, nil, nil, fmt.Errorf("source table %s not found in vschema", sourceTableName)
   566  		}
   567  	}
   568  	for _, colVindex := range sourceVSchemaTable.ColumnVindexes {
   569  		// For a conflict, the vindex name and column should match.
   570  		if colVindex.Name != vindexName {
   571  			continue
   572  		}
   573  		colName := colVindex.Column
   574  		if len(colVindex.Columns) != 0 {
   575  			colName = colVindex.Columns[0]
   576  		}
   577  		if colName == sourceVindexColumns[0] {
   578  			return nil, nil, nil, fmt.Errorf("ColumnVindex for table %v already exists: %v, please remove it and try again", sourceTableName, colName)
   579  		}
   580  	}
   581  
   582  	// Validate against source schema
   583  	sourceShards, err := wr.ts.GetServingShards(ctx, keyspace)
   584  	if err != nil {
   585  		return nil, nil, nil, err
   586  	}
   587  	onesource := sourceShards[0]
   588  	if onesource.PrimaryAlias == nil {
   589  		return nil, nil, nil, fmt.Errorf("source shard has no primary: %v", onesource.ShardName())
   590  	}
   591  	req := &tabletmanagerdatapb.GetSchemaRequest{Tables: []string{sourceTableName}}
   592  	tableSchema, err := schematools.GetSchema(ctx, wr.ts, wr.tmc, onesource.PrimaryAlias, req)
   593  	if err != nil {
   594  		return nil, nil, nil, err
   595  	}
   596  	if len(tableSchema.TableDefinitions) != 1 {
   597  		return nil, nil, nil, fmt.Errorf("unexpected number of tables returned from schema: %v", tableSchema.TableDefinitions)
   598  	}
   599  
   600  	// Generate "create table" statement
   601  	lines := strings.Split(tableSchema.TableDefinitions[0].Schema, "\n")
   602  	if len(lines) < 3 {
   603  		// Unreachable
   604  		return nil, nil, nil, fmt.Errorf("schema looks incorrect: %s, expecting at least four lines", tableSchema.TableDefinitions[0].Schema)
   605  	}
   606  	var modified []string
   607  	modified = append(modified, strings.Replace(lines[0], sourceTableName, targetTableName, 1))
   608  	for i := range sourceVindexColumns {
   609  		line, err := generateColDef(lines, sourceVindexColumns[i], vindexFromCols[i])
   610  		if err != nil {
   611  			return nil, nil, nil, err
   612  		}
   613  		modified = append(modified, line)
   614  	}
   615  
   616  	if vindex.Params["data_type"] == "" || strings.EqualFold(vindex.Type, "consistent_lookup_unique") || strings.EqualFold(vindex.Type, "consistent_lookup") {
   617  		modified = append(modified, fmt.Sprintf("  %s varbinary(128),", sqlescape.EscapeID(vindexToCol)))
   618  	} else {
   619  		modified = append(modified, fmt.Sprintf("  %s %s,", sqlescape.EscapeID(vindexToCol), sqlescape.EscapeID(vindex.Params["data_type"])))
   620  	}
   621  	buf := sqlparser.NewTrackedBuffer(nil)
   622  	fmt.Fprintf(buf, "  PRIMARY KEY (")
   623  	prefix := ""
   624  	for _, col := range vindexFromCols {
   625  		fmt.Fprintf(buf, "%s%s", prefix, sqlescape.EscapeID(col))
   626  		prefix = ", "
   627  	}
   628  	fmt.Fprintf(buf, ")")
   629  	modified = append(modified, buf.String())
   630  	modified = append(modified, ")")
   631  	createDDL = strings.Join(modified, "\n")
   632  
   633  	// Generate vreplication query
   634  	buf = sqlparser.NewTrackedBuffer(nil)
   635  	buf.Myprintf("select ")
   636  	for i := range vindexFromCols {
   637  		buf.Myprintf("%v as %v, ", sqlparser.NewIdentifierCI(sourceVindexColumns[i]), sqlparser.NewIdentifierCI(vindexFromCols[i]))
   638  	}
   639  	if strings.EqualFold(vindexToCol, "keyspace_id") || strings.EqualFold(vindex.Type, "consistent_lookup_unique") || strings.EqualFold(vindex.Type, "consistent_lookup") {
   640  		buf.Myprintf("keyspace_id() as %v ", sqlparser.NewIdentifierCI(vindexToCol))
   641  	} else {
   642  		buf.Myprintf("%v as %v ", sqlparser.NewIdentifierCI(vindexToCol), sqlparser.NewIdentifierCI(vindexToCol))
   643  	}
   644  	buf.Myprintf("from %v", sqlparser.NewIdentifierCS(sourceTableName))
   645  	if vindex.Owner != "" {
   646  		// Only backfill
   647  		buf.Myprintf(" group by ")
   648  		for i := range vindexFromCols {
   649  			buf.Myprintf("%v, ", sqlparser.NewIdentifierCI(vindexFromCols[i]))
   650  		}
   651  		buf.Myprintf("%v", sqlparser.NewIdentifierCI(vindexToCol))
   652  	}
   653  	materializeQuery = buf.String()
   654  
   655  	// Update targetVSchema
   656  	var targetTable *vschemapb.Table
   657  	if targetVSchema.Sharded {
   658  		// Choose a primary vindex type for target table based on source specs
   659  		var targetVindexType string
   660  		var targetVindex *vschemapb.Vindex
   661  		for _, field := range tableSchema.TableDefinitions[0].Fields {
   662  			if sourceVindexColumns[0] == field.Name {
   663  				targetVindexType, err = vindexes.ChooseVindexForType(field.Type)
   664  				if err != nil {
   665  					return nil, nil, nil, err
   666  				}
   667  				targetVindex = &vschemapb.Vindex{
   668  					Type: targetVindexType,
   669  				}
   670  				break
   671  			}
   672  		}
   673  		if targetVindex == nil {
   674  			// Unreachable. We validated column names when generating the DDL.
   675  			return nil, nil, nil, fmt.Errorf("column %s not found in schema %v", sourceVindexColumns[0], tableSchema.TableDefinitions[0])
   676  		}
   677  		if existing, ok := targetVSchema.Vindexes[targetVindexType]; ok {
   678  			if !proto.Equal(existing, targetVindex) {
   679  				return nil, nil, nil, fmt.Errorf("a conflicting vindex named %v already exists in the target vschema", targetVindexType)
   680  			}
   681  		} else {
   682  			targetVSchema.Vindexes[targetVindexType] = targetVindex
   683  		}
   684  
   685  		targetTable = &vschemapb.Table{
   686  			ColumnVindexes: []*vschemapb.ColumnVindex{{
   687  				Column: vindexFromCols[0],
   688  				Name:   targetVindexType,
   689  			}},
   690  		}
   691  	} else {
   692  		targetTable = &vschemapb.Table{}
   693  	}
   694  	if existing, ok := targetVSchema.Tables[targetTableName]; ok {
   695  		if !proto.Equal(existing, targetTable) {
   696  			return nil, nil, nil, fmt.Errorf("a conflicting table named %v already exists in the target vschema", targetTableName)
   697  		}
   698  	} else {
   699  		targetVSchema.Tables[targetTableName] = targetTable
   700  	}
   701  
   702  	ms = &vtctldatapb.MaterializeSettings{
   703  		Workflow:              targetTableName + "_vdx",
   704  		MaterializationIntent: vtctldatapb.MaterializationIntent_CREATELOOKUPINDEX,
   705  		SourceKeyspace:        keyspace,
   706  		TargetKeyspace:        targetKeyspace,
   707  		StopAfterCopy:         vindex.Owner != "" && !continueAfterCopyWithOwner,
   708  		TableSettings: []*vtctldatapb.TableMaterializeSettings{{
   709  			TargetTable:      targetTableName,
   710  			SourceExpression: materializeQuery,
   711  			CreateDdl:        createDDL,
   712  		}},
   713  	}
   714  
   715  	// Update sourceVSchema
   716  	sourceVSchema.Vindexes[vindexName] = vindex
   717  	sourceVSchemaTable.ColumnVindexes = append(sourceVSchemaTable.ColumnVindexes, sourceTable.ColumnVindexes[0])
   718  
   719  	return ms, sourceVSchema, targetVSchema, nil
   720  }
   721  
   722  func generateColDef(lines []string, sourceVindexCol, vindexFromCol string) (string, error) {
   723  	source := sqlescape.EscapeID(sourceVindexCol)
   724  	target := sqlescape.EscapeID(vindexFromCol)
   725  
   726  	for _, line := range lines[1:] {
   727  		if strings.Contains(line, source) {
   728  			line = strings.Replace(line, source, target, 1)
   729  			line = strings.Replace(line, " AUTO_INCREMENT", "", 1)
   730  			line = strings.Replace(line, " DEFAULT NULL", "", 1)
   731  			return line, nil
   732  		}
   733  	}
   734  	return "", fmt.Errorf("column %s not found in schema %v", sourceVindexCol, lines)
   735  }
   736  
   737  // ExternalizeVindex externalizes a lookup vindex that's finished backfilling or has caught up.
   738  func (wr *Wrangler) ExternalizeVindex(ctx context.Context, qualifiedVindexName string) error {
   739  	splits := strings.Split(qualifiedVindexName, ".")
   740  	if len(splits) != 2 {
   741  		return fmt.Errorf("vindex name should be of the form keyspace.vindex: %s", qualifiedVindexName)
   742  	}
   743  	sourceKeyspace, vindexName := splits[0], splits[1]
   744  	sourceVSchema, err := wr.ts.GetVSchema(ctx, sourceKeyspace)
   745  	if err != nil {
   746  		return err
   747  	}
   748  	sourceVindex := sourceVSchema.Vindexes[vindexName]
   749  	if sourceVindex == nil {
   750  		return fmt.Errorf("vindex %s not found in vschema", qualifiedVindexName)
   751  	}
   752  
   753  	targetKeyspace, targetTableName, err := sqlparser.ParseTable(sourceVindex.Params["table"])
   754  	if err != nil || targetKeyspace == "" {
   755  		return fmt.Errorf("vindex table name must be in the form <keyspace>.<table>. Got: %v", sourceVindex.Params["table"])
   756  	}
   757  	workflow := targetTableName + "_vdx"
   758  	targetShards, err := wr.ts.GetServingShards(ctx, targetKeyspace)
   759  	if err != nil {
   760  		return err
   761  	}
   762  
   763  	// Create a parallelizer function.
   764  	forAllTargets := func(f func(*topo.ShardInfo) error) error {
   765  		var wg sync.WaitGroup
   766  		allErrors := &concurrency.AllErrorRecorder{}
   767  		for _, targetShard := range targetShards {
   768  			wg.Add(1)
   769  			go func(targetShard *topo.ShardInfo) {
   770  				defer wg.Done()
   771  
   772  				if err := f(targetShard); err != nil {
   773  					allErrors.RecordError(err)
   774  				}
   775  			}(targetShard)
   776  		}
   777  		wg.Wait()
   778  		return allErrors.AggrError(vterrors.Aggregate)
   779  	}
   780  
   781  	err = forAllTargets(func(targetShard *topo.ShardInfo) error {
   782  		targetPrimary, err := wr.ts.GetTablet(ctx, targetShard.PrimaryAlias)
   783  		if err != nil {
   784  			return err
   785  		}
   786  		p3qr, err := wr.tmc.VReplicationExec(ctx, targetPrimary.Tablet, fmt.Sprintf("select id, state, message, source from _vt.vreplication where workflow=%s and db_name=%s", encodeString(workflow), encodeString(targetPrimary.DbName())))
   787  		if err != nil {
   788  			return err
   789  		}
   790  		qr := sqltypes.Proto3ToResult(p3qr)
   791  		for _, row := range qr.Rows {
   792  			id, err := evalengine.ToInt64(row[0])
   793  			if err != nil {
   794  				return err
   795  			}
   796  			state := row[1].ToString()
   797  			message := row[2].ToString()
   798  			var bls binlogdatapb.BinlogSource
   799  			sourceBytes, err := row[3].ToBytes()
   800  			if err != nil {
   801  				return err
   802  			}
   803  			if err := prototext.Unmarshal(sourceBytes, &bls); err != nil {
   804  				return err
   805  			}
   806  			if sourceVindex.Owner == "" || !bls.StopAfterCopy {
   807  				// If there's no owner or we've requested that the workflow NOT be stopped
   808  				// after the copy phase completes, then all streams need to be running.
   809  				if state != binlogplayer.BlpRunning {
   810  					return fmt.Errorf("stream %d for %v.%v is not in Running state: %v", id, targetShard.Keyspace(), targetShard.ShardName(), state)
   811  				}
   812  			} else {
   813  				// If there is an owner, all streams need to be stopped after copy.
   814  				if state != binlogplayer.BlpStopped || !strings.Contains(message, "Stopped after copy") {
   815  					return fmt.Errorf("stream %d for %v.%v is not in Stopped after copy state: %v, %v", id, targetShard.Keyspace(), targetShard.ShardName(), state, message)
   816  				}
   817  			}
   818  		}
   819  		return nil
   820  	})
   821  	if err != nil {
   822  		return err
   823  	}
   824  
   825  	if sourceVindex.Owner != "" {
   826  		// If there is an owner, we have to delete the streams.
   827  		err := forAllTargets(func(targetShard *topo.ShardInfo) error {
   828  			targetPrimary, err := wr.ts.GetTablet(ctx, targetShard.PrimaryAlias)
   829  			if err != nil {
   830  				return err
   831  			}
   832  			query := fmt.Sprintf("delete from _vt.vreplication where db_name=%s and workflow=%s", encodeString(targetPrimary.DbName()), encodeString(workflow))
   833  			_, err = wr.tmc.VReplicationExec(ctx, targetPrimary.Tablet, query)
   834  			if err != nil {
   835  				return err
   836  			}
   837  			return nil
   838  		})
   839  		if err != nil {
   840  			return err
   841  		}
   842  	}
   843  
   844  	// Remove the write_only param and save the source vschema.
   845  	delete(sourceVindex.Params, "write_only")
   846  	if err := wr.ts.SaveVSchema(ctx, sourceKeyspace, sourceVSchema); err != nil {
   847  		return err
   848  	}
   849  	return wr.ts.RebuildSrvVSchema(ctx, nil)
   850  }
   851  
   852  func (wr *Wrangler) collectTargetStreams(ctx context.Context, mz *materializer) ([]string, error) {
   853  	var shardTablets []string
   854  	var mu sync.Mutex
   855  	err := mz.forAllTargets(func(target *topo.ShardInfo) error {
   856  		var qrproto *querypb.QueryResult
   857  		var id int64
   858  		var err error
   859  		targetPrimary, err := mz.wr.ts.GetTablet(ctx, target.PrimaryAlias)
   860  		if err != nil {
   861  			return vterrors.Wrapf(err, "GetTablet(%v) failed", target.PrimaryAlias)
   862  		}
   863  		query := fmt.Sprintf("select id from _vt.vreplication where db_name=%s and workflow=%s", encodeString(targetPrimary.DbName()), encodeString(mz.ms.Workflow))
   864  		if qrproto, err = mz.wr.tmc.VReplicationExec(ctx, targetPrimary.Tablet, query); err != nil {
   865  			return vterrors.Wrapf(err, "VReplicationExec(%v, %s)", targetPrimary.Tablet, query)
   866  		}
   867  		qr := sqltypes.Proto3ToResult(qrproto)
   868  		for i := 0; i < len(qr.Rows); i++ {
   869  			id, err = evalengine.ToInt64(qr.Rows[i][0])
   870  			if err != nil {
   871  				return err
   872  			}
   873  			mu.Lock()
   874  			shardTablets = append(shardTablets, fmt.Sprintf("%s:%d", target.ShardName(), id))
   875  			mu.Unlock()
   876  		}
   877  		return nil
   878  	})
   879  	if err != nil {
   880  		return nil, err
   881  	}
   882  	return shardTablets, nil
   883  }
   884  
   885  // getMigrationID produces a reproducible hash based on the input parameters.
   886  func getMigrationID(targetKeyspace string, shardTablets []string) (int64, error) {
   887  	sort.Strings(shardTablets)
   888  	hasher := fnv.New64()
   889  	hasher.Write([]byte(targetKeyspace))
   890  	for _, str := range shardTablets {
   891  		hasher.Write([]byte(str))
   892  	}
   893  	// Convert to int64 after dropping the highest bit.
   894  	return int64(hasher.Sum64() & math.MaxInt64), nil
   895  }
   896  
   897  // createDefaultShardRoutingRules creates a reverse routing rule for
   898  // each shard in a new partial keyspace migration workflow that does
   899  // not already have an existing routing rule in place.
   900  func (wr *Wrangler) createDefaultShardRoutingRules(ctx context.Context, ms *vtctldatapb.MaterializeSettings) error {
   901  	srr, err := topotools.GetShardRoutingRules(ctx, wr.ts)
   902  	if err != nil {
   903  		return err
   904  	}
   905  	allShards, err := wr.sourceTs.GetServingShards(ctx, ms.SourceKeyspace)
   906  	if err != nil {
   907  		return err
   908  	}
   909  	changed := false
   910  	for _, si := range allShards {
   911  		fromSource := fmt.Sprintf("%s.%s", ms.SourceKeyspace, si.ShardName())
   912  		fromTarget := fmt.Sprintf("%s.%s", ms.TargetKeyspace, si.ShardName())
   913  		if srr[fromSource] == "" && srr[fromTarget] == "" {
   914  			srr[fromTarget] = ms.SourceKeyspace
   915  			changed = true
   916  			wr.Logger().Infof("Added default shard routing rule from %q to %q", fromTarget, fromSource)
   917  		}
   918  	}
   919  	if changed {
   920  		if err := topotools.SaveShardRoutingRules(ctx, wr.ts, srr); err != nil {
   921  			return err
   922  		}
   923  		if err := wr.ts.RebuildSrvVSchema(ctx, nil); err != nil {
   924  			return err
   925  		}
   926  	}
   927  	return nil
   928  }
   929  
   930  func (wr *Wrangler) prepareMaterializerStreams(ctx context.Context, ms *vtctldatapb.MaterializeSettings) (*materializer, error) {
   931  	if err := wr.validateNewWorkflow(ctx, ms.TargetKeyspace, ms.Workflow); err != nil {
   932  		return nil, err
   933  	}
   934  	mz, err := wr.buildMaterializer(ctx, ms)
   935  	if err != nil {
   936  		return nil, err
   937  	}
   938  	if mz.isPartial {
   939  		if err := wr.createDefaultShardRoutingRules(ctx, ms); err != nil {
   940  			return nil, err
   941  		}
   942  	}
   943  	if err := mz.deploySchema(ctx); err != nil {
   944  		return nil, err
   945  	}
   946  	insertMap := make(map[string]string, len(mz.targetShards))
   947  	for _, targetShard := range mz.targetShards {
   948  		inserts, err := mz.generateInserts(ctx, targetShard)
   949  		if err != nil {
   950  			return nil, err
   951  		}
   952  		insertMap[targetShard.ShardName()] = inserts
   953  	}
   954  	if err := mz.createStreams(ctx, insertMap); err != nil {
   955  		return nil, err
   956  	}
   957  	return mz, nil
   958  }
   959  
   960  // Materialize performs the steps needed to materialize a list of tables based on the materialization specs.
   961  func (wr *Wrangler) Materialize(ctx context.Context, ms *vtctldatapb.MaterializeSettings) error {
   962  	mz, err := wr.prepareMaterializerStreams(ctx, ms)
   963  	if err != nil {
   964  		return err
   965  	}
   966  	return mz.startStreams(ctx)
   967  }
   968  
   969  func (wr *Wrangler) buildMaterializer(ctx context.Context, ms *vtctldatapb.MaterializeSettings) (*materializer, error) {
   970  	vschema, err := wr.ts.GetVSchema(ctx, ms.TargetKeyspace)
   971  	if err != nil {
   972  		return nil, err
   973  	}
   974  	targetVSchema, err := vindexes.BuildKeyspaceSchema(vschema, ms.TargetKeyspace)
   975  	if err != nil {
   976  		return nil, err
   977  	}
   978  	if targetVSchema.Keyspace.Sharded {
   979  		for _, ts := range ms.TableSettings {
   980  			if targetVSchema.Tables[ts.TargetTable] == nil {
   981  				return nil, fmt.Errorf("table %s not found in vschema for keyspace %s", ts.TargetTable, ms.TargetKeyspace)
   982  			}
   983  		}
   984  	}
   985  	isPartial := false
   986  	sourceShards, err := wr.sourceTs.GetServingShards(ctx, ms.SourceKeyspace)
   987  	if err != nil {
   988  		return nil, err
   989  	}
   990  	if len(ms.SourceShards) > 0 {
   991  		isPartial = true
   992  		var sourceShards2 []*topo.ShardInfo
   993  		for _, shard := range sourceShards {
   994  			for _, shard2 := range ms.SourceShards {
   995  				if shard.ShardName() == shard2 {
   996  					sourceShards2 = append(sourceShards2, shard)
   997  					break
   998  				}
   999  			}
  1000  		}
  1001  		sourceShards = sourceShards2
  1002  	}
  1003  	if len(sourceShards) == 0 {
  1004  		return nil, fmt.Errorf("no source shards specified for workflow %s ", ms.Workflow)
  1005  	}
  1006  
  1007  	targetShards, err := wr.ts.GetServingShards(ctx, ms.TargetKeyspace)
  1008  	if err != nil {
  1009  		return nil, err
  1010  	}
  1011  	if len(ms.SourceShards) > 0 {
  1012  		var targetShards2 []*topo.ShardInfo
  1013  		for _, shard := range targetShards {
  1014  			for _, shard2 := range ms.SourceShards {
  1015  				if shard.ShardName() == shard2 {
  1016  					targetShards2 = append(targetShards2, shard)
  1017  					break
  1018  				}
  1019  			}
  1020  		}
  1021  		targetShards = targetShards2
  1022  	}
  1023  	if len(targetShards) == 0 {
  1024  		return nil, fmt.Errorf("no target shards specified for workflow %s ", ms.Workflow)
  1025  	}
  1026  
  1027  	return &materializer{
  1028  		wr:            wr,
  1029  		ms:            ms,
  1030  		targetVSchema: targetVSchema,
  1031  		sourceShards:  sourceShards,
  1032  		targetShards:  targetShards,
  1033  		isPartial:     isPartial,
  1034  	}, nil
  1035  }
  1036  
  1037  func (mz *materializer) getSourceTableDDLs(ctx context.Context) (map[string]string, error) {
  1038  	sourceDDLs := make(map[string]string)
  1039  	allTables := []string{"/.*/"}
  1040  
  1041  	sourcePrimary := mz.sourceShards[0].PrimaryAlias
  1042  	if sourcePrimary == nil {
  1043  		return nil, fmt.Errorf("source shard must have a primary for copying schema: %v", mz.sourceShards[0].ShardName())
  1044  	}
  1045  
  1046  	ti, err := mz.wr.sourceTs.GetTablet(ctx, sourcePrimary)
  1047  	if err != nil {
  1048  		return nil, err
  1049  	}
  1050  	req := &tabletmanagerdatapb.GetSchemaRequest{Tables: allTables}
  1051  	sourceSchema, err := mz.wr.tmc.GetSchema(ctx, ti.Tablet, req)
  1052  	if err != nil {
  1053  		return nil, err
  1054  	}
  1055  
  1056  	for _, td := range sourceSchema.TableDefinitions {
  1057  		sourceDDLs[td.Name] = td.Schema
  1058  	}
  1059  	return sourceDDLs, nil
  1060  }
  1061  
  1062  func (mz *materializer) deploySchema(ctx context.Context) error {
  1063  	var sourceDDLs map[string]string
  1064  	var mu sync.Mutex
  1065  
  1066  	return mz.forAllTargets(func(target *topo.ShardInfo) error {
  1067  		allTables := []string{"/.*/"}
  1068  
  1069  		hasTargetTable := map[string]bool{}
  1070  		req := &tabletmanagerdatapb.GetSchemaRequest{Tables: allTables}
  1071  		targetSchema, err := schematools.GetSchema(ctx, mz.wr.ts, mz.wr.tmc, target.PrimaryAlias, req)
  1072  		if err != nil {
  1073  			return err
  1074  		}
  1075  
  1076  		for _, td := range targetSchema.TableDefinitions {
  1077  			hasTargetTable[td.Name] = true
  1078  		}
  1079  
  1080  		targetTablet, err := mz.wr.ts.GetTablet(ctx, target.PrimaryAlias)
  1081  		if err != nil {
  1082  			return err
  1083  		}
  1084  
  1085  		var applyDDLs []string
  1086  		for _, ts := range mz.ms.TableSettings {
  1087  			if hasTargetTable[ts.TargetTable] {
  1088  				// Table already exists.
  1089  				continue
  1090  			}
  1091  			if ts.CreateDdl == "" {
  1092  				return fmt.Errorf("target table %v does not exist and there is no create ddl defined", ts.TargetTable)
  1093  			}
  1094  
  1095  			var err error
  1096  			mu.Lock()
  1097  			if len(sourceDDLs) == 0 {
  1098  				//only get ddls for tables, once and lazily: if we need to copy the schema from source to target
  1099  				//we copy schemas from primaries on the source keyspace
  1100  				//and we have found use cases where user just has a replica (no primary) in the source keyspace
  1101  				sourceDDLs, err = mz.getSourceTableDDLs(ctx)
  1102  			}
  1103  			mu.Unlock()
  1104  			if err != nil {
  1105  				log.Errorf("Error getting DDLs of source tables: %s", err.Error())
  1106  				return err
  1107  			}
  1108  
  1109  			createDDL := ts.CreateDdl
  1110  			if createDDL == createDDLAsCopy || createDDL == createDDLAsCopyDropConstraint || createDDL == createDDLAsCopyDropForeignKeys {
  1111  				if ts.SourceExpression != "" {
  1112  					// Check for table if non-empty SourceExpression.
  1113  					sourceTableName, err := sqlparser.TableFromStatement(ts.SourceExpression)
  1114  					if err != nil {
  1115  						return err
  1116  					}
  1117  					if sourceTableName.Name.String() != ts.TargetTable {
  1118  						return fmt.Errorf("source and target table names must match for copying schema: %v vs %v", sqlparser.String(sourceTableName), ts.TargetTable)
  1119  
  1120  					}
  1121  				}
  1122  
  1123  				ddl, ok := sourceDDLs[ts.TargetTable]
  1124  				if !ok {
  1125  					return fmt.Errorf("source table %v does not exist", ts.TargetTable)
  1126  				}
  1127  
  1128  				if createDDL == createDDLAsCopyDropConstraint {
  1129  					strippedDDL, err := stripTableConstraints(ddl)
  1130  					if err != nil {
  1131  						return err
  1132  					}
  1133  
  1134  					ddl = strippedDDL
  1135  				}
  1136  
  1137  				if createDDL == createDDLAsCopyDropForeignKeys {
  1138  					strippedDDL, err := stripTableForeignKeys(ddl)
  1139  					if err != nil {
  1140  						return err
  1141  					}
  1142  
  1143  					ddl = strippedDDL
  1144  				}
  1145  				createDDL = ddl
  1146  			}
  1147  
  1148  			applyDDLs = append(applyDDLs, createDDL)
  1149  		}
  1150  
  1151  		if len(applyDDLs) > 0 {
  1152  			sql := strings.Join(applyDDLs, ";\n")
  1153  
  1154  			_, err = mz.wr.tmc.ApplySchema(ctx, targetTablet.Tablet, &tmutils.SchemaChange{
  1155  				SQL:              sql,
  1156  				Force:            false,
  1157  				AllowReplication: true,
  1158  				SQLMode:          vreplication.SQLMode,
  1159  			})
  1160  			if err != nil {
  1161  				return err
  1162  			}
  1163  		}
  1164  
  1165  		return nil
  1166  	})
  1167  }
  1168  
  1169  func stripTableForeignKeys(ddl string) (string, error) {
  1170  
  1171  	ast, err := sqlparser.ParseStrictDDL(ddl)
  1172  	if err != nil {
  1173  		return "", err
  1174  	}
  1175  
  1176  	stripFKConstraints := func(cursor *sqlparser.Cursor) bool {
  1177  		switch node := cursor.Node().(type) {
  1178  		case sqlparser.DDLStatement:
  1179  			if node.GetTableSpec() != nil {
  1180  				var noFKConstraints []*sqlparser.ConstraintDefinition
  1181  				for _, constraint := range node.GetTableSpec().Constraints {
  1182  					if constraint.Details != nil {
  1183  						if _, ok := constraint.Details.(*sqlparser.ForeignKeyDefinition); !ok {
  1184  							noFKConstraints = append(noFKConstraints, constraint)
  1185  						}
  1186  					}
  1187  				}
  1188  				node.GetTableSpec().Constraints = noFKConstraints
  1189  			}
  1190  		}
  1191  		return true
  1192  	}
  1193  
  1194  	noFKConstraintAST := sqlparser.Rewrite(ast, stripFKConstraints, nil)
  1195  	newDDL := sqlparser.String(noFKConstraintAST)
  1196  	return newDDL, nil
  1197  }
  1198  
  1199  func stripTableConstraints(ddl string) (string, error) {
  1200  	ast, err := sqlparser.ParseStrictDDL(ddl)
  1201  	if err != nil {
  1202  		return "", err
  1203  	}
  1204  
  1205  	stripConstraints := func(cursor *sqlparser.Cursor) bool {
  1206  		switch node := cursor.Node().(type) {
  1207  		case sqlparser.DDLStatement:
  1208  			if node.GetTableSpec() != nil {
  1209  				node.GetTableSpec().Constraints = nil
  1210  			}
  1211  		}
  1212  		return true
  1213  	}
  1214  
  1215  	noConstraintAST := sqlparser.Rewrite(ast, stripConstraints, nil)
  1216  	newDDL := sqlparser.String(noConstraintAST)
  1217  
  1218  	return newDDL, nil
  1219  }
  1220  
  1221  func (mz *materializer) generateInserts(ctx context.Context, targetShard *topo.ShardInfo) (string, error) {
  1222  	ig := vreplication.NewInsertGenerator(binlogplayer.BlpStopped, "{{.dbname}}")
  1223  
  1224  	for _, sourceShard := range mz.sourceShards {
  1225  		// Don't create streams from sources which won't contain data for the target shard.
  1226  		// We only do it for MoveTables for now since this doesn't hold for materialize flows
  1227  		// where the target's sharding key might differ from that of the source
  1228  		if mz.ms.MaterializationIntent == vtctldatapb.MaterializationIntent_MOVETABLES &&
  1229  			!key.KeyRangesIntersect(sourceShard.KeyRange, targetShard.KeyRange) {
  1230  			continue
  1231  		}
  1232  		bls := &binlogdatapb.BinlogSource{
  1233  			Keyspace:        mz.ms.SourceKeyspace,
  1234  			Shard:           sourceShard.ShardName(),
  1235  			Filter:          &binlogdatapb.Filter{},
  1236  			StopAfterCopy:   mz.ms.StopAfterCopy,
  1237  			ExternalCluster: mz.ms.ExternalCluster,
  1238  			SourceTimeZone:  mz.ms.SourceTimeZone,
  1239  			TargetTimeZone:  mz.ms.TargetTimeZone,
  1240  			OnDdl:           binlogdatapb.OnDDLAction(binlogdatapb.OnDDLAction_value[mz.ms.OnDdl]),
  1241  		}
  1242  		for _, ts := range mz.ms.TableSettings {
  1243  			rule := &binlogdatapb.Rule{
  1244  				Match: ts.TargetTable,
  1245  			}
  1246  
  1247  			if ts.SourceExpression == "" {
  1248  				bls.Filter.Rules = append(bls.Filter.Rules, rule)
  1249  				continue
  1250  			}
  1251  
  1252  			// Validate non-empty query.
  1253  			stmt, err := sqlparser.Parse(ts.SourceExpression)
  1254  			if err != nil {
  1255  				return "", err
  1256  			}
  1257  			sel, ok := stmt.(*sqlparser.Select)
  1258  			if !ok {
  1259  				return "", fmt.Errorf("unrecognized statement: %s", ts.SourceExpression)
  1260  			}
  1261  			filter := ts.SourceExpression
  1262  			if mz.targetVSchema.Keyspace.Sharded && mz.targetVSchema.Tables[ts.TargetTable].Type != vindexes.TypeReference {
  1263  				cv, err := vindexes.FindBestColVindex(mz.targetVSchema.Tables[ts.TargetTable])
  1264  				if err != nil {
  1265  					return "", err
  1266  				}
  1267  				mappedCols := make([]*sqlparser.ColName, 0, len(cv.Columns))
  1268  				for _, col := range cv.Columns {
  1269  					colName, err := matchColInSelect(col, sel)
  1270  					if err != nil {
  1271  						return "", err
  1272  					}
  1273  					mappedCols = append(mappedCols, colName)
  1274  				}
  1275  				subExprs := make(sqlparser.SelectExprs, 0, len(mappedCols)+2)
  1276  				for _, mappedCol := range mappedCols {
  1277  					subExprs = append(subExprs, &sqlparser.AliasedExpr{Expr: mappedCol})
  1278  				}
  1279  				vindexName := fmt.Sprintf("%s.%s", mz.ms.TargetKeyspace, cv.Name)
  1280  				subExprs = append(subExprs, &sqlparser.AliasedExpr{Expr: sqlparser.NewStrLiteral(vindexName)})
  1281  				subExprs = append(subExprs, &sqlparser.AliasedExpr{Expr: sqlparser.NewStrLiteral("{{.keyrange}}")})
  1282  				inKeyRange := &sqlparser.FuncExpr{
  1283  					Name:  sqlparser.NewIdentifierCI("in_keyrange"),
  1284  					Exprs: subExprs,
  1285  				}
  1286  				if sel.Where != nil {
  1287  					sel.Where = &sqlparser.Where{
  1288  						Type: sqlparser.WhereClause,
  1289  						Expr: &sqlparser.AndExpr{
  1290  							Left:  inKeyRange,
  1291  							Right: sel.Where.Expr,
  1292  						},
  1293  					}
  1294  				} else {
  1295  					sel.Where = &sqlparser.Where{
  1296  						Type: sqlparser.WhereClause,
  1297  						Expr: inKeyRange,
  1298  					}
  1299  				}
  1300  
  1301  				filter = sqlparser.String(sel)
  1302  			}
  1303  
  1304  			rule.Filter = filter
  1305  
  1306  			bls.Filter.Rules = append(bls.Filter.Rules, rule)
  1307  		}
  1308  		workflowSubType := binlogdatapb.VReplicationWorkflowSubType_None
  1309  		if mz.isPartial {
  1310  			workflowSubType = binlogdatapb.VReplicationWorkflowSubType_Partial
  1311  		}
  1312  		ig.AddRow(mz.ms.Workflow, bls, "", mz.ms.Cell, mz.ms.TabletTypes,
  1313  			int64(mz.ms.MaterializationIntent),
  1314  			int64(workflowSubType),
  1315  			mz.ms.DeferSecondaryKeys,
  1316  		)
  1317  	}
  1318  	return ig.String(), nil
  1319  }
  1320  
  1321  func matchColInSelect(col sqlparser.IdentifierCI, sel *sqlparser.Select) (*sqlparser.ColName, error) {
  1322  	for _, selExpr := range sel.SelectExprs {
  1323  		switch selExpr := selExpr.(type) {
  1324  		case *sqlparser.StarExpr:
  1325  			return &sqlparser.ColName{Name: col}, nil
  1326  		case *sqlparser.AliasedExpr:
  1327  			match := selExpr.As
  1328  			if match.IsEmpty() {
  1329  				if colExpr, ok := selExpr.Expr.(*sqlparser.ColName); ok {
  1330  					match = colExpr.Name
  1331  				} else {
  1332  					// Cannot match against a complex expression.
  1333  					continue
  1334  				}
  1335  			}
  1336  			if match.Equal(col) {
  1337  				colExpr, ok := selExpr.Expr.(*sqlparser.ColName)
  1338  				if !ok {
  1339  					return nil, fmt.Errorf("vindex column cannot be a complex expression: %v", sqlparser.String(selExpr))
  1340  				}
  1341  				return colExpr, nil
  1342  			}
  1343  		default:
  1344  			return nil, fmt.Errorf("unsupported select expression: %v", sqlparser.String(selExpr))
  1345  		}
  1346  	}
  1347  	return nil, fmt.Errorf("could not find vindex column %v", sqlparser.String(col))
  1348  }
  1349  
  1350  func (mz *materializer) createStreams(ctx context.Context, insertsMap map[string]string) error {
  1351  	return mz.forAllTargets(func(target *topo.ShardInfo) error {
  1352  		inserts := insertsMap[target.ShardName()]
  1353  		targetPrimary, err := mz.wr.ts.GetTablet(ctx, target.PrimaryAlias)
  1354  		if err != nil {
  1355  			return vterrors.Wrapf(err, "GetTablet(%v) failed", target.PrimaryAlias)
  1356  		}
  1357  		buf := &strings.Builder{}
  1358  		t := template.Must(template.New("").Parse(inserts))
  1359  		input := map[string]string{
  1360  			"keyrange": key.KeyRangeString(target.KeyRange),
  1361  			"dbname":   targetPrimary.DbName(),
  1362  		}
  1363  		if err := t.Execute(buf, input); err != nil {
  1364  			return err
  1365  		}
  1366  		if _, err := mz.wr.TabletManagerClient().VReplicationExec(ctx, targetPrimary.Tablet, buf.String()); err != nil {
  1367  			return err
  1368  		}
  1369  		return nil
  1370  	})
  1371  }
  1372  
  1373  func (mz *materializer) startStreams(ctx context.Context) error {
  1374  	return mz.forAllTargets(func(target *topo.ShardInfo) error {
  1375  		targetPrimary, err := mz.wr.ts.GetTablet(ctx, target.PrimaryAlias)
  1376  		if err != nil {
  1377  			return vterrors.Wrapf(err, "GetTablet(%v) failed", target.PrimaryAlias)
  1378  		}
  1379  		query := fmt.Sprintf("update _vt.vreplication set state='Running' where db_name=%s and workflow=%s", encodeString(targetPrimary.DbName()), encodeString(mz.ms.Workflow))
  1380  		if _, err := mz.wr.tmc.VReplicationExec(ctx, targetPrimary.Tablet, query); err != nil {
  1381  			return vterrors.Wrapf(err, "VReplicationExec(%v, %s)", targetPrimary.Tablet, query)
  1382  		}
  1383  		return nil
  1384  	})
  1385  }
  1386  
  1387  func (mz *materializer) forAllTargets(f func(*topo.ShardInfo) error) error {
  1388  	var wg sync.WaitGroup
  1389  	allErrors := &concurrency.AllErrorRecorder{}
  1390  	for _, target := range mz.targetShards {
  1391  		wg.Add(1)
  1392  		go func(target *topo.ShardInfo) {
  1393  			defer wg.Done()
  1394  
  1395  			if err := f(target); err != nil {
  1396  				allErrors.RecordError(err)
  1397  			}
  1398  		}(target)
  1399  	}
  1400  	wg.Wait()
  1401  	return allErrors.AggrError(vterrors.Aggregate)
  1402  }
  1403  
  1404  // checkTZConversion is a light-weight consistency check to validate that, if a source time zone is specified to MoveTables,
  1405  // that the current primary has the time zone loaded in order to run the convert_tz() function used by VReplication to do the
  1406  // datetime conversions. We only check the current primaries on each shard and note here that it is possible a new primary
  1407  // gets elected: in this case user will either see errors during vreplication or vdiff will report mismatches.
  1408  func (mz *materializer) checkTZConversion(ctx context.Context, tz string) error {
  1409  	err := mz.forAllTargets(func(target *topo.ShardInfo) error {
  1410  		targetPrimary, err := mz.wr.ts.GetTablet(ctx, target.PrimaryAlias)
  1411  		if err != nil {
  1412  			return vterrors.Wrapf(err, "GetTablet(%v) failed", target.PrimaryAlias)
  1413  		}
  1414  		testDateTime := "2006-01-02 15:04:05"
  1415  		query := fmt.Sprintf("select convert_tz(%s, %s, 'UTC')", encodeString(testDateTime), encodeString(tz))
  1416  		qrproto, err := mz.wr.tmc.ExecuteFetchAsApp(ctx, targetPrimary.Tablet, false, &tabletmanagerdatapb.ExecuteFetchAsAppRequest{
  1417  			Query:   []byte(query),
  1418  			MaxRows: 1,
  1419  		})
  1420  		if err != nil {
  1421  			return vterrors.Wrapf(err, "ExecuteFetchAsApp(%v, %s)", targetPrimary.Tablet, query)
  1422  		}
  1423  		qr := sqltypes.Proto3ToResult(qrproto)
  1424  		if gotDate, err := time.Parse(testDateTime, qr.Rows[0][0].ToString()); err != nil {
  1425  			return fmt.Errorf("unable to perform time_zone conversions from %s to UTC — result of the attempt was: %s. Either the specified source time zone is invalid or the time zone tables have not been loaded on the %s tablet",
  1426  				tz, gotDate, targetPrimary.Alias)
  1427  		}
  1428  		return nil
  1429  	})
  1430  	return err
  1431  }