diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index d6f081ff18..b38e2b8c35 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,18 @@ +* Allow passing raw SQL as `returning` statement to `#upsert_all`: + + ```ruby + Article.insert_all( + [ + {title: "Article 1", slug: "article-1", published: false}, + {title: "Article 2", slug: "article-2", published: false} + ], + # Some PostgreSQL magic here to detect which rows have been actually inserted + returning: "id, (xmax = '0') as inserted, name as new_name" + ) + ``` + + *Vladimir Dementyev* + * Deprecate `legacy_connection_handling`. *Eileen M. Uchitelle* diff --git a/activerecord/lib/active_record/insert_all.rb b/activerecord/lib/active_record/insert_all.rb index 97c440cd9c..61c13ab4ab 100644 --- a/activerecord/lib/active_record/insert_all.rb +++ b/activerecord/lib/active_record/insert_all.rb @@ -151,7 +151,13 @@ module ActiveRecord end def returning - format_columns(insert_all.returning) if insert_all.returning + return unless insert_all.returning + + if insert_all.returning.is_a?(String) + insert_all.returning + else + format_columns(insert_all.returning) + end end def conflict_target diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 9406b84274..7004f447a9 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -91,6 +91,9 @@ module ActiveRecord # or returning: false to omit the underlying RETURNING SQL # clause entirely. # + # You can also pass an SQL string if you need more control on the return values + # (for example, returning: "id, name as new_name"). + # # [:unique_by] # (PostgreSQL and SQLite only) By default rows are considered to be unique # by every unique index on the table. Any duplicate rows are skipped. @@ -168,6 +171,9 @@ module ActiveRecord # or returning: false to omit the underlying RETURNING SQL # clause entirely. # + # You can also pass an SQL string if you need more control on the return values + # (for example, returning: "id, name as new_name"). + # # ==== Examples # # # Insert multiple records @@ -216,6 +222,9 @@ module ActiveRecord # or returning: false to omit the underlying RETURNING SQL # clause entirely. # + # You can also pass an SQL string if you need more control on the return values + # (for example, returning: "id, name as new_name"). + # # [:unique_by] # (PostgreSQL and SQLite only) By default rows are considered to be unique # by every unique index on the table. Any duplicate rows are skipped. diff --git a/activerecord/test/cases/insert_all_test.rb b/activerecord/test/cases/insert_all_test.rb index b35c4724fe..87851518c2 100644 --- a/activerecord/test/cases/insert_all_test.rb +++ b/activerecord/test/cases/insert_all_test.rb @@ -109,6 +109,13 @@ class InsertAllTest < ActiveRecord::TestCase assert_equal %w[ Rework ], result.pluck("name") end + def test_insert_all_returns_requested_sql_fields + skip unless supports_insert_returning? + + result = Book.insert_all! [{ name: "Rework", author_id: 1 }], returning: "UPPER(name) as name" + assert_equal %w[ REWORK ], result.pluck("name") + end + def test_insert_all_can_skip_duplicate_records skip unless supports_insert_on_duplicate_skip?