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 }