8a72f5c427
This commit adds the module `FromUnion`, which provides the class method `from_union`. This simplifies the process of selecting data from the result of a UNION, and reduces the likelihood of making mistakes. As a result, instead of this: union = Gitlab::SQL::Union.new([foo, bar]) Foo.from("(#{union.to_sql}) #{Foo.table_name}") We can now write this instead: Foo.from_union([foo, bar]) This commit also includes some changes to make this new setup work properly. For example, a bug in Rails 4 (https://github.com/rails/rails/issues/24193) would break the use of `from("sub-query-here").includes(:relation)` in certain cases. There was also a CI query which appeared to repeat a lot of conditions from an outer query on an inner query, which isn't necessary. Finally, we include a RuboCop cop to ensure developers use this new module, instead of using Gitlab::SQL::Union directly. Fixes https://gitlab.com/gitlab-org/gitlab-ce/issues/51307
51 lines
1.6 KiB
Ruby
51 lines
1.6 KiB
Ruby
# 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
|