Returns true if the DDL event is a compatible change, or false if it is not. This performs a naive parsing of the DDL command looking for modification of columns 1. ALTER TABLE .. ALTER COLUMN 2. ALTER TABLE .. DROP COLUMN See
(&self, included_columns: &[Arc<str>])
| 621 | /// |
| 622 | /// See <https://learn.microsoft.com/en-us/sql/t-sql/statements/alter-table-transact-sql?view=sql-server-ver17> |
| 623 | pub fn is_compatible(&self, included_columns: &[Arc<str>]) -> bool { |
| 624 | // TODO (maz): This is currently a basic check that doesn't take into account type changes. |
| 625 | // At some point, we will need to move this to SqlServerTableDesc and expand it. |
| 626 | let mut words = self.ddl_command.split_ascii_whitespace(); |
| 627 | match ( |
| 628 | words.next().map(str::to_ascii_lowercase).as_deref(), |
| 629 | words.next().map(str::to_ascii_lowercase).as_deref(), |
| 630 | ) { |
| 631 | (Some("alter"), Some("table")) => { |
| 632 | let mut peekable = words.peekable(); |
| 633 | let mut compatible = true; |
| 634 | while compatible && let Some(token) = peekable.next() { |
| 635 | compatible = match token.to_ascii_lowercase().as_str() { |
| 636 | "alter" | "drop" => { |
| 637 | let target = peekable.next(); |
| 638 | match target { |
| 639 | // Targeting a column |
| 640 | Some(t) if t.eq_ignore_ascii_case("column") => { |
| 641 | let mut all_excluded = true; |
| 642 | while let Some(tok) = peekable.next() { |
| 643 | // The column name(s) can be preceeded by the pair of keywords "IF EXISTS", so we want to skip those. |
| 644 | match tok.to_ascii_lowercase().as_str() { |
| 645 | "if" | "exists" | "," | "column" => continue, |
| 646 | col_str => { |
| 647 | // If any column is in the included list, then it is not okay to alter/drop it |
| 648 | // The col_str token may be a comma-separated list of columns as whitespace is not required |
| 649 | // between column names in SQL Server DDL. |
| 650 | if !col_str.trim_matches(',').split(',').all( |
| 651 | |col_name| { |
| 652 | !included_columns.iter().any(|included| { |
| 653 | included.eq_ignore_ascii_case( |
| 654 | col_name.trim_matches( |
| 655 | ['[', ']', '"'].as_ref(), |
| 656 | ), |
| 657 | ) |
| 658 | }) |
| 659 | }, |
| 660 | ) { |
| 661 | all_excluded = false; |
| 662 | break; |
| 663 | } |
| 664 | // If this is the only/last column, then we can break out of the while loop. |
| 665 | // Check if this string has no trailing comma, and if not, peek to see if the next token |
| 666 | // contains a leading comma. |
| 667 | if !col_str.ends_with(",") { |
| 668 | match peekable.peek() { |
| 669 | Some(x) if x.starts_with(",") => continue, |
| 670 | _ => break, |
| 671 | } |
| 672 | } |
| 673 | } |
| 674 | }; |
| 675 | } |
| 676 | all_excluded |
| 677 | } |
| 678 | // No target token after "alter" or "drop" |
| 679 | None => false, |
| 680 | // Other targets are considered compatible |