summaryrefslogtreecommitdiff
path: root/app/models/concerns/from_union.rb
diff options
context:
space:
mode:
Diffstat (limited to 'app/models/concerns/from_union.rb')
-rw-r--r--app/models/concerns/from_union.rb51
1 files changed, 51 insertions, 0 deletions
diff --git a/app/models/concerns/from_union.rb b/app/models/concerns/from_union.rb
new file mode 100644
index 00000000000..9b8595b1211
--- /dev/null
+++ b/app/models/concerns/from_union.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+module FromUnion
+ extend ActiveSupport::Concern
+
+ class_methods do
+ # Produces a query that uses a FROM to select data using a UNION.
+ #
+ # Using a FROM for a UNION has in the past lead to better query plans. As
+ # such, we generally recommend this pattern instead of using a WHERE IN.
+ #
+ # Example:
+ # users = User.from_union([User.where(id: 1), User.where(id: 2)])
+ #
+ # This would produce the following SQL query:
+ #
+ # SELECT *
+ # FROM (
+ # SELECT *
+ # FROM users
+ # WHERE id = 1
+ #
+ # UNION
+ #
+ # SELECT *
+ # FROM users
+ # WHERE id = 2
+ # ) users;
+ #
+ # members - An Array of ActiveRecord::Relation objects to use in the UNION.
+ #
+ # remove_duplicates - A boolean indicating if duplicate entries should be
+ # removed. Defaults to true.
+ #
+ # alias_as - The alias to use for the sub query. Defaults to the name of the
+ # table of the current model.
+ # rubocop: disable Gitlab/Union
+ def from_union(members, remove_duplicates: true, alias_as: table_name)
+ union = Gitlab::SQL::Union
+ .new(members, remove_duplicates: remove_duplicates)
+ .to_sql
+
+ # This pattern is necessary as a bug in Rails 4 can cause the use of
+ # `from("string here").includes(:foo)` to break ActiveRecord. This is
+ # fixed in https://github.com/rails/rails/pull/25374, which is released as
+ # part of Rails 5.
+ from([Arel.sql("(#{union}) #{alias_as}")])
+ end
+ # rubocop: enable Gitlab/Union
+ end
+end