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.

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