activerecord 5.0.7.2 → 5.1.0.beta1
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 +389 -2252
- data/MIT-LICENSE +1 -1
- data/README.rdoc +1 -1
- data/examples/performance.rb +28 -28
- data/examples/simple.rb +3 -3
- data/lib/active_record.rb +20 -20
- data/lib/active_record/aggregations.rb +244 -244
- data/lib/active_record/association_relation.rb +5 -5
- data/lib/active_record/associations.rb +1579 -1569
- data/lib/active_record/associations/alias_tracker.rb +1 -1
- data/lib/active_record/associations/association.rb +23 -15
- data/lib/active_record/associations/association_scope.rb +83 -81
- data/lib/active_record/associations/belongs_to_association.rb +0 -1
- data/lib/active_record/associations/builder/belongs_to.rb +16 -14
- data/lib/active_record/associations/builder/collection_association.rb +1 -2
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +27 -27
- data/lib/active_record/associations/collection_association.rb +74 -241
- data/lib/active_record/associations/collection_proxy.rb +144 -70
- data/lib/active_record/associations/has_many_association.rb +15 -19
- data/lib/active_record/associations/has_many_through_association.rb +12 -5
- data/lib/active_record/associations/has_one_association.rb +22 -28
- data/lib/active_record/associations/has_one_through_association.rb +5 -1
- data/lib/active_record/associations/join_dependency.rb +117 -115
- data/lib/active_record/associations/join_dependency/join_association.rb +16 -13
- data/lib/active_record/associations/join_dependency/join_base.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
- data/lib/active_record/associations/preloader.rb +94 -94
- data/lib/active_record/associations/preloader/association.rb +87 -64
- data/lib/active_record/associations/preloader/belongs_to.rb +0 -2
- data/lib/active_record/associations/preloader/collection_association.rb +6 -6
- data/lib/active_record/associations/preloader/has_many.rb +0 -2
- data/lib/active_record/associations/preloader/singular_association.rb +6 -8
- data/lib/active_record/associations/preloader/through_association.rb +34 -41
- data/lib/active_record/associations/singular_association.rb +8 -25
- data/lib/active_record/associations/through_association.rb +3 -6
- data/lib/active_record/attribute.rb +98 -71
- data/lib/active_record/attribute/user_provided_default.rb +4 -2
- data/lib/active_record/attribute_assignment.rb +61 -61
- data/lib/active_record/attribute_decorators.rb +35 -13
- data/lib/active_record/attribute_methods.rb +56 -65
- data/lib/active_record/attribute_methods/before_type_cast.rb +7 -7
- data/lib/active_record/attribute_methods/dirty.rb +216 -34
- data/lib/active_record/attribute_methods/primary_key.rb +78 -73
- data/lib/active_record/attribute_methods/read.rb +39 -35
- data/lib/active_record/attribute_methods/serialization.rb +7 -7
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +35 -58
- data/lib/active_record/attribute_methods/write.rb +36 -30
- data/lib/active_record/attribute_mutation_tracker.rb +53 -10
- data/lib/active_record/attribute_set.rb +9 -6
- data/lib/active_record/attribute_set/builder.rb +41 -49
- data/lib/active_record/attribute_set/yaml_encoder.rb +41 -0
- data/lib/active_record/attributes.rb +21 -21
- data/lib/active_record/autosave_association.rb +13 -13
- data/lib/active_record/base.rb +24 -22
- data/lib/active_record/callbacks.rb +52 -14
- data/lib/active_record/coders/yaml_column.rb +9 -11
- data/lib/active_record/collection_cache_key.rb +6 -17
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +320 -278
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +1 -3
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +22 -34
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -27
- data/lib/active_record/connection_adapters/abstract/quoting.rb +44 -57
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +9 -19
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +78 -79
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +53 -41
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +99 -93
- data/lib/active_record/connection_adapters/abstract/transaction.rb +1 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +156 -128
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +424 -382
- data/lib/active_record/connection_adapters/column.rb +27 -5
- data/lib/active_record/connection_adapters/connection_specification.rb +128 -118
- data/lib/active_record/connection_adapters/mysql/column.rb +6 -31
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +45 -43
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +22 -22
- data/lib/active_record/connection_adapters/mysql/quoting.rb +6 -12
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +49 -45
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +16 -19
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +49 -31
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +5 -6
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +24 -26
- data/lib/active_record/connection_adapters/postgresql/column.rb +1 -28
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +46 -35
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid.rb +22 -21
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +9 -9
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +5 -3
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +16 -16
- data/lib/active_record/connection_adapters/postgresql/oid/{rails_5_1_point.rb → legacy_point.rb} +9 -16
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +28 -8
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +28 -30
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +2 -1
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +51 -51
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +38 -36
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +37 -24
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +19 -23
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +161 -170
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +4 -4
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -7
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +179 -152
- data/lib/active_record/connection_adapters/schema_cache.rb +16 -7
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +3 -3
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +1 -1
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +16 -20
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +1 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +28 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +17 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +187 -130
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -7
- data/lib/active_record/connection_handling.rb +14 -26
- data/lib/active_record/core.rb +110 -93
- data/lib/active_record/counter_cache.rb +62 -13
- data/lib/active_record/define_callbacks.rb +20 -0
- data/lib/active_record/dynamic_matchers.rb +80 -79
- data/lib/active_record/enum.rb +8 -6
- data/lib/active_record/errors.rb +58 -15
- data/lib/active_record/explain.rb +1 -2
- data/lib/active_record/explain_registry.rb +1 -1
- data/lib/active_record/explain_subscriber.rb +7 -4
- data/lib/active_record/fixture_set/file.rb +11 -8
- data/lib/active_record/fixtures.rb +66 -53
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +93 -79
- data/lib/active_record/integration.rb +7 -7
- data/lib/active_record/internal_metadata.rb +3 -16
- data/lib/active_record/legacy_yaml_adapter.rb +1 -1
- data/lib/active_record/locking/optimistic.rb +64 -56
- data/lib/active_record/locking/pessimistic.rb +10 -1
- data/lib/active_record/log_subscriber.rb +29 -29
- data/lib/active_record/migration.rb +155 -172
- data/lib/active_record/migration/command_recorder.rb +94 -94
- data/lib/active_record/migration/compatibility.rb +76 -37
- data/lib/active_record/migration/join_table.rb +6 -6
- data/lib/active_record/model_schema.rb +85 -119
- data/lib/active_record/nested_attributes.rb +200 -199
- data/lib/active_record/null_relation.rb +10 -33
- data/lib/active_record/persistence.rb +45 -38
- data/lib/active_record/query_cache.rb +4 -8
- data/lib/active_record/querying.rb +2 -3
- data/lib/active_record/railtie.rb +16 -17
- data/lib/active_record/railties/controller_runtime.rb +6 -2
- data/lib/active_record/railties/databases.rake +125 -140
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +2 -2
- data/lib/active_record/reflection.rb +79 -96
- data/lib/active_record/relation.rb +72 -115
- data/lib/active_record/relation/batches.rb +87 -58
- data/lib/active_record/relation/batches/batch_enumerator.rb +1 -1
- data/lib/active_record/relation/calculations.rb +154 -160
- data/lib/active_record/relation/delegation.rb +30 -29
- data/lib/active_record/relation/finder_methods.rb +195 -226
- data/lib/active_record/relation/merger.rb +58 -62
- data/lib/active_record/relation/predicate_builder.rb +92 -89
- data/lib/active_record/relation/predicate_builder/array_handler.rb +7 -5
- data/lib/active_record/relation/predicate_builder/association_query_handler.rb +23 -23
- data/lib/active_record/relation/predicate_builder/base_handler.rb +3 -1
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +0 -8
- data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +12 -10
- data/lib/active_record/relation/predicate_builder/range_handler.rb +0 -8
- data/lib/active_record/relation/query_attribute.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +247 -295
- data/lib/active_record/relation/record_fetch_warning.rb +3 -3
- data/lib/active_record/relation/spawn_methods.rb +4 -5
- data/lib/active_record/relation/where_clause.rb +79 -65
- data/lib/active_record/relation/where_clause_factory.rb +47 -8
- data/lib/active_record/result.rb +29 -31
- data/lib/active_record/runtime_registry.rb +3 -3
- data/lib/active_record/sanitization.rb +182 -197
- data/lib/active_record/schema.rb +3 -3
- data/lib/active_record/schema_dumper.rb +14 -37
- data/lib/active_record/schema_migration.rb +3 -3
- data/lib/active_record/scoping.rb +9 -10
- data/lib/active_record/scoping/default.rb +87 -91
- data/lib/active_record/scoping/named.rb +16 -28
- data/lib/active_record/secure_token.rb +2 -2
- data/lib/active_record/statement_cache.rb +13 -15
- data/lib/active_record/store.rb +31 -32
- data/lib/active_record/suppressor.rb +2 -1
- data/lib/active_record/table_metadata.rb +9 -5
- data/lib/active_record/tasks/database_tasks.rb +72 -65
- data/lib/active_record/tasks/mysql_database_tasks.rb +75 -72
- data/lib/active_record/tasks/postgresql_database_tasks.rb +53 -48
- data/lib/active_record/tasks/sqlite_database_tasks.rb +18 -16
- data/lib/active_record/timestamp.rb +39 -25
- data/lib/active_record/touch_later.rb +1 -2
- data/lib/active_record/transactions.rb +98 -110
- data/lib/active_record/type.rb +17 -13
- data/lib/active_record/type/adapter_specific_registry.rb +46 -42
- data/lib/active_record/type/decimal_without_scale.rb +9 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +3 -3
- data/lib/active_record/type/serialized.rb +8 -8
- data/lib/active_record/type/text.rb +9 -0
- data/lib/active_record/type/time.rb +0 -1
- data/lib/active_record/type/type_map.rb +11 -15
- data/lib/active_record/type/unsigned_integer.rb +15 -0
- data/lib/active_record/type_caster.rb +2 -2
- data/lib/active_record/type_caster/connection.rb +8 -6
- data/lib/active_record/type_caster/map.rb +3 -1
- data/lib/active_record/validations.rb +4 -4
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record/validations/presence.rb +2 -2
- data/lib/active_record/validations/uniqueness.rb +8 -39
- data/lib/active_record/version.rb +1 -1
- data/lib/rails/generators/active_record.rb +4 -4
- data/lib/rails/generators/active_record/migration.rb +2 -2
- data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -34
- data/lib/rails/generators/active_record/model/model_generator.rb +9 -9
- metadata +22 -13
- data/lib/active_record/relation/predicate_builder/class_handler.rb +0 -27
@@ -2,15 +2,15 @@ module ActiveRecord
|
|
2
2
|
class Relation
|
3
3
|
module RecordFetchWarning
|
4
4
|
# When this module is prepended to ActiveRecord::Relation and
|
5
|
-
#
|
5
|
+
# +config.active_record.warn_on_records_fetched_greater_than+ is
|
6
6
|
# set to an integer, if the number of records a query returns is
|
7
|
-
# greater than the value of
|
7
|
+
# greater than the value of +warn_on_records_fetched_greater_than+,
|
8
8
|
# a warning is logged. This allows for the detection of queries that
|
9
9
|
# return a large number of records, which could cause memory bloat.
|
10
10
|
#
|
11
11
|
# In most cases, fetching large number of records can be performed
|
12
12
|
# efficiently using the ActiveRecord::Batches methods.
|
13
|
-
# See
|
13
|
+
# See ActiveRecord::Batches for more information.
|
14
14
|
def exec_queries
|
15
15
|
QueryRegistry.reset
|
16
16
|
|
@@ -1,10 +1,9 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require "active_support/core_ext/hash/except"
|
2
|
+
require "active_support/core_ext/hash/slice"
|
3
|
+
require "active_record/relation/merger"
|
4
4
|
|
5
5
|
module ActiveRecord
|
6
6
|
module SpawnMethods
|
7
|
-
|
8
7
|
# This is overridden by Associations::CollectionProxy
|
9
8
|
def spawn #:nodoc:
|
10
9
|
clone
|
@@ -67,7 +66,7 @@ module ActiveRecord
|
|
67
66
|
|
68
67
|
private
|
69
68
|
|
70
|
-
def relation_with(values)
|
69
|
+
def relation_with(values)
|
71
70
|
result = Relation.create(klass, table, predicate_builder, values)
|
72
71
|
result.extend(*extending_values) if extending_values.any?
|
73
72
|
result
|
@@ -25,10 +25,7 @@ module ActiveRecord
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def except(*columns)
|
28
|
-
WhereClause.new(
|
29
|
-
predicates_except(columns),
|
30
|
-
binds_except(columns),
|
31
|
-
)
|
28
|
+
WhereClause.new(*except_predicates_and_binds(columns))
|
32
29
|
end
|
33
30
|
|
34
31
|
def or(other)
|
@@ -84,91 +81,108 @@ module ActiveRecord
|
|
84
81
|
@empty ||= new([], [])
|
85
82
|
end
|
86
83
|
|
84
|
+
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
85
|
+
# Workaround for Ruby 2.2 "private attribute?" warning.
|
87
86
|
protected
|
88
87
|
|
89
|
-
|
88
|
+
attr_reader :predicates
|
90
89
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
90
|
+
def referenced_columns
|
91
|
+
@referenced_columns ||= begin
|
92
|
+
equality_nodes = predicates.select { |n| equality_node?(n) }
|
93
|
+
Set.new(equality_nodes, &:left)
|
94
|
+
end
|
95
95
|
end
|
96
|
-
end
|
97
96
|
|
98
97
|
private
|
99
98
|
|
100
|
-
|
101
|
-
|
102
|
-
|
99
|
+
def predicates_unreferenced_by(other)
|
100
|
+
predicates.reject do |n|
|
101
|
+
equality_node?(n) && other.referenced_columns.include?(n.left)
|
102
|
+
end
|
103
103
|
end
|
104
|
-
end
|
105
|
-
|
106
|
-
def equality_node?(node)
|
107
|
-
node.respond_to?(:operator) && node.operator == :==
|
108
|
-
end
|
109
104
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
binds.reject { |attr| conflicts.include?(attr.name) }
|
114
|
-
end
|
105
|
+
def equality_node?(node)
|
106
|
+
node.respond_to?(:operator) && node.operator == :==
|
107
|
+
end
|
115
108
|
|
116
|
-
|
117
|
-
|
118
|
-
|
109
|
+
def non_conflicting_binds(other)
|
110
|
+
conflicts = referenced_columns & other.referenced_columns
|
111
|
+
conflicts.map! { |node| node.name.to_s }
|
112
|
+
binds.reject { |attr| conflicts.include?(attr.name) }
|
113
|
+
end
|
119
114
|
|
120
|
-
|
121
|
-
|
122
|
-
when NilClass
|
123
|
-
raise ArgumentError, 'Invalid argument for .where.not(), got nil.'
|
124
|
-
when Arel::Nodes::In
|
125
|
-
Arel::Nodes::NotIn.new(node.left, node.right)
|
126
|
-
when Arel::Nodes::Equality
|
127
|
-
Arel::Nodes::NotEqual.new(node.left, node.right)
|
128
|
-
when String
|
129
|
-
Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(node))
|
130
|
-
else
|
131
|
-
Arel::Nodes::Not.new(node)
|
115
|
+
def inverted_predicates
|
116
|
+
predicates.map { |node| invert_predicate(node) }
|
132
117
|
end
|
133
|
-
end
|
134
118
|
|
135
|
-
|
136
|
-
predicates.reject do |node|
|
119
|
+
def invert_predicate(node)
|
137
120
|
case node
|
138
|
-
when
|
139
|
-
|
140
|
-
|
121
|
+
when NilClass
|
122
|
+
raise ArgumentError, "Invalid argument for .where.not(), got nil."
|
123
|
+
when Arel::Nodes::In
|
124
|
+
Arel::Nodes::NotIn.new(node.left, node.right)
|
125
|
+
when Arel::Nodes::Equality
|
126
|
+
Arel::Nodes::NotEqual.new(node.left, node.right)
|
127
|
+
when String
|
128
|
+
Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(node))
|
129
|
+
else
|
130
|
+
Arel::Nodes::Not.new(node)
|
141
131
|
end
|
142
132
|
end
|
143
|
-
end
|
144
133
|
|
145
|
-
|
146
|
-
|
147
|
-
|
134
|
+
def except_predicates_and_binds(columns)
|
135
|
+
except_binds = []
|
136
|
+
binds_index = 0
|
137
|
+
|
138
|
+
predicates = self.predicates.reject do |node|
|
139
|
+
except = \
|
140
|
+
case node
|
141
|
+
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
|
142
|
+
binds_contains = node.grep(Arel::Nodes::BindParam).size
|
143
|
+
subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right)
|
144
|
+
columns.include?(subrelation.name.to_s)
|
145
|
+
end
|
146
|
+
|
147
|
+
if except && binds_contains > 0
|
148
|
+
(binds_index...(binds_index + binds_contains)).each do |i|
|
149
|
+
except_binds[i] = true
|
150
|
+
end
|
151
|
+
|
152
|
+
binds_index += binds_contains
|
153
|
+
end
|
154
|
+
|
155
|
+
except
|
156
|
+
end
|
157
|
+
|
158
|
+
binds = self.binds.reject.with_index do |_, i|
|
159
|
+
except_binds[i]
|
160
|
+
end
|
161
|
+
|
162
|
+
[predicates, binds]
|
148
163
|
end
|
149
|
-
end
|
150
164
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
165
|
+
def predicates_with_wrapped_sql_literals
|
166
|
+
non_empty_predicates.map do |node|
|
167
|
+
if Arel::Nodes::Equality === node
|
168
|
+
node
|
169
|
+
else
|
170
|
+
wrap_sql_literal(node)
|
171
|
+
end
|
157
172
|
end
|
158
173
|
end
|
159
|
-
end
|
160
174
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
175
|
+
ARRAY_WITH_EMPTY_STRING = [""]
|
176
|
+
def non_empty_predicates
|
177
|
+
predicates - ARRAY_WITH_EMPTY_STRING
|
178
|
+
end
|
165
179
|
|
166
|
-
|
167
|
-
|
168
|
-
|
180
|
+
def wrap_sql_literal(node)
|
181
|
+
if ::String === node
|
182
|
+
node = Arel.sql(node)
|
183
|
+
end
|
184
|
+
Arel::Nodes::Grouping.new(node)
|
169
185
|
end
|
170
|
-
Arel::Nodes::Grouping.new(node)
|
171
|
-
end
|
172
186
|
end
|
173
187
|
end
|
174
188
|
end
|
@@ -7,8 +7,6 @@ module ActiveRecord
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def build(opts, other)
|
10
|
-
binds = []
|
11
|
-
|
12
10
|
case opts
|
13
11
|
when String, Array
|
14
12
|
parts = [klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
|
@@ -17,22 +15,63 @@ module ActiveRecord
|
|
17
15
|
attributes = klass.send(:expand_hash_conditions_for_aggregates, attributes)
|
18
16
|
attributes.stringify_keys!
|
19
17
|
|
20
|
-
|
21
|
-
|
22
|
-
|
18
|
+
if perform_case_sensitive?(options = other.last)
|
19
|
+
parts, binds = build_for_case_sensitive(attributes, options)
|
20
|
+
else
|
21
|
+
attributes, binds = predicate_builder.create_binds(attributes)
|
22
|
+
parts = predicate_builder.build_from_hash(attributes)
|
23
|
+
end
|
23
24
|
when Arel::Nodes::Node
|
24
25
|
parts = [opts]
|
25
|
-
binds = other
|
26
26
|
else
|
27
27
|
raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
|
28
28
|
end
|
29
29
|
|
30
|
-
WhereClause.new(parts, binds)
|
30
|
+
WhereClause.new(parts, binds || [])
|
31
31
|
end
|
32
32
|
|
33
|
+
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
34
|
+
# Workaround for Ruby 2.2 "private attribute?" warning.
|
33
35
|
protected
|
34
36
|
|
35
|
-
|
37
|
+
attr_reader :klass, :predicate_builder
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def perform_case_sensitive?(options)
|
42
|
+
options && options.key?(:case_sensitive)
|
43
|
+
end
|
44
|
+
|
45
|
+
def build_for_case_sensitive(attributes, options)
|
46
|
+
parts, binds = [], []
|
47
|
+
table = klass.arel_table
|
48
|
+
|
49
|
+
attributes.each do |attribute, value|
|
50
|
+
if reflection = klass._reflect_on_association(attribute)
|
51
|
+
attribute = reflection.foreign_key.to_s
|
52
|
+
value = value[reflection.klass.primary_key] unless value.nil?
|
53
|
+
end
|
54
|
+
|
55
|
+
if value.nil?
|
56
|
+
parts << table[attribute].eq(value)
|
57
|
+
else
|
58
|
+
column = klass.column_for_attribute(attribute)
|
59
|
+
|
60
|
+
binds << predicate_builder.send(:build_bind_param, attribute, value)
|
61
|
+
value = Arel::Nodes::BindParam.new
|
62
|
+
|
63
|
+
predicate = if options[:case_sensitive]
|
64
|
+
klass.connection.case_sensitive_comparison(table, attribute, column, value)
|
65
|
+
else
|
66
|
+
klass.connection.case_insensitive_comparison(table, attribute, column, value)
|
67
|
+
end
|
68
|
+
|
69
|
+
parts << predicate
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
[parts, binds]
|
74
|
+
end
|
36
75
|
end
|
37
76
|
end
|
38
77
|
end
|
data/lib/active_record/result.rb
CHANGED
@@ -32,8 +32,6 @@ module ActiveRecord
|
|
32
32
|
class Result
|
33
33
|
include Enumerable
|
34
34
|
|
35
|
-
IDENTITY_TYPE = Type::Value.new # :nodoc:
|
36
|
-
|
37
35
|
attr_reader :columns, :rows, :column_types
|
38
36
|
|
39
37
|
def initialize(columns, rows, column_types = {})
|
@@ -103,36 +101,36 @@ module ActiveRecord
|
|
103
101
|
|
104
102
|
private
|
105
103
|
|
106
|
-
|
107
|
-
|
108
|
-
|
104
|
+
def column_type(name, type_overrides = {})
|
105
|
+
type_overrides.fetch(name) do
|
106
|
+
column_types.fetch(name, Type.default_value)
|
107
|
+
end
|
109
108
|
end
|
110
|
-
end
|
111
109
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
110
|
+
def hash_rows
|
111
|
+
@hash_rows ||=
|
112
|
+
begin
|
113
|
+
# We freeze the strings to prevent them getting duped when
|
114
|
+
# used as keys in ActiveRecord::Base's @attributes hash
|
115
|
+
columns = @columns.map { |c| c.dup.freeze }
|
116
|
+
@rows.map { |row|
|
117
|
+
# In the past we used Hash[columns.zip(row)]
|
118
|
+
# though elegant, the verbose way is much more efficient
|
119
|
+
# both time and memory wise cause it avoids a big array allocation
|
120
|
+
# this method is called a lot and needs to be micro optimised
|
121
|
+
hash = {}
|
122
|
+
|
123
|
+
index = 0
|
124
|
+
length = columns.length
|
125
|
+
|
126
|
+
while index < length
|
127
|
+
hash[columns[index]] = row[index]
|
128
|
+
index += 1
|
129
|
+
end
|
130
|
+
|
131
|
+
hash
|
132
|
+
}
|
133
|
+
end
|
134
|
+
end
|
137
135
|
end
|
138
136
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "active_support/per_thread_registry"
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
4
|
# This is a thread locals registry for Active Record. For example:
|
@@ -12,9 +12,9 @@ module ActiveRecord
|
|
12
12
|
class RuntimeRegistry # :nodoc:
|
13
13
|
extend ActiveSupport::PerThreadRegistry
|
14
14
|
|
15
|
-
attr_accessor :connection_handler, :sql_runtime
|
15
|
+
attr_accessor :connection_handler, :sql_runtime
|
16
16
|
|
17
|
-
[:connection_handler, :sql_runtime
|
17
|
+
[:connection_handler, :sql_runtime].each do |val|
|
18
18
|
class_eval %{ def self.#{val}; instance.#{val}; end }, __FILE__, __LINE__
|
19
19
|
class_eval %{ def self.#{val}=(x); instance.#{val}=x; end }, __FILE__, __LINE__
|
20
20
|
end
|
@@ -1,230 +1,215 @@
|
|
1
|
+
|
1
2
|
module ActiveRecord
|
2
3
|
module Sanitization
|
3
4
|
extend ActiveSupport::Concern
|
4
5
|
|
5
6
|
module ClassMethods
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
def sanitize_sql_for_conditions(condition)
|
30
|
-
return nil if condition.blank?
|
31
|
-
|
32
|
-
case condition
|
33
|
-
when Array; sanitize_sql_array(condition)
|
34
|
-
else condition
|
7
|
+
private
|
8
|
+
|
9
|
+
# Accepts an array or string of SQL conditions and sanitizes
|
10
|
+
# them into a valid SQL fragment for a WHERE clause.
|
11
|
+
#
|
12
|
+
# sanitize_sql_for_conditions(["name=? and group_id=?", "foo'bar", 4])
|
13
|
+
# # => "name='foo''bar' and group_id=4"
|
14
|
+
#
|
15
|
+
# sanitize_sql_for_conditions(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
|
16
|
+
# # => "name='foo''bar' and group_id='4'"
|
17
|
+
#
|
18
|
+
# sanitize_sql_for_conditions(["name='%s' and group_id='%s'", "foo'bar", 4])
|
19
|
+
# # => "name='foo''bar' and group_id='4'"
|
20
|
+
#
|
21
|
+
# sanitize_sql_for_conditions("name='foo''bar' and group_id='4'")
|
22
|
+
# # => "name='foo''bar' and group_id='4'"
|
23
|
+
def sanitize_sql_for_conditions(condition) # :doc:
|
24
|
+
return nil if condition.blank?
|
25
|
+
|
26
|
+
case condition
|
27
|
+
when Array; sanitize_sql_array(condition)
|
28
|
+
else condition
|
29
|
+
end
|
35
30
|
end
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
31
|
+
alias :sanitize_sql :sanitize_sql_for_conditions
|
32
|
+
alias :sanitize_conditions :sanitize_sql
|
33
|
+
deprecate sanitize_conditions: :sanitize_sql
|
34
|
+
|
35
|
+
# Accepts an array, hash, or string of SQL conditions and sanitizes
|
36
|
+
# them into a valid SQL fragment for a SET clause.
|
37
|
+
#
|
38
|
+
# sanitize_sql_for_assignment(["name=? and group_id=?", nil, 4])
|
39
|
+
# # => "name=NULL and group_id=4"
|
40
|
+
#
|
41
|
+
# sanitize_sql_for_assignment(["name=:name and group_id=:group_id", name: nil, group_id: 4])
|
42
|
+
# # => "name=NULL and group_id=4"
|
43
|
+
#
|
44
|
+
# Post.send(:sanitize_sql_for_assignment, { name: nil, group_id: 4 })
|
45
|
+
# # => "`posts`.`name` = NULL, `posts`.`group_id` = 4"
|
46
|
+
#
|
47
|
+
# sanitize_sql_for_assignment("name=NULL and group_id='4'")
|
48
|
+
# # => "name=NULL and group_id='4'"
|
49
|
+
def sanitize_sql_for_assignment(assignments, default_table_name = table_name) # :doc:
|
50
|
+
case assignments
|
51
|
+
when Array; sanitize_sql_array(assignments)
|
52
|
+
when Hash; sanitize_sql_hash_for_assignment(assignments, default_table_name)
|
53
|
+
else assignments
|
54
|
+
end
|
59
55
|
end
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
56
|
+
|
57
|
+
# Accepts an array, or string of SQL conditions and sanitizes
|
58
|
+
# them into a valid SQL fragment for an ORDER clause.
|
59
|
+
#
|
60
|
+
# sanitize_sql_for_order(["field(id, ?)", [1,3,2]])
|
61
|
+
# # => "field(id, 1,3,2)"
|
62
|
+
#
|
63
|
+
# sanitize_sql_for_order("id ASC")
|
64
|
+
# # => "id ASC"
|
65
|
+
def sanitize_sql_for_order(condition) # :doc:
|
66
|
+
if condition.is_a?(Array) && condition.first.to_s.include?("?")
|
67
|
+
sanitize_sql_array(condition)
|
68
|
+
else
|
69
|
+
condition
|
70
|
+
end
|
75
71
|
end
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
72
|
+
|
73
|
+
# Accepts a hash of SQL conditions and replaces those attributes
|
74
|
+
# that correspond to a {#composed_of}[rdoc-ref:Aggregations::ClassMethods#composed_of]
|
75
|
+
# relationship with their expanded aggregate attribute values.
|
76
|
+
#
|
77
|
+
# Given:
|
78
|
+
#
|
79
|
+
# class Person < ActiveRecord::Base
|
80
|
+
# composed_of :address, class_name: "Address",
|
81
|
+
# mapping: [%w(address_street street), %w(address_city city)]
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
# Then:
|
85
|
+
#
|
86
|
+
# { address: Address.new("813 abc st.", "chicago") }
|
87
|
+
# # => { address_street: "813 abc st.", address_city: "chicago" }
|
88
|
+
def expand_hash_conditions_for_aggregates(attrs) # :doc:
|
89
|
+
expanded_attrs = {}
|
90
|
+
attrs.each do |attr, value|
|
91
|
+
if aggregation = reflect_on_aggregation(attr.to_sym)
|
92
|
+
mapping = aggregation.mapping
|
93
|
+
mapping.each do |field_attr, aggregate_attr|
|
94
|
+
if mapping.size == 1 && !value.respond_to?(aggregate_attr)
|
95
|
+
expanded_attrs[field_attr] = value
|
96
|
+
else
|
97
|
+
expanded_attrs[field_attr] = value.send(aggregate_attr)
|
98
|
+
end
|
103
99
|
end
|
100
|
+
else
|
101
|
+
expanded_attrs[attr] = value
|
104
102
|
end
|
105
|
-
else
|
106
|
-
expanded_attrs[attr] = value
|
107
103
|
end
|
104
|
+
expanded_attrs
|
108
105
|
end
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
c = connection
|
118
|
-
attrs.map do |attr, value|
|
119
|
-
if value.is_a?(Base)
|
120
|
-
require "active_support/core_ext/string/filters"
|
121
|
-
ActiveSupport::Deprecation.warn(<<-WARNING.squish)
|
122
|
-
Passing `ActiveRecord::Base` objects to
|
123
|
-
`sanitize_sql_hash_for_assignment` (or methods which call it,
|
124
|
-
such as `update_all`) is deprecated. Please pass the id directly,
|
125
|
-
instead.
|
126
|
-
WARNING
|
127
|
-
else
|
106
|
+
|
107
|
+
# Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
|
108
|
+
#
|
109
|
+
# sanitize_sql_hash_for_assignment({ status: nil, group_id: 1 }, "posts")
|
110
|
+
# # => "`posts`.`status` = NULL, `posts`.`group_id` = 1"
|
111
|
+
def sanitize_sql_hash_for_assignment(attrs, table) # :doc:
|
112
|
+
c = connection
|
113
|
+
attrs.map do |attr, value|
|
128
114
|
value = type_for_attribute(attr.to_s).serialize(value)
|
129
|
-
|
130
|
-
|
131
|
-
end
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
string.gsub(pattern) { |x| [escape_character, x].join }
|
151
|
-
end
|
152
|
-
|
153
|
-
# Accepts an array of conditions. The array has each value
|
154
|
-
# sanitized and interpolated into the SQL statement.
|
155
|
-
#
|
156
|
-
# sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4])
|
157
|
-
# # => "name='foo''bar' and group_id=4"
|
158
|
-
#
|
159
|
-
# sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
|
160
|
-
# # => "name='foo''bar' and group_id=4"
|
161
|
-
#
|
162
|
-
# sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4])
|
163
|
-
# # => "name='foo''bar' and group_id='4'"
|
164
|
-
def sanitize_sql_array(ary)
|
165
|
-
statement, *values = ary
|
166
|
-
if values.first.is_a?(Hash) && statement =~ /:\w+/
|
167
|
-
replace_named_bind_variables(statement, values.first)
|
168
|
-
elsif statement.include?('?')
|
169
|
-
replace_bind_variables(statement, values)
|
170
|
-
elsif statement.blank?
|
171
|
-
statement
|
172
|
-
else
|
173
|
-
statement % values.collect { |value| connection.quote_string(value.to_s) }
|
115
|
+
"#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}"
|
116
|
+
end.join(", ")
|
117
|
+
end
|
118
|
+
|
119
|
+
# Sanitizes a +string+ so that it is safe to use within an SQL
|
120
|
+
# LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%".
|
121
|
+
#
|
122
|
+
# sanitize_sql_like("100%")
|
123
|
+
# # => "100\\%"
|
124
|
+
#
|
125
|
+
# sanitize_sql_like("snake_cased_string")
|
126
|
+
# # => "snake\\_cased\\_string"
|
127
|
+
#
|
128
|
+
# sanitize_sql_like("100%", "!")
|
129
|
+
# # => "100!%"
|
130
|
+
#
|
131
|
+
# sanitize_sql_like("snake_cased_string", "!")
|
132
|
+
# # => "snake!_cased!_string"
|
133
|
+
def sanitize_sql_like(string, escape_character = "\\") # :doc:
|
134
|
+
pattern = Regexp.union(escape_character, "%", "_")
|
135
|
+
string.gsub(pattern) { |x| [escape_character, x].join }
|
174
136
|
end
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
137
|
+
|
138
|
+
# Accepts an array of conditions. The array has each value
|
139
|
+
# sanitized and interpolated into the SQL statement.
|
140
|
+
#
|
141
|
+
# sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4])
|
142
|
+
# # => "name='foo''bar' and group_id=4"
|
143
|
+
#
|
144
|
+
# sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
|
145
|
+
# # => "name='foo''bar' and group_id=4"
|
146
|
+
#
|
147
|
+
# sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4])
|
148
|
+
# # => "name='foo''bar' and group_id='4'"
|
149
|
+
def sanitize_sql_array(ary) # :doc:
|
150
|
+
statement, *values = ary
|
151
|
+
if values.first.is_a?(Hash) && /:\w+/.match?(statement)
|
152
|
+
replace_named_bind_variables(statement, values.first)
|
153
|
+
elsif statement.include?("?")
|
154
|
+
replace_bind_variables(statement, values)
|
155
|
+
elsif statement.blank?
|
156
|
+
statement
|
157
|
+
else
|
158
|
+
statement % values.collect { |value| connection.quote_string(value.to_s) }
|
159
|
+
end
|
183
160
|
end
|
184
|
-
end
|
185
161
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
162
|
+
def replace_bind_variables(statement, values)
|
163
|
+
raise_if_bind_arity_mismatch(statement, statement.count("?"), values.size)
|
164
|
+
bound = values.dup
|
165
|
+
c = connection
|
166
|
+
statement.gsub(/\?/) do
|
167
|
+
replace_bind_variable(bound.shift, c)
|
168
|
+
end
|
191
169
|
end
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
if $1 == ':' # skip postgresql casts
|
197
|
-
match # return the whole match
|
198
|
-
elsif bind_vars.include?(match = $2.to_sym)
|
199
|
-
replace_bind_variable(bind_vars[match])
|
170
|
+
|
171
|
+
def replace_bind_variable(value, c = connection)
|
172
|
+
if ActiveRecord::Relation === value
|
173
|
+
value.to_sql
|
200
174
|
else
|
201
|
-
|
175
|
+
quote_bound_value(value, c)
|
202
176
|
end
|
203
177
|
end
|
204
|
-
end
|
205
178
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
179
|
+
def replace_named_bind_variables(statement, bind_vars)
|
180
|
+
statement.gsub(/(:?):([a-zA-Z]\w*)/) do |match|
|
181
|
+
if $1 == ":" # skip postgresql casts
|
182
|
+
match # return the whole match
|
183
|
+
elsif bind_vars.include?(match = $2.to_sym)
|
184
|
+
replace_bind_variable(bind_vars[match])
|
185
|
+
else
|
186
|
+
raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def quote_bound_value(value, c = connection)
|
192
|
+
if value.respond_to?(:map) && !value.acts_like?(:string)
|
193
|
+
if value.respond_to?(:empty?) && value.empty?
|
194
|
+
c.quote(nil)
|
195
|
+
else
|
196
|
+
value.map { |v| c.quote(v) }.join(",")
|
197
|
+
end
|
210
198
|
else
|
211
|
-
|
199
|
+
c.quote(value)
|
212
200
|
end
|
213
|
-
else
|
214
|
-
c.quote(value)
|
215
201
|
end
|
216
|
-
end
|
217
202
|
|
218
|
-
|
219
|
-
|
220
|
-
|
203
|
+
def raise_if_bind_arity_mismatch(statement, expected, provided)
|
204
|
+
unless expected == provided
|
205
|
+
raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}"
|
206
|
+
end
|
221
207
|
end
|
222
|
-
end
|
223
208
|
end
|
224
209
|
|
225
210
|
# TODO: Deprecate this
|
226
211
|
def quoted_id # :nodoc:
|
227
|
-
self.class.
|
212
|
+
self.class.connection.quote(@attributes[self.class.primary_key].value_for_database)
|
228
213
|
end
|
229
214
|
end
|
230
215
|
end
|