From 79096618f04e08b9c1e9ba3100dd889ba8fd08e6 Mon Sep 17 00:00:00 2001 From: Iliana Hadzhiatanasova Date: Mon, 1 Aug 2022 18:26:27 +0100 Subject: [PATCH] Optimize add_timestamps to use a single SQL statement --- activerecord/CHANGELOG.md | 14 ++++++++++++++ .../abstract/schema_statements.rb | 10 ++-------- .../connection_adapters/sqlite3_adapter.rb | 13 +++++++++++++ .../test/cases/migration/columns_test.rb | 17 +++++++++++++++++ 4 files changed, 46 insertions(+), 8 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 501ef1bd32092..aae2d4850ecfb 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,17 @@ +* Optimize `add_timestamps` to use a single SQL statement. + + ```ruby + add_timestamps :my_table + ``` + + Now results in the following SQL: + + ```sql + ALTER TABLE "my_table" ADD COLUMN "created_at" datetime(6) NOT NULL, ADD COLUMN "updated_at" datetime(6) NOT NULL + ``` + + *Iliana Hadzhiatanasova* + * Add `drop_enum` migration command for PostgreSQL This does the inverse of `create_enum`. Before dropping an enum, ensure you have diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 8eb4dd9e061fb..bbfe017709b53 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -1355,14 +1355,8 @@ def distinct_relation_for_primary_key(relation) # :nodoc: # add_timestamps(:suppliers, null: true) # def add_timestamps(table_name, **options) - options[:null] = false if options[:null].nil? - - if !options.key?(:precision) && supports_datetime_with_precision? - options[:precision] = 6 - end - - add_column table_name, :created_at, :datetime, **options - add_column table_name, :updated_at, :datetime, **options + fragments = add_timestamps_for_alter(table_name, **options) + execute "ALTER TABLE #{quote_table_name(table_name)} #{fragments.join(', ')}" end # Removes the timestamp columns (+created_at+ and +updated_at+) from the table definition. diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 6c0d7c9064de7..a5f1e8f3d6f78 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -318,6 +318,19 @@ def rename_column(table_name, column_name, new_column_name) # :nodoc: rename_column_indexes(table_name, column.name, new_column_name) end + def add_timestamps(table_name, **options) + options[:null] = false if options[:null].nil? + + if !options.key?(:precision) + options[:precision] = 6 + end + + alter_table(table_name) do |definition| + definition.column :created_at, :datetime, **options + definition.column :updated_at, :datetime, **options + end + end + def add_reference(table_name, ref_name, **options) # :nodoc: super(table_name, ref_name, type: :integer, **options) end diff --git a/activerecord/test/cases/migration/columns_test.rb b/activerecord/test/cases/migration/columns_test.rb index 73252b885ce47..496825c397671 100644 --- a/activerecord/test/cases/migration/columns_test.rb +++ b/activerecord/test/cases/migration/columns_test.rb @@ -368,6 +368,23 @@ def test_remove_columns_single_statement ensure connection.drop_table :my_table, if_exists: true end + + def test_add_timestamps_single_statement + connection.create_table "my_table" + + # SQLite3's ALTER TABLE statement has several limitations. To manage + # this, the adapter creates a temporary table, copies the data, drops + # the old table, creates the new table, then copies the data back. + expected_query_count = current_adapter?(:SQLite3Adapter) ? 12 : 1 + assert_queries(expected_query_count) do + connection.add_timestamps("my_table") + end + + columns = connection.columns("my_table").map(&:name) + assert_equal ["id", "created_at", "updated_at"], columns + ensure + connection.drop_table :my_table, if_exists: true + end end end end