Class: QueryGuard::Migrations::TableSizeResolver

Inherits:
Object
  • Object
show all
Defined in:
lib/query_guard/migrations/table_size_resolver.rb

Overview

Extracts table names from Rails migration code

Pragmatically identifies which tables a migration affects by parsing common Rails migration method calls.

Detects: add_column, remove_column, change_column, create_table, drop_table, etc. on both active table operations and raw SQL.

Class Method Summary collapse

Class Method Details

.affected_tables(migration_content) ⇒ Array<String>

Extract table names that schema changes would directly affect

Parameters:

  • migration_content (String) —

    Migration file content

Returns:

  • (Array<String>) —

    Directly affected tables



146
147
148
149
# File 'lib/query_guard/migrations/table_size_resolver.rb', line 146

def self.affected_tables(migration_content)
  all_tables = extract_table_names(migration_content)
  filter_schema_tables(all_tables)
end

.extract_table_names(migration_content) ⇒ Array<String>

Extract all table names referenced in a migration

Parameters:

  • migration_content (String) —

    Migration file content

Returns:

  • (Array<String>) —

    Table names (deduplicated)



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/query_guard/migrations/table_size_resolver.rb', line 17

def self.extract_table_names(migration_content)
  tables = Set.new

  lines = migration_content.lines

  lines.each do |line|
    next if line.strip.start_with?("#")
    next if line.strip.empty?

    # Try to capture table name: could be :symbol or "string" or 'string'
    # add_column :users, :name, :string
    if line.include?("add_column")
      # Match: add_column :table_name or add_column "table_name" or add_column 'table_name'
      if match = line.match(/add_column\s+[:"`']?(\w+)/)
        tables << match[1]
      end
    end

    # remove_column :posts, :old_field
    if line.include?("remove_column")
      if match = line.match(/remove_column\s+[:"`']?(\w+)/)
        tables << match[1]
      end
    end

    # change_column :users, :email, :text
    if line.include?("change_column")
      if match = line.match(/change_column\s+[:"`']?(\w+)/)
        tables << match[1]
      end
    end

    # rename_column :users, :old_name, :new_name
    if line.include?("rename_column")
      if match = line.match(/rename_column\s+[:"`']?(\w+)/)
        tables << match[1]
      end
    end

    # add_index :users, :email
    if line.include?("add_index")
      if match = line.match(/add_index\s+[:"`']?(\w+)/)
        tables << match[1]
      end
    end

    # remove_index :users, :email
    if line.include?("remove_index")
      if match = line.match(/remove_index\s+[:"`']?(\w+)/)
        tables << match[1]
      end
    end

    # drop_table :posts
    if line.include?("drop_table")
      if match = line.match(/drop_table\s+[:"`']?(\w+)/)
        tables << match[1]
      end
    end

    # create_table :accounts do ... end
    if line.include?("create_table")
      if match = line.match(/create_table\s+[:"`']?(\w+)/)
        tables << match[1]
      end
    end

    # Raw SQL UPDATE public.users SET ...
    if line.upcase.include?("UPDATE")
      if match = line.match(/UPDATE\s+(?:public\.)?(\w+)/i)
        tables << match[1]
      end
    end

    # Raw SQL DELETE FROM public.users
    if line.upcase.include?("DELETE")
      if match = line.match(/DELETE\s+FROM\s+(?:public\.)?(\w+)/i)
        tables << match[1]
      end
    end

    # Raw SQL INSERT INTO public.users
    if line.upcase.include?("INSERT")
      if match = line.match(/INSERT\s+INTO\s+(?:public\.)?(\w+)/i)
        tables << match[1]
      end
    end

    # Model.update_all(status: 'active') - convert model to table name
    # or User.delete_all
    if (line.include?("update_all") || line.include?("delete_all"))
      if match = line.match(/(\b[A-Z]\w*)\s*\.\s*(?:update_all|delete_all)/)
        model_name = match[1]
        # Convert CamelCase to snake_case: User -> user, UserProfile -> user_profile
        table_name = model_name
          .gsub(/([A-Z])/, '_\1')
          .downcase
          .sub(/^_/, '')
        # Pluralize simple table names: add 's' for most words
        # This is a simple heuristic; full pluralization would require a library
        table_name = table_name + 's' unless table_name.end_with?('s', 'x', 'z')
        tables << table_name if table_name && !table_name.empty?
      end
    end
  end

  tables.to_a.sort
end

.filter_schema_tables(table_names) ⇒ Array<String>

Filter tables to only those likely affected by schema changes

Excludes internal Rails tables like schema_migrations

Parameters:

  • table_names (Array<String>) —

    List of table names

Returns:

  • (Array<String>) —

    Filtered list



132
133
134
135
136
137
138
139
140
# File 'lib/query_guard/migrations/table_size_resolver.rb', line 132

def self.filter_schema_tables(table_names)
  # Internal rails tables to ignore
  internal_tables = %w[
    schema_migrations ar_internal_metadata
    delayed_jobs sidekiq_jobs
  ]

  table_names.reject { |name| internal_tables.include?(name) }
end