Class: ClickHouse::Client::QueryBuilder

Inherits:
QueryLike
  • Object
show all
Defined in:
lib/click_house/client/query_builder.rb

Constant Summary collapse

VALID_NODES =
[
  Arel::Nodes::In,
  Arel::Nodes::Equality,
  Arel::Nodes::LessThan,
  Arel::Nodes::LessThanOrEqual,
  Arel::Nodes::GreaterThan,
  Arel::Nodes::GreaterThanOrEqual,
  Arel::Nodes::NamedFunction,
  Arel::Nodes::NotIn,
  Arel::Nodes::NotEqual,
  Arel::Nodes::Between,
  Arel::Nodes::And,
  Arel::Nodes::Or,
  Arel::Nodes::Grouping
].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from QueryLike

#prepared_placeholders

Constructor Details

#initialize(table_name) ⇒ QueryBuilder

Returns a new instance of QueryBuilder.



27
28
29
30
# File 'lib/click_house/client/query_builder.rb', line 27

def initialize(table_name)
  @table = Arel::Table.new(table_name)
  @manager = Arel::SelectManager.new(Arel::Table.engine).from(@table).project(Arel.star)
end

Instance Attribute Details

#managerObject

Returns the value of attribute manager.



9
10
11
# File 'lib/click_house/client/query_builder.rb', line 9

def manager
  @manager
end

#tableObject (readonly)

Returns the value of attribute table.



8
9
10
# File 'lib/click_house/client/query_builder.rb', line 8

def table
  @table
end

Instance Method Details

#from(subquery, alias_name) ⇒ Object



123
124
125
126
127
128
129
130
131
# File 'lib/click_house/client/query_builder.rb', line 123

def from(subquery, alias_name)
  clone.tap do |new_instance|
    if subquery.is_a?(self.class)
      new_instance.manager.from(subquery.to_arel.as(alias_name))
    else
      new_instance.manager.from(Arel::Nodes::TableAlias.new(subquery, alias_name))
    end
  end
end

#group(*columns) ⇒ Object



107
108
109
110
111
# File 'lib/click_house/client/query_builder.rb', line 107

def group(*columns)
  clone.tap do |new_instance|
    new_instance.manager.group(*columns)
  end
end

#initialize_copy(other) ⇒ Object



32
33
34
35
36
# File 'lib/click_house/client/query_builder.rb', line 32

def initialize_copy(other)
  super

  @manager = other.manager.clone
end

#joins(table_name, constraint = nil) ⇒ Object



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/click_house/client/query_builder.rb', line 133

def joins(table_name, constraint = nil)
  clone.tap do |new_instance|
    join_table = table_name.is_a?(Arel::Table) ? table_name : Arel::Table.new(table_name)

    join_condition = case constraint
                     when Hash
                       # Handle hash based constraints like { table1.id: table2.ref_id } or {id: :ref_id}
                       constraint_conditions = constraint.map do |left, right|
                         left_field = left.is_a?(Arel::Attributes::Attribute) ? left : new_instance.table[left]
                         right_field = right.is_a?(Arel::Attributes::Attribute) ? right : join_table[right]
                         left_field.eq(right_field)
                       end

                       constraint_conditions.reduce(&:and)
                     when Proc
                       constraint.call(new_instance.table, join_table)
                     when Arel::Nodes::Node
                       constraint
                     end

    if join_condition
      new_instance.manager.join(join_table).on(join_condition)
    else
      new_instance.manager.join(join_table)
    end
  end
end

#limit(count) ⇒ Object



113
114
115
116
# File 'lib/click_house/client/query_builder.rb', line 113

def limit(count)
  manager.take(count)
  self
end

#offset(count) ⇒ Object



118
119
120
121
# File 'lib/click_house/client/query_builder.rb', line 118

def offset(count)
  manager.skip(count)
  self
end

#order(field, direction = :asc) ⇒ Object



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/click_house/client/query_builder.rb', line 91

def order(field, direction = :asc)
  validate_order_direction!(direction)

  clone.tap do |new_instance|
    order_node = case field
                 when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
                   field
                 else
                   new_instance.table[field]
                 end

    new_order = direction.to_s.casecmp('desc').zero? ? order_node.desc : order_node.asc
    new_instance.manager.order(new_order)
  end
end

#select(*fields) ⇒ Object



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/click_house/client/query_builder.rb', line 61

def select(*fields)
  clone.tap do |new_instance|
    existing_fields = new_instance.manager.projections.filter_map do |projection|
      if projection.respond_to?(:to_s) && projection.to_s == '*'
        nil
      elsif projection.is_a?(Arel::Attributes::Attribute)
        projection.name.to_s
      elsif projection.is_a?(Arel::Expressions)
        projection
      end
    end

    new_projections = (existing_fields + fields).map do |field|
      if field.is_a?(Symbol)
        field.to_s
      else
        field
      end
    end

    new_instance.manager.projections = new_projections.uniq.map do |field|
      if field.is_a?(Arel::Expressions)
        field
      else
        new_instance.table[field.to_s]
      end
    end
  end
end

#to_arelObject



170
171
172
# File 'lib/click_house/client/query_builder.rb', line 170

def to_arel
  manager
end

#to_redacted_sql(bind_index_manager = ClickHouse::Client::BindIndexManager.new) ⇒ Object



166
167
168
# File 'lib/click_house/client/query_builder.rb', line 166

def to_redacted_sql(bind_index_manager = ClickHouse::Client::BindIndexManager.new)
  ClickHouse::Client::Redactor.redact(self, bind_index_manager)
end

#to_sqlObject



161
162
163
164
# File 'lib/click_house/client/query_builder.rb', line 161

def to_sql
  visitor = Arel::Visitors::ToSql.new(ClickHouse::Client::ArelEngine.new)
  visitor.accept(manager.ast, Arel::Collectors::SQLString.new).value
end

#where(constraints) ⇒ ClickHouse::QueryBuilder

The ‘where` method currently only supports IN and equal to queries along with above listed VALID_NODES. For example, using a range (start_date..end_date) will result in incorrect SQL. If you need to query a range, use greater than and less than constraints with Arel.

Correct usage:

query.where(query.table[:created_at].lteq(Date.today)).to_sql
"SELECT * FROM \"table\" WHERE \"table\".\"created_at\" <= '2023-08-01'"

This also supports array constraints which will result in an IN query.

query.where(entity_id: [1,2,3]).to_sql
"SELECT * FROM \"table\" WHERE \"table\".\"entity_id\" IN (1, 2, 3)"

Range support and more ‘Arel::Nodes` could be considered for future iterations.

Returns:

  • (ClickHouse::QueryBuilder)

    New instance of query builder.



53
54
55
56
57
58
59
# File 'lib/click_house/client/query_builder.rb', line 53

def where(constraints)
  validate_constraint_type!(constraints)

  clone.tap do |new_instance|
    add_constraints_to(new_instance, constraints)
  end
end