Module: ActiveRecord::ConnectionAdapters::Sunstone::DatabaseStatements

Included in:
ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter
Defined in:
lib/active_record/connection_adapters/sunstone/database_statements.rb

Defined Under Namespace

Classes: SunstonePartialQueryCollector

Instance Method Summary collapse

Instance Method Details

#affected_rows(raw_result) ⇒ Object



215
216
217
# File 'lib/active_record/connection_adapters/sunstone/database_statements.rb', line 215

def affected_rows(raw_result)
  @last_affected_rows
end

#cacheable_query(klass, arel) ⇒ Object

This is used in the StatementCache object. It returns an object that can be used to query the database repeatedly.



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/active_record/connection_adapters/sunstone/database_statements.rb', line 64

def cacheable_query(klass, arel) # :nodoc:
  if prepared_statements
    sql, binds = visitor.compile(arel.ast, collector)
    query = klass.query(sql)
  elsif self.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter)
    collector = SunstonePartialQueryCollector.new(self.collector)
    parts, binds = visitor.compile(arel.ast, collector)
    query = StatementCache::PartialQuery.new(parts, retryable: collector.retryable, sunstone: true)
  else
    collector = klass.partial_query_collector
    parts, binds = visitor.compile(arel.ast, collector)
    query = klass.partial_query(parts)
  end
  [query, binds]
end

#cast_result(raw_result) ⇒ Object

Receive a native adapter result object and returns an ActiveRecord::Result object.



204
205
206
207
208
209
210
211
212
213
# File 'lib/active_record/connection_adapters/sunstone/database_statements.rb', line 204

def cast_result(raw_result)
  if raw_result.instance_variable_defined?(:@sunstone_calculation) && raw_result.instance_variable_get(:@sunstone_calculation)
    # this is a count, min, max.... yea i know..
    ActiveRecord::Result.new(['all'], [raw_result], {:all => @type_map.lookup('integer', {})})
  elsif raw_result.is_a?(Array)
    ActiveRecord::Result.new(raw_result[0] ? raw_result[0].keys : [], raw_result.map{|r| r.values})
  else
    ActiveRecord::Result.new(raw_result.keys, [raw_result.values])
  end
end

#delete(arel, name = nil, binds = []) ⇒ Object

Executes the delete statement and returns the number of rows affected.



236
237
238
239
# File 'lib/active_record/connection_adapters/sunstone/database_statements.rb', line 236

def delete(arel, name = nil, binds = [])
  sql, binds = to_sar_and_binds(arel, binds)
  exec_delete(sql, name, binds)
end

#exec_delete(arel, name = nil, binds = []) ⇒ Object



124
125
126
127
# File 'lib/active_record/connection_adapters/sunstone/database_statements.rb', line 124

def exec_delete(arel, name = nil, binds = [])
  x = internal_execute(arel, name, binds)
  x.nil? ? 1 : x
end

#exec_insert(arel, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil) ⇒ Object

Executes insert sql statement in the context of this connection using binds as the bind substitutes. name is logged along with the executed sql statement. Some adapters support the returning keyword argument which allows to control the result of the query: nil is the default value and maintains default behavior. If an array of column names is passed - the result will contain values of the specified columns from the inserted row.

TODO: Add support for returning



119
120
121
122
# File 'lib/active_record/connection_adapters/sunstone/database_statements.rb', line 119

def exec_insert(arel, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil)
  sar, binds = sar_for_insert(arel, pk, binds, returning)
  internal_exec_query(sar, name, binds)
end

#insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [], returning: nil) ⇒ Object Also known as: create



219
220
221
222
223
224
225
226
# File 'lib/active_record/connection_adapters/sunstone/database_statements.rb', line 219

def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [], returning: nil)
  sar, binds = to_sar_and_binds(arel, binds)
  value = exec_insert(sar, name, binds, pk, sequence_name, returning: returning)

  return returning_column_values(value) unless returning.nil?

  id_value || last_inserted_id(value)
end

#last_inserted_id(result) ⇒ Object



241
242
243
244
# File 'lib/active_record/connection_adapters/sunstone/database_statements.rb', line 241

def last_inserted_id(result)
  row = result.rows.first
  row && row['id']
end

#perform_query(raw_connection, sar, prepare:, notification_payload:, batch: false) ⇒ Object



192
193
194
195
196
197
198
199
200
201
# File 'lib/active_record/connection_adapters/sunstone/database_statements.rb', line 192

def perform_query(raw_connection, sar, prepare:, notification_payload:, batch: false)
  response = raw_connection.send_request(sar)
  result = response.is_a?(Net::HTTPNoContent) ? nil : JSON.parse(response.body)

  verified!
  # handle_warnings(result)
  @last_affected_rows = response['Affected-Rows'] || result&.count || 0
  notification_payload[:row_count] = @last_affected_rows
  result
end

#raw_execute(arel, name = nil, binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true, batch: false) ⇒ Object

Lowest level way to execute a query. Doesn't check for illegal writes, doesn't annotate queries, yields a native result object.



130
131
132
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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/active_record/connection_adapters/sunstone/database_statements.rb', line 130

def raw_execute(arel, name = nil, binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true, batch: false)
  multiple_requests = arel.is_a?(Arel::Collectors::Sunstone)
  type_casted_binds = binds#type_casted_binds(binds)
  
  if multiple_requests
    allowed_limit = limit_definition(arel.table)
    limit_bind_index = nil#binds.find_index { |x| x.name == 'LIMIT' }
    requested_limit = if limit_bind_index
      type_casted_binds[limit_bind_index]
    else
      arel.limit
    end

    if allowed_limit.nil?
      multiple_requests = false
    elsif requested_limit && requested_limit <= allowed_limit
      multiple_requests = false
    else
      multiple_requests = true
    end
  end
  
  send_request = lambda { |conn, req_arel, batch|
    sar = to_sar(req_arel, type_casted_binds)
    log_mess = sar.path.split('?', 2)
    log("#{sar.method} #{log_mess[0]} #{(log_mess[1] && !log_mess[1].empty?) ? MessagePack.unpack(CGI.unescape(log_mess[1])) : '' }", name) do |notification_payload|
      result = perform_query(conn, sar, prepare:, notification_payload:, batch: batch)
      result.instance_variable_set(:@sunstone_calculation, true) if result && sar.instance_variable_get(:@sunstone_calculation)
      result
    end
  }
  
  result = with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
    if multiple_requests
      binds.delete_at(limit_bind_index) if limit_bind_index

      limit, offset, results = allowed_limit, 0, nil
      last_affected_rows = 0
      while requested_limit ? offset < requested_limit : true
        split_arel = arel.dup
        split_arel.limit = limit
        split_arel.offset = offset
        request_results = send_request.call(conn, split_arel, true)
        last_affected_rows += @last_affected_rows
        if results
          results.push(*request_results)
        else
          results = request_results
        end
        break if request_results.size < limit
        offset = offset + limit
      end
      @last_affected_rows = last_affected_rows
      results
    else
      send_request.call(conn, arel, true)
    end
  end

  result
end

#returning_column_values(result) ⇒ Object



246
247
248
# File 'lib/active_record/connection_adapters/sunstone/database_statements.rb', line 246

def returning_column_values(result)
  result.rows.first
end

#sar_for_insert(sql, pk, binds, returning) ⇒ Object



56
57
58
59
60
# File 'lib/active_record/connection_adapters/sunstone/database_statements.rb', line 56

def sar_for_insert(sql, pk, binds, returning)
  # TODO: when StandardAPI supports returning we can do this; it might
  # already need to investigate
  to_sar_and_binds(sql, binds)
end

#select_all(arel, name = nil, binds = [], preparable: nil, async: false, allow_retry: false) ⇒ Object

Returns an ActiveRecord::Result instance.



98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/active_record/connection_adapters/sunstone/database_statements.rb', line 98

def select_all(arel, name = nil, binds = [], preparable: nil, async: false, allow_retry: false)
  arel = arel_from_relation(arel)
  sar, binds, preparable, allow_retry = to_sar_and_binds(arel, binds, preparable, allow_retry)

  select(sar, name, binds,
    prepare: prepared_statements && preparable,
    async: async && FutureResult::SelectAll,
    allow_retry: allow_retry
  )
rescue ::RangeError
  ActiveRecord::Result.empty(async: async)
end

#to_sar(arel_or_sar_string, binds = nil) ⇒ Object

Converts an arel AST to a Sunstone API Request



23
24
25
26
27
28
29
30
31
32
# File 'lib/active_record/connection_adapters/sunstone/database_statements.rb', line 23

def to_sar(arel_or_sar_string, binds = nil)
  if arel_or_sar_string.respond_to?(:ast)
    sar = visitor.accept(arel_or_sar_string.ast, collector)
    binds = sar.binds if binds.nil?
  else
    sar = arel_or_sar_string
  end

  sar.compile(binds)
end

#to_sar_and_binds(arel_or_sar_string, binds = [], preparable = nil, allow_retry = false) ⇒ Object



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/active_record/connection_adapters/sunstone/database_statements.rb', line 34

def to_sar_and_binds(arel_or_sar_string, binds = [], preparable = nil, allow_retry = false)
  # Arel::TreeManager -> Arel::Node
  if arel_or_sar_string.respond_to?(:ast)
    arel_or_sar_string = arel_or_sar_string.ast
  end

  if Arel.arel_node?(arel_or_sar_string) && !(String === arel_or_sar_string)
    unless binds.empty?
      raise "Passing bind parameters with an arel AST is forbidden. " \
        "The values must be stored on the AST directly"
    end
    
    col = collector()
    col.retryable = true
    sar = visitor.compile(arel_or_sar_string, col)
    [sar.freeze, sar.binds, false, allow_retry]
  else
    arel_or_sar_string = arel_or_sar_string.dup.freeze unless arel_or_sar_string.frozen?
    [arel_or_sar_string, binds, false, allow_retry]
  end
end

#to_sql(arel, binds = []) ⇒ Object



10
11
12
13
14
15
16
17
18
19
20
# File 'lib/active_record/connection_adapters/sunstone/database_statements.rb', line 10

def to_sql(arel, binds = [])
  if arel.respond_to?(:ast)
    unless binds.empty?
      raise "Passing bind parameters with an arel AST is forbidden. " \
        "The values must be stored on the AST directly"
    end
    Arel::Visitors::ToSql.new(self).accept(arel.ast, Arel::Collectors::SubstituteBinds.new(self, Arel::Collectors::SQLString.new)).value
  else
    arel.dup.freeze
  end
end

#update(arel, name = nil, binds = []) ⇒ Object

Executes the update statement and returns the number of rows affected.



230
231
232
233
# File 'lib/active_record/connection_adapters/sunstone/database_statements.rb', line 230

def update(arel, name = nil, binds = [])
  sar, binds = to_sar_and_binds(arel, binds)
  internal_exec_query(sar, name, binds)
end

#write_query?(arel) ⇒ Boolean

Returns:

  • (Boolean)


250
251
252
# File 'lib/active_record/connection_adapters/sunstone/database_statements.rb', line 250

def write_query?(arel)
  arel.request_type != Net::HTTP::Get
end