sunstone 5.1.0.4 → 5.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|