Class: SimpleQuery::Builder

Inherits:
Object
  • Object
show all
Includes:
Stream::MysqlStream, Stream::PostgresStream
Defined in:
lib/simple_query/builder.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Stream::MysqlStream

#stream_each_mysql

Methods included from Stream::PostgresStream

#stream_each_postgres

Constructor Details

#initialize(source) ⇒ Builder

Returns a new instance of Builder.



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/simple_query/builder.rb', line 10

def initialize(source)
  @model = source
  @arel_table = @model.arel_table

  @selects = []
  @wheres = WhereClause.new(@arel_table)
  @joins = JoinClause.new
  @group_having = GroupHavingClause.new(@arel_table)
  @orders = OrderClause.new(@arel_table)
  @limits = LimitOffsetClause.new
  @distinct_flag = DistinctClause.new
  @aggregations = AggregationClause.new(@arel_table)

  @query_cache = {}
  @query_built = false
  @read_model_class = nil
  @result_struct = nil
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_name, *args, &block) ⇒ Object (private)



395
396
397
398
399
400
401
402
# File 'lib/simple_query/builder.rb', line 395

def method_missing(method_name, *args, &block)
  if (scope_block = find_scope(method_name))
    instance_exec(*args, &scope_block)
    self
  else
    super
  end
end

Instance Attribute Details

#arel_tableObject (readonly)

Returns the value of attribute arel_table.



8
9
10
# File 'lib/simple_query/builder.rb', line 8

def arel_table
  @arel_table
end

#modelObject (readonly)

Returns the value of attribute model.



8
9
10
# File 'lib/simple_query/builder.rb', line 8

def model
  @model
end

Instance Method Details

#avg(column, alias_name: nil) ⇒ Object



108
109
110
111
112
# File 'lib/simple_query/builder.rb', line 108

def avg(column, alias_name: nil)
  @aggregations.avg(column, alias_name: alias_name)
  reset_query
  self
end

#build_queryObject



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/simple_query/builder.rb', line 236

def build_query
  return @query if @query_built

  @query = Arel::SelectManager.new(Arel::Table.engine)
  @query.from(@arel_table)

  # Combine regular selects with aggregations
  all_selects = build_select_expressions
  @query.project(*all_selects)

  apply_distinct
  apply_where_conditions
  apply_joins
  apply_group_and_having
  apply_order_conditions
  apply_limit_and_offset

  @query_built = true
  @query
end

#bulk_update(set:) ⇒ Object

Raises:

  • (ArgumentError)


193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/simple_query/builder.rb', line 193

def bulk_update(set:)
  table_name = @arel_table.name
  set_sql = SetClause.new(set).to_sql

  raise ArgumentError, "No columns to update" if set_sql.empty?

  where_sql = build_where_sql
  sql = "UPDATE #{table_name} SET #{set_sql}"
  sql += " WHERE #{where_sql}" unless where_sql.nil? || where_sql.empty?

  ActiveRecord::Base.connection.execute(sql)
end

#count(column = nil, alias_name: nil) ⇒ Object

Aggregation methods



96
97
98
99
100
# File 'lib/simple_query/builder.rb', line 96

def count(column = nil, alias_name: nil)
  @aggregations.count(column, alias_name: alias_name)
  reset_query
  self
end

#custom_aggregation(expression, alias_name) ⇒ Object



144
145
146
147
148
# File 'lib/simple_query/builder.rb', line 144

def custom_aggregation(expression, alias_name)
  @aggregations.custom(expression, alias_name)
  reset_query
  self
end

#distinctObject



77
78
79
80
81
# File 'lib/simple_query/builder.rb', line 77

def distinct
  @distinct_flag.set_distinct
  reset_query
  self
end

#executeObject



217
218
219
220
# File 'lib/simple_query/builder.rb', line 217

def execute
  records = ActiveRecord::Base.connection.select_all(cached_sql)
  build_result_objects_from_rows(records)
end

#first_by(column, alias_name: nil) ⇒ Object

Method to get first/top record by column



166
167
168
169
170
# File 'lib/simple_query/builder.rb', line 166

def first_by(column, alias_name: nil)
  alias_name ||= "first_#{column}"
  custom_aggregation("FIRST_VALUE(#{resolve_column_name(column)}) OVER (ORDER BY #{resolve_column_name(column)})",
                     alias_name)
end

#full_join(table1, table2, foreign_key:, primary_key:) ⇒ Object



55
56
57
# File 'lib/simple_query/builder.rb', line 55

def full_join(table1, table2, foreign_key:, primary_key:)
  join(table1, table2, foreign_key: foreign_key, primary_key: primary_key, type: :full)
end

#group(*fields) ⇒ Object



83
84
85
86
87
# File 'lib/simple_query/builder.rb', line 83

def group(*fields)
  @group_having.add_group(*fields)
  reset_query
  self
end

#group_concat(column, separator: ",", alias_name: nil) ⇒ Object



138
139
140
141
142
# File 'lib/simple_query/builder.rb', line 138

def group_concat(column, separator: ",", alias_name: nil)
  @aggregations.group_concat(column, separator: separator, alias_name: alias_name)
  reset_query
  self
end

#having(condition) ⇒ Object



89
90
91
92
93
# File 'lib/simple_query/builder.rb', line 89

def having(condition)
  @group_having.add_having(condition)
  reset_query
  self
end

#join(table1, table2, foreign_key:, primary_key:, type: :inner) ⇒ Object



41
42
43
44
45
# File 'lib/simple_query/builder.rb', line 41

def join(table1, table2, foreign_key:, primary_key:, type: :inner)
  @joins.add(table1, table2, foreign_key: foreign_key, primary_key: primary_key, join_type: type)
  reset_query
  self
end

#last_by(column, alias_name: nil) ⇒ Object

Method to get last/bottom record by column



173
174
175
176
177
# File 'lib/simple_query/builder.rb', line 173

def last_by(column, alias_name: nil)
  alias_name ||= "last_#{column}"
  custom_aggregation("LAST_VALUE(#{resolve_column_name(column)}) OVER (ORDER BY #{resolve_column_name(column)})",
                     alias_name)
end

#lazy_executeObject



222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/simple_query/builder.rb', line 222

def lazy_execute
  Enumerator.new do |yielder|
    records = ActiveRecord::Base.connection.select_all(cached_sql)
    if @read_model_class
      build_read_models_enumerator(records, yielder)
    else
      struct = result_struct(records.columns)
      records.rows.each do |row_array|
        yielder << struct.new(*row_array)
      end
    end
  end
end

#left_join(table1, table2, foreign_key:, primary_key:) ⇒ Object



47
48
49
# File 'lib/simple_query/builder.rb', line 47

def left_join(table1, table2, foreign_key:, primary_key:)
  join(table1, table2, foreign_key: foreign_key, primary_key: primary_key, type: :left)
end

#limit(number) ⇒ Object



65
66
67
68
69
# File 'lib/simple_query/builder.rb', line 65

def limit(number)
  @limits.with_limit(number)
  reset_query
  self
end

#map_to(klass) ⇒ Object



187
188
189
190
191
# File 'lib/simple_query/builder.rb', line 187

def map_to(klass)
  @read_model_class = klass
  reset_query
  self
end

#max(column, alias_name: nil) ⇒ Object



120
121
122
123
124
# File 'lib/simple_query/builder.rb', line 120

def max(column, alias_name: nil)
  @aggregations.max(column, alias_name: alias_name)
  reset_query
  self
end

#min(column, alias_name: nil) ⇒ Object



114
115
116
117
118
# File 'lib/simple_query/builder.rb', line 114

def min(column, alias_name: nil)
  @aggregations.min(column, alias_name: alias_name)
  reset_query
  self
end

#offset(number) ⇒ Object



71
72
73
74
75
# File 'lib/simple_query/builder.rb', line 71

def offset(number)
  @limits.with_offset(number)
  reset_query
  self
end

#order(order_conditions) ⇒ Object



59
60
61
62
63
# File 'lib/simple_query/builder.rb', line 59

def order(order_conditions)
  @orders.add(order_conditions)
  reset_query
  self
end

#percentage_of_total(column, alias_name: nil) ⇒ Object

Percentage calculations



180
181
182
183
184
185
# File 'lib/simple_query/builder.rb', line 180

def percentage_of_total(column, alias_name: nil)
  alias_name ||= "#{column}_percentage"
  column_expr = resolve_column_name(column)
  expression = "ROUND((#{column_expr} * 100.0 / SUM(#{column_expr}) OVER ()), 2)"
  custom_aggregation(expression, alias_name)
end

#right_join(table1, table2, foreign_key:, primary_key:) ⇒ Object



51
52
53
# File 'lib/simple_query/builder.rb', line 51

def right_join(table1, table2, foreign_key:, primary_key:)
  join(table1, table2, foreign_key: foreign_key, primary_key: primary_key, type: :right)
end

#select(*fields) ⇒ Object



29
30
31
32
33
# File 'lib/simple_query/builder.rb', line 29

def select(*fields)
  @selects.concat(fields.map { |field| parse_select_field(field) })
  reset_query
  self
end

#stats(column, alias_prefix: nil) ⇒ Object



155
156
157
158
159
160
161
162
163
# File 'lib/simple_query/builder.rb', line 155

def stats(column, alias_prefix: nil)
  prefix = alias_prefix || column.to_s
  count(alias_name: "#{prefix}_count")
  sum(column, alias_name: "#{prefix}_sum")
  avg(column, alias_name: "#{prefix}_avg")
  min(column, alias_name: "#{prefix}_min")
  max(column, alias_name: "#{prefix}_max")
  self
end

#stddev(column, alias_name: nil) ⇒ Object



132
133
134
135
136
# File 'lib/simple_query/builder.rb', line 132

def stddev(column, alias_name: nil)
  @aggregations.stddev(column, alias_name: alias_name)
  reset_query
  self
end

#stream_each(batch_size: 1000, &block) ⇒ Object



206
207
208
209
210
211
212
213
214
215
# File 'lib/simple_query/builder.rb', line 206

def stream_each(batch_size: 1000, &block)
  adapter = ActiveRecord::Base.connection.adapter_name.downcase
  if adapter.include?("postgres")
    stream_each_postgres(batch_size, &block)
  elsif adapter.include?("mysql")
    stream_each_mysql(&block)
  else
    raise "stream_each is only implemented for Postgres and MySQL."
  end
end

#sum(column, alias_name: nil) ⇒ Object



102
103
104
105
106
# File 'lib/simple_query/builder.rb', line 102

def sum(column, alias_name: nil)
  @aggregations.sum(column, alias_name: alias_name)
  reset_query
  self
end

#total_count(alias_name: "total") ⇒ Object

Convenience methods for common aggregation patterns



151
152
153
# File 'lib/simple_query/builder.rb', line 151

def total_count(alias_name: "total")
  count(alias_name: alias_name)
end

#variance(column, alias_name: nil) ⇒ Object



126
127
128
129
130
# File 'lib/simple_query/builder.rb', line 126

def variance(column, alias_name: nil)
  @aggregations.variance(column, alias_name: alias_name)
  reset_query
  self
end

#where(condition) ⇒ Object



35
36
37
38
39
# File 'lib/simple_query/builder.rb', line 35

def where(condition)
  @wheres.add(condition)
  reset_query
  self
end