activerecord 5.2.0.beta2 → 5.2.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +231 -2
- data/MIT-LICENSE +1 -1
- data/README.rdoc +1 -1
- data/lib/active_record.rb +1 -1
- data/lib/active_record/aggregations.rb +4 -5
- data/lib/active_record/association_relation.rb +2 -2
- data/lib/active_record/associations.rb +18 -12
- data/lib/active_record/associations/alias_tracker.rb +2 -10
- data/lib/active_record/associations/association.rb +1 -1
- data/lib/active_record/associations/belongs_to_association.rb +9 -9
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -6
- data/lib/active_record/associations/builder/association.rb +2 -2
- data/lib/active_record/associations/builder/belongs_to.rb +7 -3
- data/lib/active_record/associations/collection_association.rb +2 -2
- data/lib/active_record/associations/collection_proxy.rb +1 -1
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +4 -17
- data/lib/active_record/associations/has_one_through_association.rb +5 -6
- data/lib/active_record/associations/preloader.rb +1 -1
- data/lib/active_record/associations/preloader/association.rb +2 -2
- data/lib/active_record/associations/through_association.rb +22 -9
- data/lib/active_record/attribute_methods.rb +1 -5
- data/lib/active_record/attribute_methods/dirty.rb +2 -4
- data/lib/active_record/attributes.rb +1 -1
- data/lib/active_record/autosave_association.rb +3 -0
- data/lib/active_record/callbacks.rb +2 -2
- data/lib/active_record/collection_cache_key.rb +5 -6
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +1 -3
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +57 -21
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +1 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +20 -3
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +55 -15
- data/lib/active_record/connection_adapters/abstract_adapter.rb +19 -6
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +55 -64
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +8 -1
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +0 -4
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +21 -6
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +9 -1
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +12 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +13 -4
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +170 -48
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +15 -5
- data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +63 -18
- data/lib/active_record/core.rb +12 -3
- data/lib/active_record/enum.rb +2 -0
- data/lib/active_record/fixtures.rb +28 -37
- data/lib/active_record/gem_version.rb +1 -1
- data/lib/active_record/inheritance.rb +3 -4
- data/lib/active_record/log_subscriber.rb +41 -0
- data/lib/active_record/migration.rb +138 -120
- data/lib/active_record/migration/compatibility.rb +20 -0
- data/lib/active_record/model_schema.rb +19 -16
- data/lib/active_record/persistence.rb +8 -11
- data/lib/active_record/railtie.rb +7 -2
- data/lib/active_record/railties/databases.rake +8 -11
- data/lib/active_record/reflection.rb +10 -13
- data/lib/active_record/relation.rb +27 -17
- data/lib/active_record/relation/calculations.rb +17 -12
- data/lib/active_record/relation/finder_methods.rb +30 -37
- data/lib/active_record/relation/merger.rb +30 -2
- data/lib/active_record/relation/predicate_builder.rb +12 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +1 -1
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +14 -24
- data/lib/active_record/relation/spawn_methods.rb +1 -1
- data/lib/active_record/relation/where_clause.rb +16 -2
- data/lib/active_record/relation/where_clause_factory.rb +1 -2
- data/lib/active_record/sanitization.rb +130 -128
- data/lib/active_record/schema.rb +1 -1
- data/lib/active_record/schema_dumper.rb +12 -3
- data/lib/active_record/scoping/named.rb +6 -0
- data/lib/active_record/store.rb +1 -1
- data/lib/active_record/table_metadata.rb +10 -3
- data/lib/active_record/tasks/database_tasks.rb +4 -4
- data/lib/active_record/type_caster/map.rb +1 -1
- metadata +9 -9
@@ -23,7 +23,11 @@ module ActiveRecord
|
|
23
23
|
# build a relation to merge in rather than directly merging
|
24
24
|
# the values.
|
25
25
|
def other
|
26
|
-
other = Relation.create(
|
26
|
+
other = Relation.create(
|
27
|
+
relation.klass,
|
28
|
+
table: relation.table,
|
29
|
+
predicate_builder: relation.predicate_builder
|
30
|
+
)
|
27
31
|
hash.each { |k, v|
|
28
32
|
if k == :joins
|
29
33
|
if Hash === v
|
@@ -52,7 +56,7 @@ module ActiveRecord
|
|
52
56
|
|
53
57
|
NORMAL_VALUES = Relation::VALUE_METHODS -
|
54
58
|
Relation::CLAUSE_METHODS -
|
55
|
-
[:includes, :preload, :joins, :order, :reverse_order, :lock, :create_with, :reordering] # :nodoc:
|
59
|
+
[:includes, :preload, :joins, :left_outer_joins, :order, :reverse_order, :lock, :create_with, :reordering] # :nodoc:
|
56
60
|
|
57
61
|
def normal_values
|
58
62
|
NORMAL_VALUES
|
@@ -79,6 +83,7 @@ module ActiveRecord
|
|
79
83
|
merge_clauses
|
80
84
|
merge_preloads
|
81
85
|
merge_joins
|
86
|
+
merge_outer_joins
|
82
87
|
|
83
88
|
relation
|
84
89
|
end
|
@@ -129,6 +134,29 @@ module ActiveRecord
|
|
129
134
|
end
|
130
135
|
end
|
131
136
|
|
137
|
+
def merge_outer_joins
|
138
|
+
return if other.left_outer_joins_values.blank?
|
139
|
+
|
140
|
+
if other.klass == relation.klass
|
141
|
+
relation.left_outer_joins!(*other.left_outer_joins_values)
|
142
|
+
else
|
143
|
+
alias_tracker = nil
|
144
|
+
joins_dependency = other.left_outer_joins_values.map do |join|
|
145
|
+
case join
|
146
|
+
when Hash, Symbol, Array
|
147
|
+
alias_tracker ||= other.alias_tracker
|
148
|
+
ActiveRecord::Associations::JoinDependency.new(
|
149
|
+
other.klass, other.table, join, alias_tracker
|
150
|
+
)
|
151
|
+
else
|
152
|
+
join
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
relation.left_outer_joins!(*joins_dependency)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
132
160
|
def merge_multi_values
|
133
161
|
if other.reordering_value
|
134
162
|
# override any order specified in the original relation
|
@@ -86,6 +86,18 @@ module ActiveRecord
|
|
86
86
|
expand_from_hash(query).reduce(&:and)
|
87
87
|
end
|
88
88
|
queries.reduce(&:or)
|
89
|
+
elsif table.aggregated_with?(key)
|
90
|
+
mapping = table.reflect_on_aggregation(key).mapping
|
91
|
+
queries = Array.wrap(value).map do |object|
|
92
|
+
mapping.map do |field_attr, aggregate_attr|
|
93
|
+
if mapping.size == 1 && !object.respond_to?(aggregate_attr)
|
94
|
+
build(table.arel_attribute(field_attr), object)
|
95
|
+
else
|
96
|
+
build(table.arel_attribute(field_attr), object.send(aggregate_attr))
|
97
|
+
end
|
98
|
+
end.reduce(&:and)
|
99
|
+
end
|
100
|
+
queries.reduce(&:or)
|
89
101
|
# FIXME: Deprecate this and provide a public API to force equality
|
90
102
|
elsif (value.is_a?(Range) || value.is_a?(Array)) &&
|
91
103
|
table.type(key.to_s).respond_to?(:subtype)
|
@@ -327,8 +327,8 @@ module ActiveRecord
|
|
327
327
|
end
|
328
328
|
|
329
329
|
VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
|
330
|
-
:limit, :offset, :joins, :
|
331
|
-
:readonly, :having])
|
330
|
+
:limit, :offset, :joins, :left_outer_joins,
|
331
|
+
:includes, :from, :readonly, :having])
|
332
332
|
|
333
333
|
# Removes an unwanted relation that is already defined on a chain of relations.
|
334
334
|
# This is useful when passing around chains of relations and would like to
|
@@ -375,10 +375,11 @@ module ActiveRecord
|
|
375
375
|
args.each do |scope|
|
376
376
|
case scope
|
377
377
|
when Symbol
|
378
|
+
scope = :left_outer_joins if scope == :left_joins
|
378
379
|
if !VALID_UNSCOPING_VALUES.include?(scope)
|
379
380
|
raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
|
380
381
|
end
|
381
|
-
set_value(scope,
|
382
|
+
set_value(scope, DEFAULT_VALUES[scope])
|
382
383
|
when Hash
|
383
384
|
scope.each do |key, target_value|
|
384
385
|
if key != :where
|
@@ -444,15 +445,13 @@ module ActiveRecord
|
|
444
445
|
#
|
445
446
|
def left_outer_joins(*args)
|
446
447
|
check_if_method_has_arguments!(__callee__, args)
|
447
|
-
|
448
|
-
args.compact!
|
449
|
-
args.flatten!
|
450
|
-
|
451
448
|
spawn.left_outer_joins!(*args)
|
452
449
|
end
|
453
450
|
alias :left_joins :left_outer_joins
|
454
451
|
|
455
452
|
def left_outer_joins!(*args) # :nodoc:
|
453
|
+
args.compact!
|
454
|
+
args.flatten!
|
456
455
|
self.left_outer_joins_values += args
|
457
456
|
self
|
458
457
|
end
|
@@ -907,7 +906,7 @@ module ActiveRecord
|
|
907
906
|
protected
|
908
907
|
# Returns a relation value with a given name
|
909
908
|
def get_value(name) # :nodoc:
|
910
|
-
@values[name]
|
909
|
+
@values.fetch(name, DEFAULT_VALUES[name])
|
911
910
|
end
|
912
911
|
|
913
912
|
# Sets the relation value with the given name
|
@@ -980,6 +979,8 @@ module ActiveRecord
|
|
980
979
|
case join
|
981
980
|
when Hash, Symbol, Array
|
982
981
|
:association_join
|
982
|
+
when ActiveRecord::Associations::JoinDependency
|
983
|
+
:stashed_join
|
983
984
|
else
|
984
985
|
raise ArgumentError, "only Hash, Symbol and Array are allowed"
|
985
986
|
end
|
@@ -1015,7 +1016,7 @@ module ActiveRecord
|
|
1015
1016
|
join_nodes = buckets[:join_node].uniq
|
1016
1017
|
string_joins = buckets[:string_join].map(&:strip).uniq
|
1017
1018
|
|
1018
|
-
join_list = join_nodes + convert_join_strings_to_ast(
|
1019
|
+
join_list = join_nodes + convert_join_strings_to_ast(string_joins)
|
1019
1020
|
alias_tracker = alias_tracker(join_list, aliases)
|
1020
1021
|
|
1021
1022
|
join_dependency = ActiveRecord::Associations::JoinDependency.new(
|
@@ -1030,7 +1031,7 @@ module ActiveRecord
|
|
1030
1031
|
alias_tracker.aliases
|
1031
1032
|
end
|
1032
1033
|
|
1033
|
-
def convert_join_strings_to_ast(
|
1034
|
+
def convert_join_strings_to_ast(joins)
|
1034
1035
|
joins
|
1035
1036
|
.flatten
|
1036
1037
|
.reject(&:blank?)
|
@@ -1040,8 +1041,8 @@ module ActiveRecord
|
|
1040
1041
|
def build_select(arel)
|
1041
1042
|
if select_values.any?
|
1042
1043
|
arel.project(*arel_columns(select_values.uniq))
|
1043
|
-
elsif
|
1044
|
-
arel.project(*
|
1044
|
+
elsif klass.ignored_columns.any?
|
1045
|
+
arel.project(*klass.column_names.map { |field| arel_attribute(field) })
|
1045
1046
|
else
|
1046
1047
|
arel.project(table[Arel.star])
|
1047
1048
|
end
|
@@ -1121,7 +1122,7 @@ module ActiveRecord
|
|
1121
1122
|
|
1122
1123
|
def preprocess_order_args(order_args)
|
1123
1124
|
order_args.map! do |arg|
|
1124
|
-
klass.
|
1125
|
+
klass.sanitize_sql_for_order(arg)
|
1125
1126
|
end
|
1126
1127
|
order_args.flatten!
|
1127
1128
|
|
@@ -1192,7 +1193,6 @@ module ActiveRecord
|
|
1192
1193
|
|
1193
1194
|
DEFAULT_VALUES = {
|
1194
1195
|
create_with: FROZEN_EMPTY_HASH,
|
1195
|
-
readonly: false,
|
1196
1196
|
where: Relation::WhereClause.empty,
|
1197
1197
|
having: Relation::WhereClause.empty,
|
1198
1198
|
from: Relation::FromClause.empty
|
@@ -1201,15 +1201,5 @@ module ActiveRecord
|
|
1201
1201
|
Relation::MULTI_VALUE_METHODS.each do |value|
|
1202
1202
|
DEFAULT_VALUES[value] ||= FROZEN_EMPTY_ARRAY
|
1203
1203
|
end
|
1204
|
-
|
1205
|
-
Relation::SINGLE_VALUE_METHODS.each do |value|
|
1206
|
-
DEFAULT_VALUES[value] = nil if DEFAULT_VALUES[value].nil?
|
1207
|
-
end
|
1208
|
-
|
1209
|
-
def default_value_for(name)
|
1210
|
-
DEFAULT_VALUES.fetch(name) do
|
1211
|
-
raise ArgumentError, "unknown relation value #{name.inspect}"
|
1212
|
-
end
|
1213
|
-
end
|
1214
1204
|
end
|
1215
1205
|
end
|
@@ -69,7 +69,7 @@ module ActiveRecord
|
|
69
69
|
private
|
70
70
|
|
71
71
|
def relation_with(values)
|
72
|
-
result = Relation.create(klass,
|
72
|
+
result = Relation.create(klass, values: values)
|
73
73
|
result.extend(*extending_values) if extending_values.any?
|
74
74
|
result
|
75
75
|
end
|
@@ -47,7 +47,7 @@ module ActiveRecord
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def to_h(table_name = nil)
|
50
|
-
equalities = predicates
|
50
|
+
equalities = equalities(predicates)
|
51
51
|
if table_name
|
52
52
|
equalities = equalities.select do |node|
|
53
53
|
node.left.relation.name == table_name
|
@@ -90,6 +90,20 @@ module ActiveRecord
|
|
90
90
|
end
|
91
91
|
|
92
92
|
private
|
93
|
+
def equalities(predicates)
|
94
|
+
equalities = []
|
95
|
+
|
96
|
+
predicates.each do |node|
|
97
|
+
case node
|
98
|
+
when Arel::Nodes::Equality
|
99
|
+
equalities << node
|
100
|
+
when Arel::Nodes::And
|
101
|
+
equalities.concat equalities(node.children)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
equalities
|
106
|
+
end
|
93
107
|
|
94
108
|
def predicates_unreferenced_by(other)
|
95
109
|
predicates.reject do |n|
|
@@ -121,7 +135,7 @@ module ActiveRecord
|
|
121
135
|
end
|
122
136
|
|
123
137
|
def except_predicates(columns)
|
124
|
-
|
138
|
+
predicates.reject do |node|
|
125
139
|
case node
|
126
140
|
when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual
|
127
141
|
subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right)
|
@@ -11,10 +11,9 @@ module ActiveRecord
|
|
11
11
|
def build(opts, other)
|
12
12
|
case opts
|
13
13
|
when String, Array
|
14
|
-
parts = [klass.
|
14
|
+
parts = [klass.sanitize_sql(other.empty? ? opts : ([opts] + other))]
|
15
15
|
when Hash
|
16
16
|
attributes = predicate_builder.resolve_column_aliases(opts)
|
17
|
-
attributes = klass.send(:expand_hash_conditions_for_aggregates, attributes)
|
18
17
|
attributes.stringify_keys!
|
19
18
|
|
20
19
|
parts = predicate_builder.build_from_hash(attributes)
|
@@ -5,80 +5,135 @@ module ActiveRecord
|
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
7
|
module ClassMethods
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
when Array; sanitize_sql_array(condition)
|
29
|
-
else condition
|
30
|
-
end
|
8
|
+
# Accepts an array or string of SQL conditions and sanitizes
|
9
|
+
# them into a valid SQL fragment for a WHERE clause.
|
10
|
+
#
|
11
|
+
# sanitize_sql_for_conditions(["name=? and group_id=?", "foo'bar", 4])
|
12
|
+
# # => "name='foo''bar' and group_id=4"
|
13
|
+
#
|
14
|
+
# sanitize_sql_for_conditions(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
|
15
|
+
# # => "name='foo''bar' and group_id='4'"
|
16
|
+
#
|
17
|
+
# sanitize_sql_for_conditions(["name='%s' and group_id='%s'", "foo'bar", 4])
|
18
|
+
# # => "name='foo''bar' and group_id='4'"
|
19
|
+
#
|
20
|
+
# sanitize_sql_for_conditions("name='foo''bar' and group_id='4'")
|
21
|
+
# # => "name='foo''bar' and group_id='4'"
|
22
|
+
def sanitize_sql_for_conditions(condition)
|
23
|
+
return nil if condition.blank?
|
24
|
+
|
25
|
+
case condition
|
26
|
+
when Array; sanitize_sql_array(condition)
|
27
|
+
else condition
|
31
28
|
end
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
29
|
+
end
|
30
|
+
alias :sanitize_sql :sanitize_sql_for_conditions
|
31
|
+
|
32
|
+
# Accepts an array, hash, or string of SQL conditions and sanitizes
|
33
|
+
# them into a valid SQL fragment for a SET clause.
|
34
|
+
#
|
35
|
+
# sanitize_sql_for_assignment(["name=? and group_id=?", nil, 4])
|
36
|
+
# # => "name=NULL and group_id=4"
|
37
|
+
#
|
38
|
+
# sanitize_sql_for_assignment(["name=:name and group_id=:group_id", name: nil, group_id: 4])
|
39
|
+
# # => "name=NULL and group_id=4"
|
40
|
+
#
|
41
|
+
# Post.sanitize_sql_for_assignment({ name: nil, group_id: 4 })
|
42
|
+
# # => "`posts`.`name` = NULL, `posts`.`group_id` = 4"
|
43
|
+
#
|
44
|
+
# sanitize_sql_for_assignment("name=NULL and group_id='4'")
|
45
|
+
# # => "name=NULL and group_id='4'"
|
46
|
+
def sanitize_sql_for_assignment(assignments, default_table_name = table_name)
|
47
|
+
case assignments
|
48
|
+
when Array; sanitize_sql_array(assignments)
|
49
|
+
when Hash; sanitize_sql_hash_for_assignment(assignments, default_table_name)
|
50
|
+
else assignments
|
54
51
|
end
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
Arel.sql(sanitize_sql_array(condition))
|
77
|
-
else
|
78
|
-
condition
|
52
|
+
end
|
53
|
+
|
54
|
+
# Accepts an array, or string of SQL conditions and sanitizes
|
55
|
+
# them into a valid SQL fragment for an ORDER clause.
|
56
|
+
#
|
57
|
+
# sanitize_sql_for_order(["field(id, ?)", [1,3,2]])
|
58
|
+
# # => "field(id, 1,3,2)"
|
59
|
+
#
|
60
|
+
# sanitize_sql_for_order("id ASC")
|
61
|
+
# # => "id ASC"
|
62
|
+
def sanitize_sql_for_order(condition)
|
63
|
+
if condition.is_a?(Array) && condition.first.to_s.include?("?")
|
64
|
+
enforce_raw_sql_whitelist([condition.first],
|
65
|
+
whitelist: AttributeMethods::ClassMethods::COLUMN_NAME_ORDER_WHITELIST
|
66
|
+
)
|
67
|
+
|
68
|
+
# Ensure we aren't dealing with a subclass of String that might
|
69
|
+
# override methods we use (eg. Arel::Nodes::SqlLiteral).
|
70
|
+
if condition.first.kind_of?(String) && !condition.first.instance_of?(String)
|
71
|
+
condition = [String.new(condition.first), *condition[1..-1]]
|
79
72
|
end
|
73
|
+
|
74
|
+
Arel.sql(sanitize_sql_array(condition))
|
75
|
+
else
|
76
|
+
condition
|
80
77
|
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
|
81
|
+
#
|
82
|
+
# sanitize_sql_hash_for_assignment({ status: nil, group_id: 1 }, "posts")
|
83
|
+
# # => "`posts`.`status` = NULL, `posts`.`group_id` = 1"
|
84
|
+
def sanitize_sql_hash_for_assignment(attrs, table)
|
85
|
+
c = connection
|
86
|
+
attrs.map do |attr, value|
|
87
|
+
type = type_for_attribute(attr)
|
88
|
+
value = type.serialize(type.cast(value))
|
89
|
+
"#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}"
|
90
|
+
end.join(", ")
|
91
|
+
end
|
92
|
+
|
93
|
+
# Sanitizes a +string+ so that it is safe to use within an SQL
|
94
|
+
# LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%".
|
95
|
+
#
|
96
|
+
# sanitize_sql_like("100%")
|
97
|
+
# # => "100\\%"
|
98
|
+
#
|
99
|
+
# sanitize_sql_like("snake_cased_string")
|
100
|
+
# # => "snake\\_cased\\_string"
|
101
|
+
#
|
102
|
+
# sanitize_sql_like("100%", "!")
|
103
|
+
# # => "100!%"
|
104
|
+
#
|
105
|
+
# sanitize_sql_like("snake_cased_string", "!")
|
106
|
+
# # => "snake!_cased!_string"
|
107
|
+
def sanitize_sql_like(string, escape_character = "\\")
|
108
|
+
pattern = Regexp.union(escape_character, "%", "_")
|
109
|
+
string.gsub(pattern) { |x| [escape_character, x].join }
|
110
|
+
end
|
111
|
+
|
112
|
+
# Accepts an array of conditions. The array has each value
|
113
|
+
# sanitized and interpolated into the SQL statement.
|
114
|
+
#
|
115
|
+
# sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4])
|
116
|
+
# # => "name='foo''bar' and group_id=4"
|
117
|
+
#
|
118
|
+
# sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
|
119
|
+
# # => "name='foo''bar' and group_id=4"
|
120
|
+
#
|
121
|
+
# sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4])
|
122
|
+
# # => "name='foo''bar' and group_id='4'"
|
123
|
+
def sanitize_sql_array(ary)
|
124
|
+
statement, *values = ary
|
125
|
+
if values.first.is_a?(Hash) && /:\w+/.match?(statement)
|
126
|
+
replace_named_bind_variables(statement, values.first)
|
127
|
+
elsif statement.include?("?")
|
128
|
+
replace_bind_variables(statement, values)
|
129
|
+
elsif statement.blank?
|
130
|
+
statement
|
131
|
+
else
|
132
|
+
statement % values.collect { |value| connection.quote_string(value.to_s) }
|
133
|
+
end
|
134
|
+
end
|
81
135
|
|
136
|
+
private
|
82
137
|
# Accepts a hash of SQL conditions and replaces those attributes
|
83
138
|
# that correspond to a {#composed_of}[rdoc-ref:Aggregations::ClassMethods#composed_of]
|
84
139
|
# relationship with their expanded aggregate attribute values.
|
@@ -100,10 +155,12 @@ module ActiveRecord
|
|
100
155
|
if aggregation = reflect_on_aggregation(attr.to_sym)
|
101
156
|
mapping = aggregation.mapping
|
102
157
|
mapping.each do |field_attr, aggregate_attr|
|
103
|
-
|
104
|
-
|
158
|
+
expanded_attrs[field_attr] = if value.is_a?(Array)
|
159
|
+
value.map { |it| it.send(aggregate_attr) }
|
160
|
+
elsif mapping.size == 1 && !value.respond_to?(aggregate_attr)
|
161
|
+
value
|
105
162
|
else
|
106
|
-
|
163
|
+
value.send(aggregate_attr)
|
107
164
|
end
|
108
165
|
end
|
109
166
|
else
|
@@ -112,62 +169,7 @@ module ActiveRecord
|
|
112
169
|
end
|
113
170
|
expanded_attrs
|
114
171
|
end
|
115
|
-
|
116
|
-
# Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
|
117
|
-
#
|
118
|
-
# sanitize_sql_hash_for_assignment({ status: nil, group_id: 1 }, "posts")
|
119
|
-
# # => "`posts`.`status` = NULL, `posts`.`group_id` = 1"
|
120
|
-
def sanitize_sql_hash_for_assignment(attrs, table) # :doc:
|
121
|
-
c = connection
|
122
|
-
attrs.map do |attr, value|
|
123
|
-
type = type_for_attribute(attr.to_s)
|
124
|
-
value = type.serialize(type.cast(value))
|
125
|
-
"#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}"
|
126
|
-
end.join(", ")
|
127
|
-
end
|
128
|
-
|
129
|
-
# Sanitizes a +string+ so that it is safe to use within an SQL
|
130
|
-
# LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%".
|
131
|
-
#
|
132
|
-
# sanitize_sql_like("100%")
|
133
|
-
# # => "100\\%"
|
134
|
-
#
|
135
|
-
# sanitize_sql_like("snake_cased_string")
|
136
|
-
# # => "snake\\_cased\\_string"
|
137
|
-
#
|
138
|
-
# sanitize_sql_like("100%", "!")
|
139
|
-
# # => "100!%"
|
140
|
-
#
|
141
|
-
# sanitize_sql_like("snake_cased_string", "!")
|
142
|
-
# # => "snake!_cased!_string"
|
143
|
-
def sanitize_sql_like(string, escape_character = "\\") # :doc:
|
144
|
-
pattern = Regexp.union(escape_character, "%", "_")
|
145
|
-
string.gsub(pattern) { |x| [escape_character, x].join }
|
146
|
-
end
|
147
|
-
|
148
|
-
# Accepts an array of conditions. The array has each value
|
149
|
-
# sanitized and interpolated into the SQL statement.
|
150
|
-
#
|
151
|
-
# sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4])
|
152
|
-
# # => "name='foo''bar' and group_id=4"
|
153
|
-
#
|
154
|
-
# sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
|
155
|
-
# # => "name='foo''bar' and group_id=4"
|
156
|
-
#
|
157
|
-
# sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4])
|
158
|
-
# # => "name='foo''bar' and group_id='4'"
|
159
|
-
def sanitize_sql_array(ary) # :doc:
|
160
|
-
statement, *values = ary
|
161
|
-
if values.first.is_a?(Hash) && /:\w+/.match?(statement)
|
162
|
-
replace_named_bind_variables(statement, values.first)
|
163
|
-
elsif statement.include?("?")
|
164
|
-
replace_bind_variables(statement, values)
|
165
|
-
elsif statement.blank?
|
166
|
-
statement
|
167
|
-
else
|
168
|
-
statement % values.collect { |value| connection.quote_string(value.to_s) }
|
169
|
-
end
|
170
|
-
end
|
172
|
+
deprecate :expand_hash_conditions_for_aggregates
|
171
173
|
|
172
174
|
def replace_bind_variables(statement, values)
|
173
175
|
raise_if_bind_arity_mismatch(statement, statement.count("?"), values.size)
|