updateMutatesTable reports whether the UPDATE's SET clauses actually mutate (a column of) the specified table. JOIN-only references that do not appear as a SET-clause qualifier do NOT count as mutation. Resolution strategy mirrors extractUpdateColumns: build the alias map (lowercased per Bug 5), th
(stmt *ast.UpdateStmt, database, table string, targetCols map[string]bool)
| 571 | // UPDATE with unqualified col is ambiguous; safer to over-include than |
| 572 | // miss). |
| 573 | func updateMutatesTable(stmt *ast.UpdateStmt, database, table string, targetCols map[string]bool) bool { |
| 574 | // Precondition: target must be in stmt.Tables at all (else there's |
| 575 | // nothing to discuss). |
| 576 | targetInScope := false |
| 577 | for _, expr := range stmt.Tables { |
| 578 | if tableExprReferences(expr, database, table) { |
| 579 | targetInScope = true |
| 580 | break |
| 581 | } |
| 582 | } |
| 583 | if !targetInScope { |
| 584 | return false |
| 585 | } |
| 586 | |
| 587 | singleTables := extractSingleTablesFromTableExprs(database, stmt.Tables) |
| 588 | for _, assignment := range stmt.SetList { |
| 589 | col := assignment.Column |
| 590 | if col == nil { |
| 591 | continue |
| 592 | } |
| 593 | if col.Schema != "" && !strings.EqualFold(col.Schema, database) { |
| 594 | continue |
| 595 | } |
| 596 | if col.Table == "" { |
| 597 | // Unqualified column. Resolve against the target table's |
| 598 | // actual columns — only count as mutation if the column |
| 599 | // exists on the target. Pre-fix this branch returned true |
| 600 | // unconditionally (over-counted); for `UPDATE test JOIN t1 |
| 601 | // ON ... SET name = 1` where `name` is on t1 but not test, |
| 602 | // the over-count produced empty `... ON DUPLICATE KEY |
| 603 | // UPDATE ;`. Per Codex P1 catch on PR #20345. |
| 604 | if targetCols[strings.ToLower(col.Column)] { |
| 605 | return true |
| 606 | } |
| 607 | continue |
| 608 | } |
| 609 | // Qualified — resolve qualifier through the alias map. Both |
| 610 | // Database AND Table must match: for cross-database joins with |
| 611 | // homonymous tables (e.g. `UPDATE db1.test t1 JOIN db2.test t2 |
| 612 | // SET t2.a = ...`), the alias t2 resolves to db2.test; without |
| 613 | // the Database check, a backup item targeting db1.test would |
| 614 | // incorrectly match db2.test's SET assignments. Per Codex P1 |
| 615 | // catch on PR #20345. |
| 616 | if entry, ok := singleTables[strings.ToLower(col.Table)]; ok && strings.EqualFold(entry.Database, database) && strings.EqualFold(entry.Table, table) { |
| 617 | return true |
| 618 | } |
| 619 | // Fallback: qualifier IS the bare table name (no alias used). |
| 620 | // The col.Schema check at the top of the loop already filtered |
| 621 | // out assignments whose explicit schema doesn't match `database`, |
| 622 | // so reaching here implies col.Schema is empty or matches. |
| 623 | if strings.EqualFold(col.Table, table) { |
| 624 | return true |
| 625 | } |
| 626 | } |
| 627 | return false |
| 628 | } |
| 629 | |
| 630 | func tableExprReferences(expr ast.TableExpr, database, table string) bool { |
no test coverage detected