sunstone 8.0.0 → 8.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +43 -0
- data/README.md +221 -0
- data/ext/active_record/associations/collection_association.rb +45 -63
- data/ext/active_record/associations/has_many_through_association.rb +21 -0
- data/ext/active_record/associations.rb +12 -10
- data/ext/active_record/attribute_methods.rb +4 -2
- data/ext/active_record/base.rb +16 -0
- data/ext/active_record/callbacks.rb +4 -2
- data/ext/active_record/locking/optimistic.rb +60 -0
- data/ext/active_record/persistence.rb +35 -12
- data/ext/active_record/relation/calculations.rb +26 -24
- data/ext/active_record/relation/finder_methods.rb +165 -0
- data/ext/active_record/relation/predicate_builder.rb +91 -0
- data/ext/active_record/relation/query_methods.rb +6 -1
- data/ext/active_record/relation.rb +5 -0
- data/ext/active_record/statement_cache.rb +11 -3
- data/ext/active_record/transactions.rb +29 -15
- data/ext/active_support/core_ext/object/to_query.rb +2 -0
- data/ext/arel/attributes/empty_relation.rb +2 -0
- data/ext/arel/nodes/eager_load.rb +2 -0
- data/ext/arel/nodes/select_statement.rb +2 -0
- data/ext/arel/select_manager.rb +2 -0
- data/lib/active_record/connection_adapters/sunstone/column.rb +4 -1
- data/lib/active_record/connection_adapters/sunstone/database_statements.rb +12 -1
- data/lib/active_record/connection_adapters/sunstone/quoting.rb +2 -0
- data/lib/active_record/connection_adapters/sunstone/schema_dumper.rb +2 -0
- data/lib/active_record/connection_adapters/sunstone/schema_statements.rb +6 -4
- data/lib/active_record/connection_adapters/sunstone/type/array.rb +2 -0
- data/lib/active_record/connection_adapters/sunstone/type/binary.rb +2 -0
- data/lib/active_record/connection_adapters/sunstone/type/date_time.rb +2 -0
- data/lib/active_record/connection_adapters/sunstone/type/ewkb.rb +2 -0
- data/lib/active_record/connection_adapters/sunstone/type/json.rb +2 -0
- data/lib/active_record/connection_adapters/sunstone/type/uuid.rb +2 -0
- data/lib/active_record/connection_adapters/sunstone/type_metadata.rb +2 -0
- data/lib/active_record/connection_adapters/sunstone_adapter.rb +2 -0
- data/lib/arel/collectors/sunstone.rb +15 -5
- data/lib/arel/visitors/sunstone.rb +2 -3
- data/lib/sunstone/connection.rb +2 -0
- data/lib/sunstone/exception.rb +2 -0
- data/lib/sunstone/gis.rb +2 -0
- data/lib/sunstone/version.rb +3 -1
- data/lib/sunstone.rb +9 -1
- metadata +23 -16
- data/ext/active_record/finder_methods.rb +0 -246
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The last ref that this code was synced with Rails
|
|
4
|
+
# ref: 90a1eaa1b3
|
|
5
|
+
|
|
1
6
|
module ActiveRecord
|
|
2
7
|
# = Active Record \Persistence
|
|
3
8
|
module Persistence
|
|
@@ -59,7 +64,7 @@ module ActiveRecord
|
|
|
59
64
|
|
|
60
65
|
result = new_record? ? _create_record(&block) : _update_record(&block)
|
|
61
66
|
|
|
62
|
-
if self.
|
|
67
|
+
if self.sunstone? && result != 0 && !result[0].nil?
|
|
63
68
|
row_hash = result[0]
|
|
64
69
|
|
|
65
70
|
seen = Hash.new { |h, parent_klass|
|
|
@@ -70,7 +75,7 @@ module ActiveRecord
|
|
|
70
75
|
|
|
71
76
|
model_cache = Hash.new { |h,klass| h[klass] = {} }
|
|
72
77
|
parents = model_cache[self.class.base_class]
|
|
73
|
-
|
|
78
|
+
|
|
74
79
|
row_hash.each do |key, value|
|
|
75
80
|
if self.class.column_names.include?(key.to_s)
|
|
76
81
|
_write_attribute(key, value)
|
|
@@ -105,7 +110,7 @@ module ActiveRecord
|
|
|
105
110
|
attribute_names = attributes_for_create(attribute_names)
|
|
106
111
|
attribute_values = attributes_with_values(attribute_names)
|
|
107
112
|
returning_values = nil
|
|
108
|
-
|
|
113
|
+
|
|
109
114
|
self.class.with_connection do |connection|
|
|
110
115
|
returning_columns = self.class._returning_columns_for_insert(connection)
|
|
111
116
|
|
|
@@ -115,25 +120,41 @@ module ActiveRecord
|
|
|
115
120
|
returning_columns
|
|
116
121
|
)
|
|
117
122
|
|
|
118
|
-
if !self.
|
|
123
|
+
if !self.sunstone?
|
|
119
124
|
returning_columns.zip(returning_values).each do |column, value|
|
|
120
|
-
_write_attribute(column, value) if !_read_attribute(column)
|
|
125
|
+
_write_attribute(column, type_for_attribute(column).deserialize(value)) if !_read_attribute(column)
|
|
121
126
|
end if returning_values
|
|
122
127
|
end
|
|
123
128
|
end
|
|
124
129
|
|
|
125
130
|
@new_record = false
|
|
126
131
|
@previously_new_record = true
|
|
127
|
-
|
|
132
|
+
|
|
128
133
|
yield(self) if block_given?
|
|
129
|
-
|
|
130
|
-
if self.
|
|
134
|
+
|
|
135
|
+
if self.sunstone?
|
|
131
136
|
returning_values
|
|
132
137
|
else
|
|
133
138
|
id
|
|
134
139
|
end
|
|
135
140
|
end
|
|
136
141
|
|
|
142
|
+
def _touch_row(attribute_names, time)
|
|
143
|
+
time ||= current_time_from_proper_timezone
|
|
144
|
+
|
|
145
|
+
attribute_names.each do |attr_name|
|
|
146
|
+
_write_attribute(attr_name, time)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
_update_row(attributes_with_values(attribute_names), "touch")
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Sunstone passes the values, not just the names. This is because if a
|
|
153
|
+
# sub resource has changed it'll send the whole shabang
|
|
154
|
+
def _update_row(attribute_values, attempted_action = "update")
|
|
155
|
+
self.class._update_record(attribute_values, _query_constraints_hash)
|
|
156
|
+
end
|
|
157
|
+
|
|
137
158
|
def _update_record(attribute_names = self.attribute_names)
|
|
138
159
|
attribute_names = attributes_for_update(attribute_names)
|
|
139
160
|
attribute_values = attributes_with_values(attribute_names)
|
|
@@ -142,8 +163,10 @@ module ActiveRecord
|
|
|
142
163
|
affected_rows = 0
|
|
143
164
|
@_trigger_update_callback = true
|
|
144
165
|
else
|
|
145
|
-
affected_rows =
|
|
146
|
-
|
|
166
|
+
affected_rows = _update_row(attribute_values)
|
|
167
|
+
|
|
168
|
+
# Suntone returns the row(s) not a int of afftecd_rows
|
|
169
|
+
@_trigger_update_callback = (sunstone? ? affected_rows.rows.size : affected_rows) == 1
|
|
147
170
|
end
|
|
148
171
|
|
|
149
172
|
@previously_new_record = false
|
|
@@ -179,7 +202,7 @@ module ActiveRecord
|
|
|
179
202
|
|
|
180
203
|
end
|
|
181
204
|
end
|
|
182
|
-
|
|
205
|
+
|
|
183
206
|
#!!!! TODO: I am duplicated from finder_methods.....
|
|
184
207
|
def construct_association(parent, reflection, attributes, seen, model_cache)
|
|
185
208
|
return if attributes.nil?
|
|
@@ -231,6 +254,6 @@ module ActiveRecord
|
|
|
231
254
|
other.set_inverse_instance(model)
|
|
232
255
|
model
|
|
233
256
|
end
|
|
234
|
-
|
|
257
|
+
|
|
235
258
|
end
|
|
236
259
|
end
|
|
@@ -1,30 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# The last ref that this code was synced with Rails
|
|
2
|
-
# ref:
|
|
4
|
+
# ref: 90a1eaa1b3
|
|
3
5
|
|
|
4
6
|
module ActiveRecord
|
|
5
7
|
module Calculations
|
|
6
8
|
|
|
7
|
-
# Prior to Rails 8 we didn't need this method becuase it would
|
|
8
|
-
# return the first value if there was just one - so we'll just
|
|
9
|
-
# do the same as prevously because it doesn't have to be joined
|
|
10
|
-
def select_for_count
|
|
11
|
-
if select_values.empty?
|
|
12
|
-
:all
|
|
13
|
-
else
|
|
14
|
-
with_connection do |conn|
|
|
15
|
-
# Rails compiles this to a string, but we don't have string we
|
|
16
|
-
# have a hash
|
|
17
|
-
if model.connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter)
|
|
18
|
-
sv = arel_columns(select_values)
|
|
19
|
-
sv.one? ? sv.first : sv
|
|
20
|
-
else
|
|
21
|
-
sv = arel_columns(select_values).map { |column| conn.visitor.compile(column) }
|
|
22
|
-
sv.one? ? sv.first : sv.join(", ")
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
|
|
28
9
|
def pluck(*column_names)
|
|
29
10
|
if @none
|
|
30
11
|
if @async
|
|
@@ -46,7 +27,7 @@ module ActiveRecord
|
|
|
46
27
|
if has_include?(column_names.first)
|
|
47
28
|
relation = apply_join_dependency
|
|
48
29
|
relation.pluck(*column_names)
|
|
49
|
-
elsif model.
|
|
30
|
+
elsif model.sunstone?
|
|
50
31
|
load
|
|
51
32
|
return records.pluck(*column_names.map{|n| n.to_s.sub(/^#{model.table_name}\./, "")})
|
|
52
33
|
else
|
|
@@ -55,7 +36,11 @@ module ActiveRecord
|
|
|
55
36
|
columns = relation.arel_columns(column_names)
|
|
56
37
|
relation.select_values = columns
|
|
57
38
|
result = skip_query_cache_if_necessary do
|
|
58
|
-
|
|
39
|
+
# TODO: drop this Rails 8.0 compatibility guard when Rails 8.0 support is
|
|
40
|
+
# dropped, and call possible_aggregation?(column_names) directly.
|
|
41
|
+
# possible_aggregation? was added in Rails 8.1; on 8.0 a contradiction
|
|
42
|
+
# short-circuits to an empty result unconditionally.
|
|
43
|
+
if where_clause.contradiction? && !(respond_to?(:possible_aggregation?, true) && possible_aggregation?(column_names))
|
|
59
44
|
ActiveRecord::Result.empty(async: @async)
|
|
60
45
|
else
|
|
61
46
|
model.with_connection do |c|
|
|
@@ -69,5 +54,22 @@ module ActiveRecord
|
|
|
69
54
|
end
|
|
70
55
|
end
|
|
71
56
|
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
# Prior to Rails 8 we didn't need this method becuase it would
|
|
60
|
+
# return the first value if there was just one - so we'll just
|
|
61
|
+
# do the same as prevously because it doesn't have to be joined
|
|
62
|
+
def select_for_count
|
|
63
|
+
if select_values.empty?
|
|
64
|
+
:all
|
|
65
|
+
elsif model.sunstone?
|
|
66
|
+
select_values.one? ? select_values.first : select_values
|
|
67
|
+
else
|
|
68
|
+
with_connection do |conn|
|
|
69
|
+
arel_columns(select_values).map { |column| conn.visitor.compile(column) }.join(", ")
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
72
74
|
end
|
|
73
75
|
end
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The last ref that this code was synced with Rails
|
|
4
|
+
# ref: 90a1eaa1b3
|
|
5
|
+
|
|
6
|
+
module ActiveRecord::FinderMethods
|
|
7
|
+
class SunstoneJoinDependency
|
|
8
|
+
def initialize(klass)
|
|
9
|
+
@klass = klass
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def reflections
|
|
13
|
+
[]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def apply_column_aliases(relation)
|
|
17
|
+
relation
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def instantiate(result_set, strict_loading_value, &block)
|
|
21
|
+
seen = Hash.new { |i, object_id|
|
|
22
|
+
i[object_id] = Hash.new { |j, child_class|
|
|
23
|
+
j[child_class] = {}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
model_cache = Hash.new { |h, klass| h[klass] = {} }
|
|
28
|
+
parents = model_cache[@klass]
|
|
29
|
+
|
|
30
|
+
message_bus = ActiveSupport::Notifications.instrumenter
|
|
31
|
+
|
|
32
|
+
payload = {
|
|
33
|
+
record_count: result_set.length,
|
|
34
|
+
class_name: @klass.name
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
message_bus.instrument("instantiation.active_record", payload) do
|
|
38
|
+
result_set.each { |row_hash|
|
|
39
|
+
parent_key = @klass.primary_key ? row_hash[@klass.primary_key] : row_hash
|
|
40
|
+
parent = parents[parent_key] ||= @klass.instantiate(row_hash.select{|k,v| @klass.column_names.include?(k.to_s) }, &block)
|
|
41
|
+
construct(parent, row_hash.select{|k,v| !@klass.column_names.include?(k.to_s) }, seen, model_cache, strict_loading_value)
|
|
42
|
+
}
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
parents.values
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def construct(parent, relations, seen, model_cache, strict_loading_value)
|
|
49
|
+
relations.each do |key, attributes|
|
|
50
|
+
reflection = parent.class.reflect_on_association(key)
|
|
51
|
+
next unless reflection
|
|
52
|
+
|
|
53
|
+
if reflection.collection?
|
|
54
|
+
other = parent.association(reflection.name)
|
|
55
|
+
other.loaded!
|
|
56
|
+
else
|
|
57
|
+
if parent.association_cached?(reflection.name)
|
|
58
|
+
model = parent.association(reflection.name).target
|
|
59
|
+
construct(model, attributes.select{|k,v| !model.class.column_names.include?(k.to_s) }, seen, model_cache, strict_loading_value)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
if !reflection.collection?
|
|
64
|
+
construct_association(parent, reflection, attributes, seen, model_cache, strict_loading_value)
|
|
65
|
+
else
|
|
66
|
+
attributes.each do |row|
|
|
67
|
+
construct_association(parent, reflection, row, seen, model_cache, strict_loading_value)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def construct_association(parent, reflection, attributes, seen, model_cache, strict_loading_value)
|
|
75
|
+
return if attributes.nil?
|
|
76
|
+
|
|
77
|
+
klass = if reflection.polymorphic?
|
|
78
|
+
parent.send(reflection.foreign_type).constantize.base_class
|
|
79
|
+
else
|
|
80
|
+
reflection.klass
|
|
81
|
+
end
|
|
82
|
+
id = attributes[klass.primary_key]
|
|
83
|
+
model = seen[parent.object_id][klass][id]
|
|
84
|
+
|
|
85
|
+
if model
|
|
86
|
+
construct(model, attributes.select{|k,v| !klass.column_names.include?(k.to_s) }, seen, model_cache, strict_loading_value)
|
|
87
|
+
|
|
88
|
+
other = parent.association(reflection.name)
|
|
89
|
+
|
|
90
|
+
if reflection.collection?
|
|
91
|
+
other.target.push(model)
|
|
92
|
+
else
|
|
93
|
+
other.target = model
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
other.set_inverse_instance(model)
|
|
97
|
+
else
|
|
98
|
+
model = construct_model(parent, reflection, id, attributes.select{|k,v| klass.column_names.include?(k.to_s) }, seen, model_cache, strict_loading_value)
|
|
99
|
+
seen[parent.object_id][model.class.base_class][id] = model
|
|
100
|
+
construct(model, attributes.select{|k,v| !klass.column_names.include?(k.to_s) }, seen, model_cache, strict_loading_value)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def construct_model(record, reflection, id, attributes, seen, model_cache, strict_loading_value)
|
|
106
|
+
klass = if reflection.polymorphic?
|
|
107
|
+
record.send(reflection.foreign_type).constantize
|
|
108
|
+
else
|
|
109
|
+
reflection.klass
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
model = model_cache[klass][id] ||= klass.instantiate(attributes)
|
|
113
|
+
other = record.association(reflection.name)
|
|
114
|
+
|
|
115
|
+
if reflection.collection?
|
|
116
|
+
other.target.push(model)
|
|
117
|
+
else
|
|
118
|
+
other.target = model
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
other.set_inverse_instance(model)
|
|
122
|
+
model
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
def apply_join_dependency(eager_loading: group_values.empty?)
|
|
130
|
+
if model.sunstone?
|
|
131
|
+
join_dependency = SunstoneJoinDependency.new(base_class)
|
|
132
|
+
relation = except(:includes, :eager_load, :preload)
|
|
133
|
+
relation.arel.eager_load = Arel::Nodes::EagerLoad.new(eager_load_values)
|
|
134
|
+
else
|
|
135
|
+
join_dependency = construct_join_dependency(
|
|
136
|
+
eager_load_values | includes_values, Arel::Nodes::OuterJoin
|
|
137
|
+
)
|
|
138
|
+
relation = except(:includes, :eager_load, :preload).joins!(join_dependency)
|
|
139
|
+
|
|
140
|
+
if eager_loading && has_limit_or_offset? && !(
|
|
141
|
+
using_limitable_reflections?(join_dependency.reflections) &&
|
|
142
|
+
using_limitable_reflections?(
|
|
143
|
+
construct_join_dependency(
|
|
144
|
+
select_association_list(joins_values).concat(
|
|
145
|
+
select_association_list(left_outer_joins_values)
|
|
146
|
+
), nil
|
|
147
|
+
).reflections
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
relation = skip_query_cache_if_necessary do
|
|
151
|
+
model.with_connection do |c|
|
|
152
|
+
c.distinct_relation_for_primary_key(relation)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
if block_given?
|
|
159
|
+
yield relation, join_dependency
|
|
160
|
+
else
|
|
161
|
+
relation
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The last ref that this code was synced with Rails
|
|
4
|
+
# ref: 90a1eaa1b3
|
|
5
|
+
|
|
6
|
+
class ActiveRecord::PredicateBuilder # :nodoc:
|
|
7
|
+
|
|
8
|
+
def expand_from_hash(attributes, &block)
|
|
9
|
+
return [Arel.sql("1=0", retryable: true)] if attributes.empty?
|
|
10
|
+
|
|
11
|
+
attributes.flat_map do |key, value|
|
|
12
|
+
if key.is_a?(Array) && key.size == 1
|
|
13
|
+
key = key.first
|
|
14
|
+
value = value.flatten
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
if key.is_a?(Array)
|
|
18
|
+
queries = Array(value).map do |ids_set|
|
|
19
|
+
raise ArgumentError, "Expected corresponding value for #{key} to be an Array" unless ids_set.is_a?(Array)
|
|
20
|
+
expand_from_hash(key.zip(ids_set).to_h)
|
|
21
|
+
end
|
|
22
|
+
grouping_queries(queries)
|
|
23
|
+
elsif value.is_a?(Hash) && !table.has_column?(key)
|
|
24
|
+
ka = table.associated_table(key, &block)
|
|
25
|
+
.predicate_builder.expand_from_hash(value.stringify_keys)
|
|
26
|
+
|
|
27
|
+
if self.table.instance_variable_get(:@klass).sunstone?
|
|
28
|
+
ka.each { |k|
|
|
29
|
+
if k.left.is_a?(Arel::Attributes::Attribute) || k.left.is_a?(Arel::Attributes::Relation)
|
|
30
|
+
k.left = Arel::Attributes::Relation.new(k.left, key)
|
|
31
|
+
end
|
|
32
|
+
}
|
|
33
|
+
end
|
|
34
|
+
ka
|
|
35
|
+
# TODO: drop this Rails 8.0 compatibility branch when Rails 8.0 support is
|
|
36
|
+
# dropped, and always use table.associated_with(key). (8.0 named it associated_with?)
|
|
37
|
+
elsif (associated_reflection = (table.respond_to?(:associated_with) ? table.associated_with(key) : table.associated_with?(key)))
|
|
38
|
+
# Find the foreign key when using queries such as:
|
|
39
|
+
# Post.where(author: author)
|
|
40
|
+
#
|
|
41
|
+
# For polymorphic relationships, find the foreign key and type:
|
|
42
|
+
# PriceEstimate.where(estimate_of: treasure)
|
|
43
|
+
|
|
44
|
+
if associated_reflection.polymorphic?
|
|
45
|
+
value = [value] unless value.is_a?(Array)
|
|
46
|
+
klass = PolymorphicArrayValue
|
|
47
|
+
elsif associated_reflection.through_reflection?
|
|
48
|
+
associated_table = table.associated_table(key)
|
|
49
|
+
|
|
50
|
+
next associated_table.predicate_builder.expand_from_hash(
|
|
51
|
+
associated_table.primary_key => value
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
klass ||= AssociationQueryValue
|
|
56
|
+
# TODO: drop this Rails 8.0 compatibility branch when Rails 8.0 support is
|
|
57
|
+
# dropped, and always pass associated_reflection.
|
|
58
|
+
# Rails 8.1 threads the reflection through the value objects; 8.0 threads the table metadata.
|
|
59
|
+
query_source = table.respond_to?(:associated_with) ? associated_reflection : table.associated_table(key)
|
|
60
|
+
queries = klass.new(query_source, value).queries.map! do |query|
|
|
61
|
+
# If the query produced is identical to attributes don't go any deeper.
|
|
62
|
+
# Prevents stack level too deep errors when association and foreign_key are identical.
|
|
63
|
+
query == attributes ? self[key, value] : expand_from_hash(query)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
grouping_queries(queries)
|
|
67
|
+
elsif table.aggregated_with?(key)
|
|
68
|
+
mapping = table.reflect_on_aggregation(key).mapping
|
|
69
|
+
values = value.nil? ? [nil] : Array.wrap(value)
|
|
70
|
+
if mapping.length == 1 || values.empty?
|
|
71
|
+
column_name, aggr_attr = mapping.first
|
|
72
|
+
values = values.map do |object|
|
|
73
|
+
object.respond_to?(aggr_attr) ? object.public_send(aggr_attr) : object
|
|
74
|
+
end
|
|
75
|
+
self[column_name, values]
|
|
76
|
+
else
|
|
77
|
+
queries = values.map do |object|
|
|
78
|
+
mapping.map do |field_attr, aggregate_attr|
|
|
79
|
+
self[field_attr, object.try!(aggregate_attr)]
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
grouping_queries(queries)
|
|
84
|
+
end
|
|
85
|
+
else
|
|
86
|
+
self[key, value]
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
end
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The last ref that this code was synced with Rails
|
|
4
|
+
# ref: 90a1eaa1b3
|
|
5
|
+
|
|
1
6
|
module ActiveRecord
|
|
2
7
|
module QueryMethods
|
|
3
8
|
private
|
|
4
9
|
|
|
5
10
|
def assert_modifiable!
|
|
6
11
|
raise UnmodifiableRelation if @loaded
|
|
7
|
-
raise UnmodifiableRelation if @arel && !model.
|
|
12
|
+
raise UnmodifiableRelation if @arel && !model.sunstone?
|
|
8
13
|
end
|
|
9
14
|
|
|
10
15
|
end
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The last ref that this code was synced with Rails
|
|
4
|
+
# ref: 90a1eaa1b3
|
|
5
|
+
|
|
1
6
|
module ActiveRecord
|
|
2
7
|
class StatementCache
|
|
3
8
|
class PartialQuery
|
|
4
9
|
|
|
5
|
-
|
|
10
|
+
# TODO: drop this Rails 8.0 compatibility default when Rails 8.0 support is
|
|
11
|
+
# dropped, and make retryable: a required keyword again.
|
|
12
|
+
# retryable: is required on Rails 8.1 but absent from 8.0's caller.
|
|
13
|
+
def initialize(values, retryable: false, sunstone: false)
|
|
6
14
|
@values = values
|
|
7
15
|
@indexes = if sunstone
|
|
8
16
|
else
|
|
@@ -10,8 +18,9 @@ module ActiveRecord
|
|
|
10
18
|
Substitute === thing
|
|
11
19
|
}.map(&:last)
|
|
12
20
|
end
|
|
21
|
+
@retryable = retryable
|
|
13
22
|
end
|
|
14
|
-
|
|
23
|
+
|
|
15
24
|
def sql_for(binds, connection)
|
|
16
25
|
if connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter)
|
|
17
26
|
@values
|
|
@@ -29,6 +38,5 @@ module ActiveRecord
|
|
|
29
38
|
end
|
|
30
39
|
|
|
31
40
|
end
|
|
32
|
-
|
|
33
41
|
end
|
|
34
42
|
end
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The last ref that this code was synced with Rails
|
|
4
|
+
# ref: 90a1eaa1b3
|
|
5
|
+
|
|
1
6
|
require 'active_record'
|
|
2
7
|
require 'active_record/transactions'
|
|
3
8
|
|
|
@@ -35,26 +40,35 @@ module ActiveRecord
|
|
|
35
40
|
|
|
36
41
|
def with_transaction_returning_status
|
|
37
42
|
self.class.with_connection do |connection|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter) && instance_variable_defined?(:@sunstone_updating) && @sunstone_updating
|
|
42
|
-
status = yield
|
|
43
|
-
else
|
|
44
|
-
ensure_finalize = !connection.transaction_open?
|
|
45
|
-
connection.transaction do
|
|
46
|
-
add_to_transaction(ensure_finalize || has_transactional_callbacks?)
|
|
47
|
-
remember_transaction_record_state
|
|
43
|
+
transaction_body = proc do
|
|
44
|
+
status = nil
|
|
48
45
|
|
|
46
|
+
if sunstone? && instance_variable_defined?(:@sunstone_updating) && @sunstone_updating
|
|
49
47
|
status = yield
|
|
50
|
-
|
|
48
|
+
else
|
|
49
|
+
ensure_finalize = !connection.transaction_open?
|
|
50
|
+
connection.transaction do
|
|
51
|
+
add_to_transaction(ensure_finalize || has_transactional_callbacks?)
|
|
52
|
+
remember_transaction_record_state
|
|
53
|
+
|
|
54
|
+
status = yield
|
|
55
|
+
raise ActiveRecord::Rollback unless status
|
|
56
|
+
end
|
|
57
|
+
@_last_transaction_return_status = status
|
|
58
|
+
status
|
|
51
59
|
end
|
|
52
60
|
end
|
|
53
|
-
|
|
54
|
-
|
|
61
|
+
|
|
62
|
+
# TODO: drop this Rails 8.0 compatibility branch when Rails 8.0
|
|
63
|
+
# support is dropped, and always use with_pool_transaction_isolation_level.
|
|
64
|
+
# Rails 8.1 wraps the transaction in a pool-level isolation context;
|
|
65
|
+
# this API doesn't exist on 8.0, so run the body directly there.
|
|
66
|
+
if ActiveRecord.respond_to?(:default_transaction_isolation_level)
|
|
67
|
+
connection.pool.with_pool_transaction_isolation_level(ActiveRecord.default_transaction_isolation_level, connection.transaction_open?, &transaction_body)
|
|
68
|
+
else
|
|
69
|
+
transaction_body.call
|
|
70
|
+
end
|
|
55
71
|
end
|
|
56
72
|
end
|
|
57
|
-
|
|
58
|
-
|
|
59
73
|
end
|
|
60
74
|
end
|
data/ext/arel/select_manager.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ActiveRecord
|
|
2
4
|
module ConnectionAdapters
|
|
3
5
|
# Sunstone-specific extensions to column definitions in a table.
|
|
@@ -6,8 +8,9 @@ module ActiveRecord
|
|
|
6
8
|
|
|
7
9
|
attr_reader :array
|
|
8
10
|
|
|
9
|
-
def initialize(name, sql_type_metadata, options={})
|
|
11
|
+
def initialize(name, cast_type, sql_type_metadata, options={})
|
|
10
12
|
@name = name.freeze
|
|
13
|
+
@cast_type = cast_type
|
|
11
14
|
@sql_type_metadata = sql_type_metadata
|
|
12
15
|
@null = options['null']
|
|
13
16
|
@default = options['default'] ? JSON.generate(options['default']) : options['default']
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "arel/collectors/sql_string"
|
|
2
4
|
|
|
3
5
|
module ActiveRecord
|
|
@@ -66,7 +68,7 @@ module ActiveRecord
|
|
|
66
68
|
elsif self.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter)
|
|
67
69
|
collector = SunstonePartialQueryCollector.new(self.collector)
|
|
68
70
|
parts, binds = visitor.compile(arel.ast, collector)
|
|
69
|
-
query = StatementCache::PartialQuery.new(parts, true)
|
|
71
|
+
query = StatementCache::PartialQuery.new(parts, retryable: collector.retryable, sunstone: true)
|
|
70
72
|
else
|
|
71
73
|
collector = klass.partial_query_collector
|
|
72
74
|
parts, binds = visitor.compile(arel.ast, collector)
|
|
@@ -118,6 +120,11 @@ module ActiveRecord
|
|
|
118
120
|
sar, binds = sar_for_insert(arel, pk, binds, returning)
|
|
119
121
|
internal_exec_query(sar, name, binds)
|
|
120
122
|
end
|
|
123
|
+
|
|
124
|
+
def exec_delete(arel, name = nil, binds = [])
|
|
125
|
+
x = internal_execute(arel, name, binds)
|
|
126
|
+
x.nil? ? 1 : x
|
|
127
|
+
end
|
|
121
128
|
|
|
122
129
|
# Lowest level way to execute a query. Doesn't check for illegal writes, doesn't annotate queries, yields a native result object.
|
|
123
130
|
def raw_execute(arel, name = nil, binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true, batch: false)
|
|
@@ -240,6 +247,10 @@ module ActiveRecord
|
|
|
240
247
|
result.rows.first
|
|
241
248
|
end
|
|
242
249
|
|
|
250
|
+
def write_query?(arel)
|
|
251
|
+
arel.request_type != Net::HTTP::Get
|
|
252
|
+
end
|
|
253
|
+
|
|
243
254
|
end
|
|
244
255
|
end
|
|
245
256
|
end
|