# UNDF: UNDF-2026-000000255 Fixes rails-0016: SQLite3Adapter#copy_table_indexes + copy_table_contents — to_column_names and from_columns are Arrays used inside .select and .find_all loops O(I×C×N). --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ DEFECT rails-0016a: lines 705-730 copy_table_indexes def copy_table_indexes(from, to, rename = {}) indexes(from).each do |index| ... columns = index.columns if columns.is_a?(Array) - to_column_names = columns(to).map(&:name) # Array rebuilt per index - columns = columns.map { |c| rename[c] || c }.select do |column| - to_column_names.include?(column) # O(N) Array scan per column + to_column_names_set = columns(to).map(&:name).to_set # FIX: build Set once per index + columns = columns.map { |c| rename[c] || c }.select do |column| + to_column_names_set.include?(column) # O(1) per column end end ... end end @@ DEFECT rails-0016b: lines 733-737 copy_table_contents def copy_table_contents(from, to, columns, rename = {}) column_mappings = Hash[columns.map { |name| [name, name] }] rename.each { |a| column_mappings[a.last] = a.first } - from_columns = columns(from).collect(&:name) # Array - columns = columns.find_all { |col| from_columns.include?(column_mappings[col]) } # O(C×N) + from_columns_set = columns(from).collect(&:name).to_set # FIX: Set for O(1) lookup + columns = columns.find_all { |col| from_columns_set.include?(column_mappings[col]) } ... end # BEFORE: copy_table_indexes: to_column_names is Array rebuilt for each index; include? O(N) per column # I indexes × C columns per index × N columns per table = O(I×C×N) # copy_table_contents: from_columns is Array; find_all with include? is O(C×N) # AFTER: to_column_names_set / from_columns_set are Sets: O(1) include? # copy_table_indexes: O(I×C); copy_table_contents: O(C) # Severity: MEDIUM — hot during ALTER TABLE simulations (SQLite has no native ALTER) # large tables with many indexes trigger this repeatedly # Speedup: ~N× where N = column count # At I=20 indexes, C=10 col/index, N=50 columns: 10,000 → 200 ops