Class: Gitlab::SQL::SetOperator

Inherits:
Object
  • Object
show all
Defined in:
lib/gitlab/sql/set_operator.rb

Overview

Class for building SQL set operator statements (UNION, INTERSECT, and EXCEPT).

ORDER BYs are dropped from the relations as the final sort order is not guaranteed any way.

remove_order: false option can be used in special cases where the ORDER BY is necessary for the query.

Example usage:

union = Gitlab::SQL::Union.new([user.personal_projects, user.projects])
sql   = union.to_sql

Project.where("id IN (#{sql})")

Direct Known Subclasses

Except, Intersect, Union

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(relations, remove_duplicates: true, remove_order: true) ⇒ SetOperator

Returns a new instance of SetOperator.



21
22
23
24
25
26
# File 'lib/gitlab/sql/set_operator.rb', line 21

def initialize(relations, remove_duplicates: true, remove_order: true)
  verify_select_values!(relations) if Rails.env.test? || Rails.env.development?
  @relations = relations
  @remove_duplicates = remove_duplicates
  @remove_order = remove_order
end

Class Method Details

.operator_keywordObject

Raises:

  • (NotImplementedError)


28
29
30
# File 'lib/gitlab/sql/set_operator.rb', line 28

def self.operator_keyword
  raise NotImplementedError
end

Instance Method Details

#operator_keyword_fragmentObject

UNION [ALL] | INTERSECT [ALL] | EXCEPT [ALL]



54
55
56
# File 'lib/gitlab/sql/set_operator.rb', line 54

def operator_keyword_fragment
  remove_duplicates ? self.class.operator_keyword : "#{self.class.operator_keyword} ALL"
end

#to_sqlObject



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/gitlab/sql/set_operator.rb', line 32

def to_sql
  # Some relations may include placeholders for prepared statements, these
  # aren't incremented properly when joining relations together this way.
  # By using "unprepared_statements" we remove the usage of placeholders
  # (thus fixing this problem), at a slight performance cost.
  fragments = ApplicationRecord.connection.unprepared_statement do
    relations.filter_map do |rel|
      next if rel.is_a?(ActiveRecord::NullRelation)

      sql = remove_order ? rel.reorder(nil).to_sql : rel.to_sql
      sql.presence
    end
  end

  if fragments.any?
    "(" + fragments.join(")\n#{operator_keyword_fragment}\n(") + ")"
  else
    'NULL'
  end
end