sunstone 5.1.0.4 → 5.2.1
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 +5 -5
- data/.travis.yml +5 -13
- data/ext/active_record/associations.rb +2 -7
- data/ext/active_record/associations/collection_association.rb +25 -18
- data/ext/active_record/attribute_methods.rb +19 -35
- data/ext/active_record/finder_methods.rb +138 -90
- data/ext/active_record/persistence.rb +16 -11
- data/ext/active_record/relation.rb +7 -47
- data/ext/active_record/relation/calculations.rb +5 -3
- data/ext/active_record/relation/query_methods.rb +9 -0
- data/ext/active_record/statement_cache.rb +4 -6
- data/ext/active_record/transactions.rb +9 -19
- data/ext/arel/nodes/select_statement.rb +26 -12
- data/lib/active_record/connection_adapters/sunstone/database_statements.rb +76 -25
- data/lib/active_record/connection_adapters/sunstone/type/json.rb +1 -1
- data/lib/active_record/connection_adapters/sunstone_adapter.rb +2 -3
- data/lib/arel/collectors/sunstone.rb +21 -19
- data/lib/arel/visitors/sunstone.rb +4 -8
- data/lib/sunstone.rb +2 -3
- data/lib/sunstone/connection.rb +2 -2
- data/lib/sunstone/exception.rb +8 -1
- data/lib/sunstone/version.rb +1 -1
- data/sunstone.gemspec +3 -3
- data/test/active_record/associations/has_many_test.rb +30 -2
- data/test/active_record/eager_loading_test.rb +10 -0
- data/test/active_record/persistance_test.rb +7 -7
- data/test/active_record/query/count_test.rb +13 -0
- data/test/active_record/query_test.rb +6 -6
- data/test/sunstone/connection/send_request_test.rb +1 -1
- data/test/test_helper.rb +1 -1
- metadata +9 -10
- data/ext/active_record/associations/association.rb +0 -16
- data/ext/active_record/batches.rb +0 -12
@@ -14,12 +14,14 @@ module ActiveRecord
|
|
14
14
|
|
15
15
|
private
|
16
16
|
|
17
|
-
def create_or_update(*args)
|
17
|
+
def create_or_update(*args, &block)
|
18
|
+
_raise_readonly_record_error if readonly?
|
19
|
+
return false if destroyed?
|
20
|
+
|
18
21
|
@updating = new_record? ? :creating : :updating
|
19
22
|
Thread.current[:sunstone_updating_model] = self
|
20
23
|
|
21
|
-
|
22
|
-
result = new_record? ? _create_record : _update_record(*args)
|
24
|
+
result = new_record? ? _create_record(&block) : _update_record(*args, &block)
|
23
25
|
|
24
26
|
if self.class.connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter) && result != 0
|
25
27
|
row_hash = result.rows.first
|
@@ -61,9 +63,10 @@ module ActiveRecord
|
|
61
63
|
# Creates a record with values matching those of the instance attributes
|
62
64
|
# and returns its id.
|
63
65
|
def _create_record(attribute_names = self.attribute_names)
|
64
|
-
|
65
|
-
|
66
|
-
|
66
|
+
attribute_names &= self.class.column_names
|
67
|
+
attributes_values = attributes_with_values_for_create(attribute_names)
|
68
|
+
|
69
|
+
new_id = self.class._insert_record(attributes_values)
|
67
70
|
|
68
71
|
@new_record = false
|
69
72
|
|
@@ -76,18 +79,20 @@ module ActiveRecord
|
|
76
79
|
end
|
77
80
|
|
78
81
|
def _update_record(attribute_names = self.attribute_names)
|
79
|
-
|
82
|
+
attribute_names &= self.class.column_names
|
83
|
+
attributes_values = attributes_with_values_for_update(attribute_names)
|
84
|
+
|
80
85
|
if attributes_values.empty?
|
81
|
-
|
86
|
+
affected_rows = 0
|
82
87
|
@_trigger_update_callback = true
|
83
88
|
else
|
84
|
-
|
85
|
-
@_trigger_update_callback =
|
89
|
+
affected_rows = self.class._update_record( attributes_values, self.class.primary_key => id_in_database )
|
90
|
+
@_trigger_update_callback = affected_rows == 1
|
86
91
|
end
|
87
92
|
|
88
93
|
yield(self) if block_given?
|
89
94
|
|
90
|
-
|
95
|
+
affected_rows
|
91
96
|
end
|
92
97
|
|
93
98
|
#!!!! TODO: I am duplicated from finder_methods.....
|
@@ -1,59 +1,19 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
class Relation
|
3
3
|
|
4
|
-
def to_sql
|
5
|
-
@to_sql ||= begin
|
6
|
-
relation = self
|
7
|
-
|
8
|
-
if eager_loading?
|
9
|
-
find_with_associations { |rel| relation = rel }
|
10
|
-
end
|
11
|
-
|
12
|
-
conn = klass.connection
|
13
|
-
conn.unprepared_statement {
|
14
|
-
conn.to_sql(relation.arel, relation.bound_attributes)
|
15
|
-
}
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
4
|
def to_sar
|
20
5
|
@to_sar ||= begin
|
21
|
-
relation = self
|
22
|
-
|
23
6
|
if eager_loading?
|
24
|
-
|
7
|
+
apply_join_dependency do |relation, join_dependency|
|
8
|
+
relation = join_dependency.apply_column_aliases(relation)
|
9
|
+
relation.to_sar
|
10
|
+
end
|
11
|
+
else
|
12
|
+
conn = klass.connection
|
13
|
+
conn.unprepared_statement { conn.to_sar(arel) }
|
25
14
|
end
|
26
|
-
|
27
|
-
conn = klass.connection
|
28
|
-
conn.unprepared_statement {
|
29
|
-
conn.to_sar(relation.arel, relation.bound_attributes)
|
30
|
-
}
|
31
15
|
end
|
32
16
|
end
|
33
17
|
|
34
|
-
def _update_record(values, id, id_was) # :nodoc:
|
35
|
-
substitutes, binds = substitute_values values
|
36
|
-
|
37
|
-
scope = @klass.unscoped
|
38
|
-
|
39
|
-
if @klass.finder_needs_type_condition?
|
40
|
-
scope.unscope!(where: @klass.inheritance_column)
|
41
|
-
end
|
42
|
-
|
43
|
-
relation = scope.where(@klass.primary_key => (id_was || id))
|
44
|
-
bvs = binds + relation.bound_attributes
|
45
|
-
um = relation
|
46
|
-
.arel
|
47
|
-
.compile_update(substitutes, @klass.primary_key)
|
48
|
-
um.table @klass.arel_table
|
49
|
-
|
50
|
-
@klass.connection.update(
|
51
|
-
um,
|
52
|
-
'SQL',
|
53
|
-
bvs,
|
54
|
-
)
|
55
|
-
end
|
56
|
-
|
57
|
-
|
58
18
|
end
|
59
19
|
end
|
@@ -7,19 +7,21 @@ module ActiveRecord
|
|
7
7
|
end
|
8
8
|
|
9
9
|
if has_include?(column_names.first)
|
10
|
-
|
10
|
+
relation = apply_join_dependency
|
11
|
+
relation.pluck(*column_names)
|
11
12
|
elsif klass.connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter)
|
12
13
|
load
|
13
14
|
return records.pluck(*column_names.map{|n| n.sub(/^#{klass.table_name}\./, "")})
|
14
15
|
else
|
16
|
+
enforce_raw_sql_whitelist(column_names)
|
15
17
|
relation = spawn
|
16
18
|
relation.select_values = column_names.map { |cn|
|
17
19
|
@klass.has_attribute?(cn) || @klass.attribute_alias?(cn) ? arel_attribute(cn) : cn
|
18
20
|
}
|
19
|
-
result = klass.connection.select_all(relation.arel, nil
|
21
|
+
result = skip_query_cache_if_necessary { klass.connection.select_all(relation.arel, nil) }
|
20
22
|
result.cast_values(klass.attribute_types)
|
21
23
|
end
|
22
24
|
end
|
23
|
-
|
25
|
+
|
24
26
|
end
|
25
27
|
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module QueryMethods
|
3
|
+
private
|
4
|
+
def assert_mutability!
|
5
|
+
raise ImmutableRelation if @loaded
|
6
|
+
raise ImmutableRelation if defined?(@arel) && !klass.connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter) && @arel
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
@@ -5,9 +5,7 @@ module ActiveRecord
|
|
5
5
|
def initialize(values, sunstone=false)
|
6
6
|
@values = values
|
7
7
|
@indexes = if sunstone
|
8
|
-
|
9
|
-
Arel::Nodes::BindParam === thing
|
10
|
-
}
|
8
|
+
|
11
9
|
else
|
12
10
|
values.each_with_index.find_all { |thing,i|
|
13
11
|
Arel::Nodes::BindParam === thing
|
@@ -16,11 +14,11 @@ module ActiveRecord
|
|
16
14
|
end
|
17
15
|
|
18
16
|
def sql_for(binds, connection)
|
19
|
-
casted_binds = binds.map(&:value_for_database)
|
20
|
-
|
21
17
|
if connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter)
|
22
|
-
|
18
|
+
binds.map!(&:value_for_database)
|
19
|
+
@values
|
23
20
|
else
|
21
|
+
casted_binds = binds.map(&:value_for_database)
|
24
22
|
val = @values.dup
|
25
23
|
@indexes.each { |i| val[i] = connection.quote(casted_binds.shift) }
|
26
24
|
val.join
|
@@ -34,29 +34,19 @@ module ActiveRecord
|
|
34
34
|
# end
|
35
35
|
|
36
36
|
def with_transaction_returning_status
|
37
|
-
if self.class.connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter) && instance_variable_defined?(:@updating) && @updating
|
38
|
-
begin
|
39
|
-
status = yield
|
40
|
-
rescue ActiveRecord::Rollback
|
41
|
-
clear_transaction_record_state
|
42
|
-
status = nil
|
43
|
-
end
|
44
|
-
return status
|
45
|
-
end
|
46
|
-
|
47
37
|
status = nil
|
48
|
-
|
49
|
-
|
50
|
-
|
38
|
+
|
39
|
+
if self.class.connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter) && instance_variable_defined?(:@updating) && @updating
|
40
|
+
status = yield
|
41
|
+
status
|
42
|
+
else
|
43
|
+
self.class.transaction do
|
44
|
+
add_to_transaction
|
51
45
|
status = yield
|
52
|
-
|
53
|
-
clear_transaction_record_state
|
54
|
-
status = nil
|
46
|
+
raise ActiveRecord::Rollback unless status
|
55
47
|
end
|
56
|
-
|
57
|
-
raise ActiveRecord::Rollback unless status
|
48
|
+
status
|
58
49
|
end
|
59
|
-
status
|
60
50
|
ensure
|
61
51
|
if @transaction_state && @transaction_state.committed?
|
62
52
|
clear_transaction_record_state
|
@@ -4,24 +4,38 @@ module Arel
|
|
4
4
|
|
5
5
|
attr_accessor :eager_load
|
6
6
|
|
7
|
-
def
|
8
|
-
|
9
|
-
@
|
7
|
+
def initialize cores = [SelectCore.new]
|
8
|
+
super()
|
9
|
+
@cores = cores
|
10
|
+
@orders = []
|
11
|
+
@limit = nil
|
12
|
+
@lock = nil
|
13
|
+
@offset = nil
|
14
|
+
@with = nil
|
15
|
+
@eager_load = nil
|
10
16
|
end
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
17
|
+
|
18
|
+
def initialize_copy other
|
19
|
+
super
|
20
|
+
@cores = @cores.map { |x| x.clone }
|
21
|
+
@orders = @orders.map { |x| x.clone }
|
22
|
+
@eager_load = @eager_load&.map { |x| x.clone }
|
23
|
+
end
|
24
|
+
|
15
25
|
def hash
|
16
26
|
[@cores, @orders, @limit, @lock, @offset, @with, @eager_load].hash
|
17
27
|
end
|
18
28
|
|
19
|
-
def
|
20
|
-
|
29
|
+
def eql? other
|
30
|
+
self.class == other.class &&
|
31
|
+
self.cores == other.cores &&
|
32
|
+
self.orders == other.orders &&
|
33
|
+
self.limit == other.limit &&
|
34
|
+
self.lock == other.lock &&
|
35
|
+
self.offset == other.offset &&
|
36
|
+
self.with == other.with &&
|
37
|
+
self.eager_load == other.eager_load
|
21
38
|
end
|
22
|
-
alias_method :eql_without_eager_load?, :eql?
|
23
|
-
alias_method :eql?, :eql_with_eager_load?
|
24
|
-
alias_method :==, :eql?
|
25
39
|
|
26
40
|
end
|
27
41
|
end
|
@@ -7,49 +7,97 @@ module ActiveRecord
|
|
7
7
|
|
8
8
|
def to_sql(arel, binds = [])
|
9
9
|
if arel.respond_to?(:ast)
|
10
|
-
|
11
|
-
|
10
|
+
unless binds.empty?
|
11
|
+
raise "Passing bind parameters with an arel AST is forbidden. " \
|
12
|
+
"The values must be stored on the AST directly"
|
13
|
+
end
|
14
|
+
Arel::Visitors::ToSql.new(self).accept(arel.ast, Arel::Collectors::SubstituteBinds.new(self, Arel::Collectors::SQLString.new)).value
|
12
15
|
else
|
13
16
|
arel.dup.freeze
|
14
17
|
end
|
15
18
|
end
|
16
19
|
|
17
20
|
# Converts an arel AST to a Sunstone API Request
|
18
|
-
def to_sar(
|
19
|
-
if
|
20
|
-
|
21
|
-
|
21
|
+
def to_sar(arel_or_sar_string, binds = nil)
|
22
|
+
if arel_or_sar_string.respond_to?(:ast)
|
23
|
+
sar = visitor.accept(arel_or_sar_string.ast, collector)
|
24
|
+
binds = sar.binds if binds.nil?
|
22
25
|
else
|
23
|
-
|
26
|
+
sar = arel_or_sar_string
|
24
27
|
end
|
28
|
+
sar.compile(binds)
|
25
29
|
end
|
26
30
|
|
31
|
+
def to_sar_and_binds(arel_or_sar_string, binds = []) # :nodoc:
|
32
|
+
if arel_or_sar_string.respond_to?(:ast)
|
33
|
+
unless binds.empty?
|
34
|
+
raise "Passing bind parameters with an arel AST is forbidden. " \
|
35
|
+
"The values must be stored on the AST directly"
|
36
|
+
end
|
37
|
+
sar = visitor.accept(arel_or_sar_string.ast, collector)
|
38
|
+
# puts ['a', sar.freeze, sar.binds].map(&:inspect)
|
39
|
+
[sar.freeze, sar.binds]
|
40
|
+
else
|
41
|
+
# puts ['b',arel_or_sar_string.dup.freeze, binds].map(&:inspect)
|
42
|
+
[arel_or_sar_string.dup.freeze, binds]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# This is used in the StatementCache object. It returns an object that
|
47
|
+
# can be used to query the database repeatedly.
|
27
48
|
def cacheable_query(klass, arel) # :nodoc:
|
28
|
-
collected = visitor.accept(arel.ast, collector)
|
29
49
|
if prepared_statements
|
30
|
-
|
50
|
+
sql, binds = visitor.accept(arel.ast, collector).value
|
51
|
+
query = klass.query(sql)
|
52
|
+
elsif self.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter)
|
53
|
+
collector = SunstonePartialQueryCollector.new(self.collector)
|
54
|
+
parts, binds = visitor.accept(arel.ast, collector).value
|
55
|
+
query = StatementCache::PartialQuery.new(parts, true)
|
31
56
|
else
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
57
|
+
collector = PartialQueryCollector.new
|
58
|
+
parts, binds = visitor.accept(arel.ast, collector).value
|
59
|
+
query = klass.partial_query(parts)
|
60
|
+
end
|
61
|
+
[query, binds]
|
62
|
+
end
|
63
|
+
|
64
|
+
class SunstonePartialQueryCollector
|
65
|
+
delegate_missing_to :@collector
|
66
|
+
|
67
|
+
def initialize(collector)
|
68
|
+
@collector = collector
|
69
|
+
@binds = []
|
70
|
+
end
|
71
|
+
|
72
|
+
def add_bind(obj)
|
73
|
+
@binds << obj
|
74
|
+
end
|
75
|
+
|
76
|
+
def value
|
77
|
+
[@collector, @binds]
|
37
78
|
end
|
38
79
|
end
|
39
80
|
|
40
81
|
# Returns an ActiveRecord::Result instance.
|
41
82
|
def select_all(arel, name = nil, binds = [], preparable: nil)
|
42
|
-
arel
|
43
|
-
|
83
|
+
arel = arel_from_relation(arel)
|
84
|
+
sar, binds = to_sar_and_binds(arel, binds)
|
85
|
+
select(sar, name, binds)
|
44
86
|
end
|
45
87
|
|
46
88
|
def exec_query(arel, name = 'SAR', binds = [], prepare: false)
|
47
89
|
sars = []
|
48
|
-
multiple_requests = arel.is_a?(Arel::
|
49
|
-
|
90
|
+
multiple_requests = arel.is_a?(Arel::Collectors::Sunstone)
|
91
|
+
type_casted_binds = binds#type_casted_binds(binds)
|
92
|
+
|
50
93
|
if multiple_requests
|
51
|
-
allowed_limit = limit_definition(arel.
|
52
|
-
|
94
|
+
allowed_limit = limit_definition(arel.table)
|
95
|
+
limit_bind_index = nil#binds.find_index { |x| x.name == 'LIMIT' }
|
96
|
+
requested_limit = if limit_bind_index
|
97
|
+
type_casted_binds[limit_bind_index]
|
98
|
+
else
|
99
|
+
arel.limit&.value&.value_for_database
|
100
|
+
end
|
53
101
|
|
54
102
|
if allowed_limit.nil?
|
55
103
|
multiple_requests = false
|
@@ -61,7 +109,7 @@ module ActiveRecord
|
|
61
109
|
end
|
62
110
|
|
63
111
|
send_request = lambda { |req_arel|
|
64
|
-
sar = to_sar(req_arel,
|
112
|
+
sar = to_sar(req_arel, type_casted_binds)
|
65
113
|
sars.push(sar)
|
66
114
|
log_mess = sar.path.split('?', 2)
|
67
115
|
log("#{sar.method} #{log_mess[0]} #{(log_mess[1] && !log_mess[1].empty?) ? MessagePack.unpack(CGI.unescape(log_mess[1])) : '' }", name) do
|
@@ -75,12 +123,11 @@ module ActiveRecord
|
|
75
123
|
}
|
76
124
|
|
77
125
|
result = if multiple_requests
|
78
|
-
|
79
|
-
binds.delete(bind)
|
126
|
+
binds.delete_at(limit_bind_index) if limit_bind_index
|
80
127
|
|
81
128
|
limit, offset, results = allowed_limit, 0, []
|
82
129
|
while requested_limit ? offset < requested_limit : true
|
83
|
-
split_arel = arel.
|
130
|
+
split_arel = arel.dup
|
84
131
|
split_arel.limit = limit
|
85
132
|
split_arel.offset = offset
|
86
133
|
request_results = send_request.call(split_arel)
|
@@ -104,8 +151,12 @@ module ActiveRecord
|
|
104
151
|
end
|
105
152
|
|
106
153
|
def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
|
107
|
-
|
154
|
+
sar, binds = to_sar_and_binds(arel, binds)
|
155
|
+
value = exec_insert(sar, name, binds, pk, sequence_name)
|
108
156
|
id_value || last_inserted_id(value)
|
157
|
+
|
158
|
+
# value = exec_insert(arel, name, binds, pk, sequence_name)
|
159
|
+
# id_value || last_inserted_id(value)
|
109
160
|
end
|
110
161
|
|
111
162
|
def update(arel, name = nil, binds = [])
|
@@ -130,7 +130,7 @@ module ActiveRecord
|
|
130
130
|
def collector
|
131
131
|
Arel::Collectors::Sunstone.new
|
132
132
|
end
|
133
|
-
|
133
|
+
|
134
134
|
def server_config
|
135
135
|
JSON.parse(@connection.get("/configuration").body)
|
136
136
|
end
|
@@ -173,8 +173,7 @@ module ActiveRecord
|
|
173
173
|
# If the next id was calculated in advance (as in Oracle), it should be
|
174
174
|
# passed in as +id_value+.
|
175
175
|
def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
|
176
|
-
|
177
|
-
exec_insert(sql, name, binds, pk, sequence_name)
|
176
|
+
exec_insert(arel, name, binds, pk, sequence_name)
|
178
177
|
end
|
179
178
|
alias create insert
|
180
179
|
|