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



213
214
215
# File 'lib/active_record/connection_adapters/sunstone/database_statements.rb', line 213

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.



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

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, 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.



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

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.



234
235
236
237
# File 'lib/active_record/connection_adapters/sunstone/database_statements.rb', line 234

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



122
123
124
125
# File 'lib/active_record/connection_adapters/sunstone/database_statements.rb', line 122

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



117
118
119
120
# File 'lib/active_record/connection_adapters/sunstone/database_statements.rb', line 117

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



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

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



239
240
241
242
# File 'lib/active_record/connection_adapters/sunstone/database_statements.rb', line 239

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

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



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

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.



128
129
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
# File 'lib/active_record/connection_adapters/sunstone/database_statements.rb', line 128

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



244
245
246
# File 'lib/active_record/connection_adapters/sunstone/database_statements.rb', line 244

def returning_column_values(result)
  result.rows.first
end

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



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

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.



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

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



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

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



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

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



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

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.



228
229
230
231
# File 'lib/active_record/connection_adapters/sunstone/database_statements.rb', line 228

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